485 lines
17 KiB
Cheetah
485 lines
17 KiB
Cheetah
apiVersion: tekton.dev/v1
|
|
kind: Task
|
|
metadata:
|
|
name: {{ .AppName }}-update-manifest
|
|
namespace: tekton-pipelines
|
|
spec:
|
|
stepTemplate:
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 1000
|
|
runAsGroup: 1000
|
|
allowPrivilegeEscalation: false
|
|
capabilities:
|
|
drop: [ALL]
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
params:
|
|
- name: manifests-url
|
|
- name: manifests-branch
|
|
- name: app-name
|
|
- name: app-repository
|
|
- name: image
|
|
- name: tag
|
|
- name: environment
|
|
- name: pr-number
|
|
default: ""
|
|
- name: app-url
|
|
default: ""
|
|
- name: app-revision
|
|
default: ""
|
|
- name: forgejo-base-url
|
|
default: ""
|
|
- name: forgejo-owner
|
|
default: ""
|
|
- name: manifests-repo
|
|
default: ""
|
|
steps:
|
|
- name: update
|
|
image: alpine/git:2.47.2
|
|
env:
|
|
- name: MANIFESTS_URL
|
|
value: $(params.manifests-url)
|
|
- name: MANIFESTS_BRANCH
|
|
value: $(params.manifests-branch)
|
|
- name: APP_NAME
|
|
value: $(params.app-name)
|
|
- name: APP_REPOSITORY
|
|
value: $(params.app-repository)
|
|
- name: IMAGE
|
|
value: $(params.image)
|
|
- name: TAG
|
|
value: $(params.tag)
|
|
- name: ENVIRONMENT
|
|
value: $(params.environment)
|
|
- name: PR_NUMBER
|
|
value: $(params.pr-number)
|
|
- name: APP_URL
|
|
value: $(params.app-url)
|
|
- name: APP_REVISION
|
|
value: $(params.app-revision)
|
|
- name: FORGEJO_BASE_URL
|
|
value: $(params.forgejo-base-url)
|
|
- name: FORGEJO_OWNER
|
|
value: $(params.forgejo-owner)
|
|
- name: MANIFESTS_REPO
|
|
value: $(params.manifests-repo)
|
|
script: |
|
|
#!/bin/sh
|
|
set -eu
|
|
fail() { exit 1; }
|
|
valid_name() { case "$1" in ''|*[!a-z0-9-]*|-*|*-) fail ;; esac; }
|
|
valid_revision() { case "$1" in [A-Za-z0-9]*) ;; *) fail ;; esac; case "$1" in *[!A-Za-z0-9._/-]*|*..*|*//*|*/) fail ;; esac; }
|
|
valid_commit() { [ "${#1}" -eq 40 ] || fail; case "$1" in *[!0-9a-fA-F]*) fail ;; esac; }
|
|
valid_url() { case "$1" in https://*/*.git) ;; *) fail ;; esac; case "$1" in *[@?#]*) fail ;; esac; }
|
|
valid_repository() { case "$1" in */*) ;; *) fail ;; esac; case "$1" in *..*|*//*|/*|*/) fail ;; esac; }
|
|
valid_repository_part() { case "$1" in ''|*[!A-Za-z0-9._-]*|.*|*.) fail ;; esac; }
|
|
valid_forgejo_base_url() { case "$1" in https://*) ;; *) fail ;; esac; host=${1#https://}; case "$host" in *:*) name=${host%:*}; port=${host##*:}; case "$port" in ''|*[!0-9]*) fail ;; esac ;; *) name=$host ;; esac; case "$name" in ''|*[!A-Za-z0-9.-]*|.*|*.) fail ;; esac; }
|
|
valid_pr_number() { case "$1" in [1-9]*) ;; *) fail ;; esac; case "$1" in *[!0-9]*) fail ;; esac; [ $((${#APP_NAME} + ${#1} + 4)) -le 63 ] || fail; }
|
|
valid_name "$APP_NAME"
|
|
valid_url "$MANIFESTS_URL"
|
|
valid_revision "$MANIFESTS_BRANCH"
|
|
valid_repository "$APP_REPOSITORY"
|
|
valid_commit "$TAG"
|
|
if [ "$ENVIRONMENT" = production ]; then
|
|
valid_forgejo_base_url "$FORGEJO_BASE_URL"
|
|
valid_repository_part "$FORGEJO_OWNER"
|
|
valid_repository_part "$MANIFESTS_REPO"
|
|
[ "$MANIFESTS_URL" = "$FORGEJO_BASE_URL/$FORGEJO_OWNER/$MANIFESTS_REPO.git" ] || fail
|
|
PROMOTION_BRANCH="maidn/promotion-$APP_NAME-$TAG"
|
|
if git ls-remote --exit-code "$MANIFESTS_URL" "refs/heads/$PROMOTION_BRANCH" >/dev/null 2>&1; then
|
|
git clone --branch "$PROMOTION_BRANCH" "$MANIFESTS_URL" /tmp/manifests
|
|
else
|
|
git clone --branch "$MANIFESTS_BRANCH" "$MANIFESTS_URL" /tmp/manifests
|
|
git -C /tmp/manifests checkout -b "$PROMOTION_BRANCH"
|
|
fi
|
|
else
|
|
git clone --branch "$MANIFESTS_BRANCH" "$MANIFESTS_URL" /tmp/manifests
|
|
fi
|
|
cd /tmp/manifests
|
|
if [ "$ENVIRONMENT" = preview ]; then
|
|
valid_pr_number "$PR_NUMBER"
|
|
valid_url "$APP_URL"
|
|
valid_revision "$APP_REVISION"
|
|
app_dir="apps/previews/$APP_NAME-pr-$PR_NUMBER"
|
|
marker="$app_dir/ownership.yaml"
|
|
if [ -e "$app_dir" ]; then
|
|
[ -d "$app_dir" ] && [ ! -L "$app_dir" ] && [ -f "$marker" ] && [ ! -L "$marker" ] || fail
|
|
expected_marker=$(mktemp)
|
|
trap 'rm -f "$expected_marker"' EXIT
|
|
cat > "$expected_marker" <<EOF
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: maidn-preview-owner
|
|
namespace: $APP_NAME-pr-$PR_NUMBER
|
|
labels:
|
|
maidn.io/preview-owner: "true"
|
|
maidn.io/preview-app: "$APP_NAME"
|
|
maidn.io/preview-pr: "$PR_NUMBER"
|
|
annotations:
|
|
maidn.io/preview-repository: "$APP_REPOSITORY"
|
|
data:
|
|
app: "$APP_NAME"
|
|
repository: "$APP_REPOSITORY"
|
|
pr-number: "$PR_NUMBER"
|
|
EOF
|
|
cmp -s "$expected_marker" "$marker" || fail
|
|
fi
|
|
git clone "$APP_URL" /tmp/app
|
|
git -C /tmp/app checkout "$APP_REVISION"
|
|
[ -f /tmp/app/preview/values.yaml ] || fail
|
|
mkdir -p "$app_dir"
|
|
cp /tmp/app/preview/values.yaml "$app_dir/values.yaml"
|
|
sed -i "s/PLACEHOLDER_PR/$PR_NUMBER/g" "$app_dir/values.yaml"
|
|
cat > "$marker" <<EOF
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: maidn-preview-owner
|
|
namespace: $APP_NAME-pr-$PR_NUMBER
|
|
labels:
|
|
maidn.io/preview-owner: "true"
|
|
maidn.io/preview-app: "$APP_NAME"
|
|
maidn.io/preview-pr: "$PR_NUMBER"
|
|
annotations:
|
|
maidn.io/preview-repository: "$APP_REPOSITORY"
|
|
data:
|
|
app: "$APP_NAME"
|
|
repository: "$APP_REPOSITORY"
|
|
pr-number: "$PR_NUMBER"
|
|
EOF
|
|
cat > "$app_dir/namespace.yaml" <<EOF
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: $APP_NAME-pr-$PR_NUMBER
|
|
EOF
|
|
cat > "$app_dir/release.yaml" <<EOF
|
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
|
kind: HelmRelease
|
|
metadata:
|
|
name: $APP_NAME
|
|
namespace: $APP_NAME-pr-$PR_NUMBER
|
|
spec:
|
|
interval: 5m
|
|
chart:
|
|
spec:
|
|
chart: ./charts/$APP_NAME
|
|
sourceRef:
|
|
kind: GitRepository
|
|
name: $APP_NAME
|
|
namespace: flux-system
|
|
values:
|
|
image:
|
|
repository: $IMAGE
|
|
tag: $TAG
|
|
EOF
|
|
cat > "$app_dir/kustomization.yaml" <<EOF
|
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
kind: Kustomization
|
|
namespace: $APP_NAME-pr-$PR_NUMBER
|
|
resources:
|
|
- namespace.yaml
|
|
- ownership.yaml
|
|
- release.yaml
|
|
EOF
|
|
root=apps/previews/kustomization.yaml
|
|
grep -q '^resources:' "$root" || fail
|
|
grep -qxF " - $APP_NAME-pr-$PR_NUMBER" "$root" 2>/dev/null || printf ' - %s-pr-%s\n' "$APP_NAME" "$PR_NUMBER" >> "$root"
|
|
elif [ "$ENVIRONMENT" = staging ]; then
|
|
app_dir="apps/staging/$APP_NAME"
|
|
root=apps/staging/kustomization.yaml
|
|
if [ -e "$app_dir/release.yaml" ]; then
|
|
[ -d "$app_dir" ] && [ ! -L "$app_dir" ] && [ -f "$app_dir/release.yaml" ] && [ ! -L "$app_dir/release.yaml" ] || fail
|
|
sed -i -E "s|^([[:space:]]*tag:).*|\1 $TAG|" "$app_dir/release.yaml"
|
|
else
|
|
[ ! -e "$app_dir" ] || fail
|
|
mkdir -p "$app_dir"
|
|
cat > "$app_dir/release.yaml" <<EOF
|
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
|
kind: HelmRelease
|
|
metadata:
|
|
name: $APP_NAME
|
|
spec:
|
|
interval: 5m
|
|
chart:
|
|
spec:
|
|
chart: ./charts/$APP_NAME
|
|
sourceRef:
|
|
kind: GitRepository
|
|
name: $APP_NAME
|
|
namespace: flux-system
|
|
values:
|
|
image:
|
|
repository: $IMAGE
|
|
tag: $TAG
|
|
EOF
|
|
cat > "$app_dir/kustomization.yaml" <<EOF
|
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
kind: Kustomization
|
|
resources:
|
|
- release.yaml
|
|
EOF
|
|
if [ ! -e "$root" ]; then
|
|
mkdir -p "$(dirname "$root")"
|
|
cat > "$root" <<EOF
|
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
kind: Kustomization
|
|
resources:
|
|
EOF
|
|
fi
|
|
[ -f "$root" ] && [ ! -L "$root" ] && grep -qx 'resources:' "$root" || fail
|
|
grep -qxF " - $APP_NAME" "$root" 2>/dev/null || printf ' - %s\n' "$APP_NAME" >> "$root"
|
|
fi
|
|
elif [ "$ENVIRONMENT" = production ]; then
|
|
app_dir="apps/production/$APP_NAME"
|
|
root=apps/production/kustomization.yaml
|
|
if [ -e "$app_dir" ]; then
|
|
[ -d "$app_dir" ] && [ ! -L "$app_dir" ] || fail
|
|
fi
|
|
mkdir -p "$app_dir"
|
|
for file in "$app_dir/release.yaml" "$app_dir/kustomization.yaml"; do
|
|
[ ! -e "$file" ] || { [ -f "$file" ] && [ ! -L "$file" ]; } || fail
|
|
done
|
|
cat > "$app_dir/release.yaml" <<EOF
|
|
apiVersion: helm.toolkit.fluxcd.io/v2
|
|
kind: HelmRelease
|
|
metadata:
|
|
name: $APP_NAME
|
|
spec:
|
|
interval: 5m
|
|
chart:
|
|
spec:
|
|
chart: ./charts/$APP_NAME
|
|
sourceRef:
|
|
kind: GitRepository
|
|
name: $APP_NAME
|
|
namespace: flux-system
|
|
values:
|
|
image:
|
|
repository: $IMAGE
|
|
tag: $TAG
|
|
EOF
|
|
cat > "$app_dir/kustomization.yaml" <<EOF
|
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
kind: Kustomization
|
|
resources:
|
|
- release.yaml
|
|
EOF
|
|
if [ ! -e "$root" ]; then
|
|
mkdir -p "$(dirname "$root")"
|
|
cat > "$root" <<EOF
|
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
kind: Kustomization
|
|
resources:
|
|
EOF
|
|
fi
|
|
[ -f "$root" ] && [ ! -L "$root" ] && grep -qx 'resources:' "$root" || fail
|
|
grep -qxF " - $APP_NAME" "$root" 2>/dev/null || printf ' - %s\n' "$APP_NAME" >> "$root"
|
|
else
|
|
fail
|
|
fi
|
|
git config user.name Maidn
|
|
git config user.email maidn@free-maidn.com
|
|
git add apps
|
|
git diff --cached --quiet || git commit -m "chore: deploy $APP_NAME $TAG"
|
|
if [ "$ENVIRONMENT" = production ]; then
|
|
git push origin "HEAD:$PROMOTION_BRANCH"
|
|
credentials=$(printf 'url=%s\n\n' "$MANIFESTS_URL" | GIT_TERMINAL_PROMPT=0 git credential fill)
|
|
forgejo_user=$(printf '%s\n' "$credentials" | sed -n 's/^username=//p')
|
|
forgejo_password=$(printf '%s\n' "$credentials" | sed -n 's/^password=//p')
|
|
unset credentials
|
|
[ -n "$forgejo_user" ] && [ -n "$forgejo_password" ] || fail
|
|
forgejo_auth=$(printf '%s:%s' "$forgejo_user" "$forgejo_password" | base64 | tr -d '\n')
|
|
unset forgejo_user forgejo_password
|
|
pr_endpoint="$FORGEJO_BASE_URL/api/v1/repos/$FORGEJO_OWNER/$MANIFESTS_REPO/pulls"
|
|
pr_query="$pr_endpoint?state=open&head=$FORGEJO_OWNER%3A$PROMOTION_BRANCH"
|
|
pr_response=$(mktemp)
|
|
pr_body=$(mktemp)
|
|
trap 'rm -f "$pr_response" "$pr_body"' EXIT
|
|
open_pr_count() {
|
|
grep -q '^[[:space:]]*\[' "$pr_response" || fail
|
|
grep -o '"number"[[:space:]]*:[[:space:]]*[0-9][0-9]*' "$pr_response" | wc -l | tr -d ' '
|
|
}
|
|
wget -q -O "$pr_response" --header="Authorization: Basic $forgejo_auth" "$pr_query" || fail
|
|
case "$(open_pr_count)" in
|
|
0)
|
|
printf '{"title":"chore: promote %s %s","head":"%s","base":"%s"}' "$APP_NAME" "$TAG" "$PROMOTION_BRANCH" "$MANIFESTS_BRANCH" > "$pr_body"
|
|
wget -q -O "$pr_response" --header="Authorization: Basic $forgejo_auth" --header="Content-Type: application/json" --post-file "$pr_body" "$pr_endpoint" || true
|
|
wget -q -O "$pr_response" --header="Authorization: Basic $forgejo_auth" "$pr_query" || fail
|
|
[ "$(open_pr_count)" = 1 ] || fail
|
|
;;
|
|
1) ;;
|
|
*) fail ;;
|
|
esac
|
|
unset forgejo_auth
|
|
else
|
|
git push origin "$MANIFESTS_BRANCH"
|
|
fi
|
|
---
|
|
apiVersion: tekton.dev/v1
|
|
kind: Pipeline
|
|
metadata:
|
|
name: {{ .AppName }}
|
|
namespace: tekton-pipelines
|
|
spec:
|
|
params:
|
|
- name: git-url
|
|
default: {{ quote .AppRepoURL }}
|
|
- name: git-revision
|
|
default: {{ quote .AppRepoRef }}
|
|
- name: branch
|
|
default: {{ quote .AppRepoRef }}
|
|
- name: event-type
|
|
default: push
|
|
- name: event-action
|
|
default: ""
|
|
- name: pr-number
|
|
default: ""
|
|
- name: image
|
|
default: {{ quote .ImageRepository }}
|
|
- name: manifests-url
|
|
default: {{ quote .ManifestsURL }}
|
|
- name: manifests-branch
|
|
default: {{ quote .ManifestsBranch }}
|
|
- name: forgejo-base-url
|
|
default: {{ quote .ForgejoBaseURL }}
|
|
- name: forgejo-owner
|
|
default: {{ quote .ForgejoOwner }}
|
|
- name: manifests-repo
|
|
default: {{ quote .ManifestsRepo }}
|
|
workspaces:
|
|
- name: source
|
|
taskRunTemplate:
|
|
serviceAccountName: tekton-delivery
|
|
tasks:
|
|
- name: build-and-push
|
|
when:
|
|
- input: $(params.event-action)
|
|
operator: notin
|
|
values: [closed]
|
|
taskRef:
|
|
name: maidn-node-static-image
|
|
params:
|
|
- name: url
|
|
value: {{ quote .AppRepoURL }}
|
|
- name: revision
|
|
value: $(params.git-revision)
|
|
- name: image
|
|
value: $(params.image)
|
|
- name: output-directory
|
|
value: {{ quote .BuildOutputDirectory }}
|
|
- name: build-configuration
|
|
value: {{ quote .BuildConfiguration }}
|
|
workspaces:
|
|
- name: source
|
|
workspace: source
|
|
- name: update-preview
|
|
runAfter: [build-and-push]
|
|
when:
|
|
- input: $(params.event-type)
|
|
operator: in
|
|
values: [pull_request]
|
|
- input: $(params.event-action)
|
|
operator: in
|
|
values: [opened, reopened, synchronize]
|
|
taskRef:
|
|
name: {{ .AppName }}-update-manifest
|
|
params:
|
|
- name: manifests-url
|
|
value: $(params.manifests-url)
|
|
- name: manifests-branch
|
|
value: $(params.manifests-branch)
|
|
- name: app-name
|
|
value: {{ quote .AppName }}
|
|
- name: app-repository
|
|
value: {{ quote .AppRepository }}
|
|
- name: image
|
|
value: $(params.image)
|
|
- name: tag
|
|
value: $(params.git-revision)
|
|
- name: environment
|
|
value: preview
|
|
- name: pr-number
|
|
value: $(params.pr-number)
|
|
- name: app-url
|
|
value: {{ quote .AppRepoURL }}
|
|
- name: app-revision
|
|
value: $(params.git-revision)
|
|
- name: update-staging
|
|
runAfter: [build-and-push]
|
|
when:
|
|
- input: $(params.event-type)
|
|
operator: in
|
|
values: [push]
|
|
- input: $(params.branch)
|
|
operator: in
|
|
values: [{{ quote .AppRepoRef }}]
|
|
taskRef:
|
|
name: {{ .AppName }}-update-manifest
|
|
params:
|
|
- name: manifests-url
|
|
value: $(params.manifests-url)
|
|
- name: manifests-branch
|
|
value: $(params.manifests-branch)
|
|
- name: app-name
|
|
value: {{ quote .AppName }}
|
|
- name: app-repository
|
|
value: {{ quote .AppRepository }}
|
|
- name: image
|
|
value: $(params.image)
|
|
- name: tag
|
|
value: $(params.git-revision)
|
|
- name: environment
|
|
value: staging
|
|
- name: promote-production
|
|
runAfter: [build-and-push]
|
|
when:
|
|
- input: $(params.event-type)
|
|
operator: in
|
|
values: [promotion]
|
|
- input: $(params.branch)
|
|
operator: in
|
|
values: [{{ quote .ProductionBranch }}]
|
|
taskRef:
|
|
name: {{ .AppName }}-update-manifest
|
|
params:
|
|
- name: manifests-url
|
|
value: $(params.manifests-url)
|
|
- name: manifests-branch
|
|
value: $(params.manifests-branch)
|
|
- name: app-name
|
|
value: {{ quote .AppName }}
|
|
- name: app-repository
|
|
value: {{ quote .AppRepository }}
|
|
- name: image
|
|
value: $(params.image)
|
|
- name: tag
|
|
value: $(params.git-revision)
|
|
- name: environment
|
|
value: production
|
|
- name: forgejo-base-url
|
|
value: $(params.forgejo-base-url)
|
|
- name: forgejo-owner
|
|
value: $(params.forgejo-owner)
|
|
- name: manifests-repo
|
|
value: $(params.manifests-repo)
|
|
- name: cleanup-preview
|
|
when:
|
|
- input: $(params.event-type)
|
|
operator: in
|
|
values: [pull_request]
|
|
- input: $(params.event-action)
|
|
operator: in
|
|
values: [closed]
|
|
taskRef:
|
|
name: maidn-preview-orphan-reconciler
|
|
params:
|
|
- name: app-name
|
|
value: {{ quote .AppName }}
|
|
- name: pr-number
|
|
value: $(params.pr-number)
|
|
- name: app-repository
|
|
value: {{ quote .AppRepository }}
|