Compare commits
No commits in common. "6dabcefff84b93fcc5691dc7da3f3d9f621f4b16" and "8069a225d67d728ea45759a6476bfe45ad4d55f1" have entirely different histories.
6dabcefff8
...
8069a225d6
|
|
@ -1,4 +0,0 @@
|
||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
||||||
kind: Kustomization
|
|
||||||
resources:
|
|
||||||
- pipeline.yaml
|
|
||||||
|
|
@ -1,831 +0,0 @@
|
||||||
apiVersion: tekton.dev/v1
|
|
||||||
kind: Task
|
|
||||||
metadata:
|
|
||||||
name: maidn-e2e-angular-update-manifest
|
|
||||||
namespace: tekton-pipelines
|
|
||||||
spec:
|
|
||||||
stepTemplate:
|
|
||||||
env:
|
|
||||||
- name: HOME
|
|
||||||
value: /tekton/home
|
|
||||||
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"
|
|
||||||
grep -qxF " namespace: staging" "$app_dir/release.yaml" || sed -i "/^ name: $APP_NAME$/a\ namespace: staging" "$app_dir/release.yaml"
|
|
||||||
grep -qxF " namespace: staging" "$app_dir/release.yaml" || fail
|
|
||||||
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
|
|
||||||
namespace: staging
|
|
||||||
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
|
|
||||||
namespace: production
|
|
||||||
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
|
|
||||||
promotion_pr_number=$(grep -o '"number"[[:space:]]*:[[:space:]]*[0-9][0-9]*' "$pr_response" | sed -n '1s/.*:[[:space:]]*//p')
|
|
||||||
valid_pr_number "$promotion_pr_number"
|
|
||||||
printf 'Promotion PR opened or updated: %s/%s/%s/pulls/%s\n' "$FORGEJO_BASE_URL" "$FORGEJO_OWNER" "$MANIFESTS_REPO" "$promotion_pr_number"
|
|
||||||
unset forgejo_auth
|
|
||||||
else
|
|
||||||
git push origin "$MANIFESTS_BRANCH"
|
|
||||||
fi
|
|
||||||
---
|
|
||||||
apiVersion: tekton.dev/v1
|
|
||||||
kind: Task
|
|
||||||
metadata:
|
|
||||||
name: maidn-e2e-angular-wait-delivery
|
|
||||||
namespace: tekton-pipelines
|
|
||||||
spec:
|
|
||||||
params:
|
|
||||||
- name: app-name
|
|
||||||
- name: environment
|
|
||||||
- name: image
|
|
||||||
- name: tag
|
|
||||||
- name: pr-number
|
|
||||||
default: ""
|
|
||||||
results:
|
|
||||||
- name: preview-url
|
|
||||||
description: Verified preview or staging HTTPRoute URL.
|
|
||||||
stepTemplate:
|
|
||||||
env:
|
|
||||||
- name: HOME
|
|
||||||
value: /tmp
|
|
||||||
securityContext:
|
|
||||||
runAsNonRoot: true
|
|
||||||
runAsUser: 1000
|
|
||||||
runAsGroup: 1000
|
|
||||||
allowPrivilegeEscalation: false
|
|
||||||
capabilities:
|
|
||||||
drop: [ALL]
|
|
||||||
seccompProfile:
|
|
||||||
type: RuntimeDefault
|
|
||||||
steps:
|
|
||||||
- name: wait-for-traffic
|
|
||||||
image: alpine/k8s:1.33.4
|
|
||||||
env:
|
|
||||||
- name: APP_NAME
|
|
||||||
value: $(params.app-name)
|
|
||||||
- name: ENVIRONMENT
|
|
||||||
value: $(params.environment)
|
|
||||||
- name: PR_NUMBER
|
|
||||||
value: $(params.pr-number)
|
|
||||||
- name: IMAGE
|
|
||||||
value: $(params.image)
|
|
||||||
- name: TAG
|
|
||||||
value: $(params.tag)
|
|
||||||
- name: RESULT_PATH
|
|
||||||
value: $(results.preview-url.path)
|
|
||||||
script: |
|
|
||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
fail() { exit 1; }
|
|
||||||
valid_app() { case "$1" in ''|*[!a-z0-9-]*|-*|*-) fail ;; esac; [ "${#1}" -le 47 ] || fail; }
|
|
||||||
valid_pr() { case "$1" in [1-9]*) ;; *) fail ;; esac; case "$1" in *[!0-9]*) fail ;; esac; }
|
|
||||||
valid_image() { case "$1" in ''|/*|*/|*..*|*//*|*[!A-Za-z0-9._/:-]*) fail ;; esac; }
|
|
||||||
valid_tag() { [ "${#1}" -eq 40 ] || fail; case "$1" in *[!0-9a-fA-F]*) fail ;; esac; }
|
|
||||||
valid_host() { case "$1" in ''|.*|*.) fail ;; esac; case "$1" in *[!A-Za-z0-9.-]*) fail ;; esac; }
|
|
||||||
valid_app "$APP_NAME"
|
|
||||||
valid_image "$IMAGE"
|
|
||||||
valid_tag "$TAG"
|
|
||||||
case "$ENVIRONMENT" in
|
|
||||||
preview)
|
|
||||||
valid_pr "$PR_NUMBER"
|
|
||||||
namespace="$APP_NAME-pr-$PR_NUMBER"
|
|
||||||
;;
|
|
||||||
staging) namespace=staging ;;
|
|
||||||
*) fail ;;
|
|
||||||
esac
|
|
||||||
attempts=120
|
|
||||||
while :; do
|
|
||||||
deployed_image=$(kubectl -n "$namespace" get "deployment/$APP_NAME" -o jsonpath='{.spec.template.spec.containers[0].image}' 2>/dev/null || true)
|
|
||||||
[ "$deployed_image" = "$IMAGE:$TAG" ] && break
|
|
||||||
attempts=$((attempts - 1))
|
|
||||||
[ "$attempts" -gt 0 ] || fail
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
kubectl -n "$namespace" rollout status "deployment/$APP_NAME" --timeout=600s
|
|
||||||
kubectl -n "$namespace" wait --for=condition=Available "deployment/$APP_NAME" --timeout=600s
|
|
||||||
kubectl -n "$namespace" wait --for=jsonpath='{.status.parents[0].conditions[?(@.type=="Accepted")].status}'=True "httproute/$APP_NAME" --timeout=600s
|
|
||||||
host=$(kubectl -n "$namespace" get "httproute/$APP_NAME" -o jsonpath='{.spec.hostnames[0]}')
|
|
||||||
valid_host "$host"
|
|
||||||
url="https://$host"
|
|
||||||
attempts=12
|
|
||||||
while ! wget -q --spider --timeout=10 "$url" >/dev/null 2>&1; do
|
|
||||||
attempts=$((attempts - 1))
|
|
||||||
[ "$attempts" -gt 0 ] || fail
|
|
||||||
sleep 5
|
|
||||||
done
|
|
||||||
printf '%s' "$url" > "$RESULT_PATH"
|
|
||||||
---
|
|
||||||
apiVersion: tekton.dev/v1
|
|
||||||
kind: Task
|
|
||||||
metadata:
|
|
||||||
name: maidn-e2e-angular-report-delivery
|
|
||||||
namespace: tekton-pipelines
|
|
||||||
spec:
|
|
||||||
params:
|
|
||||||
- name: app-name
|
|
||||||
- name: app-repository
|
|
||||||
- name: pr-number
|
|
||||||
- name: event-action
|
|
||||||
- name: pipeline-run
|
|
||||||
- name: clone-status
|
|
||||||
- name: build-status
|
|
||||||
- name: push-status
|
|
||||||
- name: update-status
|
|
||||||
- name: readiness-status
|
|
||||||
- name: cleanup-status
|
|
||||||
volumes:
|
|
||||||
- name: delivery-status
|
|
||||||
emptyDir: {}
|
|
||||||
stepTemplate:
|
|
||||||
securityContext:
|
|
||||||
runAsNonRoot: true
|
|
||||||
runAsUser: 1000
|
|
||||||
runAsGroup: 1000
|
|
||||||
allowPrivilegeEscalation: false
|
|
||||||
capabilities:
|
|
||||||
drop: [ALL]
|
|
||||||
seccompProfile:
|
|
||||||
type: RuntimeDefault
|
|
||||||
steps:
|
|
||||||
- name: read-preview-url
|
|
||||||
image: alpine/k8s:1.33.4
|
|
||||||
env:
|
|
||||||
- name: APP_NAME
|
|
||||||
value: $(params.app-name)
|
|
||||||
- name: PR_NUMBER
|
|
||||||
value: $(params.pr-number)
|
|
||||||
- name: EVENT_ACTION
|
|
||||||
value: $(params.event-action)
|
|
||||||
- name: READINESS_STATUS
|
|
||||||
value: $(params.readiness-status)
|
|
||||||
volumeMounts:
|
|
||||||
- name: delivery-status
|
|
||||||
mountPath: /delivery
|
|
||||||
script: |
|
|
||||||
#!/bin/sh
|
|
||||||
set -eu
|
|
||||||
fail() { exit 1; }
|
|
||||||
valid_app() { case "$1" in ''|*[!a-z0-9-]*|-*|*-) fail ;; esac; [ "${#1}" -le 47 ] || fail; }
|
|
||||||
valid_pr() { case "$1" in [1-9]*) ;; *) fail ;; esac; case "$1" in *[!0-9]*) fail ;; esac; }
|
|
||||||
valid_host() { case "$1" in ''|.*|*.) fail ;; esac; case "$1" in *[!A-Za-z0-9.-]*) fail ;; esac; }
|
|
||||||
[ "$EVENT_ACTION" = closed ] && exit 0
|
|
||||||
[ "$READINESS_STATUS" = Succeeded ] || exit 0
|
|
||||||
valid_app "$APP_NAME"
|
|
||||||
valid_pr "$PR_NUMBER"
|
|
||||||
host=$(kubectl -n "$APP_NAME-pr-$PR_NUMBER" get "httproute/$APP_NAME" -o jsonpath='{.spec.hostnames[0]}')
|
|
||||||
valid_host "$host"
|
|
||||||
printf 'https://%s' "$host" > /delivery/preview-url
|
|
||||||
- name: update-pr-comment
|
|
||||||
image: python:3.13-alpine
|
|
||||||
env:
|
|
||||||
- name: FORGEJO_BASE_URL
|
|
||||||
value: "https://git.pingu.pw"
|
|
||||||
- name: APP_NAME
|
|
||||||
value: $(params.app-name)
|
|
||||||
- name: APP_REPOSITORY
|
|
||||||
value: $(params.app-repository)
|
|
||||||
- name: PR_NUMBER
|
|
||||||
value: $(params.pr-number)
|
|
||||||
- name: EVENT_ACTION
|
|
||||||
value: $(params.event-action)
|
|
||||||
- name: PIPELINE_RUN
|
|
||||||
value: $(params.pipeline-run)
|
|
||||||
- name: CLONE_STATUS
|
|
||||||
value: $(params.clone-status)
|
|
||||||
- name: BUILD_STATUS
|
|
||||||
value: $(params.build-status)
|
|
||||||
- name: PUSH_STATUS
|
|
||||||
value: $(params.push-status)
|
|
||||||
- name: UPDATE_STATUS
|
|
||||||
value: $(params.update-status)
|
|
||||||
- name: READINESS_STATUS
|
|
||||||
value: $(params.readiness-status)
|
|
||||||
- name: CLEANUP_STATUS
|
|
||||||
value: $(params.cleanup-status)
|
|
||||||
- name: FORGEJO_DELIVERY_TOKEN
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: forgejo-delivery-status
|
|
||||||
key: token
|
|
||||||
- name: TEKTON_DASHBOARD_URL
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: maidn-preview-delivery-config
|
|
||||||
key: tekton-dashboard-url
|
|
||||||
optional: true
|
|
||||||
volumeMounts:
|
|
||||||
- name: delivery-status
|
|
||||||
mountPath: /delivery
|
|
||||||
script: |
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from urllib.parse import quote, urlsplit
|
|
||||||
from urllib.request import Request, urlopen
|
|
||||||
|
|
||||||
marker = "<!-- maidn-delivery-status -->"
|
|
||||||
statuses = {"Succeeded", "Failed", "None", "Skipped", "Cancelled", "Unknown", "Pending"}
|
|
||||||
|
|
||||||
def fail():
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
def origin(value):
|
|
||||||
parsed = urlsplit(value)
|
|
||||||
if parsed.scheme != "https" or not parsed.netloc or parsed.username or parsed.password or parsed.path or parsed.query or parsed.fragment:
|
|
||||||
fail()
|
|
||||||
if not re.fullmatch(r"[A-Za-z0-9.-]+(?::[0-9]{1,5})?", parsed.netloc):
|
|
||||||
fail()
|
|
||||||
return value
|
|
||||||
|
|
||||||
def status(name):
|
|
||||||
value = os.environ.get(name, "Unknown")
|
|
||||||
return value if value in statuses else "Unknown"
|
|
||||||
|
|
||||||
def request(method, endpoint, payload=None):
|
|
||||||
data = None if payload is None else json.dumps(payload).encode()
|
|
||||||
req = Request(endpoint, data=data, method=method)
|
|
||||||
req.add_header("Authorization", "token " + token)
|
|
||||||
req.add_header("Content-Type", "application/json")
|
|
||||||
with urlopen(req, timeout=15) as response:
|
|
||||||
return json.load(response) if response.length != 0 else None
|
|
||||||
|
|
||||||
try:
|
|
||||||
base = origin(os.environ["FORGEJO_BASE_URL"])
|
|
||||||
app = os.environ["APP_NAME"]
|
|
||||||
repository = os.environ["APP_REPOSITORY"]
|
|
||||||
pr = os.environ["PR_NUMBER"]
|
|
||||||
run = os.environ["PIPELINE_RUN"]
|
|
||||||
action = os.environ["EVENT_ACTION"]
|
|
||||||
token = os.environ["FORGEJO_DELIVERY_TOKEN"]
|
|
||||||
if not re.fullmatch(r"[a-z0-9](?:[a-z0-9-]{0,45}[a-z0-9])?", app) or not re.fullmatch(r"[1-9][0-9]{0,8}", pr) or not re.fullmatch(r"[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?", run) or action not in {"opened", "reopened", "synchronize", "closed"} or not token:
|
|
||||||
fail()
|
|
||||||
owner, repo = repository.split("/", 1)
|
|
||||||
if not all(re.fullmatch(r"[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?", part) and ".." not in part for part in (owner, repo)):
|
|
||||||
fail()
|
|
||||||
task_status = {name: status(name) for name in ("CLONE_STATUS", "BUILD_STATUS", "PUSH_STATUS", "UPDATE_STATUS", "READINESS_STATUS", "CLEANUP_STATUS")}
|
|
||||||
if action == "closed":
|
|
||||||
delivery = "closed"
|
|
||||||
preview = "Preview cleanup requested."
|
|
||||||
elif task_status["READINESS_STATUS"] == "Succeeded" and task_status["UPDATE_STATUS"] == "Succeeded":
|
|
||||||
delivery = "ready"
|
|
||||||
preview_url = Path("/delivery/preview-url").read_text() if Path("/delivery/preview-url").is_file() else ""
|
|
||||||
parsed_preview = urlsplit(preview_url)
|
|
||||||
if parsed_preview.scheme != "https" or not re.fullmatch(r"[A-Za-z0-9.-]+", parsed_preview.netloc) or parsed_preview.path or parsed_preview.query or parsed_preview.fragment:
|
|
||||||
fail()
|
|
||||||
preview = "Preview: " + preview_url
|
|
||||||
else:
|
|
||||||
delivery = "failed"
|
|
||||||
preview = "Preview unavailable."
|
|
||||||
dashboard = os.environ.get("TEKTON_DASHBOARD_URL", "").strip()
|
|
||||||
run_text = "PipelineRun: `" + run + "`"
|
|
||||||
if dashboard:
|
|
||||||
dashboard = origin(dashboard.rstrip("/"))
|
|
||||||
run_text += " ([details](" + dashboard + "/#/pipelineruns/tekton-pipelines/" + quote(run, safe="") + "))"
|
|
||||||
summary = ", ".join(name.removesuffix("_STATUS").lower() + "=" + value for name, value in task_status.items())
|
|
||||||
body = "\n".join((marker, "## Maidn delivery", "Status: **" + delivery + "**", preview, run_text, "Summary: " + summary))
|
|
||||||
endpoint = base + "/api/v1/repos/" + quote(owner, safe="") + "/" + quote(repo, safe="") + "/issues/" + pr + "/comments"
|
|
||||||
comments = request("GET", endpoint + "?limit=100")
|
|
||||||
matches = [comment for comment in comments if marker in comment.get("body", "")]
|
|
||||||
if len(matches) > 1:
|
|
||||||
fail()
|
|
||||||
if matches:
|
|
||||||
request("PATCH", base + "/api/v1/repos/" + quote(owner, safe="") + "/" + quote(repo, safe="") + "/issues/comments/" + str(matches[0]["id"]), {"body": body})
|
|
||||||
else:
|
|
||||||
request("POST", endpoint, {"body": body})
|
|
||||||
except Exception:
|
|
||||||
sys.exit("delivery status update failed")
|
|
||||||
---
|
|
||||||
apiVersion: tekton.dev/v1
|
|
||||||
kind: Pipeline
|
|
||||||
metadata:
|
|
||||||
name: maidn-e2e-angular
|
|
||||||
namespace: tekton-pipelines
|
|
||||||
spec:
|
|
||||||
params:
|
|
||||||
- name: git-url
|
|
||||||
default: "https://git.pingu.pw/Maidn/maidn-e2e-angular.git"
|
|
||||||
- name: git-revision
|
|
||||||
default: "main"
|
|
||||||
- name: branch
|
|
||||||
default: "main"
|
|
||||||
- name: event-type
|
|
||||||
default: push
|
|
||||||
- name: event-action
|
|
||||||
default: ""
|
|
||||||
- name: pr-number
|
|
||||||
default: ""
|
|
||||||
- name: image
|
|
||||||
default: "registry.pingu.pw/test-org-2/maidn-e2e-angular"
|
|
||||||
- name: manifests-url
|
|
||||||
default: "https://git.pingu.pw/test-org-2/cicd-deployment-manifests.git"
|
|
||||||
- name: manifests-branch
|
|
||||||
default: "main"
|
|
||||||
- name: forgejo-base-url
|
|
||||||
default: "https://git.pingu.pw"
|
|
||||||
- name: forgejo-owner
|
|
||||||
default: "test-org-2"
|
|
||||||
- name: manifests-repo
|
|
||||||
default: "cicd-deployment-manifests"
|
|
||||||
tasks:
|
|
||||||
- name: build-layer
|
|
||||||
when:
|
|
||||||
- input: $(params.event-action)
|
|
||||||
operator: notin
|
|
||||||
values: [closed]
|
|
||||||
taskRef:
|
|
||||||
name: maidn-node-static-image
|
|
||||||
params:
|
|
||||||
- name: url
|
|
||||||
value: "https://git.pingu.pw/Maidn/maidn-e2e-angular.git"
|
|
||||||
- name: revision
|
|
||||||
value: $(params.git-revision)
|
|
||||||
- name: image
|
|
||||||
value: $(params.image)
|
|
||||||
|
|
||||||
- name: output-directory
|
|
||||||
value: "dist/maidn-e2e-angular/browser"
|
|
||||||
- name: build-configuration
|
|
||||||
value: "ci"
|
|
||||||
|
|
||||||
- name: update-preview
|
|
||||||
runAfter: [build-layer]
|
|
||||||
when:
|
|
||||||
- input: $(params.event-type)
|
|
||||||
operator: in
|
|
||||||
values: [pull_request]
|
|
||||||
- input: $(params.event-action)
|
|
||||||
operator: in
|
|
||||||
values: [opened, reopened, synchronize]
|
|
||||||
taskRef:
|
|
||||||
name: maidn-e2e-angular-update-manifest
|
|
||||||
params:
|
|
||||||
- name: manifests-url
|
|
||||||
value: $(params.manifests-url)
|
|
||||||
- name: manifests-branch
|
|
||||||
value: $(params.manifests-branch)
|
|
||||||
- name: app-name
|
|
||||||
value: "maidn-e2e-angular"
|
|
||||||
- name: app-repository
|
|
||||||
value: "Maidn/maidn-e2e-angular"
|
|
||||||
- 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: "https://git.pingu.pw/Maidn/maidn-e2e-angular.git"
|
|
||||||
- name: app-revision
|
|
||||||
value: $(params.git-revision)
|
|
||||||
- name: update-staging
|
|
||||||
runAfter: [build-layer]
|
|
||||||
when:
|
|
||||||
- input: $(params.event-type)
|
|
||||||
operator: in
|
|
||||||
values: [push]
|
|
||||||
- input: $(params.branch)
|
|
||||||
operator: in
|
|
||||||
values: ["main"]
|
|
||||||
taskRef:
|
|
||||||
name: maidn-e2e-angular-update-manifest
|
|
||||||
params:
|
|
||||||
- name: manifests-url
|
|
||||||
value: $(params.manifests-url)
|
|
||||||
- name: manifests-branch
|
|
||||||
value: $(params.manifests-branch)
|
|
||||||
- name: app-name
|
|
||||||
value: "maidn-e2e-angular"
|
|
||||||
- name: app-repository
|
|
||||||
value: "Maidn/maidn-e2e-angular"
|
|
||||||
- name: image
|
|
||||||
value: $(params.image)
|
|
||||||
- name: tag
|
|
||||||
value: $(params.git-revision)
|
|
||||||
- name: environment
|
|
||||||
value: staging
|
|
||||||
- name: wait-preview
|
|
||||||
runAfter: [update-preview]
|
|
||||||
when:
|
|
||||||
- input: $(params.event-type)
|
|
||||||
operator: in
|
|
||||||
values: [pull_request]
|
|
||||||
- input: $(params.event-action)
|
|
||||||
operator: in
|
|
||||||
values: [opened, reopened, synchronize]
|
|
||||||
taskRef:
|
|
||||||
name: maidn-e2e-angular-wait-delivery
|
|
||||||
params:
|
|
||||||
- name: app-name
|
|
||||||
value: "maidn-e2e-angular"
|
|
||||||
- name: environment
|
|
||||||
value: preview
|
|
||||||
- name: image
|
|
||||||
value: $(params.image)
|
|
||||||
- name: tag
|
|
||||||
value: $(params.git-revision)
|
|
||||||
- name: pr-number
|
|
||||||
value: $(params.pr-number)
|
|
||||||
- name: wait-staging
|
|
||||||
runAfter: [update-staging]
|
|
||||||
when:
|
|
||||||
- input: $(params.event-type)
|
|
||||||
operator: in
|
|
||||||
values: [push]
|
|
||||||
- input: $(params.branch)
|
|
||||||
operator: in
|
|
||||||
values: ["main"]
|
|
||||||
taskRef:
|
|
||||||
name: maidn-e2e-angular-wait-delivery
|
|
||||||
params:
|
|
||||||
- name: app-name
|
|
||||||
value: "maidn-e2e-angular"
|
|
||||||
- name: environment
|
|
||||||
value: staging
|
|
||||||
- name: image
|
|
||||||
value: $(params.image)
|
|
||||||
- name: tag
|
|
||||||
value: $(params.git-revision)
|
|
||||||
- name: promote-production
|
|
||||||
runAfter: [build-layer]
|
|
||||||
when:
|
|
||||||
- input: $(params.event-type)
|
|
||||||
operator: in
|
|
||||||
values: [promotion]
|
|
||||||
- input: $(params.branch)
|
|
||||||
operator: in
|
|
||||||
values: ["production"]
|
|
||||||
taskRef:
|
|
||||||
name: maidn-e2e-angular-update-manifest
|
|
||||||
params:
|
|
||||||
- name: manifests-url
|
|
||||||
value: $(params.manifests-url)
|
|
||||||
- name: manifests-branch
|
|
||||||
value: $(params.manifests-branch)
|
|
||||||
- name: app-name
|
|
||||||
value: "maidn-e2e-angular"
|
|
||||||
- name: app-repository
|
|
||||||
value: "Maidn/maidn-e2e-angular"
|
|
||||||
- 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: "maidn-e2e-angular"
|
|
||||||
- name: pr-number
|
|
||||||
value: $(params.pr-number)
|
|
||||||
- name: app-repository
|
|
||||||
value: "Maidn/maidn-e2e-angular"
|
|
||||||
finally:
|
|
||||||
- name: report-delivery
|
|
||||||
when:
|
|
||||||
- input: $(params.event-type)
|
|
||||||
operator: in
|
|
||||||
values: [pull_request]
|
|
||||||
taskRef:
|
|
||||||
name: maidn-e2e-angular-report-delivery
|
|
||||||
params:
|
|
||||||
- name: app-name
|
|
||||||
value: "maidn-e2e-angular"
|
|
||||||
- name: app-repository
|
|
||||||
value: "Maidn/maidn-e2e-angular"
|
|
||||||
- name: pr-number
|
|
||||||
value: $(params.pr-number)
|
|
||||||
- name: event-action
|
|
||||||
value: $(params.event-action)
|
|
||||||
- name: pipeline-run
|
|
||||||
value: $(context.pipelineRun.name)
|
|
||||||
- name: clone-status
|
|
||||||
value: $(tasks.build-layer.status)
|
|
||||||
- name: build-status
|
|
||||||
value: $(tasks.build-layer.status)
|
|
||||||
- name: push-status
|
|
||||||
value: $(tasks.build-layer.status)
|
|
||||||
- name: update-status
|
|
||||||
value: $(tasks.update-preview.status)
|
|
||||||
- name: readiness-status
|
|
||||||
value: $(tasks.wait-preview.status)
|
|
||||||
- name: cleanup-status
|
|
||||||
value: $(tasks.cleanup-preview.status)
|
|
||||||
7441
package-lock.json
generated
7441
package-lock.json
generated
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue