diff --git a/.azd/hooks/postprovision.sh b/.azd/hooks/postprovision.sh new file mode 100755 index 0000000..437af83 --- /dev/null +++ b/.azd/hooks/postprovision.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +merge_env_files() { + local base=$1 + local with=$2 + local output=$3 + + local -a temp_vars=() + local -a merged_vars=() + + while IFS= read -r line || [ -n "$line" ]; do + # Remove comments and trim whitespace + line=$(echo "$line" | cut -d'#' -f1 ) + # Trim whitespace + line=$(echo "$line" | sed 's/^[ \t]*//;s/[ \t]*$//') + + # Skip empty lines + if [[ -z $line ]]; then + continue + fi + + # Split the line into key and value + IFS='=' read -r key value <<< "$line" + + # Remove newlines and carriage returns + value=$(echo "$value" | tr -d '\n\r') + + # Store the key-value pair in the temp array + temp_vars+=("$key=$value") + done < <(cat "$base" "$with") + + for entry in "${temp_vars[@]}"; do + key=$(echo "$entry" | cut -d'=' -f1) + value=$(echo "$entry" | cut -d'=' -f2-) + found=false + + for i in "${!merged_vars[@]}"; do + if [[ "${merged_vars[$i]}" == "$key="* ]]; then + merged_vars[$i]="$key=$value" + found=true + break + fi + done + + if [[ $found == false ]]; then + merged_vars+=("$key=$value") + fi + done + + { + for entry in "${merged_vars[@]}"; do + key=$(echo "$entry" | cut -d'=' -f1) + value=$(echo "$entry" | cut -d'=' -f2-) + echo "$key=$value" + done | sort + } > "$output" +} + +script_dir="$(dirname "$(readlink -f "$0")")" + +env_local="$script_dir/../../.env.local" +env_azd="$script_dir/../../.azure/${AZURE_ENV_NAME}/.env" + +env_azure="$script_dir/../../.env.azure" + +if [[ ! -f $env_local ]]; then + cp "$env_azd" "$env_azure" + exit 0 +fi + +merge_env_files "$env_local" "$env_azd" "$env_azure" diff --git a/.azd/hooks/preprovision.sh b/.azd/hooks/preprovision.sh new file mode 100755 index 0000000..6b87609 --- /dev/null +++ b/.azd/hooks/preprovision.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +script_dir="$(dirname "$(readlink -f "$0")")" + +# Run script to generate an `env-vars.json` file used by the infra scripts +"$script_dir/../scripts/create-infra-env-vars.sh" diff --git a/.azd/scripts/create-env-local.sh b/.azd/scripts/create-env-local.sh new file mode 100755 index 0000000..19848ab --- /dev/null +++ b/.azd/scripts/create-env-local.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +script_dir="$(dirname "$(readlink -f "$0")")" + +# Create a `.env.local` file by merging values from the current environment with a template + +template_path="$script_dir/../../.env.local.template" + +if [[ ! -f $template_path ]]; then + # Template file does not exist so we can't go any further + exit 0 +fi + +output_path="$script_dir/../../.env.local" + +if [[ -f $output_path ]]; then + # We only want to create the `.env.local` file if it does not already exist + # In the development environment the developer should be in control of their `.env.local` file so it should exist + # In CI the `.env.local` file should not exist as it should not be committed to the repo + exit 0 +fi + +# Read the template file + +declare -A template + +while IFS='=' read -r key value; do + template["$key"]="$value" +done < "$template_path" + +# For each key in the template, check if there is a corresponding environment variable and if so, add it to an object + +declare -A env_vars + +for key in "${!template[@]}"; do + if [[ -n ${!key} ]]; then + env_vars["$key"]="${!key}" + else + env_vars["$key"]="" + fi +done + +# Write the object to the output file in env file format + +{ + for key in "${!env_vars[@]}"; do + echo "$key=${env_vars[$key]}" + done | sort +} > "$output_path" diff --git a/.azd/scripts/create-infra-env-vars.sh b/.azd/scripts/create-infra-env-vars.sh new file mode 100755 index 0000000..b7affb3 --- /dev/null +++ b/.azd/scripts/create-infra-env-vars.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Function to remove quotes from a value +remove_quotes() { + local value=$1 + local quote_char=${2:-'"'} + echo "$value" | sed "s/^$quote_char//;s/$quote_char$//" +} + +# Function to read env vars from a file and store them in a global array +read_env_vars() { + local path=$1 + local -a temp_vars=() + + if [[ ! -f $path ]]; then + return + fi + + while IFS= read -r line || [ -n "$line" ]; do + # Remove comments and trim whitespace + line=$(echo "$line" | cut -d'#' -f1 | xargs) + + # Skip empty lines + if [[ -z $line ]]; then + continue + fi + + # Split the line into key and value + IFS='=' read -r key value <<< "$line" + + # Remove newlines and carriage returns + value=$(echo "$value" | tr -d '\n\r') + + # Remove quotes + value=$(remove_quotes "$value" '"') + value=$(remove_quotes "$value" "'") + + # Store the key-value pair in the temp array + temp_vars+=("$key=$value") + done < "$path" + + # Merge temp_vars into env_vars, overwriting existing keys + for entry in "${temp_vars[@]}"; do + key=$(echo "$entry" | cut -d'=' -f1) + value=$(echo "$entry" | cut -d'=' -f2-) + found=false + + for i in "${!env_vars[@]}"; do + if [[ "${env_vars[$i]}" == "$key="* ]]; then + env_vars[$i]="$key=$value" + found=true + break + fi + done + + if [[ $found == false ]]; then + env_vars+=("$key=$value") + fi + done +} + +# Initialize the env_vars array +env_vars=() + +# Read `.env`, `.env.production`, and `.env.local` files into the env_vars array +script_dir="$(dirname "$(readlink -f "$0")")" + +env_path="$script_dir/../../.env" +read_env_vars "$env_path" + +env_production_path="$script_dir/../../.env.production" +read_env_vars "$env_production_path" + +env_local_path="$script_dir/../../.env.local" +read_env_vars "$env_local_path" + +# Produce a `env-vars.json` file that can be used by the infra scripts +output_path="$script_dir/../../infra/env-vars.json" + +# Convert the env_vars array to JSON format and write to output_path using jq +echo '{' > "$output_path" +for entry in "${env_vars[@]}"; do + key=$(echo "$entry" | cut -d'=' -f1) + value=$(echo "$entry" | cut -d'=' -f2-) + echo " \"$key\": \"$value\"," >> "$output_path" +done +# Remove the trailing comma and close the JSON object +sed -i '' -e '$ s/,$//' "$output_path" +echo '}' >> "$output_path" + +# Verify the content of the output file +cat "$output_path" diff --git a/.azd/scripts/read-domain-verification-vars.sh b/.azd/scripts/read-domain-verification-vars.sh new file mode 100755 index 0000000..d555e1a --- /dev/null +++ b/.azd/scripts/read-domain-verification-vars.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +remove_quotes() { + local value=$1 + local quote_char=${2:-'"'} + + if [[ ${value:0:1} == "$quote_char" && ${value: -1} == "$quote_char" ]]; then + value=${value:1:-1} + fi + + echo "$value" +} + +read_env_vars() { + local path=$1 + local env_vars=() + + if [[ ! -f $path ]]; then + # File does not exist so there is nothing to do + echo "$(declare -p env_vars)" + return + fi + + while IFS='=' read -r key value; do + if [[ -z $value ]]; then + env_vars+=("$key=") + else + value=$(remove_quotes "$value" '"') + value=$(remove_quotes "$value" "'") + env_vars+=("$key=$value") + fi + done < "$path" + + echo "$(declare -p env_vars)" +} + +read_azd_env_vars() { + local env_vars=() + + azd_env=$(azd env get-values) + + while IFS= read -r entry; do + if [[ -z $entry || $entry != *"="* ]]; then + continue + fi + + key=$(echo "$entry" | cut -d'=' -f1) + value=$(echo "$entry" | cut -d'=' -f2-) + + if [[ -z $key || $key == *" "* ]]; then + continue + fi + + if [[ -z $value ]]; then + env_vars+=("$key=") + else + value=$(remove_quotes "$value" '"') + value=$(remove_quotes "$value" "'") + env_vars+=("$key=$value") + fi + done <<< "$azd_env" + + echo "$(declare -p env_vars)" +} + +merge_objects() { + local base=("${!1}") + local with=("${!2}") + local merged=("${base[@]}") + + for entry in "${with[@]}"; do + key=$(echo "$entry" | cut -d'=' -f1) + value=$(echo "$entry" | cut -d'=' -f2-) + found=false + + for i in "${!merged[@]}"; do + if [[ "${merged[$i]}" == "$key="* ]]; then + found=true + if [[ -n $value ]]; then + merged[$i]="$key=$value" + fi + break + fi + done + + if [[ $found == false ]]; then + merged+=("$key=$value") + fi + done + + echo "$(declare -p merged)" +} + +script_dir="$(dirname "$(readlink -f "$0")")" + +env_azd_path="$script_dir/../../.azure/${AZURE_ENV_NAME}/.env" + +if [[ -z $AZURE_ENV_NAME ]]; then + eval "$(read_azd_env_vars)" +else + eval "$(read_env_vars "$env_azd_path")" +fi +env_azd=("${env_vars[@]}") + +# Output info required for domain verification +for entry in "${env_azd[@]}"; do + key=$(echo "$entry" | cut -d'=' -f1) + value=$(echo "$entry" | cut -d'=' -f2-) + case "$key" in + "AZURE_CONTAINER_STATIC_IP") static_ip="$value" ;; + "AZURE_WEB_APP_FQDN") fqdn="$value" ;; + "AZURE_CONTAINER_DOMAIN_VERIFICATION_CODE") verification_code="$value" ;; + esac +done + +echo "=== Container apps domain verification ===" +echo "Static IP: $static_ip" +echo "FQDN: $fqdn" +echo "Verification code: $verification_code" diff --git a/azure.yaml b/azure.yaml index 17c0702..7059fd5 100644 --- a/azure.yaml +++ b/azure.yaml @@ -5,11 +5,19 @@ metadata: template: nextjs-aca@0.1.0 hooks: preprovision: - shell: pwsh - run: ./.azd/hooks/preprovision.ps1 + windows: + shell: pwsh + run: ./.azd/hooks/preprovision.ps1 + posix: + shell: sh + run: ./.azd/hooks/preprovision.sh postprovision: - shell: pwsh - run: ./.azd/hooks/postprovision.ps1 + windows: + shell: pwsh + run: ./.azd/hooks/postprovision.ps1 + posix: + shell: sh + run: ./.azd/hooks/postprovision.sh services: web: project: ./