Initial logic is implemented
This commit is contained in:
		
							
								
								
									
										65
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					# ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Unit tests should run on each commit
 | 
				
			||||||
 | 
					# ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					kind: pipeline
 | 
				
			||||||
 | 
					type: docker
 | 
				
			||||||
 | 
					name: Run unit tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					trigger:
 | 
				
			||||||
 | 
					  event:
 | 
				
			||||||
 | 
					    - push
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					steps:
 | 
				
			||||||
 | 
					  - name: Check formatting
 | 
				
			||||||
 | 
					    image: registry.hub.docker.com/golangci/golangci-lint
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: Run unit tests
 | 
				
			||||||
 | 
					    image: registry.hub.docker.com/library/golang
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - make test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					# ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Build a container
 | 
				
			||||||
 | 
					# ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					kind: pipeline
 | 
				
			||||||
 | 
					type: docker
 | 
				
			||||||
 | 
					name: Build a container
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					trigger:
 | 
				
			||||||
 | 
					  event:
 | 
				
			||||||
 | 
					    - push
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					steps:
 | 
				
			||||||
 | 
					  - name: Build the builder image
 | 
				
			||||||
 | 
					    image: alpine
 | 
				
			||||||
 | 
					    privileged: true
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      GITEA_TOKEN:
 | 
				
			||||||
 | 
					        from_secret: GITEA_TOKEN
 | 
				
			||||||
 | 
					      BUILDAH_REG: git.badhouseplants.net/allanger/shoebill-builder
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - ./build/build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: Cleanup the registry
 | 
				
			||||||
 | 
					    image: git.badhouseplants.net/allanger/shoebill-builder:${DRONE_COMMIT_SHA}
 | 
				
			||||||
 | 
					    privileged: true
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      GITEA_TOKEN:
 | 
				
			||||||
 | 
					        from_secret: GITEA_TOKEN
 | 
				
			||||||
 | 
					      GITEA_PACKAGE: shoebill-builder
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - cleanup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: Build shoebill container and cleanuo the registry
 | 
				
			||||||
 | 
					    image: git.badhouseplants.net/allanger/shoebill-builder:${DRONE_COMMIT_SHA}
 | 
				
			||||||
 | 
					    privileged: true
 | 
				
			||||||
 | 
					    environment:
 | 
				
			||||||
 | 
					      GITEA_TOKEN:
 | 
				
			||||||
 | 
					        from_secret: GITEA_TOKEN
 | 
				
			||||||
 | 
					    commands:
 | 
				
			||||||
 | 
					      - build-container
 | 
				
			||||||
 | 
					      - cleanup
 | 
				
			||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,13 +1,11 @@
 | 
				
			|||||||
# ---> Go
 | 
					# ---> Go
 | 
				
			||||||
# If you prefer the allow list template instead of the deny list, see community template:
 | 
					 | 
				
			||||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Binaries for programs and plugins
 | 
					# Binaries for programs and plugins
 | 
				
			||||||
*.exe
 | 
					*.exe
 | 
				
			||||||
*.exe~
 | 
					*.exe~
 | 
				
			||||||
*.dll
 | 
					*.dll
 | 
				
			||||||
*.so
 | 
					*.so
 | 
				
			||||||
*.dylib
 | 
					*.dylib
 | 
				
			||||||
 | 
					shoebill
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Test binary, built with `go test -c`
 | 
					# Test binary, built with `go test -c`
 | 
				
			||||||
*.test
 | 
					*.test
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								Containerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Containerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					FROM registry.hub.docker.com/library/golang:1.20.5-alpine3.18 as builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apk update && apk upgrade && \
 | 
				
			||||||
 | 
					    apk add --no-cache bash build-base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /opt/flux-helm-controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY go.mod .
 | 
				
			||||||
 | 
					COPY go.sum .
 | 
				
			||||||
 | 
					RUN go mod download
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COPY . .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ARG GOARCH
 | 
				
			||||||
 | 
					RUN GOOS=linux CGO_ENABLED=0 go build -tags build -o /usr/local/bin/flux-helm-controller main.go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FROM ghcr.io/allanger/dumb-downloader as dudo
 | 
				
			||||||
 | 
					RUN apt-get update -y && apt-get install tar -y
 | 
				
			||||||
 | 
					ARG HELM_VERSION=v3.12.1
 | 
				
			||||||
 | 
					ENV RUST_LOG=info
 | 
				
			||||||
 | 
					RUN dudo -l "https://get.helm.sh/helm-{{ version }}-{{ os }}-{{ arch }}.tar.gz" -d /tmp/helm.tar.gz -p $HELM_VERSION
 | 
				
			||||||
 | 
					RUN tar -xf /tmp/helm.tar.gz  -C /tmp && rm -f /tmp/helm.tar.gz 
 | 
				
			||||||
 | 
					RUN mkdir /out && for bin in `find /tmp | grep helm`; do cp $bin /out/; done
 | 
				
			||||||
 | 
					RUN chmod +x /out/helm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Final container
 | 
				
			||||||
 | 
					FROM registry.hub.docker.com/library/alpine:3.18
 | 
				
			||||||
 | 
					LABEL org.opencontainers.image.authors="Nikolai Rodionov<allanger@zohomail.com>"
 | 
				
			||||||
 | 
					COPY --from=dudo /out/ /usr/bin
 | 
				
			||||||
 | 
					RUN apk update --no-cache && apk add openssh git yq rsync --no-cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# # install operator binary
 | 
				
			||||||
 | 
					COPY --from=builder /usr/local/bin/flux-helm-controller /usr/local/bin/flux-helm-controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENTRYPOINT ["/usr/local/bin/flux-helm-controller"]
 | 
				
			||||||
							
								
								
									
										34
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					# -----------------------------------------------
 | 
				
			||||||
 | 
					# -- Main rules
 | 
				
			||||||
 | 
					# -----------------------------------------------
 | 
				
			||||||
 | 
					build: tidy
 | 
				
			||||||
 | 
						@./scripts/build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tidy: 
 | 
				
			||||||
 | 
						@go mod tidy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test: tidy
 | 
				
			||||||
 | 
						go test ./...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					lint: tidy
 | 
				
			||||||
 | 
						golangci-lint run --timeout 2m
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fmt:
 | 
				
			||||||
 | 
						go fmt ./...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# -----------------------------------------------
 | 
				
			||||||
 | 
					# -- Git helpers
 | 
				
			||||||
 | 
					# -----------------------------------------------
 | 
				
			||||||
 | 
					push_notes:
 | 
				
			||||||
 | 
						git push origin 'refs/notes/*'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fetch_notes:
 | 
				
			||||||
 | 
						git fetch origin 'refs/notes/*:refs/notes/*'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# -----------------------------------------------
 | 
				
			||||||
 | 
					# -- Helpers 
 | 
				
			||||||
 | 
					# -----------------------------------------------
 | 
				
			||||||
 | 
					run:
 | 
				
			||||||
 | 
						go run main.go --config example.config.yaml --helm /Users/allanger/.rd/bin/helm --workdir test
 | 
				
			||||||
 | 
					cleanup: 
 | 
				
			||||||
 | 
						rm -rf test
 | 
				
			||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
# giops
 | 
					# shoebill
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A templater for the gitops setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A templater for the gitops setup
 | 
					 | 
				
			||||||
							
								
								
									
										5
									
								
								build/Containerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								build/Containerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					FROM registry.hub.docker.com/library/alpine
 | 
				
			||||||
 | 
					RUN apk update --no-cache&&\
 | 
				
			||||||
 | 
					    apk add yq gettext openssl curl jq perl git\
 | 
				
			||||||
 | 
					        buildah cni-plugins iptables ip6tables fuse-overlayfs --no-cache
 | 
				
			||||||
 | 
					COPY ./scripts/ /usr/bin/
 | 
				
			||||||
							
								
								
									
										34
									
								
								build/build
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										34
									
								
								build/build
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					# ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Copyright 2023 Nikolai Rodionov (allanger)
 | 
				
			||||||
 | 
					# ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Permission is hereby granted, without written agreement and without
 | 
				
			||||||
 | 
					# -- license or royalty fees, to use, copy, modify, and distribute this
 | 
				
			||||||
 | 
					# -- software and its documentation for any purpose, provided that the
 | 
				
			||||||
 | 
					# -- above copyright notice and the following two paragraphs appear in
 | 
				
			||||||
 | 
					# -- all copies of this software.
 | 
				
			||||||
 | 
					# -- 
 | 
				
			||||||
 | 
					# -- IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 | 
				
			||||||
 | 
					# -- DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 | 
				
			||||||
 | 
					# -- ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 | 
				
			||||||
 | 
					# -- IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 | 
				
			||||||
 | 
					# -- DAMAGE.
 | 
				
			||||||
 | 
					# -- 
 | 
				
			||||||
 | 
					# -- THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 | 
				
			||||||
 | 
					# -- BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 | 
				
			||||||
 | 
					# -- FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 | 
				
			||||||
 | 
					# -- ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 | 
				
			||||||
 | 
					# -- PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 | 
				
			||||||
 | 
					# ---------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#! /bin/sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					apk update
 | 
				
			||||||
 | 
					apk add buildah cni-plugins iptables ip6tables fuse-overlayfs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					buildah login -u allanger -p $GITEA_TOKEN git.badhouseplants.net
 | 
				
			||||||
 | 
					buildah build -t $BUILDAH_REG:$DRONE_COMMIT_SHA ./build
 | 
				
			||||||
 | 
					buildah tag $BUILDAH_REG:$DRONE_COMMIT_SHA $BUILDAH_REG:latest
 | 
				
			||||||
 | 
					if [ -z ${BUILD_DEBUG+x} ]; then
 | 
				
			||||||
 | 
					    buildah push $BUILDAH_REG:$DRONE_COMMIT_SHA; 
 | 
				
			||||||
 | 
					    buildah push $BUILDAH_REG:latest; 
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										55
									
								
								build/scripts/build-container
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										55
									
								
								build/scripts/build-container
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/perl
 | 
				
			||||||
 | 
					# ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Copyright 2023 Nikolai Rodionov (allanger)
 | 
				
			||||||
 | 
					# ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Permission is hereby granted, without written agreement and without
 | 
				
			||||||
 | 
					# -- license or royalty fees, to use, copy, modify, and distribute this
 | 
				
			||||||
 | 
					# -- software and its documentation for any purpose, provided that the
 | 
				
			||||||
 | 
					# -- above copyright notice and the following two paragraphs appear in
 | 
				
			||||||
 | 
					# -- all copies of this software.
 | 
				
			||||||
 | 
					# -- 
 | 
				
			||||||
 | 
					# -- IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 | 
				
			||||||
 | 
					# -- DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 | 
				
			||||||
 | 
					# -- ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 | 
				
			||||||
 | 
					# -- IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 | 
				
			||||||
 | 
					# -- DAMAGE.
 | 
				
			||||||
 | 
					# -- 
 | 
				
			||||||
 | 
					# -- THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 | 
				
			||||||
 | 
					# -- BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 | 
				
			||||||
 | 
					# -- FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 | 
				
			||||||
 | 
					# -- ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 | 
				
			||||||
 | 
					# -- PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 | 
				
			||||||
 | 
					# ---------------------------------------------------------------------------
 | 
				
			||||||
 | 
					use strict; 
 | 
				
			||||||
 | 
					use warnings; 
 | 
				
			||||||
 | 
					# ---------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Setup Git variables
 | 
				
			||||||
 | 
					# --  by default main branch should be "main"
 | 
				
			||||||
 | 
					# ---------------------------------------------------------------------------
 | 
				
			||||||
 | 
					my $git_branch = `git rev-parse --abbrev-ref HEAD`;
 | 
				
			||||||
 | 
					my $git_commit_sha = `git rev-parse HEAD`;
 | 
				
			||||||
 | 
					my $main_branch = $ENV{'GIT_MAIN_BRANCH'} || 'main';
 | 
				
			||||||
 | 
					chomp($git_branch);
 | 
				
			||||||
 | 
					chomp($git_commit_sha);
 | 
				
			||||||
 | 
					# ---------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Build the image with SHA tag
 | 
				
			||||||
 | 
					# --  my main build system is DRONE, so I'm using DRONE variables a lot
 | 
				
			||||||
 | 
					# ---------------------------------------------------------------------------
 | 
				
			||||||
 | 
					my $container_registry = $ENV{'CONTAINER_REGISTRY'} || 'git.badhouseplants.net';
 | 
				
			||||||
 | 
					my $image_name = $ENV{'DRONE_REPO'} || "badhouseplants/badhouseplants-net";
 | 
				
			||||||
 | 
					my $tag = "$container_registry/$image_name:$git_commit_sha";
 | 
				
			||||||
 | 
					my $username = $ENV{'DRONE_USERNAME'} || "allanger";
 | 
				
			||||||
 | 
					my $password = $ENV{'GITEA_TOKEN'} || "YOU NOT AUTHORIZED, PAL";
 | 
				
			||||||
 | 
					0 == system ("buildah login --username $username --password $password $container_registry") or die $!;
 | 
				
			||||||
 | 
					0 == system ("buildah build -t $tag .") or die $!;
 | 
				
			||||||
 | 
					0 == system ("buildah push $tag") or die $!;
 | 
				
			||||||
 | 
					# ---------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Push the latest if the branch is main
 | 
				
			||||||
 | 
					# ---------------------------------------------------------------------------
 | 
				
			||||||
 | 
					if ( $git_branch eq $main_branch) {
 | 
				
			||||||
 | 
					    my $latest_tag = "$container_registry/$image_name:latest";
 | 
				
			||||||
 | 
						0 == system ("buildah tag $tag $latest_tag") or die $!;
 | 
				
			||||||
 | 
					    0 == system ("buildah push $latest_tag") or die $!;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print "Thanks!\n";
 | 
				
			||||||
							
								
								
									
										74
									
								
								build/scripts/cleanup
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										74
									
								
								build/scripts/cleanup
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/perl
 | 
				
			||||||
 | 
					# ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Copyright 2023 Nikolai Rodionov (allanger)
 | 
				
			||||||
 | 
					# ------------------------------------------------------------------------
 | 
				
			||||||
 | 
					# -- Permission is hereby granted, without written agreement and without
 | 
				
			||||||
 | 
					# -- license or royalty fees, to use, copy, modify, and distribute this
 | 
				
			||||||
 | 
					# -- software and its documentation for any purpose, provided that the
 | 
				
			||||||
 | 
					# -- above copyright notice and the following two paragraphs appear in
 | 
				
			||||||
 | 
					# -- all copies of this software.
 | 
				
			||||||
 | 
					# -- 
 | 
				
			||||||
 | 
					# -- IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
 | 
				
			||||||
 | 
					# -- DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 | 
				
			||||||
 | 
					# -- ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
 | 
				
			||||||
 | 
					# -- IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
 | 
				
			||||||
 | 
					# -- DAMAGE.
 | 
				
			||||||
 | 
					# -- 
 | 
				
			||||||
 | 
					# -- THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
 | 
				
			||||||
 | 
					# -- BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 | 
				
			||||||
 | 
					# -- FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 | 
				
			||||||
 | 
					# -- ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
 | 
				
			||||||
 | 
					# -- PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 | 
				
			||||||
 | 
					# ---------------------------------------------------------------------------
 | 
				
			||||||
 | 
					use strict; 
 | 
				
			||||||
 | 
					use warnings; 
 | 
				
			||||||
 | 
					# --------------------------------------
 | 
				
			||||||
 | 
					# -- Gitea variables
 | 
				
			||||||
 | 
					# --------------------------------------
 | 
				
			||||||
 | 
					my $gitea_url=$ENV{'GITEA_URL'} || 'https://git.badhouseplants.net/api/v1';
 | 
				
			||||||
 | 
					my $gitea_org=$ENV{'DRONE_REPO_NAMESPACE'} || 'badhouseplants';
 | 
				
			||||||
 | 
					my $gitea_package=$ENV{'GITEA_PACKAGE'} || $ENV{'DRONE_REPO_NAME'} ||'badhouseplants-net';
 | 
				
			||||||
 | 
					my $gitea_api="$gitea_url/packages/$gitea_org/container/$gitea_package";
 | 
				
			||||||
 | 
					my $gitea_list_api="$gitea_url/packages/$gitea_org?page=1&type=container&q=$gitea_package";
 | 
				
			||||||
 | 
					my $gitea_token=$ENV{'GITEA_TOKEN'};
 | 
				
			||||||
 | 
					my $gitea_user=$ENV{'GITEA_USER'} || $ENV{'DRONE_COMMIT_AUTHOR'};
 | 
				
			||||||
 | 
					# ---------------------------------------
 | 
				
			||||||
 | 
					# -- Get tags from Gitea
 | 
				
			||||||
 | 
					# ---------------------------------------
 | 
				
			||||||
 | 
					my $builds = "curl -X 'GET' \"$gitea_list_api\" -H 'accept: application/json' -H \"Authorization: token $gitea_token\" | jq -r '.[].version'";
 | 
				
			||||||
 | 
					my @builds_out = `$builds`;
 | 
				
			||||||
 | 
					chomp @builds_out;
 | 
				
			||||||
 | 
					# ---------------------------------------
 | 
				
			||||||
 | 
					# -- Get a list of all commits + 'latest'
 | 
				
			||||||
 | 
					# ---------------------------------------
 | 
				
			||||||
 | 
					my $commits = "";
 | 
				
			||||||
 | 
					if (defined $ENV{CLEANUP_ARGO}) {
 | 
				
			||||||
 | 
					    $commits = "argocd app list -o yaml -l application=badhouseplants | yq  '.[].metadata.labels.commit_sha'";
 | 
				
			||||||
 | 
					} else { 
 | 
				
			||||||
 | 
					    $commits = "git fetch && git log --format=format:%H --all";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					my @commits_out = `$commits`;
 | 
				
			||||||
 | 
					chomp @commits_out;
 | 
				
			||||||
 | 
					push @commits_out, 'latest';
 | 
				
			||||||
 | 
					# --------------------------------------
 | 
				
			||||||
 | 
					# -- Rclone variables
 | 
				
			||||||
 | 
					# -------------------------------------
 | 
				
			||||||
 | 
					my $dirs = "rclone lsf badhouseplants-minio:/badhouseplants-net";
 | 
				
			||||||
 | 
					my @dirs_out = `$dirs`;
 | 
				
			||||||
 | 
					chomp @dirs_out;
 | 
				
			||||||
 | 
					# ---------------------------------------
 | 
				
			||||||
 | 
					# -- Compare builds to commits 
 | 
				
			||||||
 | 
					# -- And remove obsolete imgages from
 | 
				
			||||||
 | 
					# --  registry
 | 
				
			||||||
 | 
					# ---------------------------------------
 | 
				
			||||||
 | 
					print "Cleaning up the container registry\n";
 | 
				
			||||||
 | 
					foreach my $line (@builds_out)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  print "Checking if $line is in @commits_out\n\n";
 | 
				
			||||||
 | 
					  if ( ! grep( /^$line$/, @commits_out ) ) {
 | 
				
			||||||
 | 
					    my $cmd = "curl -X 'DELETE' -s \"$gitea_api/$line\"  -H 'accept: application/json' -H \"Authorization: token $gitea_token\" || true";
 | 
				
			||||||
 | 
					    print "Removing ${line}\n\n";
 | 
				
			||||||
 | 
					    my $output = `$cmd`;
 | 
				
			||||||
 | 
					    print "$output \n";
 | 
				
			||||||
 | 
					  } 
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								cmd/root.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								cmd/root.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					package cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/build"
 | 
				
			||||||
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var fullVersion = fmt.Sprintf("%s - %s", build.Version, build.CommitHash)
 | 
				
			||||||
 | 
					var longDescription = `---
 | 
				
			||||||
 | 
					shoebill is just GitOps with a glottal T
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It's a tool that is supposed to help engineers follow the GitOps practies 
 | 
				
			||||||
 | 
					  without fighting with GitOps being inapplicable to the real world.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Yeah, I quite hate this GitOps obsession, but since it's already there,
 | 
				
			||||||
 | 
					 I think it makes sense to make it work.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					Information about the build:
 | 
				
			||||||
 | 
					Version: %s (build on %s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						rootCmd = &cobra.Command{
 | 
				
			||||||
 | 
							Use:           "shoebill",
 | 
				
			||||||
 | 
							Short:         "shoebill – GitOps without pain, kinda",
 | 
				
			||||||
 | 
							Long:          fmt.Sprintf(longDescription, fullVersion, build.BuildTime),
 | 
				
			||||||
 | 
							SilenceErrors: true,
 | 
				
			||||||
 | 
							SilenceUsage:  true,
 | 
				
			||||||
 | 
							Version:       build.Version,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Execute(ctx context.Context) error {
 | 
				
			||||||
 | 
						rootCmd.PersistentFlags().Bool("server", false, "Set to true, if you want to start it in the deamon mode")
 | 
				
			||||||
 | 
						if err := rootCmd.ExecuteContext(ctx); err != nil {
 | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, "Whoops. There was an error while executing your CLI '%s'", err)
 | 
				
			||||||
 | 
							os.Exit(1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										52
									
								
								cmd/sync.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								cmd/sync.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					package cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/controller"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						"github.com/spf13/cobra"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						sync = &cobra.Command{
 | 
				
			||||||
 | 
							Use:   "sync",
 | 
				
			||||||
 | 
							Short: "sync does something",
 | 
				
			||||||
 | 
							Long:  ``,
 | 
				
			||||||
 | 
							Run:   syncCmd,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						sync.Flags().StringP("config", "c", "config.yaml", "A path to the configuration file")
 | 
				
			||||||
 | 
						sync.Flags().String("workdir", "", "A path to the workdir. On the moment of running, it should be an empty dir")
 | 
				
			||||||
 | 
						sync.Flags().String("ssh-key", "", "A path to the pricate ssh key")
 | 
				
			||||||
 | 
						sync.Flags().Bool("dry-run", false, "If set to false, will not push changes to git")
 | 
				
			||||||
 | 
						sync.Flags().String("diff", "main", "If values us set, will show helm diffs for not preserved charts, values will be taken from the target branch")
 | 
				
			||||||
 | 
						sync.Flags().String("sops-bin", "/usr/bin/sops", "A path to the sops binary in your system")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rootCmd.AddCommand(sync)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func syncCmd(cmd *cobra.Command, args []string) {
 | 
				
			||||||
 | 
						config := cmd.Flag("config").Value.String()
 | 
				
			||||||
 | 
						workdir := cmd.Flag("workdir").Value.String()
 | 
				
			||||||
 | 
						sshKey := cmd.Flag("ssh-key").Value.String()
 | 
				
			||||||
 | 
						sopsBin := cmd.Flag("sops-bin").Value.String()
 | 
				
			||||||
 | 
						dryRun, err := cmd.Flags().GetBool("dry-run")
 | 
				
			||||||
 | 
						diff, err := cmd.Flags().GetString("diff")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logrus.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						configObj, err := controller.ReadTheConfig(config)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logrus.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						configObj.SopsBin = sopsBin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = controller.Sync(workdir, sshKey, configObj, dryRun, diff)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logrus.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logrus.Info("your config is synced")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								examples/merge-files/giops.config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								examples/merge-files/giops.config.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					import:
 | 
				
			||||||
 | 
					  - ./repos-oci.yaml
 | 
				
			||||||
 | 
					  - ./repos.yaml
 | 
				
			||||||
 | 
					repos:
 | 
				
			||||||
 | 
					  - name: jetstack
 | 
				
			||||||
 | 
					    url: https://charts.jetstack.io
 | 
				
			||||||
 | 
					  - name: bitnami-oci
 | 
				
			||||||
 | 
					    url: oci://registry-1.docker.io/bitnamicharts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					releases:
 | 
				
			||||||
 | 
					  - name: cert-manager
 | 
				
			||||||
 | 
					    chart: jetstack
 | 
				
			||||||
 | 
					    repo: jetstack
 | 
				
			||||||
 | 
					    version: latest
 | 
				
			||||||
 | 
					    namespace: cert-manager
 | 
				
			||||||
 | 
					  - name: postgresql-server
 | 
				
			||||||
 | 
					    chart: postgresql
 | 
				
			||||||
 | 
					    repo: bitnami-oci
 | 
				
			||||||
 | 
					    namespace: postgresql-server
 | 
				
			||||||
 | 
					    version: latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clusters:
 | 
				
			||||||
 | 
					  - name: cluster-1
 | 
				
			||||||
 | 
					    git: git@git.badhouseplants.net:giant-swarm-task/cluster-1.git
 | 
				
			||||||
 | 
					    releases:
 | 
				
			||||||
 | 
					      - cert-manager
 | 
				
			||||||
 | 
					      - postgresql-server
 | 
				
			||||||
							
								
								
									
										4
									
								
								examples/merge-files/repos-oci.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/merge-files/repos-oci.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					repos:
 | 
				
			||||||
 | 
					  - name: bitnami-oci
 | 
				
			||||||
 | 
					    url: oci://registry-1.docker.io/bitnamicharts
 | 
				
			||||||
							
								
								
									
										4
									
								
								examples/merge-files/repos.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/merge-files/repos.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					repos:
 | 
				
			||||||
 | 
					  - name: jetstack
 | 
				
			||||||
 | 
					    url: https://charts.jetstack.io
 | 
				
			||||||
							
								
								
									
										5
									
								
								examples/one-config/.sops.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								examples/one-config/.sops.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					creation_rules:
 | 
				
			||||||
 | 
					  - path_regex: secrets/.*.yaml
 | 
				
			||||||
 | 
					    key_groups:
 | 
				
			||||||
 | 
					      - age:
 | 
				
			||||||
 | 
					          - age16svfskd8x75g62f5uwpmgqzth52rr3wgv9m6rxchqv6v6kzmzf0qvhr2pk
 | 
				
			||||||
							
								
								
									
										38
									
								
								examples/one-config/giops.config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								examples/one-config/giops.config.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					repositories:
 | 
				
			||||||
 | 
					  - name: bitnami-oci
 | 
				
			||||||
 | 
					    url: oci://registry-1.docker.io/bitnamicharts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					releases:
 | 
				
			||||||
 | 
					  - name: postgresql-server-2
 | 
				
			||||||
 | 
					    chart: postgresql
 | 
				
			||||||
 | 
					    repository: bitnami-oci
 | 
				
			||||||
 | 
					    namespace: postgresql-server
 | 
				
			||||||
 | 
					    version: latest
 | 
				
			||||||
 | 
					    values:
 | 
				
			||||||
 | 
					      - ./values/postgresql.yaml
 | 
				
			||||||
 | 
					    secrets: 
 | 
				
			||||||
 | 
					      - ./secrets/postgresql.yaml
 | 
				
			||||||
 | 
					  - name: postgresql-server
 | 
				
			||||||
 | 
					    chart: postgresql
 | 
				
			||||||
 | 
					    repository: bitnami-oci
 | 
				
			||||||
 | 
					    namespace: postgresql-server
 | 
				
			||||||
 | 
					    version: latest
 | 
				
			||||||
 | 
					    values:
 | 
				
			||||||
 | 
					      - ./values/postgresql.yaml
 | 
				
			||||||
 | 
					    secrets: 
 | 
				
			||||||
 | 
					      - ./secrets/postgresql.yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clusters:
 | 
				
			||||||
 | 
					  - name: cluster-shoebill-test
 | 
				
			||||||
 | 
					    git: git@git.badhouseplants.net:allanger/shoebill-test.git
 | 
				
			||||||
 | 
					    dotsops: |
 | 
				
			||||||
 | 
					      creation_rules:
 | 
				
			||||||
 | 
					        - path_regex: secrets/.*.yaml
 | 
				
			||||||
 | 
					          key_groups:
 | 
				
			||||||
 | 
					          - age:
 | 
				
			||||||
 | 
					            - age16svfskd8x75g62f5uwpmgqzth52rr3wgv9m6rxchqv6v6kzmzf0qvhr2pk
 | 
				
			||||||
 | 
					    provider: flux
 | 
				
			||||||
 | 
					    releases:
 | 
				
			||||||
 | 
					      - postgresql-server-2
 | 
				
			||||||
 | 
					      - postgresql-server
 | 
				
			||||||
							
								
								
									
										140
									
								
								examples/one-config/helmfile.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								examples/one-config/helmfile.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					repositories:
 | 
				
			||||||
 | 
					  - name: fluxcd-community
 | 
				
			||||||
 | 
					    url: https://fluxcd-community.github.io/helm-charts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					releases:
 | 
				
			||||||
 | 
					  # ---------------------------------
 | 
				
			||||||
 | 
					  # -- FLUX
 | 
				
			||||||
 | 
					  # ---------------------------------
 | 
				
			||||||
 | 
					  - name: flux
 | 
				
			||||||
 | 
					    namespace: flux-system
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    createNamespace: true
 | 
				
			||||||
 | 
					    chart: fluxcd-community/flux2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *metrics-server
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: kube-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *istio-base
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: istio-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  - <<: *istio-gateway
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: istio-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *istiod
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: istio-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *cert-manager
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: cert-manager
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *minio
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: minio-service
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *openvpn
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: openvpn-service
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  - <<: *metallb
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: metallb-system
 | 
				
			||||||
 | 
					    createNamespace: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *drone
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: drone-service
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *drone-runner-docker
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: drone-service
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *longhorn
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: longhorn-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *argocd
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: argo-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *nrodionov
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: nrodionov-application
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *minecraft
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: minecraft-application
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *gitea
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: gitea-service
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *funkwhale
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: funkwhale-application
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *prometheus
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: monitoring-system
 | 
				
			||||||
 | 
					    createNamespace: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *loki
 | 
				
			||||||
 | 
					    installed: false
 | 
				
			||||||
 | 
					    namespace: monitoring-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *promtail
 | 
				
			||||||
 | 
					    installed: false
 | 
				
			||||||
 | 
					    namespace: monitoring-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *bitwarden
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: bitwarden-application
 | 
				
			||||||
 | 
					    createNamespace: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *redis
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: database-service
 | 
				
			||||||
 | 
					    createNamespace: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *postgres16
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: database-service
 | 
				
			||||||
 | 
					    createNamespace: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *db-operator
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: database-service
 | 
				
			||||||
 | 
					    createNamespace: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *db-instances
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: database-service
 | 
				
			||||||
 | 
					    createNamespace: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - <<: *mysql
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: database-service
 | 
				
			||||||
 | 
					    createNamespace: true
 | 
				
			||||||
							
								
								
									
										3
									
								
								examples/one-config/keys.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/one-config/keys.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					# created: 2023-09-25T10:45:28+02:00
 | 
				
			||||||
 | 
					# public key: age16svfskd8x75g62f5uwpmgqzth52rr3wgv9m6rxchqv6v6kzmzf0qvhr2pk
 | 
				
			||||||
 | 
					AGE-SECRET-KEY-1Y3FGYSHKWSSZ3G8DJ3QD7WKE5J0TTYDWSSD95EXL4A308ZWW0L9SN99ASP
 | 
				
			||||||
							
								
								
									
										24
									
								
								examples/one-config/secrets/postgresql.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								examples/one-config/secrets/postgresql.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					global:
 | 
				
			||||||
 | 
					    postgresql:
 | 
				
			||||||
 | 
					        auth:
 | 
				
			||||||
 | 
					            password: ENC[AES256_GCM,data:5QV6a1A=,iv:utR62wuLTzwihVwXXPw8DA2Ul7kfU1YgAKteRA+WKm0=,tag:EYuIa6TDmxaR0PSuaJBeBA==,type:str]
 | 
				
			||||||
 | 
					sops:
 | 
				
			||||||
 | 
					    kms: []
 | 
				
			||||||
 | 
					    gcp_kms: []
 | 
				
			||||||
 | 
					    azure_kv: []
 | 
				
			||||||
 | 
					    hc_vault: []
 | 
				
			||||||
 | 
					    age:
 | 
				
			||||||
 | 
					        - recipient: age16svfskd8x75g62f5uwpmgqzth52rr3wgv9m6rxchqv6v6kzmzf0qvhr2pk
 | 
				
			||||||
 | 
					          enc: |
 | 
				
			||||||
 | 
					            -----BEGIN AGE ENCRYPTED FILE-----
 | 
				
			||||||
 | 
					            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2SUJpdUtYWjF3K1dzbGc3
 | 
				
			||||||
 | 
					            Z2U0UDVpWmVkYXVvT1V3UWVDM2VTQ1hBU1RBCmFZMlI4ZWxWTTdCd05lVFVCN2hN
 | 
				
			||||||
 | 
					            QkZKRmlFVStXT2kxSVlUNmU0VkZCUDQKLS0tIEQ2aXZ0ZDVXcGc4RE1WMmtOaTV3
 | 
				
			||||||
 | 
					            TDloa0dHTFhyUWhid1V0aEFydmtQbU0Kwkw914se9cGEN4FKNphuJErdC1QlYqRQ
 | 
				
			||||||
 | 
					            +CInCnoy8m0/MZNhehZ/JVReEys6KDNxJ7RhnoRfs7P7wfAgBg984A==
 | 
				
			||||||
 | 
					            -----END AGE ENCRYPTED FILE-----
 | 
				
			||||||
 | 
					    lastmodified: "2023-10-11T11:13:13Z"
 | 
				
			||||||
 | 
					    mac: ENC[AES256_GCM,data:olaWkaoqqoStswMNNUY6IljoriMgpWxhQ4f0AiRkiujat7ySjuUlS/gwBO1FQp+iB1XGnZKznOWDmZn8XEoFY6q+2dgrtA+h5fTI/EshPgX8xONsGH25Chhg2ER1FMKj8jOYEzxSJfW9s3oKyFGXAH/OgLMpZBkq2uc+eM83J2w=,iv:3fs4BEeFuWU2Nd8yC9iM89a6sz11izIfx3fLI5+1eJU=,tag:Y6ESSNnm2t9zGHG57qrQaQ==,type:str]
 | 
				
			||||||
 | 
					    pgp: []
 | 
				
			||||||
 | 
					    unencrypted_suffix: _unencrypted
 | 
				
			||||||
 | 
					    version: 3.8.0
 | 
				
			||||||
							
								
								
									
										6
									
								
								examples/one-config/values/postgresql.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								examples/one-config/values/postgresql.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					global:
 | 
				
			||||||
 | 
					  postgresql:
 | 
				
			||||||
 | 
					    auth:
 | 
				
			||||||
 | 
					      username: check
 | 
				
			||||||
 | 
					      database: check
 | 
				
			||||||
							
								
								
									
										20
									
								
								examples/values-config/shoebill.config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								examples/values-config/shoebill.config.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					repositories:
 | 
				
			||||||
 | 
					  - name: bitnami-oci
 | 
				
			||||||
 | 
					    url: oci://registry-1.docker.io/bitnamicharts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					releases:
 | 
				
			||||||
 | 
					  - name: postgresql-server
 | 
				
			||||||
 | 
					    chart: postgresql
 | 
				
			||||||
 | 
					    repository: bitnami-oci
 | 
				
			||||||
 | 
					    namespace: postgresql-server
 | 
				
			||||||
 | 
					    version: latest
 | 
				
			||||||
 | 
					    values:
 | 
				
			||||||
 | 
					      - ./values/postgresql.yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clusters:
 | 
				
			||||||
 | 
					  - name: cluster-shoebill-test
 | 
				
			||||||
 | 
					    git: git@git.badhouseplants.net:allanger/shoebill-test.git
 | 
				
			||||||
 | 
					    provider: flux
 | 
				
			||||||
 | 
					    releases:
 | 
				
			||||||
 | 
					      - postgresql-server
 | 
				
			||||||
							
								
								
									
										225
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,225 @@
 | 
				
			|||||||
 | 
					module git.badhouseplants.net/allanger/shoebill
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// replace github.com/google/gnostic-models => github.com/google/gnostic-models v0.6.8
 | 
				
			||||||
 | 
					replace (
 | 
				
			||||||
 | 
						k8s.io/client-go => k8s.io/client-go v0.29.0-alpha.0
 | 
				
			||||||
 | 
						k8s.io/kubectl => k8s.io/kubectl v0.29.0-alpha.0
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/fluxcd/helm-controller/api v0.35.0
 | 
				
			||||||
 | 
						github.com/fluxcd/source-controller/api v1.0.1
 | 
				
			||||||
 | 
						github.com/getsops/sops/v3 v3.8.0
 | 
				
			||||||
 | 
						github.com/go-git/go-git/v5 v5.8.1
 | 
				
			||||||
 | 
						github.com/sirupsen/logrus v1.9.3
 | 
				
			||||||
 | 
						github.com/spf13/cobra v1.7.0
 | 
				
			||||||
 | 
						github.com/stretchr/testify v1.8.4
 | 
				
			||||||
 | 
						gopkg.in/yaml.v2 v2.4.0
 | 
				
			||||||
 | 
						helm.sh/helm/v3 v3.12.2
 | 
				
			||||||
 | 
						k8s.io/api v0.29.0-alpha.0
 | 
				
			||||||
 | 
						k8s.io/apimachinery v0.29.0-alpha.0
 | 
				
			||||||
 | 
						sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3
 | 
				
			||||||
 | 
						sigs.k8s.io/yaml v1.3.0
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						cloud.google.com/go/compute v1.23.0 // indirect
 | 
				
			||||||
 | 
						cloud.google.com/go/compute/metadata v0.2.3 // indirect
 | 
				
			||||||
 | 
						cloud.google.com/go/iam v1.1.1 // indirect
 | 
				
			||||||
 | 
						cloud.google.com/go/kms v1.15.2 // indirect
 | 
				
			||||||
 | 
						dario.cat/mergo v1.0.0 // indirect
 | 
				
			||||||
 | 
						filippo.io/age v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
 | 
				
			||||||
 | 
						github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2 // indirect
 | 
				
			||||||
 | 
						github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 // indirect
 | 
				
			||||||
 | 
						github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
 | 
				
			||||||
 | 
						github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 | 
				
			||||||
 | 
						github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/BurntSushi/toml v1.3.2 // indirect
 | 
				
			||||||
 | 
						github.com/MakeNowJust/heredoc v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/Masterminds/goutils v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/Masterminds/semver/v3 v3.2.1 // indirect
 | 
				
			||||||
 | 
						github.com/Masterminds/sprig/v3 v3.2.3 // indirect
 | 
				
			||||||
 | 
						github.com/Masterminds/squirrel v1.5.4 // indirect
 | 
				
			||||||
 | 
						github.com/Microsoft/go-winio v0.6.1 // indirect
 | 
				
			||||||
 | 
						github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
 | 
				
			||||||
 | 
						github.com/acomagu/bufpipe v1.0.4 // indirect
 | 
				
			||||||
 | 
						github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2 v1.21.0 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/config v1.18.39 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/credentials v1.13.37 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/internal/ini v1.3.42 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/service/kms v1.24.5 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/service/sso v1.13.6 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.6 // indirect
 | 
				
			||||||
 | 
						github.com/aws/aws-sdk-go-v2/service/sts v1.21.5 // indirect
 | 
				
			||||||
 | 
						github.com/aws/smithy-go v1.14.2 // indirect
 | 
				
			||||||
 | 
						github.com/beorn7/perks v1.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/blang/semver v3.5.1+incompatible // indirect
 | 
				
			||||||
 | 
						github.com/cenkalti/backoff/v3 v3.2.2 // indirect
 | 
				
			||||||
 | 
						github.com/cespare/xxhash/v2 v2.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/chai2010/gettext-go v1.0.2 // indirect
 | 
				
			||||||
 | 
						github.com/cloudflare/circl v1.3.3 // indirect
 | 
				
			||||||
 | 
						github.com/containerd/containerd v1.7.0 // indirect
 | 
				
			||||||
 | 
						github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 | 
				
			||||||
 | 
						github.com/cyphar/filepath-securejoin v0.2.3 // indirect
 | 
				
			||||||
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/docker/cli v23.0.1+incompatible // indirect
 | 
				
			||||||
 | 
						github.com/docker/distribution v2.8.2+incompatible // indirect
 | 
				
			||||||
 | 
						github.com/docker/docker v23.0.1+incompatible // indirect
 | 
				
			||||||
 | 
						github.com/docker/docker-credential-helpers v0.7.0 // indirect
 | 
				
			||||||
 | 
						github.com/docker/go-connections v0.4.0 // indirect
 | 
				
			||||||
 | 
						github.com/docker/go-metrics v0.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/docker/go-units v0.5.0 // indirect
 | 
				
			||||||
 | 
						github.com/emicklei/go-restful/v3 v3.10.1 // indirect
 | 
				
			||||||
 | 
						github.com/emirpasic/gods v1.18.1 // indirect
 | 
				
			||||||
 | 
						github.com/evanphx/json-patch v5.6.0+incompatible // indirect
 | 
				
			||||||
 | 
						github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
 | 
				
			||||||
 | 
						github.com/fatih/color v1.15.0 // indirect
 | 
				
			||||||
 | 
						github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/fluxcd/pkg/apis/kustomize v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/fluxcd/pkg/apis/meta v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/getsops/gopgagent v0.0.0-20170926210634-4d7ea76ff71a // indirect
 | 
				
			||||||
 | 
						github.com/go-errors/errors v1.4.2 // indirect
 | 
				
			||||||
 | 
						github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 | 
				
			||||||
 | 
						github.com/go-git/go-billy/v5 v5.4.1 // indirect
 | 
				
			||||||
 | 
						github.com/go-gorp/gorp/v3 v3.0.5 // indirect
 | 
				
			||||||
 | 
						github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-logr/logr v1.2.4 // indirect
 | 
				
			||||||
 | 
						github.com/go-logr/stdr v1.2.2 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/jsonpointer v0.19.6 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/jsonreference v0.20.2 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag v0.22.3 // indirect
 | 
				
			||||||
 | 
						github.com/gobwas/glob v0.2.3 // indirect
 | 
				
			||||||
 | 
						github.com/gogo/protobuf v1.3.2 // indirect
 | 
				
			||||||
 | 
						github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 | 
				
			||||||
 | 
						github.com/golang/protobuf v1.5.3 // indirect
 | 
				
			||||||
 | 
						github.com/google/btree v1.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/google/gnostic-models v0.6.8 // indirect
 | 
				
			||||||
 | 
						github.com/google/go-cmp v0.5.9 // indirect
 | 
				
			||||||
 | 
						github.com/google/gofuzz v1.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/google/s2a-go v0.1.7 // indirect
 | 
				
			||||||
 | 
						github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 | 
				
			||||||
 | 
						github.com/google/uuid v1.3.1 // indirect
 | 
				
			||||||
 | 
						github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
 | 
				
			||||||
 | 
						github.com/googleapis/gax-go/v2 v2.12.0 // indirect
 | 
				
			||||||
 | 
						github.com/gorilla/mux v1.8.0 // indirect
 | 
				
			||||||
 | 
						github.com/gosuri/uitable v0.0.4 // indirect
 | 
				
			||||||
 | 
						github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect
 | 
				
			||||||
 | 
						github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/errwrap v1.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-multierror v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-rootcerts v1.0.2 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-sockaddr v1.0.2 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/hcl v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/vault/api v1.10.0 // indirect
 | 
				
			||||||
 | 
						github.com/huandu/xstrings v1.4.0 // indirect
 | 
				
			||||||
 | 
						github.com/imdario/mergo v0.3.13 // indirect
 | 
				
			||||||
 | 
						github.com/inconshreveable/mousetrap v1.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 | 
				
			||||||
 | 
						github.com/jmoiron/sqlx v1.3.5 // indirect
 | 
				
			||||||
 | 
						github.com/josharian/intern v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/json-iterator/go v1.1.12 // indirect
 | 
				
			||||||
 | 
						github.com/kevinburke/ssh_config v1.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/klauspost/compress v1.16.0 // indirect
 | 
				
			||||||
 | 
						github.com/kylelemons/godebug v1.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
 | 
				
			||||||
 | 
						github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
 | 
				
			||||||
 | 
						github.com/lib/pq v1.10.9 // indirect
 | 
				
			||||||
 | 
						github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
 | 
				
			||||||
 | 
						github.com/mailru/easyjson v0.7.7 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-colorable v0.1.13 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-isatty v0.0.17 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-runewidth v0.0.9 // indirect
 | 
				
			||||||
 | 
						github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 | 
				
			||||||
 | 
						github.com/mitchellh/copystructure v1.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/mitchellh/go-homedir v1.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/mitchellh/go-wordwrap v1.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
				
			||||||
 | 
						github.com/mitchellh/reflectwalk v1.0.2 // indirect
 | 
				
			||||||
 | 
						github.com/moby/locker v1.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/moby/spdystream v0.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
 | 
				
			||||||
 | 
						github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 | 
				
			||||||
 | 
						github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
				
			||||||
 | 
						github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
 | 
				
			||||||
 | 
						github.com/morikuni/aec v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 | 
				
			||||||
 | 
						github.com/opencontainers/go-digest v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
 | 
				
			||||||
 | 
						github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
 | 
				
			||||||
 | 
						github.com/pjbgf/sha1cd v0.3.0 // indirect
 | 
				
			||||||
 | 
						github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
 | 
				
			||||||
 | 
						github.com/pkg/errors v0.9.1 // indirect
 | 
				
			||||||
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/prometheus/client_golang v1.16.0 // indirect
 | 
				
			||||||
 | 
						github.com/prometheus/client_model v0.4.0 // indirect
 | 
				
			||||||
 | 
						github.com/prometheus/common v0.44.0 // indirect
 | 
				
			||||||
 | 
						github.com/prometheus/procfs v0.10.1 // indirect
 | 
				
			||||||
 | 
						github.com/rubenv/sql-migrate v1.3.1 // indirect
 | 
				
			||||||
 | 
						github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/ryanuber/go-glob v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/sergi/go-diff v1.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/shopspring/decimal v1.3.1 // indirect
 | 
				
			||||||
 | 
						github.com/skeema/knownhosts v1.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/spf13/cast v1.5.0 // indirect
 | 
				
			||||||
 | 
						github.com/spf13/pflag v1.0.5 // indirect
 | 
				
			||||||
 | 
						github.com/urfave/cli v1.22.14 // indirect
 | 
				
			||||||
 | 
						github.com/xanzy/ssh-agent v0.3.3 // indirect
 | 
				
			||||||
 | 
						github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 | 
				
			||||||
 | 
						github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 | 
				
			||||||
 | 
						github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/xlab/treeprint v1.2.0 // indirect
 | 
				
			||||||
 | 
						go.opencensus.io v0.24.0 // indirect
 | 
				
			||||||
 | 
						go.opentelemetry.io/otel v1.14.0 // indirect
 | 
				
			||||||
 | 
						go.opentelemetry.io/otel/trace v1.14.0 // indirect
 | 
				
			||||||
 | 
						go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
 | 
				
			||||||
 | 
						golang.org/x/crypto v0.13.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/mod v0.10.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/net v0.15.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/oauth2 v0.12.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/sync v0.3.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/sys v0.12.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/term v0.12.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/text v0.13.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/time v0.3.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/tools v0.9.1 // indirect
 | 
				
			||||||
 | 
						google.golang.org/api v0.141.0 // indirect
 | 
				
			||||||
 | 
						google.golang.org/appengine v1.6.7 // indirect
 | 
				
			||||||
 | 
						google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
 | 
				
			||||||
 | 
						google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect
 | 
				
			||||||
 | 
						google.golang.org/genproto/googleapis/rpc v0.0.0-20230913181813-007df8e322eb // indirect
 | 
				
			||||||
 | 
						google.golang.org/grpc v1.58.1 // indirect
 | 
				
			||||||
 | 
						google.golang.org/protobuf v1.31.0 // indirect
 | 
				
			||||||
 | 
						gopkg.in/inf.v0 v0.9.1 // indirect
 | 
				
			||||||
 | 
						gopkg.in/ini.v1 v1.67.0 // indirect
 | 
				
			||||||
 | 
						gopkg.in/warnings.v0 v0.1.2 // indirect
 | 
				
			||||||
 | 
						gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
				
			||||||
 | 
						k8s.io/apiextensions-apiserver v0.27.3 // indirect
 | 
				
			||||||
 | 
						k8s.io/apiserver v0.27.3 // indirect
 | 
				
			||||||
 | 
						k8s.io/cli-runtime v0.29.0-alpha.0 // indirect
 | 
				
			||||||
 | 
						k8s.io/client-go v0.29.0-alpha.0 // indirect
 | 
				
			||||||
 | 
						k8s.io/component-base v0.29.0-alpha.0 // indirect
 | 
				
			||||||
 | 
						k8s.io/klog/v2 v2.100.1 // indirect
 | 
				
			||||||
 | 
						k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
 | 
				
			||||||
 | 
						k8s.io/kubectl v0.27.2 // indirect
 | 
				
			||||||
 | 
						k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect
 | 
				
			||||||
 | 
						oras.land/oras-go v1.2.3 // indirect
 | 
				
			||||||
 | 
						sigs.k8s.io/controller-runtime v0.15.0 // indirect
 | 
				
			||||||
 | 
						sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
 | 
				
			||||||
 | 
						sigs.k8s.io/kustomize/kyaml v0.14.3 // indirect
 | 
				
			||||||
 | 
						sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										11
									
								
								internal/build/build.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								internal/build/build.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					package build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					* Build time variables, if you don't want to use Makefile for building,
 | 
				
			||||||
 | 
					*  you still might have a look at to see how they should be configured
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						Version    = "dev-0.0.0"
 | 
				
			||||||
 | 
						CommitHash = "n/a"
 | 
				
			||||||
 | 
						BuildTime  = "n/a"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										178
									
								
								internal/controller/controller.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								internal/controller/controller.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,178 @@
 | 
				
			|||||||
 | 
					package controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/providers"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/diff"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/githelper"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/helmhelper"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/kustomize"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/sopshelper"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/workdir"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/config"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/lockfile"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/release"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ReadTheConfig(path string) (*config.Config, error) {
 | 
				
			||||||
 | 
						conf, err := config.NewConfigFromFile(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return conf, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// func cloneSnapshoot(gh githelper.Githelper, snapshotDir, snapshotBranch string) error {
 | 
				
			||||||
 | 
						// if err := gh.CloneRepo(snapshotBranch, snapshotUrl, false); err != nil {
 | 
				
			||||||
 | 
							// return err
 | 
				
			||||||
 | 
						// }
 | 
				
			||||||
 | 
						// return nil
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Sync(definedWorkdirPath, sshKeyPath string, conf *config.Config, dry bool, diffArg string) error {
 | 
				
			||||||
 | 
						// Start by creating a directory where everything should be happening
 | 
				
			||||||
 | 
						configPath := filepath.Dir(conf.ConfigPath)
 | 
				
			||||||
 | 
						workdirPath, err := workdir.CreateWorkdir(definedWorkdirPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Prepare helm repositories
 | 
				
			||||||
 | 
						for _, repository := range conf.Repositories {
 | 
				
			||||||
 | 
							if err := repository.KindFromUrl(); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Configure a git client
 | 
				
			||||||
 | 
						gh := githelper.NewGit(sshKeyPath)
 | 
				
			||||||
 | 
						// if len(diffArg) > 0 {
 | 
				
			||||||
 | 
							// snapshotDir := fmt.Sprint("%s/.snapshot", workdirPath)
 | 
				
			||||||
 | 
							// cloneSnapshoot(gh, snapshotDir, diffArg)
 | 
				
			||||||
 | 
						// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The main logic starts here
 | 
				
			||||||
 | 
						for _, cluster := range conf.Clusters {
 | 
				
			||||||
 | 
							// Create a dir for the cluster git repo
 | 
				
			||||||
 | 
							clusterWorkdirPath := fmt.Sprintf("%s/%s", workdirPath, cluster.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Init a gitops provider (Currently onle flux is supported)
 | 
				
			||||||
 | 
							provider, err := providers.NewProvider(cluster.Provider, clusterWorkdirPath, conf.SopsBin, gh)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := cluster.CloneRepo(gh, clusterWorkdirPath, dry); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := cluster.BootstrapRepo(gh, clusterWorkdirPath, dry); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Read the lockfile generated by the shoebill
 | 
				
			||||||
 | 
							lockfileData, err := lockfile.NewFromFile(clusterWorkdirPath)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							currentRepositories, err := lockfileData.ReposFromLockfile()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := conf.Releases.PopulateRepositories(conf.Repositories); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Init the helm client
 | 
				
			||||||
 | 
							hh := helmhelper.NewHelm()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Init the sops client
 | 
				
			||||||
 | 
							sops := sopshelper.NewSops()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, release := range conf.Releases {
 | 
				
			||||||
 | 
								err := release.VersionHandler(workdirPath, hh)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if len(diffArg) > 0 {
 | 
				
			||||||
 | 
									_, err := hh.PullChart(workdirPath, release.ToHelmReleaseData())
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if err := hh.RenderChart(workdirPath, release.ToHelmReleaseData()); err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := release.ValuesHandler(configPath); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := release.SecretsHandler(configPath, sops); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							releaseObj := release.FindReleaseByNames(cluster.Releases, conf.Releases)
 | 
				
			||||||
 | 
							cluster.PopulateReleases(releaseObj)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							releasesCurrent, err := release.ReleasesFromLockfile(lockfileData, conf.Repositories)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(diffArg) > 0 {
 | 
				
			||||||
 | 
								for _, releaseCurrent := range releasesCurrent {
 | 
				
			||||||
 | 
									hh.PullChart(workdirPath, releaseCurrent.ToHelmReleaseData())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Compare releases from the lockfile to ones from the current cluster config
 | 
				
			||||||
 | 
							diffReleases, err := diff.DiffReleases(releasesCurrent, cluster.ReleasesObj)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							lockfile, diffRepos, err := diffReleases.Resolve(currentRepositories, clusterWorkdirPath)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							hashesPerRelease, err := provider.SyncState(diffReleases, diffRepos)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := kustomize.Generate(clusterWorkdirPath, gh); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							lockfile.AddHashes(hashesPerRelease)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := lockfile.WriteToFile(clusterWorkdirPath); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if _, err := gh.AddAllAndCommit(clusterWorkdirPath, "Update the lockfile"); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !dry {
 | 
				
			||||||
 | 
								if err := gh.Push(clusterWorkdirPath); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !dry {
 | 
				
			||||||
 | 
							if err := workdir.RemoveWorkdir(workdirPath); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										426
									
								
								internal/providers/flux.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										426
									
								
								internal/providers/flux.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,426 @@
 | 
				
			|||||||
 | 
					package providers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/diff"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/githelper"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/lockfile"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/release"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/repository"
 | 
				
			||||||
 | 
						release_v2beta1 "github.com/fluxcd/helm-controller/api/v2beta1"
 | 
				
			||||||
 | 
						helmrepo_v1beta2 "github.com/fluxcd/source-controller/api/v1beta2"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"sigs.k8s.io/yaml"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Flux struct {
 | 
				
			||||||
 | 
						path    string
 | 
				
			||||||
 | 
						sopsBin string
 | 
				
			||||||
 | 
						gh      githelper.Githelper
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func FluxProvider(path, sopsBin string, gh githelper.Githelper) Provider {
 | 
				
			||||||
 | 
						return &Flux{
 | 
				
			||||||
 | 
							path:    path,
 | 
				
			||||||
 | 
							sopsBin: sopsBin,
 | 
				
			||||||
 | 
							gh:      gh,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: This function is ugly as hell, I need to do something about it
 | 
				
			||||||
 | 
					func (f *Flux) SyncState(releasesDiffs diff.ReleasesDiffs, repoDiffs diff.RepositoriesDiffs) (lockfile.HashesPerReleases, error) {
 | 
				
			||||||
 | 
						entity := "repository"
 | 
				
			||||||
 | 
						srcDirPath := fmt.Sprintf("%s/src", f.path)
 | 
				
			||||||
 | 
						// It should containe either release or repository as a prefix, because it's how files are called
 | 
				
			||||||
 | 
						entiryFilePath := fmt.Sprintf("%s/%s-", srcDirPath, entity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, repository := range repoDiffs {
 | 
				
			||||||
 | 
							switch repository.Action {
 | 
				
			||||||
 | 
							case diff.ACTION_ADD:
 | 
				
			||||||
 | 
								manifest, err := GenerateRepository(repository.Wished)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								file, err := os.Create(entiryFilePath + repository.Wished.Name + ".yaml")
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if _, err := file.Write(manifest); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								message := `chore(repository): Add a repo: %s
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							A new repo added to the cluster:
 | 
				
			||||||
 | 
							  Name: %s 
 | 
				
			||||||
 | 
							  URL: %s
 | 
				
			||||||
 | 
							`
 | 
				
			||||||
 | 
								if _, err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, repository.Wished.Name, repository.Wished.Name, repository.Wished.URL)); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case diff.ACTION_PRESERVE:
 | 
				
			||||||
 | 
							case diff.ACTION_UPDATE:
 | 
				
			||||||
 | 
								manifest, err := GenerateRepository(repository.Wished)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err := os.WriteFile(entiryFilePath+repository.Wished.Name+".yaml", manifest, os.ModeExclusive); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								message := `chore(repository): Update a repo: %s
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							A repo has been updated:
 | 
				
			||||||
 | 
							  Name: %s 
 | 
				
			||||||
 | 
							  URL: %s
 | 
				
			||||||
 | 
							`
 | 
				
			||||||
 | 
								if _, err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, repository.Wished.Name, repository.Wished.Name, repository.Wished.URL)); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case diff.ACTION_DELETE:
 | 
				
			||||||
 | 
								if err := os.Remove(entiryFilePath + repository.Current.Name + ".yaml"); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								message := `chore(repository): Removed a repo: %s
 | 
				
			||||||
 | 
							A repo has been removed from the cluster:
 | 
				
			||||||
 | 
							  Name: %s 
 | 
				
			||||||
 | 
							  URL: %s
 | 
				
			||||||
 | 
							`
 | 
				
			||||||
 | 
								if _, err := f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, repository.Current.Name, repository.Current.Name, repository.Current.URL)); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("unknown action is requests: %s", repository.Action)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						hashesPerReleases := lockfile.HashesPerReleases{}
 | 
				
			||||||
 | 
						entity = "release"
 | 
				
			||||||
 | 
						entiryFilePath = fmt.Sprintf("%s/%s-", srcDirPath, entity)
 | 
				
			||||||
 | 
						for _, release := range releasesDiffs {
 | 
				
			||||||
 | 
							var hash string
 | 
				
			||||||
 | 
							var err error
 | 
				
			||||||
 | 
							if err := SyncValues(release.Current, release.Wished, srcDirPath); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := SyncSecrets(release.Current, release.Wished, f.path, f.sopsBin); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch release.Action {
 | 
				
			||||||
 | 
							case diff.ACTION_ADD:
 | 
				
			||||||
 | 
								manifest, err := GenerateRelease(release.Wished)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								file, err := os.Create(entiryFilePath + release.Wished.Release + ".yaml")
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if _, err := file.Write(manifest); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								message := `chore(release): Add a new release: %s
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							A new release is added to the cluster:
 | 
				
			||||||
 | 
							  Name: %s 
 | 
				
			||||||
 | 
							  Namespace: %s
 | 
				
			||||||
 | 
							  Version: %s
 | 
				
			||||||
 | 
							  Chart: %s/%s
 | 
				
			||||||
 | 
							`
 | 
				
			||||||
 | 
								hash, err = f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, release.Wished.Release, release.Wished.Release, release.Wished.Namespace, release.Wished.Version, release.Wished.Repository, release.Wished.Release))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case diff.ACTION_UPDATE:
 | 
				
			||||||
 | 
								manifest, err := GenerateRelease(release.Wished)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := os.WriteFile(entiryFilePath+release.Wished.Release+".yaml", manifest, os.ModeExclusive); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								message := `chore(release): Update a release: %s
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
							A release has been updated:
 | 
				
			||||||
 | 
								Name: %s 
 | 
				
			||||||
 | 
								Namespace: %s
 | 
				
			||||||
 | 
								Version: %s
 | 
				
			||||||
 | 
								Chart: %s/%s
 | 
				
			||||||
 | 
							  `
 | 
				
			||||||
 | 
								hash, err = f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, release.Wished.Release, release.Wished.Release, release.Wished.Namespace, release.Wished.Version, release.Wished.Repository, release.Wished.Release))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case diff.ACTION_DELETE:
 | 
				
			||||||
 | 
								if err := os.Remove(entiryFilePath + release.Current.Release + ".yaml"); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								message := `chore(release): Remove a release: %s
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
								A release has been removed from the cluster:
 | 
				
			||||||
 | 
								Name: %s 
 | 
				
			||||||
 | 
								Namespace: %s
 | 
				
			||||||
 | 
								Version: %s
 | 
				
			||||||
 | 
								Chart: %s/%s
 | 
				
			||||||
 | 
							  `
 | 
				
			||||||
 | 
								hash, err = f.gh.AddAllAndCommit(f.path, fmt.Sprintf(message, release.Current.Release, release.Current.Release, release.Current.Namespace, release.Current.Version, release.Current.Repository, release.Current.Release))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("unknown action is requests: %s", release.Action)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							hashPerRelease := &lockfile.HashPerRelease{
 | 
				
			||||||
 | 
								Release:    release.Wished.Release,
 | 
				
			||||||
 | 
								Namespace:  release.Wished.Namespace,
 | 
				
			||||||
 | 
								CommitHash: hash,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							hashesPerReleases = append(hashesPerReleases, hashPerRelease)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return hashesPerReleases, nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GenerateRepository(repo *repository.Repository) ([]byte, error) {
 | 
				
			||||||
 | 
						fluxRepo := &helmrepo_v1beta2.HelmRepository{
 | 
				
			||||||
 | 
							TypeMeta: v1.TypeMeta{
 | 
				
			||||||
 | 
								Kind:       helmrepo_v1beta2.HelmRepositoryKind,
 | 
				
			||||||
 | 
								APIVersion: helmrepo_v1beta2.GroupVersion.String(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							ObjectMeta: v1.ObjectMeta{
 | 
				
			||||||
 | 
								Name:      repo.Name,
 | 
				
			||||||
 | 
								Namespace: "flux-system",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: helmrepo_v1beta2.HelmRepositorySpec{
 | 
				
			||||||
 | 
								URL:  repo.URL,
 | 
				
			||||||
 | 
								Type: repo.Kind,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return yaml.Marshal(&fluxRepo)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GenerateRelease and put
 | 
				
			||||||
 | 
					func GenerateRelease(release *release.Release) ([]byte, error) {
 | 
				
			||||||
 | 
						fluxRelease := &release_v2beta1.HelmRelease{
 | 
				
			||||||
 | 
							TypeMeta: v1.TypeMeta{
 | 
				
			||||||
 | 
								Kind:       release_v2beta1.HelmReleaseKind,
 | 
				
			||||||
 | 
								APIVersion: release_v2beta1.GroupVersion.String(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							ObjectMeta: v1.ObjectMeta{
 | 
				
			||||||
 | 
								Name:      release.Release,
 | 
				
			||||||
 | 
								Namespace: "flux-system",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: release_v2beta1.HelmReleaseSpec{
 | 
				
			||||||
 | 
								Chart: release_v2beta1.HelmChartTemplate{
 | 
				
			||||||
 | 
									Spec: release_v2beta1.HelmChartTemplateSpec{
 | 
				
			||||||
 | 
										Chart:   release.Chart,
 | 
				
			||||||
 | 
										Version: release.Version,
 | 
				
			||||||
 | 
										SourceRef: release_v2beta1.CrossNamespaceObjectReference{
 | 
				
			||||||
 | 
											Kind:      helmrepo_v1beta2.HelmRepositoryKind,
 | 
				
			||||||
 | 
											Name:      release.RepositoryObj.Name,
 | 
				
			||||||
 | 
											Namespace: "flux-system",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								ReleaseName: release.Release,
 | 
				
			||||||
 | 
								Install: &release_v2beta1.Install{
 | 
				
			||||||
 | 
									CRDs:            release_v2beta1.Create,
 | 
				
			||||||
 | 
									CreateNamespace: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								TargetNamespace: release.Namespace,
 | 
				
			||||||
 | 
								ValuesFrom:      []release_v2beta1.ValuesReference{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, v := range release.Values {
 | 
				
			||||||
 | 
							filename := fmt.Sprintf("%s-%s", release.Release, filepath.Base(v))
 | 
				
			||||||
 | 
							fluxRelease.Spec.ValuesFrom = append(fluxRelease.Spec.ValuesFrom, release_v2beta1.ValuesReference{
 | 
				
			||||||
 | 
								Kind:      "ConfigMap",
 | 
				
			||||||
 | 
								Name:      filename,
 | 
				
			||||||
 | 
								ValuesKey: filename,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, v := range release.Secrets {
 | 
				
			||||||
 | 
							filename := fmt.Sprintf("%s-%s", release.Release, filepath.Base(v))
 | 
				
			||||||
 | 
							fluxRelease.Spec.ValuesFrom = append(fluxRelease.Spec.ValuesFrom, release_v2beta1.ValuesReference{
 | 
				
			||||||
 | 
								Kind:      "Secret",
 | 
				
			||||||
 | 
								Name:      filename,
 | 
				
			||||||
 | 
								ValuesKey: filename,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return yaml.Marshal(&fluxRelease)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SyncValues(currentRelease, wishedRelease *release.Release, secDirPath string) error {
 | 
				
			||||||
 | 
						valuesDirPath := fmt.Sprintf("%s/values", secDirPath)
 | 
				
			||||||
 | 
						if currentRelease != nil {
 | 
				
			||||||
 | 
							for _, value := range currentRelease.DestValues {
 | 
				
			||||||
 | 
								valuesFilePath := fmt.Sprintf("%s/%s", valuesDirPath, value.DestPath)
 | 
				
			||||||
 | 
								logrus.Infof("trying to remove values file: %s", valuesFilePath)
 | 
				
			||||||
 | 
								if err := os.RemoveAll(valuesFilePath); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if wishedRelease != nil {
 | 
				
			||||||
 | 
							for _, value := range wishedRelease.DestValues {
 | 
				
			||||||
 | 
								// Prepare a dir for values
 | 
				
			||||||
 | 
								valuesPath := fmt.Sprintf("%s/%s", secDirPath, "values")
 | 
				
			||||||
 | 
								valuesFilePath := fmt.Sprintf("%s/%s", valuesDirPath, value.DestPath)
 | 
				
			||||||
 | 
								logrus.Infof("trying to create values file: %s", valuesFilePath)
 | 
				
			||||||
 | 
								if err := os.MkdirAll(valuesPath, os.ModePerm); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								var valuesFile *os.File
 | 
				
			||||||
 | 
								if _, err := os.Stat(valuesFilePath); err == nil {
 | 
				
			||||||
 | 
									valuesFile, err = os.Open(valuesFilePath)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									defer valuesFile.Close()
 | 
				
			||||||
 | 
								} else if errors.Is(err, os.ErrNotExist) {
 | 
				
			||||||
 | 
									valuesFile, err = os.Create(valuesFilePath)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									defer valuesFile.Close()
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								k8sConfigMapObj := corev1.ConfigMap{
 | 
				
			||||||
 | 
									TypeMeta: v1.TypeMeta{
 | 
				
			||||||
 | 
										Kind:       "ConfigMap",
 | 
				
			||||||
 | 
										APIVersion: "v1",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									ObjectMeta: v1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:      value.DestPath,
 | 
				
			||||||
 | 
										Namespace: "flux-system",
 | 
				
			||||||
 | 
										Labels: map[string]string{
 | 
				
			||||||
 | 
											"shoebill-release": wishedRelease.Release,
 | 
				
			||||||
 | 
											"shoebill-chart":   wishedRelease.Chart,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Data: map[string]string{
 | 
				
			||||||
 | 
										value.DestPath: string(value.Data),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								valuesFileData, err := yaml.Marshal(k8sConfigMapObj)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := os.WriteFile(valuesFilePath, valuesFileData, os.ModeAppend); err != nil {
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SyncSecrets(currentRelease, wishedRelease *release.Release, workdirPath, sopsBin string) error {
 | 
				
			||||||
 | 
						secretsDirPath := fmt.Sprintf("%s/src/secrets", workdirPath)
 | 
				
			||||||
 | 
						if currentRelease != nil {
 | 
				
			||||||
 | 
							for _, secrets := range currentRelease.DestSecrets {
 | 
				
			||||||
 | 
								secretsFilePath := fmt.Sprintf("%s/%s", secretsDirPath, secrets.DestPath)
 | 
				
			||||||
 | 
								logrus.Infof("trying to remove secrets file: %s", secretsFilePath)
 | 
				
			||||||
 | 
								if err := os.RemoveAll(secretsFilePath); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if wishedRelease != nil {
 | 
				
			||||||
 | 
							for _, secrets := range wishedRelease.DestSecrets {
 | 
				
			||||||
 | 
								// Prepare a dir for secrets
 | 
				
			||||||
 | 
								secretsFilePath := fmt.Sprintf("%s/%s", secretsDirPath, secrets.DestPath)
 | 
				
			||||||
 | 
								logrus.Infof("trying to create secrets file: %s", secretsFilePath)
 | 
				
			||||||
 | 
								if err := os.MkdirAll(secretsDirPath, os.ModePerm); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								var secretsFile *os.File
 | 
				
			||||||
 | 
								if _, err := os.Stat(secretsFilePath); err == nil {
 | 
				
			||||||
 | 
									secretsFile, err = os.Open(secretsFilePath)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									defer secretsFile.Close()
 | 
				
			||||||
 | 
								} else if errors.Is(err, os.ErrNotExist) {
 | 
				
			||||||
 | 
									secretsFile, err = os.Create(secretsFilePath)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									defer secretsFile.Close()
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								k8sSecretObj := corev1.Secret{
 | 
				
			||||||
 | 
									TypeMeta: v1.TypeMeta{
 | 
				
			||||||
 | 
										Kind:       "Secret",
 | 
				
			||||||
 | 
										APIVersion: "v1",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									ObjectMeta: v1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:      secrets.DestPath,
 | 
				
			||||||
 | 
										Namespace: "flux-system",
 | 
				
			||||||
 | 
										Labels: map[string]string{
 | 
				
			||||||
 | 
											"shoebill-release": wishedRelease.Release,
 | 
				
			||||||
 | 
											"shoebill-chart":   wishedRelease.Chart,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Data: map[string][]byte{
 | 
				
			||||||
 | 
										secrets.DestPath: secrets.Data,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								secretsFileData, err := yaml.Marshal(k8sSecretObj)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := os.WriteFile(secretsFilePath, secretsFileData, os.ModeAppend); err != nil {
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// I have to use the sops binary here, because they do not provide a go package that can be used for encryption :(
 | 
				
			||||||
 | 
								sopsConfPath := fmt.Sprintf("%s/.sops.yaml", workdirPath)
 | 
				
			||||||
 | 
								cmd := exec.Command(sopsBin, "--encrypt", "--in-place", "--config", sopsConfPath, secretsFilePath)
 | 
				
			||||||
 | 
								stderr, err := cmd.StderrPipe()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err := cmd.Start(); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								errMsg, _ := io.ReadAll(stderr)
 | 
				
			||||||
 | 
								if err := cmd.Wait(); err != nil {
 | 
				
			||||||
 | 
									err := fmt.Errorf("%s - %s", err, errMsg)
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								internal/providers/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								internal/providers/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					package providers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/diff"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/githelper"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/lockfile"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Provider interface {
 | 
				
			||||||
 | 
						SyncState(diff.ReleasesDiffs, diff.RepositoriesDiffs) (lockfile.HashesPerReleases, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewProvider(provider, path, sopsBin string, gh githelper.Githelper) (Provider, error) {
 | 
				
			||||||
 | 
						switch provider {
 | 
				
			||||||
 | 
						case "flux":
 | 
				
			||||||
 | 
							return FluxProvider(path, sopsBin, gh), nil
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("provider is not supported: %s", provider)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										179
									
								
								internal/utils/diff/diff.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								internal/utils/diff/diff.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
				
			|||||||
 | 
					package diff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/lockfile"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/release"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/repository"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ReleasesDiff struct {
 | 
				
			||||||
 | 
						Action  string
 | 
				
			||||||
 | 
						Current *release.Release
 | 
				
			||||||
 | 
						Wished  *release.Release
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ReleasesDiffs []*ReleasesDiff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RepositoriesDiff struct {
 | 
				
			||||||
 | 
						Action  string
 | 
				
			||||||
 | 
						Current *repository.Repository
 | 
				
			||||||
 | 
						Wished  *repository.Repository
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type RepositoriesDiffs []*RepositoriesDiff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						ACTION_PRESERVE = "preserve"
 | 
				
			||||||
 | 
						ACTION_ADD      = "add"
 | 
				
			||||||
 | 
						ACTION_UPDATE   = "update"
 | 
				
			||||||
 | 
						ACTION_DELETE   = "delete"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO(@allanger): Naming should be better
 | 
				
			||||||
 | 
					func DiffReleases(currentReleases, wishedReleases release.Releases) (ReleasesDiffs, error) {
 | 
				
			||||||
 | 
						newDiff := ReleasesDiffs{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, currentRelease := range currentReleases {
 | 
				
			||||||
 | 
							found := false
 | 
				
			||||||
 | 
							for _, wishedRelease := range wishedReleases {
 | 
				
			||||||
 | 
								if currentRelease.Release == wishedRelease.Release {
 | 
				
			||||||
 | 
									found = true
 | 
				
			||||||
 | 
									if reflect.DeepEqual(currentRelease, wishedRelease) {
 | 
				
			||||||
 | 
										newDiff = append(newDiff, &ReleasesDiff{
 | 
				
			||||||
 | 
											Action:  ACTION_PRESERVE,
 | 
				
			||||||
 | 
											Current: currentRelease,
 | 
				
			||||||
 | 
											Wished:  wishedRelease,
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										if err := wishedRelease.RepositoryObj.KindFromUrl(); err != nil {
 | 
				
			||||||
 | 
											return nil, err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										newDiff = append(newDiff, &ReleasesDiff{
 | 
				
			||||||
 | 
											Action:  ACTION_UPDATE,
 | 
				
			||||||
 | 
											Current: currentRelease,
 | 
				
			||||||
 | 
											Wished:  wishedRelease,
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !found {
 | 
				
			||||||
 | 
								newDiff = append(newDiff, &ReleasesDiff{
 | 
				
			||||||
 | 
									Action:  ACTION_DELETE,
 | 
				
			||||||
 | 
									Current: currentRelease,
 | 
				
			||||||
 | 
									Wished:  nil,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, wishedRelease := range wishedReleases {
 | 
				
			||||||
 | 
							found := false
 | 
				
			||||||
 | 
							for _, rSrc := range currentReleases {
 | 
				
			||||||
 | 
								if rSrc.Release == wishedRelease.Release {
 | 
				
			||||||
 | 
									found = true
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !found {
 | 
				
			||||||
 | 
								if err := wishedRelease.RepositoryObj.KindFromUrl(); err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								newDiff = append(newDiff, &ReleasesDiff{
 | 
				
			||||||
 | 
									Action:  ACTION_ADD,
 | 
				
			||||||
 | 
									Current: nil,
 | 
				
			||||||
 | 
									Wished:  wishedRelease,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return newDiff, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (diff ReleasesDiffs) Resolve(currentRepositories repository.Repositories, path string) (lockfile.LockFile, RepositoriesDiffs, error) {
 | 
				
			||||||
 | 
						lockfile := lockfile.LockFile{}
 | 
				
			||||||
 | 
						wishedRepos := repository.Repositories{}
 | 
				
			||||||
 | 
						repoDiffs := RepositoriesDiffs{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, diff := range diff {
 | 
				
			||||||
 | 
							switch diff.Action {
 | 
				
			||||||
 | 
							case ACTION_ADD:
 | 
				
			||||||
 | 
								logrus.Infof("adding %s", diff.Wished.Release)
 | 
				
			||||||
 | 
								lockfile = append(lockfile, diff.Wished.LockEntry())
 | 
				
			||||||
 | 
								wishedRepos = append(wishedRepos, diff.Wished.RepositoryObj)
 | 
				
			||||||
 | 
							case ACTION_PRESERVE:
 | 
				
			||||||
 | 
								logrus.Infof("preserving %s", diff.Wished.Release)
 | 
				
			||||||
 | 
								lockfile = append(lockfile, diff.Wished.LockEntry())
 | 
				
			||||||
 | 
								wishedRepos = append(wishedRepos, diff.Wished.RepositoryObj)
 | 
				
			||||||
 | 
							case ACTION_UPDATE:
 | 
				
			||||||
 | 
								logrus.Infof("updating %s", diff.Wished.Release)
 | 
				
			||||||
 | 
								lockfile = append(lockfile, diff.Wished.LockEntry())
 | 
				
			||||||
 | 
								wishedRepos = append(wishedRepos, diff.Wished.RepositoryObj)
 | 
				
			||||||
 | 
							case ACTION_DELETE:
 | 
				
			||||||
 | 
								logrus.Infof("removing %s", diff.Current.Release)
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								return nil, nil, fmt.Errorf("unknown action is requests: %s", diff.Action)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Repo Wished is the list of all repos that are required by the current setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Existing repos are all the repos in the lockfile
 | 
				
			||||||
 | 
						for _, currentRepo := range currentRepositories {
 | 
				
			||||||
 | 
							found := false
 | 
				
			||||||
 | 
							i := 0
 | 
				
			||||||
 | 
							for _, wishedRepo := range wishedRepos {
 | 
				
			||||||
 | 
								// If there is the same repo in the wished repos and in the lockfile
 | 
				
			||||||
 | 
								// We need either to udpate, or preserve. If it can't be found, just remove
 | 
				
			||||||
 | 
								// from the reposWished slice
 | 
				
			||||||
 | 
								if wishedRepo.Name == currentRepo.Name {
 | 
				
			||||||
 | 
									// If !found, should be gone from the repo
 | 
				
			||||||
 | 
									found = true
 | 
				
			||||||
 | 
									if err := wishedRepo.ValidateURL(); err != nil {
 | 
				
			||||||
 | 
										return nil, nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if err := wishedRepo.KindFromUrl(); err != nil {
 | 
				
			||||||
 | 
										return nil, nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if !reflect.DeepEqual(wishedRepos, currentRepo) {
 | 
				
			||||||
 | 
										repoDiffs = append(repoDiffs, &RepositoriesDiff{
 | 
				
			||||||
 | 
											Action:  ACTION_UPDATE,
 | 
				
			||||||
 | 
											Current: currentRepo,
 | 
				
			||||||
 | 
											Wished:  wishedRepo,
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										repoDiffs = append(repoDiffs, &RepositoriesDiff{
 | 
				
			||||||
 | 
											Action:  ACTION_PRESERVE,
 | 
				
			||||||
 | 
											Current: currentRepo,
 | 
				
			||||||
 | 
											Wished:  wishedRepo,
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									wishedRepos[i] = wishedRepo
 | 
				
			||||||
 | 
									i++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							wishedRepos = wishedRepos[:i]
 | 
				
			||||||
 | 
							if !found {
 | 
				
			||||||
 | 
								repoDiffs = append(repoDiffs, &RepositoriesDiff{
 | 
				
			||||||
 | 
									Action:  ACTION_DELETE,
 | 
				
			||||||
 | 
									Current: currentRepo,
 | 
				
			||||||
 | 
									Wished:  nil,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, addedRepo := range wishedRepos {
 | 
				
			||||||
 | 
							repoDiffs = append(repoDiffs, &RepositoriesDiff{
 | 
				
			||||||
 | 
								Action:  ACTION_ADD,
 | 
				
			||||||
 | 
								Current: nil,
 | 
				
			||||||
 | 
								Wished:  addedRepo,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return lockfile, repoDiffs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										115
									
								
								internal/utils/githelper/git.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								internal/utils/githelper/git.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
				
			|||||||
 | 
					package githelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/config"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing"
 | 
				
			||||||
 | 
						"github.com/go-git/go-git/v5/plumbing/transport/ssh"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Git struct {
 | 
				
			||||||
 | 
						SshPrivateKeyPath string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewGit(sshPrivateKeyPath string) Githelper {
 | 
				
			||||||
 | 
						return &Git{
 | 
				
			||||||
 | 
							SshPrivateKeyPath: sshPrivateKeyPath,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (g *Git) CloneRepo(workdir, gitURL string, dry bool) error {
 | 
				
			||||||
 | 
						// TODO(@allanger): Support ssh keys with passwords
 | 
				
			||||||
 | 
						publicKeys, err := ssh.NewPublicKeysFromFile("git", g.SshPrivateKeyPath, "")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err = git.PlainClone(workdir, false, &git.CloneOptions{URL: gitURL, Auth: publicKeys})
 | 
				
			||||||
 | 
						if err != nil && !errors.Is(err, git.ErrEmptyUrls) {
 | 
				
			||||||
 | 
							logrus.Info("the repo seems to be empty, I'll try to bootsrap it")
 | 
				
			||||||
 | 
							// Initialize the repo
 | 
				
			||||||
 | 
							err := os.Mkdir(workdir, 0077700)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							r, err := git.PlainInit(workdir, false)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							logrus.Infof("adding an origin remote: %s", gitURL)
 | 
				
			||||||
 | 
							if _, err := r.CreateRemote(&config.RemoteConfig{Name: "origin", URLs: []string{gitURL}}); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							logrus.Info("getting the worktree")
 | 
				
			||||||
 | 
							w, err := r.Worktree()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := r.Storer.SetReference(plumbing.NewHashReference(plumbing.Main, plumbing.ZeroHash)); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							logrus.Info("creating an empty 'Init Commit'")
 | 
				
			||||||
 | 
							if _, err := w.Commit("Init Commit", &git.CommitOptions{
 | 
				
			||||||
 | 
								AllowEmptyCommits: true,
 | 
				
			||||||
 | 
							}); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !dry {
 | 
				
			||||||
 | 
								if err := r.Push(&git.PushOptions{RemoteName: "origin"}); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (g *Git) AddAllAndCommit(workdir, message string) (string, error) {
 | 
				
			||||||
 | 
						r, err := git.PlainOpen(workdir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						w, err := r.Worktree()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if _, err := w.Add("."); err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sha, err := w.Commit(message, &git.CommitOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return sha.String(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (g *Git) Push(workdir string) error {
 | 
				
			||||||
 | 
						r, err := git.PlainOpen(workdir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						publicKeys, err := ssh.NewPublicKeysFromFile("git", g.SshPrivateKeyPath, "")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := r.Push(&git.PushOptions{
 | 
				
			||||||
 | 
							RemoteName: "origin",
 | 
				
			||||||
 | 
							Auth:       publicKeys,
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								internal/utils/githelper/mock.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								internal/utils/githelper/mock.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					package githelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Mock struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewGitMock() Githelper {
 | 
				
			||||||
 | 
						return &Mock{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Mock) CloneRepo(workdir, gitURL string, dry bool) error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (g *Mock) AddAllAndCommit(workdir, message string) (string, error) {
 | 
				
			||||||
 | 
						return "HASH", nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func (g *Mock) Push(workdir string) error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								internal/utils/githelper/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								internal/utils/githelper/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package githelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Githelper interface {
 | 
				
			||||||
 | 
						CloneRepo(workdir, gitURL string, dry bool) error
 | 
				
			||||||
 | 
						AddAllAndCommit(workdir, message string) (string, error)
 | 
				
			||||||
 | 
						Push(workdir string) error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										180
									
								
								internal/utils/helmhelper/helm.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								internal/utils/helmhelper/helm.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,180 @@
 | 
				
			|||||||
 | 
					package helmhelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						"gopkg.in/yaml.v2"
 | 
				
			||||||
 | 
						"helm.sh/helm/v3/pkg/action"
 | 
				
			||||||
 | 
						"helm.sh/helm/v3/pkg/chart/loader"
 | 
				
			||||||
 | 
						"helm.sh/helm/v3/pkg/chartutil"
 | 
				
			||||||
 | 
						"helm.sh/helm/v3/pkg/cli"
 | 
				
			||||||
 | 
						"helm.sh/helm/v3/pkg/engine"
 | 
				
			||||||
 | 
						"helm.sh/helm/v3/pkg/getter"
 | 
				
			||||||
 | 
						"helm.sh/helm/v3/pkg/registry"
 | 
				
			||||||
 | 
						"helm.sh/helm/v3/pkg/repo"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Helm struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewHelm() Helmhelper {
 | 
				
			||||||
 | 
						return &Helm{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getDownloadDirPath(workdirPath string) string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%s/.charts", workdirPath)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getChartDirPath(downloadDirPath string, release *ReleaseData) string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%s/%s-%s-%s", downloadDirPath, release.RepositoryName, release.Chart, release.Version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Helm) PullChart(workdirPath string, release *ReleaseData) (path string, err error) {
 | 
				
			||||||
 | 
						downloadDirPath := getDownloadDirPath(workdirPath)
 | 
				
			||||||
 | 
						if err := os.MkdirAll(downloadDirPath, 0777); err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := new(action.Configuration)
 | 
				
			||||||
 | 
						cl := cli.New()
 | 
				
			||||||
 | 
						chartDir := getChartDirPath(downloadDirPath, release)
 | 
				
			||||||
 | 
						_, err = os.Stat(chartDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil && !os.IsNotExist(err) {
 | 
				
			||||||
 | 
							return "", nil
 | 
				
			||||||
 | 
						} else if os.IsNotExist(err) {
 | 
				
			||||||
 | 
							if err := os.Mkdir(chartDir, 0777); err != nil {
 | 
				
			||||||
 | 
								return "", err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							registry, err := registry.NewClient()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return "", err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var path string
 | 
				
			||||||
 | 
							// Download the chart to the workdir
 | 
				
			||||||
 | 
							if release.RepositoryKind != "oci" {
 | 
				
			||||||
 | 
								r, err := repo.NewChartRepository(&repo.Entry{
 | 
				
			||||||
 | 
									Name: release.RepositoryName,
 | 
				
			||||||
 | 
									URL:  release.RepositoryURL,
 | 
				
			||||||
 | 
								}, getter.All(cl))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return "", err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								path = r.Config.Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								path = release.RepositoryURL
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							client := action.NewPullWithOpts(action.WithConfig(config))
 | 
				
			||||||
 | 
							client.SetRegistryClient(registry)
 | 
				
			||||||
 | 
							client.DestDir = chartDir
 | 
				
			||||||
 | 
							client.Settings = cl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							chartRemote := fmt.Sprintf("%s/%s", path, release.Chart)
 | 
				
			||||||
 | 
							logrus.Infof("trying to pull: %s", chartRemote)
 | 
				
			||||||
 | 
							if _, err = client.Run(chartRemote); err != nil {
 | 
				
			||||||
 | 
								return "", err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						path, err = getChartPathFromDir(chartDir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return path, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Helm) FindLatestVersion(workdirPath string, release *ReleaseData) (version string, err error) {
 | 
				
			||||||
 | 
						downloadDirPath := getDownloadDirPath(workdirPath)
 | 
				
			||||||
 | 
						if err := os.MkdirAll(downloadDirPath, 0777); err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config := new(action.Configuration)
 | 
				
			||||||
 | 
						cl := cli.New()
 | 
				
			||||||
 | 
						chartDir := getChartDirPath(downloadDirPath, release)
 | 
				
			||||||
 | 
						chartPath, err := h.PullChart(workdirPath, release)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						showAction := action.NewShowWithConfig(action.ShowChart, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res, err := showAction.LocateChart(fmt.Sprintf("%s/%s", chartDir, chartPath), cl)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res, err = showAction.Run(res)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						chartData, err := chartFromString(res)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						logrus.Infof("the latest version of %s is %s", release.Chart, chartData.Version)
 | 
				
			||||||
 | 
						versionedChartDir := getChartDirPath(downloadDirPath, release)
 | 
				
			||||||
 | 
						os.Rename(chartDir, versionedChartDir)
 | 
				
			||||||
 | 
						return chartData.Version, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Helm) RenderChart(workdirPath string, release *ReleaseData) error {
 | 
				
			||||||
 | 
						downloadDirPath := getDownloadDirPath(workdirPath)
 | 
				
			||||||
 | 
						chartDirPath := getChartDirPath(downloadDirPath, release)
 | 
				
			||||||
 | 
						chartPath, err := getChartPathFromDir(chartDirPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						logrus.Info(fmt.Sprintf("%s/%s", chartDirPath, chartPath))
 | 
				
			||||||
 | 
						chartObj, err := loader.Load(fmt.Sprintf("%s/%s", chartDirPath, chartPath))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						values := chartutil.Values{}
 | 
				
			||||||
 | 
						values["Values"] = chartObj.Values
 | 
				
			||||||
 | 
						values["Release"] = map[string]string{
 | 
				
			||||||
 | 
							"Name":      release.Name,
 | 
				
			||||||
 | 
							"Namespace": release.Namespace,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						values["Capabilities"] = map[string]map[string]string{
 | 
				
			||||||
 | 
							"KubeVersion": {
 | 
				
			||||||
 | 
								"Version":    "v1.27.9",
 | 
				
			||||||
 | 
								"GitVersion": "v1.27.9",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						files, err := engine.Engine{Strict: false}.Render(chartObj, values)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						logrus.Info(files)
 | 
				
			||||||
 | 
						for file, data := range files {
 | 
				
			||||||
 | 
							logrus.Infof("%s - %s", file, data)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						logrus.Info("I'm here")
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getChartPathFromDir(downloadDir string) (file string, err error) {
 | 
				
			||||||
 | 
						files, err := os.ReadDir(downloadDir)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						} else if len(files) == 0 {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("expected to have one file, got zero in a dir %s", downloadDir)
 | 
				
			||||||
 | 
						} else if len(files) > 1 {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("expected to have only one file in a dir %s", downloadDir)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return files[0].Name(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func chartFromString(info string) (*ReleaseData, error) {
 | 
				
			||||||
 | 
						releaseData := new(ReleaseData)
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal([]byte(info), &releaseData); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return releaseData, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								internal/utils/helmhelper/mock.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								internal/utils/helmhelper/mock.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					package helmhelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						MOCK_LATEST_VERSION = "v1.12.1"
 | 
				
			||||||
 | 
						MOCK_CHART_PATH     = ".charts/repo-release-latest/release-latest.gz"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Mock struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewHelmMock() Helmhelper {
 | 
				
			||||||
 | 
						return &Mock{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Mock) FindLatestVersion(workdir string, release *ReleaseData) (version string, err error) {
 | 
				
			||||||
 | 
						return MOCK_LATEST_VERSION, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Mock) PullChart(workdirPath string, release *ReleaseData) (path string, err error) {
 | 
				
			||||||
 | 
						return MOCK_CHART_PATH, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Mock) RenderChart(workdirPath string, release *ReleaseData) error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								internal/utils/helmhelper/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								internal/utils/helmhelper/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					package helmhelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Helmhelper interface {
 | 
				
			||||||
 | 
						FindLatestVersion(workdirPath string, release *ReleaseData) (string, error)
 | 
				
			||||||
 | 
						PullChart(workdirPath string, release *ReleaseData) (string, error)
 | 
				
			||||||
 | 
						RenderChart(workdirPath string, release *ReleaseData) error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ReleaseData struct {
 | 
				
			||||||
 | 
						Name           string
 | 
				
			||||||
 | 
						Chart          string
 | 
				
			||||||
 | 
						Namespace      string
 | 
				
			||||||
 | 
						Version        string
 | 
				
			||||||
 | 
						RepositoryName string
 | 
				
			||||||
 | 
						RepositoryURL  string
 | 
				
			||||||
 | 
						RepositoryKind string
 | 
				
			||||||
 | 
						ValuesData     string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										179
									
								
								internal/utils/kustomize/kustomize.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								internal/utils/kustomize/kustomize.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,179 @@
 | 
				
			|||||||
 | 
					package kustomize
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"html/template"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/githelper"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						kustomize_types "sigs.k8s.io/kustomize/api/types"
 | 
				
			||||||
 | 
						"sigs.k8s.io/yaml"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Kusmtomize struct {
 | 
				
			||||||
 | 
						Files      []string
 | 
				
			||||||
 | 
						ConfigMaps []string
 | 
				
			||||||
 | 
						Secrets    []string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k *Kusmtomize) PopulateResources(path string) error {
 | 
				
			||||||
 | 
						// Main sources
 | 
				
			||||||
 | 
						files, err := os.ReadDir(fmt.Sprintf("%s/src", path))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, file := range files {
 | 
				
			||||||
 | 
							if file.Name() != ".gitkeep" && !file.IsDir() {
 | 
				
			||||||
 | 
								k.Files = append(k.Files, fmt.Sprintf("src/%s", file.Name()))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Values
 | 
				
			||||||
 | 
						files, err = os.ReadDir(fmt.Sprintf("%s/src/values", path))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, file := range files {
 | 
				
			||||||
 | 
							k.ConfigMaps = append(k.ConfigMaps, fmt.Sprintf("src/values/%s", file.Name()))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Secrets
 | 
				
			||||||
 | 
						files, err = os.ReadDir(fmt.Sprintf("%s/src/secrets", path))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, file := range files {
 | 
				
			||||||
 | 
							k.Secrets = append(k.Secrets, fmt.Sprintf("src/secrets/%s", file.Name()))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k *Kusmtomize) SecGeneratorCreate(path string) error {
 | 
				
			||||||
 | 
						logrus.Info("preparing the secret generator file")
 | 
				
			||||||
 | 
						genFileTmpl := `---
 | 
				
			||||||
 | 
					apiVersion: viaduct.ai/v1
 | 
				
			||||||
 | 
					kind: ksops
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: shoebill-secret-gen
 | 
				
			||||||
 | 
					files:
 | 
				
			||||||
 | 
					{{- range $val := . }}
 | 
				
			||||||
 | 
					  - {{ $val }}
 | 
				
			||||||
 | 
					{{- end }}
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						destFileName := fmt.Sprintf("%s/sec-generator.yaml", path)
 | 
				
			||||||
 | 
						t := template.Must(template.New("tmpl").Parse(genFileTmpl))
 | 
				
			||||||
 | 
						var genFileData bytes.Buffer
 | 
				
			||||||
 | 
						t.Execute(&genFileData, k.Secrets)
 | 
				
			||||||
 | 
						var genFile *os.File
 | 
				
			||||||
 | 
						if _, err := os.Stat(destFileName); err == nil {
 | 
				
			||||||
 | 
							genFile, err := os.Open(destFileName)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer genFile.Close()
 | 
				
			||||||
 | 
						} else if errors.Is(err, os.ErrNotExist) {
 | 
				
			||||||
 | 
							genFile, err = os.Create(destFileName)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer genFile.Close()
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := os.WriteFile(destFileName, genFileData.Bytes(), os.ModeExclusive); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k *Kusmtomize) CmGeneratorFromFiles() []kustomize_types.ConfigMapArgs {
 | 
				
			||||||
 | 
						cmGens := []kustomize_types.ConfigMapArgs{}
 | 
				
			||||||
 | 
						for _, cm := range k.ConfigMaps {
 | 
				
			||||||
 | 
							cmName := filepath.Base(cm)
 | 
				
			||||||
 | 
							cmGen := &kustomize_types.ConfigMapArgs{
 | 
				
			||||||
 | 
								GeneratorArgs: kustomize_types.GeneratorArgs{
 | 
				
			||||||
 | 
									Namespace: "flux-system",
 | 
				
			||||||
 | 
									Name:      cmName,
 | 
				
			||||||
 | 
									KvPairSources: kustomize_types.KvPairSources{
 | 
				
			||||||
 | 
										FileSources: []string{cm},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							cmGens = append(cmGens, *cmGen)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return cmGens
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Generate(path string, gh githelper.Githelper) error {
 | 
				
			||||||
 | 
						kustomize := &Kusmtomize{}
 | 
				
			||||||
 | 
						if err := kustomize.PopulateResources(path); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kustomization := kustomize_types.Kustomization{
 | 
				
			||||||
 | 
							TypeMeta: kustomize_types.TypeMeta{
 | 
				
			||||||
 | 
								Kind:       kustomize_types.KustomizationKind,
 | 
				
			||||||
 | 
								APIVersion: kustomize_types.KustomizationVersion,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							MetaData: &kustomize_types.ObjectMeta{
 | 
				
			||||||
 | 
								Name:      "helm-root",
 | 
				
			||||||
 | 
								Namespace: "flux-system",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Resources: append(kustomize.Files, kustomize.ConfigMaps...),
 | 
				
			||||||
 | 
							GeneratorOptions: &kustomize_types.GeneratorOptions{
 | 
				
			||||||
 | 
								DisableNameSuffixHash: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(kustomize.Secrets) > 0 {
 | 
				
			||||||
 | 
							kustomization.Generators = []string{"sec-generator.yaml"}
 | 
				
			||||||
 | 
							if err := kustomize.SecGeneratorCreate(path); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if err := os.RemoveAll(fmt.Sprintf("%s/sec-generator.yaml", path)); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						manifest, err := yaml.Marshal(kustomization)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						dstFilePath := path + "/kustomization.yaml"
 | 
				
			||||||
 | 
						var dstFile *os.File
 | 
				
			||||||
 | 
						if _, err = os.Stat(dstFilePath); err == nil {
 | 
				
			||||||
 | 
							dstFile, err = os.Open(dstFilePath)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer dstFile.Close()
 | 
				
			||||||
 | 
						} else if errors.Is(err, os.ErrNotExist) {
 | 
				
			||||||
 | 
							dstFile, err = os.Create(dstFilePath)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer dstFile.Close()
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := os.WriteFile(dstFilePath, manifest, os.ModeExclusive); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := gh.AddAllAndCommit(path, "Update the root kustomization"); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								internal/utils/sopshelper/mock.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								internal/utils/sopshelper/mock.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					package sopshelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SopsMock struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewSopsMock() SopsHelper {
 | 
				
			||||||
 | 
						return &SopsMock{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (sops *SopsMock) Decrypt(filepath string) ([]byte, error) {
 | 
				
			||||||
 | 
						return nil, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								internal/utils/sopshelper/sops.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								internal/utils/sopshelper/sops.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					package sopshelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						// "go.mozilla.org/sops/v3/decrypt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/getsops/sops/v3/decrypt"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Sops struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewSops() SopsHelper {
 | 
				
			||||||
 | 
						return &Sops{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func (sops Sops) Decrypt(filepath string) ([]byte, error) {
 | 
				
			||||||
 | 
						logrus.Infof("trying to decrypt: %s", filepath)
 | 
				
			||||||
 | 
						encFile, err := os.ReadFile(filepath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						res, err := decrypt.Data(encFile, "yaml")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return res, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										5
									
								
								internal/utils/sopshelper/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								internal/utils/sopshelper/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					package sopshelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SopsHelper interface {
 | 
				
			||||||
 | 
						Decrypt(filepath string) ([]byte, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										26
									
								
								internal/utils/workdir/workdir.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								internal/utils/workdir/workdir.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					package workdir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CreateWorkdir(path string) (workdir string, err error) {
 | 
				
			||||||
 | 
						if len(path) > 0 {
 | 
				
			||||||
 | 
							// Create a dir using the path
 | 
				
			||||||
 | 
							if err := os.Mkdir(path, 0777); err != nil {
 | 
				
			||||||
 | 
								return path, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// TODO(@allanger): I've got a feeling that it doesn't have to look that bad
 | 
				
			||||||
 | 
							workdir = path
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Create a temporary dir
 | 
				
			||||||
 | 
							workdir, err = os.MkdirTemp("", "shoebill")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return workdir, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return workdir, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func RemoveWorkdir(path string) (err error) {
 | 
				
			||||||
 | 
						return os.RemoveAll(path)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/cmd"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
						if err := cmd.Execute(ctx); err != nil {
 | 
				
			||||||
 | 
							logrus.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										89
									
								
								pkg/cluster/cluster.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								pkg/cluster/cluster.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					package cluster
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/githelper"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/lockfile"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/release"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Cluster struct {
 | 
				
			||||||
 | 
						// Public
 | 
				
			||||||
 | 
						Name     string
 | 
				
			||||||
 | 
						Git      string
 | 
				
			||||||
 | 
						Releases []string
 | 
				
			||||||
 | 
						Provider string
 | 
				
			||||||
 | 
						DotSops  string
 | 
				
			||||||
 | 
						// Internal
 | 
				
			||||||
 | 
						ReleasesObj release.Releases `yaml:"-"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Clusters []*Cluster
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Cluster) CloneRepo(gh githelper.Githelper, workdir string, dry bool) error {
 | 
				
			||||||
 | 
						return gh.CloneRepo(workdir, c.Git, dry)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Cluster) BootstrapRepo(gh githelper.Githelper, workdir string, dry bool) error {
 | 
				
			||||||
 | 
						// - Create an empty lockfile
 | 
				
			||||||
 | 
						lockfilePath := fmt.Sprintf("%s/%s", workdir, lockfile.LOCKFILE_NAME)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := os.Stat(lockfilePath); errors.Is(err, os.ErrNotExist) {
 | 
				
			||||||
 | 
							file, err := os.Create(lockfilePath)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if _, err := file.WriteString("[]"); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							srcDir := fmt.Sprintf("%s/src", workdir)
 | 
				
			||||||
 | 
							if err := os.MkdirAll(srcDir, 0777); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, err = os.Create(fmt.Sprintf("%s/.gitkeep", srcDir))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if _, err := gh.AddAllAndCommit(workdir, "Bootstrap the shoebill repo"); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !dry {
 | 
				
			||||||
 | 
								if err := gh.Push(workdir); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(c.DotSops) > 0 {
 | 
				
			||||||
 | 
							dotsopsPath := fmt.Sprintf("%s/.sops.yaml", workdir)
 | 
				
			||||||
 | 
							if _, err := os.Stat(dotsopsPath); errors.Is(err, os.ErrNotExist) {
 | 
				
			||||||
 | 
								file, err := os.Create(dotsopsPath)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if _, err := file.WriteString(c.DotSops); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if _, err := gh.AddAllAndCommit(workdir, "Create a sops config file"); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !dry {
 | 
				
			||||||
 | 
									if err := gh.Push(workdir); err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Cluster) PopulateReleases(releases release.Releases) {
 | 
				
			||||||
 | 
						c.ReleasesObj = releases
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Cluster) CreateNewLockfile() error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								pkg/cluster/cluster_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								pkg/cluster/cluster_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					package cluster_test
 | 
				
			||||||
							
								
								
									
										34
									
								
								pkg/config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								pkg/config/config.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/cluster"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/release"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/repository"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						"gopkg.in/yaml.v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Config struct {
 | 
				
			||||||
 | 
						Repositories repository.Repositories
 | 
				
			||||||
 | 
						Releases     release.Releases
 | 
				
			||||||
 | 
						Clusters     cluster.Clusters
 | 
				
			||||||
 | 
						ConfigPath   string `yaml:"-"`
 | 
				
			||||||
 | 
						SopsBin      string `yaml:"-"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewConfigFromFile populates the config struct from a configuration yaml file
 | 
				
			||||||
 | 
					func NewConfigFromFile(path string) (*Config, error) {
 | 
				
			||||||
 | 
						var config Config
 | 
				
			||||||
 | 
						logrus.Infof("readig the config file: %s", path)
 | 
				
			||||||
 | 
						configFile, err := os.ReadFile(path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal(configFile, &config); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						config.ConfigPath = path
 | 
				
			||||||
 | 
						return &config, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										53
									
								
								pkg/config/config_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								pkg/config/config_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					package config_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/config"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/repository"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func helperCreateFile(t *testing.T) *os.File {
 | 
				
			||||||
 | 
						f, err := os.CreateTemp("", "sample")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t.Logf("file is created: %s", f.Name())
 | 
				
			||||||
 | 
						return f
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func helperFillFile(t *testing.T, f *os.File, content string) {
 | 
				
			||||||
 | 
						_, err := f.WriteString(content)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func TestNewConfigFromFile(t *testing.T) {
 | 
				
			||||||
 | 
						f := helperCreateFile(t)
 | 
				
			||||||
 | 
						defer os.Remove(f.Name())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const configExample = `---
 | 
				
			||||||
 | 
					repositories:
 | 
				
			||||||
 | 
					  - name: test
 | 
				
			||||||
 | 
					    url: https://test.de
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
						helperFillFile(t, f, configExample)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						configGot, err := config.NewConfigFromFile(f.Name())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						repositoryWant := &repository.Repository{
 | 
				
			||||||
 | 
							Name: "test",
 | 
				
			||||||
 | 
							URL:  "https://test.de",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						configWant := &config.Config{
 | 
				
			||||||
 | 
							Repositories: repository.Repositories{repositoryWant},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, configWant.Repositories, configGot.Repositories)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										108
									
								
								pkg/lockfile/lockfile.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								pkg/lockfile/lockfile.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
				
			|||||||
 | 
					package lockfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/repository"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
						"gopkg.in/yaml.v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const LOCKFILE_NAME = "shoebill.lock.yaml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LockEntry struct {
 | 
				
			||||||
 | 
						Chart     string
 | 
				
			||||||
 | 
						Release   string
 | 
				
			||||||
 | 
						Version   string
 | 
				
			||||||
 | 
						Namespace string
 | 
				
			||||||
 | 
						RepoUrl   string
 | 
				
			||||||
 | 
						RepoName  string
 | 
				
			||||||
 | 
						GitCommit string
 | 
				
			||||||
 | 
						Values    []string
 | 
				
			||||||
 | 
						Secrets   []string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type HashPerRelease struct {
 | 
				
			||||||
 | 
						Release    string
 | 
				
			||||||
 | 
						Namespace  string
 | 
				
			||||||
 | 
						CommitHash string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					type HashesPerReleases []*HashPerRelease
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LockRepository struct {
 | 
				
			||||||
 | 
						URL  string
 | 
				
			||||||
 | 
						Name string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LockFile []*LockEntry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Init the LockFile object by reading the yaml file
 | 
				
			||||||
 | 
					func NewFromFile(lockfileDirPath string) (LockFile, error) {
 | 
				
			||||||
 | 
						var lockEntries LockFile
 | 
				
			||||||
 | 
						lockfilePath := fmt.Sprintf("%s/%s", lockfileDirPath, LOCKFILE_NAME)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logrus.Infof("reading the lockfile file: %s", lockfilePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lockFileData, err := os.ReadFile(lockfilePath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal(lockFileData, &lockEntries); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return lockEntries, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lockfile LockFile) ReposFromLockfile() (repository.Repositories, error) {
 | 
				
			||||||
 | 
						repositories := repository.Repositories{}
 | 
				
			||||||
 | 
						for _, lockentry := range lockfile {
 | 
				
			||||||
 | 
							newRepoEntry := &repository.Repository{
 | 
				
			||||||
 | 
								URL:  lockentry.RepoUrl,
 | 
				
			||||||
 | 
								Name: lockentry.RepoName,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							repositories = append(repositories, newRepoEntry)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lockfile contains an entry per a release, so one repo might be met several times
 | 
				
			||||||
 | 
						allKeys := make(map[string]bool)
 | 
				
			||||||
 | 
						dedupedRepositories := repository.Repositories{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, repo := range repositories {
 | 
				
			||||||
 | 
							if _, value := allKeys[repo.Name]; !value {
 | 
				
			||||||
 | 
								allKeys[repo.Name] = true
 | 
				
			||||||
 | 
								dedupedRepositories = append(dedupedRepositories, repo)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, repoEntry := range dedupedRepositories {
 | 
				
			||||||
 | 
							if err := repoEntry.KindFromUrl(); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return dedupedRepositories, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lf LockFile) AddHashes(hashes HashesPerReleases) {
 | 
				
			||||||
 | 
						for _, lockEntry := range lf {
 | 
				
			||||||
 | 
							for _, hash := range hashes {
 | 
				
			||||||
 | 
								if lockEntry.Namespace == hash.Namespace && lockEntry.Release == hash.Release {
 | 
				
			||||||
 | 
									lockEntry.GitCommit = hash.CommitHash
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (lf LockFile) WriteToFile(dir string) error {
 | 
				
			||||||
 | 
						lockfilePath := fmt.Sprintf("%s/%s", dir, LOCKFILE_NAME)
 | 
				
			||||||
 | 
						lockfileContent, err := yaml.Marshal(lf)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := os.WriteFile(lockfilePath, lockfileContent, os.ModeExclusive); err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										243
									
								
								pkg/release/release.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								pkg/release/release.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,243 @@
 | 
				
			|||||||
 | 
					package release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/helmhelper"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/sopshelper"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/lockfile"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/repository"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Release struct {
 | 
				
			||||||
 | 
						// Public fields, that can be set with yaml
 | 
				
			||||||
 | 
						Repository string
 | 
				
			||||||
 | 
						// Release name
 | 
				
			||||||
 | 
						Release string `yaml:"name"`
 | 
				
			||||||
 | 
						// Chart name
 | 
				
			||||||
 | 
						Chart string
 | 
				
			||||||
 | 
						// Chart version
 | 
				
			||||||
 | 
						Version string
 | 
				
			||||||
 | 
						// Namespace to install release
 | 
				
			||||||
 | 
						Namespace string
 | 
				
			||||||
 | 
						// Value files
 | 
				
			||||||
 | 
						Values []string
 | 
				
			||||||
 | 
						// Secrets SOPS encrypted
 | 
				
			||||||
 | 
						Secrets []string
 | 
				
			||||||
 | 
						// Private fields that should be pupulated during the run-time
 | 
				
			||||||
 | 
						RepositoryObj *repository.Repository `yaml:"-"`
 | 
				
			||||||
 | 
						DestValues    ValuesHolders          `yaml:"-"`
 | 
				
			||||||
 | 
						DestSecrets   ValuesHolders          `yaml:"-"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *Release) ToHelmReleaseData() *helmhelper.ReleaseData {
 | 
				
			||||||
 | 
						// valuesData = 
 | 
				
			||||||
 | 
						// for _, data := range r.DestValues {
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
						// }
 | 
				
			||||||
 | 
						return &helmhelper.ReleaseData{
 | 
				
			||||||
 | 
							Name:           r.Release,
 | 
				
			||||||
 | 
							Chart:          r.Chart,
 | 
				
			||||||
 | 
							Version:        r.Version,
 | 
				
			||||||
 | 
							Namespace:      r.Namespace,
 | 
				
			||||||
 | 
							RepositoryName: r.RepositoryObj.Name,
 | 
				
			||||||
 | 
							RepositoryURL:  r.RepositoryObj.URL,
 | 
				
			||||||
 | 
							RepositoryKind: r.RepositoryObj.Kind,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ValuesHolder struct {
 | 
				
			||||||
 | 
						SrcPath  string
 | 
				
			||||||
 | 
						DestPath string
 | 
				
			||||||
 | 
						Data     []byte
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ValuesHolders []ValuesHolder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (vhs ValuesHolders) ToStrings() []string {
 | 
				
			||||||
 | 
						values := []string{}
 | 
				
			||||||
 | 
						for _, vh := range vhs {
 | 
				
			||||||
 | 
							values = append(values, vh.DestPath)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return values
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Releases []*Release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RepositoryObjFromName gather the whole repository object by its name
 | 
				
			||||||
 | 
					func (r *Release) RepositoryObjFromName(repos repository.Repositories) error {
 | 
				
			||||||
 | 
						for _, repo := range repos {
 | 
				
			||||||
 | 
							if repo.Name == r.Repository {
 | 
				
			||||||
 | 
								r.RepositoryObj = repo
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if r.RepositoryObj == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("couldn't gather the RepositoryObj for %s", r.Repository)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Possible version placeholders
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						VERSION_LATEST = "latest"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Replace the version placeholder with the fixed version
 | 
				
			||||||
 | 
					func (r *Release) VersionHandler(dir string, hh helmhelper.Helmhelper) error {
 | 
				
			||||||
 | 
						if len(r.Version) == 0 {
 | 
				
			||||||
 | 
							r.Version = VERSION_LATEST
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						switch r.Version {
 | 
				
			||||||
 | 
						case VERSION_LATEST:
 | 
				
			||||||
 | 
							version, err := hh.FindLatestVersion(dir, r.ToHelmReleaseData())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							r.Version = version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *Release) ValuesHandler(dir string) error {
 | 
				
			||||||
 | 
						for i := range r.Values {
 | 
				
			||||||
 | 
							r.Values[i] = fmt.Sprintf("%s/%s", dir, strings.ReplaceAll(r.Values[i], "./", ""))
 | 
				
			||||||
 | 
							destValues := fmt.Sprintf("%s-%s-%s", r.Namespace, r.Release, filepath.Base(r.Values[i]))
 | 
				
			||||||
 | 
							valuesData, err := os.ReadFile(r.Values[i])
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							r.DestValues = append(r.DestValues, ValuesHolder{
 | 
				
			||||||
 | 
								SrcPath:  r.Values[i],
 | 
				
			||||||
 | 
								DestPath: destValues,
 | 
				
			||||||
 | 
								Data:     valuesData,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *Release) SecretsHandler(dir string, sops sopshelper.SopsHelper) error {
 | 
				
			||||||
 | 
						for i := range r.Secrets {
 | 
				
			||||||
 | 
							path := fmt.Sprintf("%s/%s", dir, strings.ReplaceAll(r.Secrets[i], "./", ""))
 | 
				
			||||||
 | 
							res, err := sops.Decrypt(path)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							destSecrets := fmt.Sprintf("%s-%s-%s", r.Namespace, r.Release, filepath.Base(r.Secrets[i]))
 | 
				
			||||||
 | 
							r.DestSecrets = append(r.DestSecrets, ValuesHolder{
 | 
				
			||||||
 | 
								SrcPath:  path,
 | 
				
			||||||
 | 
								DestPath: destSecrets,
 | 
				
			||||||
 | 
								Data:     res,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func FindReleaseByNames(releases []string, releasesObj Releases) Releases {
 | 
				
			||||||
 | 
						result := Releases{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, repoObj := range releasesObj {
 | 
				
			||||||
 | 
							for _, release := range releases {
 | 
				
			||||||
 | 
								if repoObj.Release == release {
 | 
				
			||||||
 | 
									result = append(result, repoObj)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helpers
 | 
				
			||||||
 | 
					func ReleasesFromLockfile(lockfile lockfile.LockFile, repos repository.Repositories) (Releases, error) {
 | 
				
			||||||
 | 
						releases := Releases{}
 | 
				
			||||||
 | 
						for _, releaseLocked := range lockfile {
 | 
				
			||||||
 | 
							release := &Release{
 | 
				
			||||||
 | 
								Repository: releaseLocked.RepoName,
 | 
				
			||||||
 | 
								Release:    releaseLocked.Release,
 | 
				
			||||||
 | 
								Chart:      releaseLocked.Chart,
 | 
				
			||||||
 | 
								Version:    releaseLocked.Version,
 | 
				
			||||||
 | 
								Namespace:  releaseLocked.Namespace,
 | 
				
			||||||
 | 
								RepositoryObj: &repository.Repository{
 | 
				
			||||||
 | 
									Name: releaseLocked.RepoName,
 | 
				
			||||||
 | 
									URL:  releaseLocked.RepoUrl,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := release.RepositoryObj.ValidateURL(); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := release.RepositoryObj.KindFromUrl(); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							releases = append(releases, release)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return releases, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *Release) LockEntry() *lockfile.LockEntry {
 | 
				
			||||||
 | 
						return &lockfile.LockEntry{
 | 
				
			||||||
 | 
							Chart:     r.Chart,
 | 
				
			||||||
 | 
							Release:   r.Release,
 | 
				
			||||||
 | 
							Version:   r.Version,
 | 
				
			||||||
 | 
							Namespace: r.Namespace,
 | 
				
			||||||
 | 
							RepoUrl:   r.RepositoryObj.URL,
 | 
				
			||||||
 | 
							RepoName:  r.RepositoryObj.Name,
 | 
				
			||||||
 | 
							Values:    r.DestValues.ToStrings(),
 | 
				
			||||||
 | 
							Secrets:   r.DestSecrets.ToStrings(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Diff struct {
 | 
				
			||||||
 | 
						Added   Releases
 | 
				
			||||||
 | 
						Deleted Releases
 | 
				
			||||||
 | 
						Updated Releases
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO(@allanger): Naming should be better
 | 
				
			||||||
 | 
					func (src Releases) Diff(dest Releases) Diff {
 | 
				
			||||||
 | 
						diff := Diff{}
 | 
				
			||||||
 | 
						for _, rSrc := range src {
 | 
				
			||||||
 | 
							found := false
 | 
				
			||||||
 | 
							for _, rDest := range dest {
 | 
				
			||||||
 | 
								logrus.Infof("comparing %s to %s", rSrc.Release, rDest.Release)
 | 
				
			||||||
 | 
								if rSrc.Release == rDest.Release {
 | 
				
			||||||
 | 
									found = true
 | 
				
			||||||
 | 
									if reflect.DeepEqual(rSrc, rDest) {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										diff.Updated = append(diff.Updated, rDest)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !found {
 | 
				
			||||||
 | 
								diff.Deleted = append(diff.Added, rSrc)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, rDest := range dest {
 | 
				
			||||||
 | 
							found := false
 | 
				
			||||||
 | 
							for _, rSrc := range src {
 | 
				
			||||||
 | 
								if rSrc.Release == rDest.Release {
 | 
				
			||||||
 | 
									found = true
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !found {
 | 
				
			||||||
 | 
								diff.Added = append(diff.Added, rDest)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return diff
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (rs *Releases) PopulateRepositories(repos repository.Repositories) error {
 | 
				
			||||||
 | 
						for _, r := range *rs {
 | 
				
			||||||
 | 
							if err := r.RepositoryObjFromName(repos); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										126
									
								
								pkg/release/release_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								pkg/release/release_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					package release_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/internal/utils/helmhelper"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/release"
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/repository"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"gopkg.in/yaml.v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestRepositoryObjFromNameExisting(t *testing.T) {
 | 
				
			||||||
 | 
						repos := []*repository.Repository{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name: "test0",
 | 
				
			||||||
 | 
								URL:  "https://test.test",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name: "test1",
 | 
				
			||||||
 | 
								URL:  "oco://test.test",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						release := &release.Release{
 | 
				
			||||||
 | 
							Repository: "test0",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := release.RepositoryObjFromName(repos)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(
 | 
				
			||||||
 | 
							t,
 | 
				
			||||||
 | 
							release.RepositoryObj.Name,
 | 
				
			||||||
 | 
							"test0",
 | 
				
			||||||
 | 
							fmt.Sprintf("unexpected repo name: %s", release.RepositoryObj.Name),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(
 | 
				
			||||||
 | 
							t,
 | 
				
			||||||
 | 
							release.RepositoryObj.URL,
 | 
				
			||||||
 | 
							"https://test.test",
 | 
				
			||||||
 | 
							fmt.Sprintf("unexpected repo url: %s", release.RepositoryObj.URL),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestRepositoryObjFromNameNonExisting(t *testing.T) {
 | 
				
			||||||
 | 
						repos := []*repository.Repository{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name: "test0",
 | 
				
			||||||
 | 
								URL:  "https://test.test",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Name: "test1",
 | 
				
			||||||
 | 
								URL:  "oco://test.test",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						release := &release.Release{
 | 
				
			||||||
 | 
							Repository: "test_notfound",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := release.RepositoryObjFromName(repos)
 | 
				
			||||||
 | 
						assert.ErrorContains(t, err,
 | 
				
			||||||
 | 
							"couldn't gather the RepositoryObj for test_notfound",
 | 
				
			||||||
 | 
							fmt.Sprintf("got an unexpected error: %s", err),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestRepositoryObjParsing(t *testing.T) {
 | 
				
			||||||
 | 
						t.Log("Repository Object should be empty after parsing")
 | 
				
			||||||
 | 
						rls := &release.Release{}
 | 
				
			||||||
 | 
						const yamlSnippet = `---
 | 
				
			||||||
 | 
					repository: test
 | 
				
			||||||
 | 
					repositoryObj: 
 | 
				
			||||||
 | 
					  name: test
 | 
				
			||||||
 | 
					  url: test.test
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
						if err := yaml.Unmarshal([]byte(yamlSnippet), &rls); err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, (*repository.Repository)(nil), rls.RepositoryObj, "release object should be empty")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestRepositoryObjFillingUp(t *testing.T) {
 | 
				
			||||||
 | 
						rls := &release.Release{
 | 
				
			||||||
 | 
							Repository: "test1",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expectedRepo := &repository.Repository{
 | 
				
			||||||
 | 
							Name: "test1",
 | 
				
			||||||
 | 
							URL:  "oci://test.test",
 | 
				
			||||||
 | 
							Kind: repository.HELM_REPO_OCI,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var repos repository.Repositories = repository.Repositories{
 | 
				
			||||||
 | 
							&repository.Repository{
 | 
				
			||||||
 | 
								Name: "test1",
 | 
				
			||||||
 | 
								URL:  "https://test.test",
 | 
				
			||||||
 | 
								Kind: repository.HELM_REPO_DEFAULT,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedRepo,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := rls.RepositoryObjFromName(repos); err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						assert.Equal(t, expectedRepo, rls.RepositoryObj, "release object should be empty")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestVersionHandlerLatest(t *testing.T) {
 | 
				
			||||||
 | 
						hh := helmhelper.NewHelmMock()
 | 
				
			||||||
 | 
						rls := &release.Release{
 | 
				
			||||||
 | 
							Repository:    "test1",
 | 
				
			||||||
 | 
							Version:       "latest",
 | 
				
			||||||
 | 
							RepositoryObj: new(repository.Repository),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := rls.VersionHandler("", hh); err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, helmhelper.MOCK_LATEST_VERSION, rls.Version, "unexpected latest version")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										68
									
								
								pkg/repository/repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								pkg/repository/repository.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					package repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					* Helm repo kinds: default/oci
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						HELM_REPO_OCI     = "oci"
 | 
				
			||||||
 | 
						HELM_REPO_DEFAULT = "default"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Repository struct {
 | 
				
			||||||
 | 
						Name string
 | 
				
			||||||
 | 
						URL  string
 | 
				
			||||||
 | 
						Kind string `yaml:"-"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Repositories []*Repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ValidateURL returns error if the repo URL doens't follow the format
 | 
				
			||||||
 | 
					func (r *Repository) ValidateURL() error {
 | 
				
			||||||
 | 
						// An regex that should check if a string is a valid repo URL
 | 
				
			||||||
 | 
						const urlRegex = "^(http|https|oci):\\/\\/.*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						valid, err := regexp.MatchString(urlRegex, r.URL)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !valid {
 | 
				
			||||||
 | 
							return fmt.Errorf("it's not a valid repo URL: %s", r.URL)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// KindFromUrl sets Repository.Kind according to the prefix of an URL
 | 
				
			||||||
 | 
					func (r *Repository) KindFromUrl() error {
 | 
				
			||||||
 | 
						// It panics if URL is not valid,
 | 
				
			||||||
 | 
						// but invalid url should not pass the ValidateURL function
 | 
				
			||||||
 | 
						if err := r.ValidateURL(); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						prefix := r.URL[:strings.IndexByte(r.URL, ':')]
 | 
				
			||||||
 | 
						switch prefix {
 | 
				
			||||||
 | 
						case "oci":
 | 
				
			||||||
 | 
							r.Kind = HELM_REPO_OCI
 | 
				
			||||||
 | 
						case "https", "http":
 | 
				
			||||||
 | 
							r.Kind = HELM_REPO_DEFAULT
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return fmt.Errorf("unknown repo kind: %s", prefix)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (rs Repositories) NameByUrl(repoURL string) (string, error) {
 | 
				
			||||||
 | 
						for _, r := range rs {
 | 
				
			||||||
 | 
							if repoURL == r.URL {
 | 
				
			||||||
 | 
								return r.Name, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return "", fmt.Errorf("repo couldn't be found in the config: %s", repoURL)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										107
									
								
								pkg/repository/repository_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								pkg/repository/repository_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					package repository_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"git.badhouseplants.net/allanger/shoebill/pkg/repository"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateURLHttps(t *testing.T) {
 | 
				
			||||||
 | 
						repo := &repository.Repository{
 | 
				
			||||||
 | 
							Name: "test",
 | 
				
			||||||
 | 
							URL:  "https://test.test",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err := repo.ValidateURL()
 | 
				
			||||||
 | 
						assert.NoError(t, err, fmt.Sprintf("unexpected err occured: %s", err))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateURLOci(t *testing.T) {
 | 
				
			||||||
 | 
						repo := &repository.Repository{
 | 
				
			||||||
 | 
							Name: "test",
 | 
				
			||||||
 | 
							URL:  "oci://test.test",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err := repo.ValidateURL()
 | 
				
			||||||
 | 
						assert.NoError(t, err, fmt.Sprintf("unexpected err occured: %s", err))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateURLInvalid(t *testing.T) {
 | 
				
			||||||
 | 
						repo := &repository.Repository{
 | 
				
			||||||
 | 
							Name: "test",
 | 
				
			||||||
 | 
							URL:  "invalid://test.test",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err := repo.ValidateURL()
 | 
				
			||||||
 | 
						assert.ErrorContains(t, err,
 | 
				
			||||||
 | 
							"it's not a valid repo URL: invalid://test.test",
 | 
				
			||||||
 | 
							fmt.Sprintf("got unexpected err: %s", err),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateURLNonURL(t *testing.T) {
 | 
				
			||||||
 | 
						repo := &repository.Repository{
 | 
				
			||||||
 | 
							Name: "test",
 | 
				
			||||||
 | 
							URL:  "test",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err := repo.ValidateURL()
 | 
				
			||||||
 | 
						assert.ErrorContains(t, err,
 | 
				
			||||||
 | 
							"it's not a valid repo URL: test",
 | 
				
			||||||
 | 
							fmt.Sprintf("got unexpected err: %s", err),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestKindFromUrlDefaultHttps(t *testing.T) {
 | 
				
			||||||
 | 
						repo := &repository.Repository{
 | 
				
			||||||
 | 
							Name: "test",
 | 
				
			||||||
 | 
							URL:  "https://test.test",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := repo.KindFromUrl(); err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						assert.Equal(t, repo.Kind,
 | 
				
			||||||
 | 
							repository.HELM_REPO_DEFAULT,
 | 
				
			||||||
 | 
							fmt.Sprintf("got unexpected repo type: %s", repo.Kind),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestKindFromUrlDefaultHttp(t *testing.T) {
 | 
				
			||||||
 | 
						repo := &repository.Repository{
 | 
				
			||||||
 | 
							Name: "test",
 | 
				
			||||||
 | 
							URL:  "http://test.test",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := repo.KindFromUrl(); err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						assert.Equal(t, repo.Kind,
 | 
				
			||||||
 | 
							repository.HELM_REPO_DEFAULT,
 | 
				
			||||||
 | 
							fmt.Sprintf("got unexpected repo type: %s", repo.Kind),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestKindFromUrlDefaultOci(t *testing.T) {
 | 
				
			||||||
 | 
						repo := &repository.Repository{
 | 
				
			||||||
 | 
							Name: "test",
 | 
				
			||||||
 | 
							URL:  "oci://test.test",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := repo.KindFromUrl(); err != nil {
 | 
				
			||||||
 | 
							t.Error(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, repo.Kind,
 | 
				
			||||||
 | 
							repository.HELM_REPO_OCI,
 | 
				
			||||||
 | 
							fmt.Sprintf("got unexpected repo type: %s", repo.Kind),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestKindFromUrlDefaultInvalid(t *testing.T) {
 | 
				
			||||||
 | 
						repo := &repository.Repository{
 | 
				
			||||||
 | 
							Name: "test",
 | 
				
			||||||
 | 
							URL:  "invalid:url",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err := repo.KindFromUrl()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.ErrorContains(t, err,
 | 
				
			||||||
 | 
							"unknown repo kind: invalid",
 | 
				
			||||||
 | 
							fmt.Sprintf("got unexpected err: %s", err))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								scripts/build
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								scripts/build
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PACKAGE="git.badhouseplants.net/allanger/shoebill"
 | 
				
			||||||
 | 
					VERSION="$(git describe --tags --always --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' 2> /dev/null | sed 's/^.//')"
 | 
				
			||||||
 | 
					COMMIT_HASH="$(git rev-parse --short HEAD)"
 | 
				
			||||||
 | 
					BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# STEP 2: Build the ldflags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LDFLAGS=(
 | 
				
			||||||
 | 
					  "-X '${PACKAGE}/internal/build.Version=${VERSION}'"
 | 
				
			||||||
 | 
					  "-X '${PACKAGE}/internal/build.CommitHash=${COMMIT_HASH}'"
 | 
				
			||||||
 | 
					  "-X '${PACKAGE}/internal/build.BuildTime=${BUILD_TIMESTAMP}'"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# STEP 3: Actual Go build process
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go build -ldflags="${LDFLAGS[*]}"
 | 
				
			||||||
							
								
								
									
										74
									
								
								shoebill.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								shoebill.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					repositories:
 | 
				
			||||||
 | 
					  - name: metrics-server
 | 
				
			||||||
 | 
					    url: https://kubernetes-sigs.github.io/metrics-server/
 | 
				
			||||||
 | 
					  - name: jetstack
 | 
				
			||||||
 | 
					    url: https://charts.jetstack.io
 | 
				
			||||||
 | 
					  - name: istio
 | 
				
			||||||
 | 
					    url: https://istio-release.storage.googleapis.com/charts
 | 
				
			||||||
 | 
					  - name: bitnami-oci
 | 
				
			||||||
 | 
					    url: oci://registry-1.docker.io/bitnamicharts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					releases:
 | 
				
			||||||
 | 
					  - name: metrics-server
 | 
				
			||||||
 | 
					    repository: metrics-server
 | 
				
			||||||
 | 
					    chart: metrics-server
 | 
				
			||||||
 | 
					    version: 3.11.0
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: kube-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: istio-base
 | 
				
			||||||
 | 
					    repository: istio
 | 
				
			||||||
 | 
					    chart: base
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: istio-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					    version: 1.19.2
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  - name: istio-ingressgateway
 | 
				
			||||||
 | 
					    repository: istio
 | 
				
			||||||
 | 
					    chart: gateway
 | 
				
			||||||
 | 
					    version: 1.19.2
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: istio-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: istiod
 | 
				
			||||||
 | 
					    repository: istio
 | 
				
			||||||
 | 
					    version: latest
 | 
				
			||||||
 | 
					    chart: istiod
 | 
				
			||||||
 | 
					    installed: true
 | 
				
			||||||
 | 
					    namespace: istio-system
 | 
				
			||||||
 | 
					    createNamespace: false
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  - name: postgresql-server
 | 
				
			||||||
 | 
					    chart: postgresql
 | 
				
			||||||
 | 
					    repository: bitnami-oci
 | 
				
			||||||
 | 
					    namespace: postgresql-server
 | 
				
			||||||
 | 
					    version: latest
 | 
				
			||||||
 | 
					    values:
 | 
				
			||||||
 | 
					      - ./examples/one-config/values/postgresql.yaml
 | 
				
			||||||
 | 
					    secrets: 
 | 
				
			||||||
 | 
					      - ./examples/one-config/secrets/postgresql.yaml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clusters:
 | 
				
			||||||
 | 
					  - name: cluster-shoebill-test
 | 
				
			||||||
 | 
					    git: git@git.badhouseplants.net:allanger/shoebill-test.git
 | 
				
			||||||
 | 
					    dotsops: |
 | 
				
			||||||
 | 
					      creation_rules:
 | 
				
			||||||
 | 
					        - path_regex: secrets/.*.yaml
 | 
				
			||||||
 | 
					          key_groups:
 | 
				
			||||||
 | 
					          - age:
 | 
				
			||||||
 | 
					            - age16svfskd8x75g62f5uwpmgqzth52rr3wgv9m6rxchqv6v6kzmzf0qvhr2pk
 | 
				
			||||||
 | 
					    provider: flux
 | 
				
			||||||
 | 
					    releases:
 | 
				
			||||||
 | 
					      - metrics-server
 | 
				
			||||||
 | 
					      - istio-base
 | 
				
			||||||
 | 
					      - istio-ingressgateway
 | 
				
			||||||
 | 
					      - istiod
 | 
				
			||||||
 | 
					      - postgresql-server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Reference in New Issue
	
	Block a user