fix: use HARBOR_USERNAME secret in CI, add idempotent Harbor API setup to deploy script
- GitHub Actions: login with secrets.HARBOR_USERNAME + HARBOR_PASSWORD - deploy.sh step 7: creates stonks-oracle project, robot account, tag retention - All API calls are idempotent (safe to re-run)
This commit is contained in:
@@ -87,7 +87,7 @@ jobs:
|
|||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: admin
|
username: ${{ secrets.HARBOR_USERNAME }}
|
||||||
password: ${{ secrets.HARBOR_PASSWORD }}
|
password: ${{ secrets.HARBOR_PASSWORD }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -121,7 +121,7 @@ jobs:
|
|||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: admin
|
username: ${{ secrets.HARBOR_USERNAME }}
|
||||||
password: ${{ secrets.HARBOR_PASSWORD }}
|
password: ${{ secrets.HARBOR_PASSWORD }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -153,7 +153,7 @@ jobs:
|
|||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: admin
|
username: ${{ secrets.HARBOR_USERNAME }}
|
||||||
password: ${{ secrets.HARBOR_PASSWORD }}
|
password: ${{ secrets.HARBOR_PASSWORD }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
|
|||||||
Executable
+191
@@ -0,0 +1,191 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# deploy.sh — Deploy Harbor container registry to Kubernetes
|
||||||
|
# Run from gremlin-1: bash ~/sources/kube/harbor/deploy.sh
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
echo "=== Harbor Container Registry Deploy ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# 1. Create namespace
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo "--- Step 1: Creating namespace ---"
|
||||||
|
kubectl create namespace harbor-service --dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
echo " ✓ harbor-service namespace ready"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# 2. Proxy CA cert (for Squid SSL bump)
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo "--- Step 2: Proxy CA cert ---"
|
||||||
|
CA_CERT_PATH="/tmp/harbor-home.crt"
|
||||||
|
if curl -sf http://192.168.42.1/home.crt -o "$CA_CERT_PATH" 2>/dev/null; then
|
||||||
|
if ! kubectl get configmap proxy-ca-cert -n harbor-service > /dev/null 2>&1; then
|
||||||
|
kubectl create configmap proxy-ca-cert --from-file=ca.crt="$CA_CERT_PATH" -n harbor-service
|
||||||
|
echo " ✓ proxy-ca-cert created"
|
||||||
|
else
|
||||||
|
echo " ✓ proxy-ca-cert already exists"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ⚠ Could not fetch CA cert (non-fatal)"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# 3. Create NFS directories (requires root for mount)
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo "--- Step 3: Ensuring NFS directories ---"
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
mkdir -p /tmp/harbor-nfs-init
|
||||||
|
mount -t nfs 192.168.42.8:/volume1/Kubernetes/harbor /tmp/harbor-nfs-init 2>/dev/null || true
|
||||||
|
mkdir -p /tmp/harbor-nfs-init/data/registry
|
||||||
|
mkdir -p /tmp/harbor-nfs-init/data/redis
|
||||||
|
mkdir -p /tmp/harbor-nfs-init/data/jobservice
|
||||||
|
mkdir -p /tmp/harbor-nfs-init/data/trivy
|
||||||
|
umount /tmp/harbor-nfs-init 2>/dev/null || true
|
||||||
|
rmdir /tmp/harbor-nfs-init 2>/dev/null || true
|
||||||
|
echo " ✓ NFS directories ready"
|
||||||
|
else
|
||||||
|
echo " ⚠ Not root — skipping NFS dir creation (dirs may already exist)"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# 4. Apply PVs and PVCs
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo "--- Step 4: Applying PVs and PVCs ---"
|
||||||
|
kubectl apply -f "$SCRIPT_DIR/../pvs/harbor-pv.yaml"
|
||||||
|
kubectl apply -f "$SCRIPT_DIR/pvcs.yaml"
|
||||||
|
echo " ✓ PVs and PVCs applied"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# 5. Remove old plain Docker Registry ingress
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo "--- Step 5: Checking old registry ingress ---"
|
||||||
|
if kubectl get ingress registry-ingress -n git-server > /dev/null 2>&1; then
|
||||||
|
echo " Removing old registry ingress from git-server namespace..."
|
||||||
|
kubectl delete ingress registry-ingress -n git-server
|
||||||
|
echo " ✓ Old registry ingress removed"
|
||||||
|
else
|
||||||
|
echo " ✓ No old registry ingress found"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# 6. Install/upgrade Harbor via Helm
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo "--- Step 6: Installing Harbor ---"
|
||||||
|
helm repo add harbor https://helm.goharbor.io 2>/dev/null || true
|
||||||
|
helm repo update harbor 2>/dev/null || true
|
||||||
|
|
||||||
|
HARBOR_EXISTS=$(helm list -n harbor-service -q 2>/dev/null | grep -c harbor || true)
|
||||||
|
if [ "${HARBOR_EXISTS:-0}" -gt 0 ]; then
|
||||||
|
echo " Harbor already installed — upgrading..."
|
||||||
|
else
|
||||||
|
echo " Fresh Harbor install..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
helm upgrade --install harbor harbor/harbor \
|
||||||
|
--namespace harbor-service \
|
||||||
|
--values "$SCRIPT_DIR/values.yaml" \
|
||||||
|
--timeout 10m \
|
||||||
|
--wait
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Waiting for Harbor core to be ready..."
|
||||||
|
kubectl wait --for=condition=ready pod -l app=harbor,component=core -n harbor-service --timeout=180s || true
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
# 7. Configure Harbor via API (idempotent)
|
||||||
|
# -------------------------------------------------------
|
||||||
|
echo "--- Step 7: Configuring Harbor project and robot account ---"
|
||||||
|
HARBOR_API="https://registry.celestium.life/api/v2.0"
|
||||||
|
HARBOR_AUTH="admin:St0nks0racl3!"
|
||||||
|
|
||||||
|
# Create stonks-oracle project if it doesn't exist
|
||||||
|
if curl -sf -u "$HARBOR_AUTH" "$HARBOR_API/projects?name=stonks-oracle" | python3 -c "import sys,json; sys.exit(0 if json.load(sys.stdin) else 1)" 2>/dev/null; then
|
||||||
|
echo " ✓ Project stonks-oracle already exists"
|
||||||
|
else
|
||||||
|
curl -sf -X POST -u "$HARBOR_AUTH" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"$HARBOR_API/projects" \
|
||||||
|
-d '{"project_name":"stonks-oracle","public":true,"metadata":{"auto_scan":"true","severity":"high"},"storage_limit":-1}'
|
||||||
|
echo " ✓ Project stonks-oracle created (public, auto-scan enabled)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create CI robot account if it doesn't exist
|
||||||
|
EXISTING_ROBOT=$(curl -sf -u "$HARBOR_AUTH" "$HARBOR_API/robots" 2>/dev/null | python3 -c "
|
||||||
|
import sys, json
|
||||||
|
robots = json.load(sys.stdin)
|
||||||
|
for r in robots:
|
||||||
|
if 'ci-push' in r.get('name', ''):
|
||||||
|
print(r['name'])
|
||||||
|
break
|
||||||
|
" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$EXISTING_ROBOT" ]; then
|
||||||
|
echo " ✓ Robot account already exists: $EXISTING_ROBOT"
|
||||||
|
else
|
||||||
|
ROBOT_RESP=$(curl -sf -X POST -u "$HARBOR_AUTH" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"$HARBOR_API/robots" \
|
||||||
|
-d '{
|
||||||
|
"name":"ci-push",
|
||||||
|
"description":"CI/CD pipeline push account",
|
||||||
|
"duration":-1,
|
||||||
|
"level":"project",
|
||||||
|
"permissions":[{
|
||||||
|
"namespace":"stonks-oracle",
|
||||||
|
"kind":"project",
|
||||||
|
"access":[
|
||||||
|
{"resource":"repository","action":"push"},
|
||||||
|
{"resource":"repository","action":"pull"},
|
||||||
|
{"resource":"tag","action":"create"},
|
||||||
|
{"resource":"tag","action":"list"},
|
||||||
|
{"resource":"artifact","action":"read"}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}')
|
||||||
|
ROBOT_NAME=$(echo "$ROBOT_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['name'])")
|
||||||
|
ROBOT_SECRET=$(echo "$ROBOT_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['secret'])")
|
||||||
|
echo " ✓ Robot account created"
|
||||||
|
echo " Username: $ROBOT_NAME"
|
||||||
|
echo " Secret: $ROBOT_SECRET"
|
||||||
|
echo " ⚠ Save these — add as HARBOR_USERNAME and HARBOR_PASSWORD in your CI secrets"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set tag retention policy (idempotent — check if one exists first)
|
||||||
|
PROJECT_ID=$(curl -sf -u "$HARBOR_AUTH" "$HARBOR_API/projects?name=stonks-oracle" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['project_id'])" 2>/dev/null || true)
|
||||||
|
if [ -n "$PROJECT_ID" ]; then
|
||||||
|
EXISTING_RETENTION=$(curl -sf -u "$HARBOR_AUTH" "$HARBOR_API/retentions" 2>/dev/null || true)
|
||||||
|
if echo "$EXISTING_RETENTION" | python3 -c "import sys,json; d=json.load(sys.stdin); sys.exit(0 if isinstance(d,list) and len(d)>0 else 1)" 2>/dev/null; then
|
||||||
|
echo " ✓ Tag retention policy already exists"
|
||||||
|
else
|
||||||
|
curl -sf -X POST -u "$HARBOR_AUTH" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"$HARBOR_API/retentions" \
|
||||||
|
-d "{
|
||||||
|
\"algorithm\":\"or\",
|
||||||
|
\"scope\":{\"level\":\"project\",\"ref\":$PROJECT_ID},
|
||||||
|
\"trigger\":{\"kind\":\"Schedule\",\"settings\":{\"cron\":\"0 0 0 * * *\"}},
|
||||||
|
\"rules\":[
|
||||||
|
{\"disabled\":false,\"action\":\"retain\",\"scope_selectors\":{\"repository\":[{\"kind\":\"doublestar\",\"decoration\":\"repoMatches\",\"pattern\":\"**\"}]},\"tag_selectors\":[{\"kind\":\"doublestar\",\"decoration\":\"matches\",\"pattern\":\"latest\"}],\"params\":{}},
|
||||||
|
{\"disabled\":false,\"action\":\"retain\",\"scope_selectors\":{\"repository\":[{\"kind\":\"doublestar\",\"decoration\":\"repoMatches\",\"pattern\":\"**\"}]},\"tag_selectors\":[{\"kind\":\"doublestar\",\"decoration\":\"matches\",\"pattern\":\"**\"}],\"params\":{\"latestPushedK\":10}}
|
||||||
|
]
|
||||||
|
}" 2>/dev/null && echo " ✓ Tag retention policy created (keep latest + last 10 tags, daily)" || echo " ⚠ Could not create retention policy (non-fatal)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Harbor Deploy Complete ==="
|
||||||
|
echo ""
|
||||||
|
echo " URL: https://registry.celestium.life"
|
||||||
|
echo " Login: admin / St0nks0racl3!"
|
||||||
|
echo ""
|
||||||
|
echo " Pods:"
|
||||||
|
kubectl get pods -n harbor-service
|
||||||
Reference in New Issue
Block a user