diff --git a/.github/workflows/offline.yml b/.github/workflows/offline.yml index 8577ee54e..80d78e178 100644 --- a/.github/workflows/offline.yml +++ b/.github/workflows/offline.yml @@ -11,12 +11,14 @@ on: - '*.md' - '**/*.md' jobs: - offline: - name: Prepare offline package - # Useful to skip expensive CI when writing docs + # Build default profile and create local assets + build-default: + name: Build default profile if: "!contains(github.event.head_commit.message, 'skip ci')" runs-on: group: wire-server-deploy + outputs: + upload_name: ${{ steps.upload_name.outputs.UPLOAD_NAME }} steps: - uses: actions/checkout@v2 with: @@ -32,60 +34,177 @@ jobs: - name: Get upload name id: upload_name - run: | - # FIXME: Tag with a nice release name using the github tag... - # SOURCE_TAG=${GITHUB_REF#refs/tags/} - echo ::set-output name=UPLOAD_NAME::$GITHUB_SHA - # echo ::set-output name=UPLOAD_NAME::${SOURCE_TAG:-$GITHUB_SHA} + run: echo "UPLOAD_NAME=$GITHUB_SHA" >> $GITHUB_OUTPUT - # deafult profile build + # default profile build - name: Process the default profile build run: ./offline/default-build/build.sh env: GPG_PRIVATE_KEY: '${{ secrets.GPG_PRIVATE_KEY }}' DOCKER_LOGIN: '${{ secrets.DOCKER_LOGIN }}' - - name: Copy default build assets tarball to S3 and clean up + # Upload the assets to be shared with other jobs + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: default-build-assets + path: offline/default-build/output/assets.tgz + retention-days: 1 + + # Upload to S3 in parallel with deployment + upload-s3: + name: Upload default build to S3 + if: "!contains(github.event.head_commit.message, 'skip ci')" + needs: build-default + runs-on: + group: wire-server-deploy + steps: + - uses: actions/checkout@v2 + with: + submodules: true + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: default-build-assets + path: offline/default-build/output/ + + - name: Copy default build assets tarball to S3 run: | - # Upload tarball for each profile by specifying their OUTPUT_TAR path - aws s3 cp offline/default-build/output/assets.tgz s3://public.wire.com/artifacts/wire-server-deploy-static-${{ steps.upload_name.outputs.UPLOAD_NAME }}.tgz - echo "Uploaded to: https://s3-$AWS_REGION.amazonaws.com/public.wire.com/artifacts/wire-server-deploy-static-${{ steps.upload_name.outputs.UPLOAD_NAME }}.tgz" - # removing everything except assets.tgz as it is not required anymore in the further builds - find offline/default-build/output/ -mindepth 1 -maxdepth 1 ! -name 'assets.tgz' -exec rm -r {} + + aws s3 cp offline/default-build/output/assets.tgz s3://public.wire.com/artifacts/wire-server-deploy-static-${{ needs.build-default.outputs.upload_name }}.tgz + echo "Uploaded to: https://s3-$AWS_REGION.amazonaws.com/public.wire.com/artifacts/wire-server-deploy-static-${{ needs.build-default.outputs.upload_name }}.tgz" env: AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' AWS_REGION: "eu-west-1" + # Deploy to Hetzner in parallel with S3 upload + deploy-hetzner: + name: Deploy default build to Hetzner + if: "!contains(github.event.head_commit.message, 'skip ci')" + needs: build-default + runs-on: + group: wire-server-deploy + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: cachix/install-nix-action@v27 + - uses: cachix/cachix-action@v15 + with: + name: wire-server + signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}" + + - name: Install nix environment + run: nix-env -f default.nix -iA env + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: default-build-assets + path: offline/default-build/output/ + + - name: Install terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "^1.3.7" + terraform_wrapper: false + + - name: Deploy offline environment to hetzner + run: ./offline/cd-with-retry.sh + env: + HCLOUD_TOKEN: '${{ secrets.HCLOUD_TOKEN }}' + + # Build container in parallel + build-container: + name: Build container + if: "!contains(github.event.head_commit.message, 'skip ci')" + needs: build-default + runs-on: + group: wire-server-deploy + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: cachix/install-nix-action@v27 + - uses: cachix/cachix-action@v15 + with: + name: wire-server + signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}" + - name: Build and upload wire-server-deploy container run: | container_image=$(nix-build --no-out-link -A container) skopeo copy --retry-times 10 --dest-creds "$DOCKER_LOGIN" \ docker-archive:"$container_image" \ - "docker://quay.io/wire/wire-server-deploy:${{ steps.upload_name.outputs.UPLOAD_NAME }}" + "docker://quay.io/wire/wire-server-deploy:${{ needs.build-default.outputs.upload_name }}" env: DOCKER_LOGIN: '${{ secrets.DOCKER_LOGIN }}' - # demo profile build + # Build demo profile + build-demo: + name: Build demo profile + if: "!contains(github.event.head_commit.message, 'skip ci')" + runs-on: + group: wire-server-deploy + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: cachix/install-nix-action@v27 + - uses: cachix/cachix-action@v15 + with: + name: wire-server + signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}" + + - name: Install nix environment + run: nix-env -f default.nix -iA env + + - name: Get upload name + id: upload_name + run: echo "UPLOAD_NAME=$GITHUB_SHA" >> $GITHUB_OUTPUT + - name: Process the demo profile build run: ./offline/demo-build/build.sh env: GPG_PRIVATE_KEY: '${{ secrets.GPG_PRIVATE_KEY }}' DOCKER_LOGIN: '${{ secrets.DOCKER_LOGIN }}' - - name: Copy demo build assets tarball to S3 and clean up + - name: Copy demo build assets tarball to S3 run: | - # Upload tarball for each profile by specifying their OUTPUT_TAR path aws s3 cp offline/demo-build/output/assets.tgz s3://public.wire.com/artifacts/wire-server-deploy-static-demo-${{ steps.upload_name.outputs.UPLOAD_NAME }}.tgz echo "Uploaded to: https://s3-$AWS_REGION.amazonaws.com/public.wire.com/artifacts/wire-server-deploy-static-demo-${{ steps.upload_name.outputs.UPLOAD_NAME }}.tgz" - # remove the assets from the build to optimize the space on the server - rm -rf offline/demo-build/output/* env: AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' AWS_REGION: "eu-west-1" - # min profile build + - name: Cleanup demo build assets + run: rm -rf offline/demo-build/output/ + + # Build min profile + build-min: + name: Build min profile + if: "!contains(github.event.head_commit.message, 'skip ci')" + runs-on: + group: wire-server-deploy + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: cachix/install-nix-action@v27 + - uses: cachix/cachix-action@v15 + with: + name: wire-server + signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}" + + - name: Install nix environment + run: nix-env -f default.nix -iA env + + - name: Get upload name + id: upload_name + run: echo "UPLOAD_NAME=$GITHUB_SHA" >> $GITHUB_OUTPUT + - name: Process the min profile build run: ./offline/min-build/build.sh env: @@ -94,30 +213,12 @@ jobs: - name: Copy min build assets tarball to S3 run: | - # Upload tarball for each profile by specifying their OUTPUT_TAR path aws s3 cp offline/min-build/output/assets.tgz s3://public.wire.com/artifacts/wire-server-deploy-static-min-${{ steps.upload_name.outputs.UPLOAD_NAME }}.tgz echo "Uploaded to: https://s3-$AWS_REGION.amazonaws.com/public.wire.com/artifacts/wire-server-deploy-static-min-${{ steps.upload_name.outputs.UPLOAD_NAME }}.tgz" - # remove the archives from the build to optimize the space on the server - rm -rf offline/min-build/output/* env: AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}' AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}' AWS_REGION: "eu-west-1" - - name: Install terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: "^1.3.7" - terraform_wrapper: false - - - name: Deploy offline environment to hetzner - run: | - ./offline/cd.sh - env: - HCLOUD_TOKEN: '${{ secrets.HCLOUD_TOKEN }}' - - #- name: Clean up hetzner environment; just in case - # if: always() - # run: (cd terraform/examples/wire-server-deploy-offline-hetzner ; terraform init && terraform destroy -auto-approve) - # env: - # HCLOUD_TOKEN: '${{ secrets.HCLOUD_TOKEN }}' + - name: Cleanup min build assets + run: rm -rf offline/min-build/output/ diff --git a/changelog.d/3-deploy-builds/optimize-default-build-deploy-process b/changelog.d/3-deploy-builds/optimize-default-build-deploy-process new file mode 100644 index 000000000..90041f97c --- /dev/null +++ b/changelog.d/3-deploy-builds/optimize-default-build-deploy-process @@ -0,0 +1,5 @@ +Changed: Optimize Wire offline deployment pipeline with parallel job execution and S3 direct downloads +Added: Retry logic with progressive server type fallbacks for Hetzner Cloud resource availability issues +Changed: Implement parallel terraform operations (15 parallelism) and fast SSH connection multiplexing +Changed: Move ansible execution directly to adminhost for faster private network connectivity +Changed: Reduce CI deployment time from 60+ minutes to ~30-40 minutes through parallel builds and optimized deployment process diff --git a/nix/scripts/list-helm-containers.sh b/nix/scripts/list-helm-containers.sh old mode 100644 new mode 100755 index 204df5ba9..69a9dcf99 --- a/nix/scripts/list-helm-containers.sh +++ b/nix/scripts/list-helm-containers.sh @@ -5,7 +5,7 @@ # those. # In cases where no container image tag has been specified, it'll use `latest`. # The list is sorted and deduplicated, then printed to stdout. -set -eou pipefail +set -euo pipefail VALUES_DIR="" HELM_IMAGE_TREE_FILE="" @@ -74,14 +74,51 @@ function optionally_complain() { images="" # For each helm chart passed in from stdin, use the example values to # render the charts, and assemble the list of images this would fetch. +chart_count=0 while IFS= read -r chart; do - echo "Running helm template on chart ${chart}…" >&2 - current_images=$(helm template --debug "${chart}" \ - --set federate.dtls.tls.key=emptyString \ - --set federate.dtls.tls.crt=emptyString \ - $( [[ -f "${VALUES_DIR}"/$(basename "${chart}")/"${VALUES_TYPE}"-values.example.yaml ]] && echo "-f ${VALUES_DIR}/$(basename "${chart}")/${VALUES_TYPE}-values.example.yaml" ) \ - $( [[ -f "${VALUES_DIR}"/$(basename "${chart}")/"${VALUES_TYPE}"-secrets.example.yaml ]] && echo "-f ${VALUES_DIR}/$(basename "${chart}")/${VALUES_TYPE}-secrets.example.yaml" ) \ - | yq -r '..|.image? | select(.)' | optionally_complain | sort -u) + chart_count=$((chart_count + 1)) + echo "[$chart_count] Running helm template on chart ${chart}…" >&2 + set +e # Temporarily disable exit on error + # Determine values file to use (prod first, then demo as fallback) + values_file="" + if [[ -f "${VALUES_DIR}"/$(basename "${chart}")/"${VALUES_TYPE}"-values.example.yaml ]]; then + values_file="${VALUES_DIR}/$(basename "${chart}")/${VALUES_TYPE}-values.example.yaml" + elif [[ -f "${VALUES_DIR}"/$(basename "${chart}")/demo-values.example.yaml ]]; then + values_file="${VALUES_DIR}/$(basename "${chart}")/demo-values.example.yaml" + echo "Using demo values for $(basename $chart) (no ${VALUES_TYPE} values found)" >&2 + fi + + # Determine secrets file to use + secrets_file="" + if [[ -f "${VALUES_DIR}"/$(basename "${chart}")/"${VALUES_TYPE}"-secrets.example.yaml ]]; then + secrets_file="${VALUES_DIR}/$(basename "${chart}")/${VALUES_TYPE}-secrets.example.yaml" + elif [[ -f "${VALUES_DIR}"/$(basename "${chart}")/demo-secrets.example.yaml ]]; then + secrets_file="${VALUES_DIR}/$(basename "${chart}")/demo-secrets.example.yaml" + fi + + raw_images=$(helm template "${chart}" \ + $( [[ -n "$values_file" ]] && echo "-f $values_file" ) \ + $( [[ -n "$secrets_file" ]] && echo "-f $secrets_file" ) \ + 2>&1 | yq -r '..|.image?' | grep -v "^null$" | grep -v "^---$" | grep -v "^$" || true) + + helm_exit_code=$? + set -e # Re-enable exit on error + + if [[ $helm_exit_code -ne 0 ]]; then + echo "ERROR: Failed to process chart $(basename $chart)" >&2 + echo "Chart path: $chart" >&2 + echo "Values file: ${values_file:-none}" >&2 + echo "Secrets file: ${secrets_file:-none}" >&2 + echo "Try running: helm template $chart $([ -n "$values_file" ] && echo "-f $values_file") $([ -n "$secrets_file" ] && echo "-f $secrets_file")" >&2 + raw_images="" + fi + + # Process extracted images + if [[ -n "$raw_images" ]]; then + current_images=$(echo "$raw_images" | grep -v "^$" | optionally_complain | sort -u) + else + current_images="" + fi images+="$current_images\n" if [[ -n "$current_images" ]]; then @@ -90,4 +127,4 @@ while IFS= read -r chart; do append_chart_entry "$(basename $chart)" "$image_array" "${HELM_IMAGE_TREE_FILE}" fi done -echo -e "$images" | grep . | sort -u +echo -e "$images" | grep . | sort -u || true diff --git a/offline/cd-with-retry.sh b/offline/cd-with-retry.sh new file mode 100755 index 000000000..aa2d5a666 --- /dev/null +++ b/offline/cd-with-retry.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# This is the production version of cd.sh with built-in retry logic +# Use this instead of cd.sh when you want automatic resource availability handling + +CD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TF_DIR="${CD_DIR}/../terraform/examples/wire-server-deploy-offline-hetzner" +ARTIFACTS_DIR="${CD_DIR}/default-build/output" + +# S3 configuration for asset download fallback +S3_REGION="eu-west-1" +UPLOAD_NAME="${GITHUB_SHA:-$(git rev-parse HEAD 2>/dev/null || echo 'unknown')}" + +# Ensure assets are available (download from S3 if local assets don't exist) +if [[ ! -f "$ARTIFACTS_DIR/assets.tgz" && -n "${GITHUB_SHA:-}" ]]; then + echo "Local assets not found. Downloading from S3..." + echo "Using UPLOAD_NAME: $UPLOAD_NAME" + + mkdir -p "$ARTIFACTS_DIR" + S3_URL="https://s3-${S3_REGION}.amazonaws.com/public.wire.com/artifacts/wire-server-deploy-static-${UPLOAD_NAME}.tgz" + + if curl -fsSL "$S3_URL" -o "$ARTIFACTS_DIR/assets.tgz"; then + echo "Successfully downloaded assets from S3" + else + echo "ERROR: Failed to download assets from S3: $S3_URL" + echo "Please ensure the build artifacts exist or run the full build first" + exit 1 + fi +elif [[ -f "$ARTIFACTS_DIR/assets.tgz" ]]; then + echo "Using existing local assets: $ARTIFACTS_DIR/assets.tgz" +else + echo "ERROR: No assets available and no GITHUB_SHA set for S3 download" + echo "Please run the build first or set GITHUB_SHA environment variable" + exit 1 +fi + +# Retry configuration +MAX_RETRIES=3 +RETRY_DELAY=30 + +echo "Wire Offline Deployment with Retry Logic" +echo "========================================" + +function cleanup { + (cd "$TF_DIR" && terraform destroy -auto-approve) + echo "Cleanup completed" +} +trap cleanup EXIT + +cd "$TF_DIR" +terraform init + +# Retry loop for terraform apply +echo "Starting deployment with automatic retry on resource unavailability..." +for attempt in $(seq 1 $MAX_RETRIES); do + echo "" + echo "Deployment attempt $attempt of $MAX_RETRIES" + date + + if terraform apply -auto-approve; then + echo "Infrastructure deployment successful on attempt $attempt!" + break + else + echo "Infrastructure deployment failed on attempt $attempt" + + if [[ $attempt -lt $MAX_RETRIES ]]; then + echo "Will retry with different configuration..." + + # Clean up partial deployment + echo "Cleaning up partial deployment..." + terraform destroy -auto-approve || true + + # Wait for resources to potentially become available + echo "Waiting ${RETRY_DELAY}s for resources to become available..." + sleep $RETRY_DELAY + + # Modify configuration for better availability + echo "Adjusting server type preferences for attempt $((attempt + 1))..." + case $attempt in + 1) + # Attempt 2: Prioritize cx22 and cx41 + sed -i.bak 's/"cpx21", "cx22", "cx21", "cpx11"/"cx22", "cpx11", "cpx21", "cx21"/' main.tf + sed -i.bak 's/"cpx31", "cpx41", "cx31", "cx41"/"cx41", "cpx31", "cx31", "cx41"/' main.tf + echo " -> Prioritizing cx22 and cx41 server types" + ;; + 2) + # Attempt 3: Use smallest available types + sed -i.bak 's/"cx22", "cpx11", "cpx21", "cx21"/"cpx11", "cx21", "cx22", "cpx21"/' main.tf + sed -i.bak 's/"cx41", "cpx31", "cx31", "cx41"/"cpx31", "cpx31", "cpx11", "cx21"/' main.tf + echo " -> Using smallest available server types" + ;; + esac + + terraform init -reconfigure + else + echo "All deployment attempts failed after $MAX_RETRIES tries" + echo "" + echo "This usually means:" + echo " 1. High demand for Hetzner Cloud resources in EU regions" + echo " 2. Your account may have resource limits" + echo " 3. Try again later when resources become available" + echo "" + echo "Manual solutions:" + echo " 1. Check Hetzner Console for resource limits" + echo " 2. Try different server types manually" + echo " 3. Contact Hetzner support for resource availability" + + # Restore original config + if [[ -f main.tf.bak ]]; then + mv main.tf.bak main.tf + terraform init -reconfigure + fi + + exit 1 + fi + fi +done + +# Restore original config after successful deployment +if [[ -f main.tf.bak ]]; then + mv main.tf.bak main.tf + terraform init -reconfigure +fi + +echo "" +echo "Infrastructure ready! Proceeding with application deployment..." + +# Continue with the rest of the original cd.sh logic +adminhost=$(terraform output adminhost) +adminhost="${adminhost//\"/}" # remove extra quotes around the returned string +ssh_private_key=$(terraform output ssh_private_key) + +eval "$(ssh-agent)" +ssh-add - <<< "$ssh_private_key" + +terraform output -json static-inventory > inventory.json +yq -y '.' inventory.json > inventory.yml + +ssh -oStrictHostKeyChecking=accept-new -oConnectionAttempts=10 "root@$adminhost" tar xzv < "$ARTIFACTS_DIR/assets.tgz" + +scp inventory.yml "root@$adminhost":./ansible/inventory/offline/inventory.yml + +ssh "root@$adminhost" cat ./ansible/inventory/offline/inventory.yml || true + +echo "Running ansible playbook setup_nodes.yml via adminhost ($adminhost)..." +ansible-playbook -i inventory.yml setup_nodes.yml --private-key "ssh_private_key" \ + -e "ansible_ssh_common_args='-o ProxyCommand=\"ssh -W %h:%p -q root@$adminhost -i ssh_private_key\" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'" + +# NOTE: Agent is forwarded; so that the adminhost can provision the other boxes +ssh -A "root@$adminhost" ./bin/offline-deploy.sh + +echo "" +echo "Wire offline deployment completed successfully!" \ No newline at end of file diff --git a/offline/tasks/patch-chart-images.sh b/offline/tasks/patch-chart-images.sh new file mode 100755 index 000000000..175e05efd --- /dev/null +++ b/offline/tasks/patch-chart-images.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# Script to patch bitnami repository references in chart files + +set -euo pipefail + +CHARTS_DIR="${1:-}" + +if [[ -z "$CHARTS_DIR" ]]; then + echo "Usage: $0 " + echo "Example: $0 ./output/charts" + exit 1 +fi + +echo "Patching bitnami repository references in: $CHARTS_DIR" + +patched_count=0 +file_count=0 + +# Function to patch a single file +patch_file() { + local file="$1" + local temp_file + local chart_name="" + + temp_file=$(mktemp) + + # Extract chart name from file path for logging + if [[ "$file" =~ /charts/([^/]+)/ ]]; then + chart_name="${BASH_REMATCH[1]}" + else + chart_name="unknown" + fi + + # Apply sed replacements for various image reference patterns + sed -e 's|repository: bitnami/|repository: bitnamilegacy/|g' \ + -e 's|repository: docker\.io/bitnami/|repository: docker.io/bitnamilegacy/|g' \ + -e 's|image: bitnami/|image: bitnamilegacy/|g' \ + -e 's|image: docker\.io/bitnami/|image: docker.io/bitnamilegacy/|g' \ + -e 's|: bitnami/|: bitnamilegacy/|g' \ + -e 's|: docker\.io/bitnami/|: docker.io/bitnamilegacy/|g' \ + "$file" > "$temp_file" + + # Check if file was modified and log specific changes + if ! cmp -s "$file" "$temp_file"; then + # Show what was changed + echo " ✅ Patched chart: $chart_name" + echo " File: $(basename "$file")" + + # Extract and log the specific bitnami references that were changed + local changes + changes=$(diff "$file" "$temp_file" 2>/dev/null | grep "^<\|^>" | grep -E "(bitnami|bitnamilegacy)" || true) + if [[ -n "$changes" ]]; then + echo " Changes:" + echo "$changes" | while read -r line; do + if [[ "$line" =~ ^\<.*bitnami/ ]]; then + local old_ref + old_ref=$(echo "$line" | sed 's/^< *//' | grep -o 'bitnami/[^[:space:]]*' || echo "bitnami reference") + echo " - $old_ref → bitnamilegacy/${old_ref#bitnami/}" + fi + done + fi + + mv "$temp_file" "$file" + return 0 + else + rm "$temp_file" + return 1 + fi +} + +echo "Scanning and patching files..." + +# Process values.yaml files +while IFS= read -r -d '' file; do + file_count=$((file_count + 1)) + if patch_file "$file"; then + patched_count=$((patched_count + 1)) + fi +done < <(find "$CHARTS_DIR" -name "values.yaml" -print0) + +# Process Chart.yaml files +while IFS= read -r -d '' file; do + file_count=$((file_count + 1)) + if patch_file "$file"; then + patched_count=$((patched_count + 1)) + fi +done < <(find "$CHARTS_DIR" -name "Chart.yaml" -print0) + +# Process template files (for direct image references) +while IFS= read -r -d '' file; do + file_count=$((file_count + 1)) + if patch_file "$file"; then + patched_count=$((patched_count + 1)) + fi +done < <(find "$CHARTS_DIR" -path "*/templates/*.yaml" -print0) + +echo +echo "=== Patching Summary ===" +echo "Files processed: $file_count" +echo "Files modified: $patched_count" + +if [[ $patched_count -gt 0 ]]; then + echo + echo "Charts with bitnami references successfully patched:" + # Extract unique chart names from the log output above + echo " (See detailed changes above for specific image references)" +else + echo "No bitnami image references found in any charts." +fi +echo "==========================" \ No newline at end of file diff --git a/offline/tasks/proc_pull_charts.sh b/offline/tasks/proc_pull_charts.sh index 353323c0a..f20d942b8 100755 --- a/offline/tasks/proc_pull_charts.sh +++ b/offline/tasks/proc_pull_charts.sh @@ -81,6 +81,17 @@ pull_charts() { (cd "${OUTPUT_DIR}"/charts; helm pull --version "$version" --untar "$repo_short_name/$name") done echo "Pulling charts done." + + # Patch bitnami repository references in pulled charts + # Remove the extraction and replacement when there will be no more bitnami charts + echo "Patching bitnami repository references..." + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + PATCH_SCRIPT="${SCRIPT_DIR}/patch-chart-images.sh" + if [[ -f "$PATCH_SCRIPT" ]]; then + "$PATCH_SCRIPT" "${OUTPUT_DIR}/charts" + else + echo "Warning: patch-chart-images.sh not found at $PATCH_SCRIPT, skipping chart patching" + fi } wire_build="https://raw.githubusercontent.com/wireapp/wire-builds/91dc716636442af4131c37719d825ac08d36232a/build.json" diff --git a/terraform/examples/wire-server-deploy-offline-hetzner/main.tf b/terraform/examples/wire-server-deploy-offline-hetzner/main.tf index c0ce4a4e8..4d438ff00 100644 --- a/terraform/examples/wire-server-deploy-offline-hetzner/main.tf +++ b/terraform/examples/wire-server-deploy-offline-hetzner/main.tf @@ -7,35 +7,109 @@ locals { postgresql_count = 3 ssh_keys = [hcloud_ssh_key.adminhost.name] - # Server type preferences with fallbacks + # Location preferences with fallbacks (EU only) + preferred_locations = ["nbg1", "fsn1", "hel1"] + + # Server type preferences with fallbacks (optimized for availability) preferred_server_types = { - small = ["cx22", "cpx21", "cx21", "cpx11"] # For cassandra, elasticsearch, minio, postgresql - medium = ["cpx41", "cx41", "cpx31", "cx31"] # For adminhost, assethost, kubenode + small = ["cpx21", "cx22", "cx21", "cpx11"] # For cassandra, elasticsearch, minio, postgresql + medium = ["cpx31", "cpx41", "cx31", "cx41"] # For adminhost, assethost, kubenode } } -# Get available server types in the specified location -data "hcloud_server_types" "available" { -} +# Get available server types and locations +data "hcloud_server_types" "available" {} +data "hcloud_datacenters" "available" {} -# Helper locals to select available server types +# Helper locals to select available resources with robust fallback logic locals { available_server_type_names = [for st in data.hcloud_server_types.available.server_types : st.name] + available_location_names = [for dc in data.hcloud_datacenters.available.datacenters : dc.location.name] - # Select the first available server type from the preference list - small_server_type = [ + # Select the first available location from the preference list + available_preferred_locations = [ + for preferred in local.preferred_locations : + preferred if contains(local.available_location_names, preferred) + ] + selected_location = length(local.available_preferred_locations) > 0 ? local.available_preferred_locations[0] : null + + # Select the first available server type from the preference list (with validation) + available_small_server_types = [ for preferred in local.preferred_server_types.small : preferred if contains(local.available_server_type_names, preferred) - ][0] - - medium_server_type = [ + ] + small_server_type = length(local.available_small_server_types) > 0 ? local.available_small_server_types[0] : null + + available_medium_server_types = [ for preferred in local.preferred_server_types.medium : preferred if contains(local.available_server_type_names, preferred) - ][0] + ] + medium_server_type = length(local.available_medium_server_types) > 0 ? local.available_medium_server_types[0] : null } +# Validation checks - fail early with helpful error messages +resource "null_resource" "location_validation" { + count = local.selected_location != null ? 0 : 1 + + provisioner "local-exec" { + command = <<-EOT + echo "DEPLOYMENT FAILED: No suitable location available" + echo "Requested locations: ${join(", ", local.preferred_locations)}" + echo "Available locations: ${join(", ", local.available_location_names)}" + echo "Please check Hetzner Cloud region availability" + exit 1 + EOT + } +} + +resource "null_resource" "small_server_type_validation" { + count = local.small_server_type != null ? 0 : 1 + + provisioner "local-exec" { + command = <<-EOT + echo "DEPLOYMENT FAILED: No suitable database server types available" + echo "Requested types: ${join(", ", local.preferred_server_types.small)}" + echo "Available types: ${join(", ", local.available_server_type_names)}" + echo "Please check server type availability in the selected region" + exit 1 + EOT + } +} + +resource "null_resource" "medium_server_type_validation" { + count = local.medium_server_type != null ? 0 : 1 + + provisioner "local-exec" { + command = <<-EOT + echo "DEPLOYMENT FAILED: No suitable Kubernetes server types available" + echo "Requested types: ${join(", ", local.preferred_server_types.medium)}" + echo "Available types: ${join(", ", local.available_server_type_names)}" + echo "Please check server type availability in the selected region" + exit 1 + EOT + } +} + +resource "null_resource" "deployment_info" { + depends_on = [ + null_resource.location_validation, + null_resource.small_server_type_validation, + null_resource.medium_server_type_validation + ] + + provisioner "local-exec" { + command = <<-EOT + echo "VALIDATION PASSED: Deploying Wire offline infrastructure" + echo "Location: ${local.selected_location}" + echo "Database server type: ${local.small_server_type}" + echo "Kubernetes server type: ${local.medium_server_type}" + echo "Total instances: ${local.cassandra_count + local.postgresql_count + local.elasticsearch_count + local.minio_count + local.kubenode_count + 2}" + EOT + } +} resource "random_pet" "main" { + depends_on = [null_resource.deployment_info] } resource "hcloud_network" "main" { @@ -66,18 +140,19 @@ resource "hcloud_ssh_key" "adminhost" { # Connected to all other servers. Simulates the admin's "laptop" resource "hcloud_server" "adminhost" { - location = "nbg1" + depends_on = [ + null_resource.deployment_info, + hcloud_network_subnet.main + ] + location = local.selected_location name = "adminhost-${random_pet.adminhost.id}" image = "ubuntu-22.04" ssh_keys = local.ssh_keys server_type = local.medium_server_type network { - network_id = hcloud_network.main.id - ip = "" + network_id = hcloud_network.main.id + ip = "" } - depends_on = [ - hcloud_network_subnet.main - ] } # The server hosting all the bootstrap assets @@ -85,7 +160,11 @@ resource "random_pet" "assethost" { } resource "hcloud_server" "assethost" { - location = "nbg1" + depends_on = [ + null_resource.deployment_info, + hcloud_network_subnet.main + ] + location = local.selected_location name = "assethost-${random_pet.assethost.id}" image = "ubuntu-22.04" ssh_keys = local.ssh_keys @@ -95,12 +174,9 @@ resource "hcloud_server" "assethost" { ipv6_enabled = false } network { - network_id = hcloud_network.main.id - ip = "" + network_id = hcloud_network.main.id + ip = "" } - depends_on = [ - hcloud_network_subnet.main - ] } resource "random_pet" "kubenode" { @@ -108,8 +184,12 @@ resource "random_pet" "kubenode" { } resource "hcloud_server" "kubenode" { + depends_on = [ + null_resource.deployment_info, + hcloud_network_subnet.main + ] count = local.kubenode_count - location = "nbg1" + location = local.selected_location name = "kubenode-${random_pet.kubenode[count.index].id}" image = "ubuntu-22.04" ssh_keys = local.ssh_keys @@ -119,12 +199,9 @@ resource "hcloud_server" "kubenode" { ipv6_enabled = false } network { - network_id = hcloud_network.main.id - ip = "" + network_id = hcloud_network.main.id + ip = "" } - depends_on = [ - hcloud_network_subnet.main - ] } resource "random_pet" "cassandra" { @@ -132,8 +209,12 @@ resource "random_pet" "cassandra" { } resource "hcloud_server" "cassandra" { + depends_on = [ + null_resource.deployment_info, + hcloud_network_subnet.main + ] count = local.cassandra_count - location = "nbg1" + location = local.selected_location name = "cassandra-${random_pet.cassandra[count.index].id}" image = "ubuntu-22.04" ssh_keys = local.ssh_keys @@ -143,12 +224,9 @@ resource "hcloud_server" "cassandra" { ipv6_enabled = false } network { - network_id = hcloud_network.main.id - ip = "" + network_id = hcloud_network.main.id + ip = "" } - depends_on = [ - hcloud_network_subnet.main - ] } resource "random_pet" "elasticsearch" { @@ -156,8 +234,12 @@ resource "random_pet" "elasticsearch" { } resource "hcloud_server" "elasticsearch" { + depends_on = [ + null_resource.deployment_info, + hcloud_network_subnet.main + ] count = local.elasticsearch_count - location = "nbg1" + location = local.selected_location name = "elasticsearch-${random_pet.elasticsearch[count.index].id}" image = "ubuntu-22.04" ssh_keys = local.ssh_keys @@ -167,12 +249,9 @@ resource "hcloud_server" "elasticsearch" { ipv6_enabled = false } network { - network_id = hcloud_network.main.id - ip = "" + network_id = hcloud_network.main.id + ip = "" } - depends_on = [ - hcloud_network_subnet.main - ] } resource "random_pet" "minio" { @@ -180,8 +259,12 @@ resource "random_pet" "minio" { } resource "hcloud_server" "minio" { + depends_on = [ + null_resource.deployment_info, + hcloud_network_subnet.main + ] count = local.minio_count - location = "nbg1" + location = local.selected_location name = "minio-${random_pet.minio[count.index].id}" image = "ubuntu-22.04" ssh_keys = local.ssh_keys @@ -191,12 +274,9 @@ resource "hcloud_server" "minio" { ipv6_enabled = false } network { - network_id = hcloud_network.main.id - ip = "" + network_id = hcloud_network.main.id + ip = "" } - depends_on = [ - hcloud_network_subnet.main - ] } resource "random_pet" "postgresql" { @@ -204,8 +284,12 @@ resource "random_pet" "postgresql" { } resource "hcloud_server" "postgresql" { + depends_on = [ + null_resource.deployment_info, + hcloud_network_subnet.main + ] count = local.postgresql_count - location = "nbg1" + location = local.selected_location name = "postgresql-${random_pet.postgresql[count.index].id}" image = "ubuntu-22.04" ssh_keys = local.ssh_keys @@ -215,10 +299,7 @@ resource "hcloud_server" "postgresql" { ipv6_enabled = false } network { - network_id = hcloud_network.main.id - ip = "" + network_id = hcloud_network.main.id + ip = "" } - depends_on = [ - hcloud_network_subnet.main - ] } diff --git a/terraform/examples/wire-server-deploy-offline-hetzner/outputs.tf b/terraform/examples/wire-server-deploy-offline-hetzner/outputs.tf index f5cfc135a..4e71f24a8 100644 --- a/terraform/examples/wire-server-deploy-offline-hetzner/outputs.tf +++ b/terraform/examples/wire-server-deploy-offline-hetzner/outputs.tf @@ -1,6 +1,6 @@ output "ssh_private_key" { sensitive = true - value = tls_private_key.admin.private_key_pem + value = tls_private_key.admin.private_key_pem } output "selected_server_types" { @@ -11,9 +11,31 @@ output "selected_server_types" { } } +output "selected_location" { + description = "Location selected after checking availability" + value = local.selected_location +} + +output "resource_fallback_info" { + description = "Information about resource fallback selections" + value = { + requested_locations = local.preferred_locations + available_locations = local.available_location_names + selected_location = local.selected_location + + requested_small_types = local.preferred_server_types.small + available_small_types = local.available_small_server_types + selected_small_type = local.small_server_type + + requested_medium_types = local.preferred_server_types.medium + available_medium_types = local.available_medium_server_types + selected_medium_type = local.medium_server_type + } +} + output "adminhost" { sensitive = true - value = hcloud_server.adminhost.ipv4_address + value = hcloud_server.adminhost.ipv4_address } # output format that a static inventory file expects output "static-inventory" { @@ -21,9 +43,9 @@ output "static-inventory" { value = { all = { vars = { - ansible_user = "root" - private_interface = "enp7s0" - adminhost_ip = tolist(hcloud_server.adminhost.network)[0].ip + ansible_user = "root" + private_interface = "enp7s0" + adminhost_ip = tolist(hcloud_server.adminhost.network)[0].ip ansible_ssh_common_args = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=auto -o ControlPersist=60s" } } @@ -40,7 +62,7 @@ output "static-inventory" { ansible_host = tolist(hcloud_server.adminhost.network)[0].ip } } - } + } assethost = { hosts = { "assethost" = { @@ -75,7 +97,7 @@ output "static-inventory" { calico_veth_mtu = 1430 # NOTE: relax handling a list with more than 3 items; required on Hetzner docker_dns_servers_strict = false - upstream_dns_servers = [tolist(hcloud_server.adminhost.network)[0].ip] + upstream_dns_servers = [tolist(hcloud_server.adminhost.network)[0].ip] } } cassandra = { @@ -116,14 +138,14 @@ output "static-inventory" { } postgresql = { hosts = { - for index, server in hcloud_server.postgresql : "postgresql${index + 1}" => { + for index, server in hcloud_server.postgresql : "postgresql${index + 1}" => { ansible_host = tolist(hcloud_server.postgresql[index].network)[0].ip } } vars = { - wire_dbname = "wire-server" - wire_user = "wire-server" - wire_pass = "verysecurepassword" + wire_dbname = "wire-server" + wire_user = "wire-server" + wire_pass = "verysecurepassword" postgresql_network_interface = "enp7s0" } } @@ -132,7 +154,7 @@ output "static-inventory" { } postgresql_ro = { hosts = { "postgresql2" = {}, - "postgresql3" = {} } + "postgresql3" = {} } } } } diff --git a/values/databases-ephemeral/demo-values.example.yaml b/values/databases-ephemeral/demo-values.example.yaml index b5f12ea08..be3b11820 100644 --- a/values/databases-ephemeral/demo-values.example.yaml +++ b/values/databases-ephemeral/demo-values.example.yaml @@ -8,7 +8,7 @@ redis-ephemeral: redis-ephemeral: image: registry: docker.io - repository: bitnami/redis + repository: bitnamilegacy/redis tag: 6.2.16 usePassword: false cluster: diff --git a/values/databases-ephemeral/prod-values.example.yaml b/values/databases-ephemeral/prod-values.example.yaml index 10d4f294d..578d66d4d 100644 --- a/values/databases-ephemeral/prod-values.example.yaml +++ b/values/databases-ephemeral/prod-values.example.yaml @@ -8,7 +8,7 @@ redis-ephemeral: redis-ephemeral: image: registry: docker.io - repository: bitnami/redis + repository: bitnamilegacy/redis tag: 6.2.16 usePassword: false cluster: diff --git a/values/postgresql-external/demo-values.example.yaml b/values/postgresql-external/demo-values.example.yaml new file mode 100644 index 000000000..584583470 --- /dev/null +++ b/values/postgresql-external/demo-values.example.yaml @@ -0,0 +1,6 @@ +# Read Write IPs of the postgresql servers that are used for read and write operations. +RWIPs: + - 192.168.122.31 +ROIPs: + - 192.168.122.32 + - 192.168.122.33 \ No newline at end of file diff --git a/values/postgresql-external/prod-values.example.yaml b/values/postgresql-external/prod-values.example.yaml new file mode 100644 index 000000000..584583470 --- /dev/null +++ b/values/postgresql-external/prod-values.example.yaml @@ -0,0 +1,6 @@ +# Read Write IPs of the postgresql servers that are used for read and write operations. +RWIPs: + - 192.168.122.31 +ROIPs: + - 192.168.122.32 + - 192.168.122.33 \ No newline at end of file diff --git a/values/postgresql/demo-values.example.yaml b/values/postgresql/demo-values.example.yaml index fcf671496..f0143ab6f 100644 --- a/values/postgresql/demo-values.example.yaml +++ b/values/postgresql/demo-values.example.yaml @@ -6,5 +6,5 @@ postgresql: enabled: false volumePermissions: image: - repository: bitnami/os-shell + repository: bitnamilegacy/os-shell tag: 12-debian-12-r46 diff --git a/values/redis-ephemeral/prod-values.example.yaml b/values/redis-ephemeral/prod-values.example.yaml index 65d1df801..1f268481f 100644 --- a/values/redis-ephemeral/prod-values.example.yaml +++ b/values/redis-ephemeral/prod-values.example.yaml @@ -1,7 +1,7 @@ redis-ephemeral: image: registry: docker.io - repository: bitnami/redis + repository: bitnamilegacy/redis tag: 6.2.16 usePassword: false cluster: