diff --git a/.github/build/environment.mk b/.github/build/environment.mk index 4cb42bc0..1b4efa44 100644 --- a/.github/build/environment.mk +++ b/.github/build/environment.mk @@ -18,8 +18,8 @@ # Environment Setup: # make install-radius # Install Radius CLI -# make create-cluster # Create a local k3d Kubernetes cluster for testing -# make delete-cluster # Delete the local k3d Kubernetes cluster +# make create-cluster # Create a local kind Kubernetes cluster for testing +# make delete-cluster # Delete the local kind Kubernetes cluster RAD_VERSION ?= @@ -29,20 +29,28 @@ install-radius-cli: ## Install the Radius CLI. Optionally specify a version numb wget -q "https://raw.githubusercontent.com/radius-project/radius/main/deploy/install.sh" -O - | /bin/bash $(if $(RAD_VERSION),-s $(RAD_VERSION)) .PHONY: create-radius-cluster -create-radius-cluster: ## Create a local k3d Kubernetes cluster with a default Radius workspace/group/environment. - @echo -e "$(ARROW) Creating local k3d cluster and installing Radius..." +create-radius-cluster: ## Create a local kind Kubernetes cluster with a default Radius workspace/group/environment. + @echo -e "$(ARROW) Creating local kind cluster and installing Radius..." @.github/scripts/create-cluster.sh @.github/scripts/verify-ucp-readiness.sh @echo -e "$(ARROW) Creating workspace and environment..." @.github/scripts/create-workspace.sh .PHONY: clean -clean: ## Delete the local k3d cluster, Radius config, Bicep extensions (*.tgz), and bicepconfig.json files +clean: ## Delete the local kind cluster, Radius config, Bicep extensions (*.tgz), and bicepconfig.json files @echo -e "$(ARROW) Deleting Radius config file at ~/.rad/config.yaml..." @rm -f ~/.rad/config.yaml @echo -e "$(ARROW) Deleting Bicep extension files (*.tgz)..." @find . -name "*.tgz" -type f -delete @echo -e "$(ARROW) Deleting bicepconfig.json files..." @find . -name "bicepconfig.json" -type f -delete - @echo -e "$(ARROW) Deleting k3d cluster..." - @k3d cluster delete + @echo -e "$(ARROW) Deleting kind cluster..." + @kind delete cluster + +.PHONY: configure-azure-provider +configure-azure-provider: ## Configure Radius Azure workspace, environment, credential, and deployment target (requires AZURE_* env vars) + @.github/scripts/configure-azure-provider.sh + +.PHONY: cleanup-azure-resources +cleanup-azure-resources: ## Delete Azure resource group created for tests (uses AZURE_TEST_STATE_FILE or AZURE_RESOURCE_GROUP) + @.github/scripts/cleanup-azure-resources.sh diff --git a/.github/build/test.mk b/.github/build/test.mk index 04c57a44..d61b64ac 100644 --- a/.github/build/test.mk +++ b/.github/build/test.mk @@ -24,6 +24,14 @@ RECIPE_TYPE ?= all build: ## Build all resource types and recipes @./.github/scripts/build-all.sh "$(RESOURCE_TYPE_ROOT)" +.PHONY: build-kubernetes-recipes +build-kubernetes-recipes: ## Build Kubernetes recipes only + @RECIPE_PLATFORM_FILTER=kubernetes ./.github/scripts/build-all.sh "$(RESOURCE_TYPE_ROOT)" + +.PHONY: build-azure-recipes +build-azure-recipes: ## Build Azure recipes only + @RECIPE_PLATFORM_FILTER=azure ./.github/scripts/build-all.sh "$(RESOURCE_TYPE_ROOT)" + .PHONY: build-resource-type build-resource-type: ## Validate a resource type by running the 'rad resource-type create' and 'bicep publish-extension' commands (requires TYPE_FOLDER parameter) ifndef TYPE_FOLDER @@ -72,6 +80,14 @@ endif test: ## Run recipe tests (assumes already registered) @./.github/scripts/test-all-recipes.sh "$(RESOURCE_TYPE_ROOT)" "$(ENVIRONMENT)" "$(RECIPE_TYPE)" +.PHONY: test-kubernetes-recipes +test-kubernetes-recipes: ## Run recipe tests for Kubernetes platform only + @RECIPE_PLATFORM_FILTER=kubernetes ./.github/scripts/test-all-recipes.sh "$(RESOURCE_TYPE_ROOT)" + +.PHONY: test-azure-recipes +test-azure-recipes: ## Run recipe tests for Azure platform only (requires Azure configuration) + @RECIPE_PLATFORM_FILTER=azure ./.github/scripts/test-all-recipes.sh "$(RESOURCE_TYPE_ROOT)" + .PHONY: list-resource-types list-resource-types: ## List resource type folders under the specified root @./.github/scripts/list-resource-type-folders.sh "$(RESOURCE_TYPE_ROOT)" diff --git a/.github/scripts/build-all.sh b/.github/scripts/build-all.sh index 581ea69f..9ae137f7 100755 --- a/.github/scripts/build-all.sh +++ b/.github/scripts/build-all.sh @@ -38,6 +38,39 @@ set -euo pipefail ROOT_DIR="${1:-}" +PLATFORM_FILTER_RAW="${RECIPE_PLATFORM_FILTER:-}" + +declare -a PLATFORM_FILTERS=() +if [[ -n "$PLATFORM_FILTER_RAW" ]]; then + IFS=',' read -ra _raw_filters <<< "$PLATFORM_FILTER_RAW" + for _entry in "${_raw_filters[@]}"; do + _trimmed="$(printf '%s' "$_entry" | xargs)" + if [[ -n "$_trimmed" ]]; then + PLATFORM_FILTERS+=("$_trimmed") + fi + done +fi + +should_include_platform() { + local recipe_path="$1" + if [[ ${#PLATFORM_FILTERS[@]} -eq 0 ]]; then + return 0 + fi + + # Extract segment after recipes/ (path is relative: recipes/platform/...) + # Remove "recipes/" prefix if present + local rel="${recipe_path#recipes/}" + # Get the first path segment (the platform) + local platform="${rel%%/*}" + + for _filter in "${PLATFORM_FILTERS[@]}"; do + if [[ "$platform" == "$_filter" ]]; then + return 0 + fi + done + + return 1 +} # Iterate over all resource type folders while IFS= read -r type_dir; do @@ -49,14 +82,26 @@ while IFS= read -r type_dir; do recipes_root="$type_dir/recipes" if [[ -d "$recipes_root" ]]; then while IFS= read -r -d '' recipe_file; do - make -s build-bicep-recipe RECIPE_PATH="$recipe_file" + recipe_rel_path="${recipe_file#$type_dir/}" + if should_include_platform "$recipe_rel_path"; then + echo "Building Bicep recipe: $recipe_file" + make -s build-bicep-recipe RECIPE_PATH="$recipe_file" + else + echo "Skipping Bicep recipe (platform filter): $recipe_file" + fi done < <(find "$recipes_root" -type f -name '*.bicep' -print0) fi # Build/publish all Terraform recipes under this resource type, if any if [[ -d "$recipes_root" ]]; then while IFS= read -r -d '' recipe_dir; do - make -s build-terraform-recipe RECIPE_PATH="$recipe_dir" + recipe_rel_path="${recipe_dir#$type_dir/}" + if should_include_platform "$recipe_rel_path"; then + echo "Building Terraform recipe: $recipe_dir" + make -s build-terraform-recipe RECIPE_PATH="$recipe_dir" + else + echo "Skipping Terraform recipe (platform filter): $recipe_dir" + fi done < <(find "$recipes_root" -type d -name 'terraform' -print0) fi done < <(./.github/scripts/list-resource-type-folders.sh ${ROOT_DIR:+"$ROOT_DIR"}) diff --git a/.github/scripts/cleanup-azure-resources.sh b/.github/scripts/cleanup-azure-resources.sh new file mode 100755 index 00000000..36319065 --- /dev/null +++ b/.github/scripts/cleanup-azure-resources.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# ------------------------------------------------------------ +# Copyright 2025 The Radius Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +# ============================================================================= +# Cleanup script for Azure resources created during recipe validation. +# Deletes the resource group recorded in the Azure test state file (if any). +# ============================================================================= + +set -euo pipefail + +AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-}" + +if [[ -z "$AZURE_RESOURCE_GROUP" ]]; then + echo "No Azure resource group provided, skipping cleanup." + exit 0 +fi + +if ! command -v az >/dev/null 2>&1; then + echo "Azure CLI not available; cannot delete resource group '$AZURE_RESOURCE_GROUP'." >&2 + exit 1 +fi + +echo "Deleting Azure resource group '$AZURE_RESOURCE_GROUP'" +SUB_ARGS=() +if [[ -n "${AZURE_SUBSCRIPTION_ID:-}" ]]; then + SUB_ARGS+=(--subscription "$AZURE_SUBSCRIPTION_ID") +fi + +az group delete --name "$AZURE_RESOURCE_GROUP" --yes --no-wait "${SUB_ARGS[@]}" + +echo "Azure cleanup initiated for resource group '$AZURE_RESOURCE_GROUP'" diff --git a/.github/scripts/configure-azure-provider.sh b/.github/scripts/configure-azure-provider.sh new file mode 100755 index 00000000..a72f412e --- /dev/null +++ b/.github/scripts/configure-azure-provider.sh @@ -0,0 +1,82 @@ +#!/bin/bash + +# ------------------------------------------------------------ +# Copyright 2025 The Radius Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +# ============================================================================= +# Configure the Radius control plane with Azure workload identity credentials +# so Azure recipes can be tested. This script assumes the caller has +# already authenticated with Azure via `az login` or the GitHub Actions +# `azure/login` action. It creates (or reuses) a dedicated workspace, +# environment, and credential, then updates the environment with the Azure +# subscription and resource group used for testing. +# ============================================================================= + +set -euo pipefail + +require_env() { + local name="$1" + if [[ -z "${!name:-}" ]]; then + echo "Error: environment variable '$name' must be set" >&2 + exit 1 + fi +} + +ensure_command() { + local cmd="$1" + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: required command '$cmd' not found in PATH" >&2 + exit 1 + fi +} + +ensure_command "az" +ensure_command "rad" + +require_env "AZURE_SUBSCRIPTION_ID" +require_env "AZURE_TENANT_ID" +require_env "AZURE_CLIENT_ID" + +AZURE_LOCATION="${AZURE_LOCATION:-westus3}" +AZURE_RESOURCE_GROUP="${AZURE_RESOURCE_GROUP:-}" +AZURE_WORKSPACE_NAME="${AZURE_WORKSPACE_NAME:-default}" +AZURE_ENVIRONMENT_NAME="${AZURE_ENVIRONMENT_NAME:-default}" + +if [[ -z "$AZURE_RESOURCE_GROUP" ]]; then + echo "Error: AZURE_RESOURCE_GROUP must be provided" >&2 + exit 1 +fi + +printf "\033[34;1m=>\033[0m Configuring Azure provider for Radius tests\n" + +# az group exists returns "true" or "false" as text, not an exit code +if [[ "$(az group exists --name "$AZURE_RESOURCE_GROUP" --subscription "$AZURE_SUBSCRIPTION_ID" 2>/dev/null)" != "true" ]]; then + echo "Error: Azure resource group '$AZURE_RESOURCE_GROUP' not found. Create it before running configure-azure-provider." >&2 + exit 1 +fi + +printf "\033[34;1m=>\033[0m Updating environment '%s' with Azure provider settings\n" "$AZURE_ENVIRONMENT_NAME" +rad env update "$AZURE_ENVIRONMENT_NAME" \ + --azure-subscription-id "$AZURE_SUBSCRIPTION_ID" \ + --azure-resource-group "$AZURE_RESOURCE_GROUP" \ + --preview + +printf "\033[34;1m=>\033[0m Registering Azure workload identity credential\n" +rad credential register azure wi \ + --tenant-id "$AZURE_TENANT_ID" \ + --client-id "$AZURE_CLIENT_ID" + +printf "\033[34;1m=>\033[0m Azure provider configured successfully\n" diff --git a/.github/scripts/create-cluster.sh b/.github/scripts/create-cluster.sh index 914c6a82..ce5bf8e3 100755 --- a/.github/scripts/create-cluster.sh +++ b/.github/scripts/create-cluster.sh @@ -19,7 +19,8 @@ set -e # Script: Setup Kubernetes environment and initialize Radius -# This script sets up k3d cluster, installs rad CLI, and initializes the default environment +# This script sets up KinD cluster with OIDC support for Azure Workload Identity, +# installs rad CLI, and initializes the default environment # Validation function validate_command() { @@ -33,20 +34,165 @@ validate_command() { # Run validations echo "Validating required dependencies..." -validate_command "k3d" -validate_command "oras" -validate_command "helm" - -echo "Setting up k3d cluster..." -k3d cluster create \ - -p "8081:80@loadbalancer" \ - --k3s-arg "--disable=traefik@server:*" \ - --k3s-arg "--disable=servicelb@server:*" \ - --registry-create reciperegistry:5000 \ - --wait +validate_command "kind" +validate_command "kubectl" +validate_command "docker" + +echo "Setting up KinD cluster..." + +# Check if this is for Azure deployments by looking for AZURE_TENANT_ID +AZURE_WORKLOAD_IDENTITY_ENABLED="false" +KIND_CLUSTER_NAME="radius" + +if [[ -n "${AZURE_TENANT_ID:-}" ]]; then + echo "Azure Tenant ID detected. Checking for OIDC configuration..." + + # Populate the following environment variables for Azure workload identity from secrets. + # AZURE_OIDC_ISSUER_PUBLIC_KEY + # AZURE_OIDC_ISSUER_PRIVATE_KEY + # AZURE_OIDC_ISSUER + if [[ -n "${TEST_AZURE_OIDC_JSON:-}" ]]; then + echo "Extracting OIDC configuration from TEST_AZURE_OIDC_JSON..." + + # Safely parse JSON secret into environment variables + AZURE_OIDC_ISSUER_PUBLIC_KEY=$(echo "$TEST_AZURE_OIDC_JSON" | jq -r '.AZURE_OIDC_ISSUER_PUBLIC_KEY // empty') + AZURE_OIDC_ISSUER_PRIVATE_KEY=$(echo "$TEST_AZURE_OIDC_JSON" | jq -r '.AZURE_OIDC_ISSUER_PRIVATE_KEY // empty') + AZURE_OIDC_ISSUER=$(echo "$TEST_AZURE_OIDC_JSON" | jq -r '.AZURE_OIDC_ISSUER // empty') + + # Mask derived secrets in GitHub Actions logs + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo "::add-mask::$AZURE_OIDC_ISSUER_PUBLIC_KEY" + echo "::add-mask::$AZURE_OIDC_ISSUER_PRIVATE_KEY" + echo "::add-mask::$AZURE_OIDC_ISSUER" + fi + fi + + # Validate required OIDC variables are set + if [[ -z "${AZURE_OIDC_ISSUER_PUBLIC_KEY:-}" ]] || [[ -z "${AZURE_OIDC_ISSUER_PRIVATE_KEY:-}" ]] || [[ -z "${AZURE_OIDC_ISSUER:-}" ]]; then + echo "Error: OIDC configuration is required for Azure Workload Identity but not found." + echo "Please ensure the TEST_AZURE_OIDC_JSON secret is set correctly in GitHub." + exit 1 + fi + + echo "Using provided OIDC configuration..." + AZURE_WORKLOAD_IDENTITY_ENABLED="true" +fi + +if [[ "${AZURE_WORKLOAD_IDENTITY_ENABLED}" == "true" ]]; then + # Decode OIDC issuer keys if they were provided as base64 + if [[ -n "${AZURE_OIDC_ISSUER_PUBLIC_KEY:-}" ]] && [[ -n "${AZURE_OIDC_ISSUER_PRIVATE_KEY:-}" ]]; then + echo "$AZURE_OIDC_ISSUER_PUBLIC_KEY" | base64 -d > sa.pub + echo "$AZURE_OIDC_ISSUER_PRIVATE_KEY" | base64 -d > sa.key + fi + + echo "Creating KinD cluster with OIDC support..." + + cat < /dev/null; then + helm repo add azure-workload-identity https://azure.github.io/azure-workload-identity/charts || true + helm repo update + + helm install workload-identity-webhook \ + azure-workload-identity/workload-identity-webhook \ + --namespace radius-default \ + --create-namespace \ + --version 1.3.0 \ + --set azureTenantID="${AZURE_TENANT_ID}" \ + --wait || echo "Warning: Failed to install Azure Workload Identity webhook. Azure Recipes may not work." + + echo "Waiting for webhook to be fully registered..." + for i in {1..30}; do + if kubectl get mutatingwebhookconfiguration azure-wi-webhook-mutating-webhook-configuration &>/dev/null; then + echo "✓ Webhook configuration registered" + sleep 5 # Give it a few extra seconds to be fully functional + break + fi + echo "Waiting for webhook configuration... ($i/30)" + sleep 2 + done + else + echo "Warning: Helm is not installed. Skipping Azure Workload Identity webhook installation." + echo "Azure Recipes will not work without this component." + fi +fi + +if [[ -z "${AZURE_TENANT_ID:-}" ]]; then + echo "No Azure Tenant ID found. Creating basic KinD cluster..." + kind create cluster --name ${KIND_CLUSTER_NAME} +fi + +echo "Setting up local container registry..." +# Create a local registry for recipes if it doesn't exist +if ! docker ps | grep -q "reciperegistry"; then + docker run -d --restart=always -p 5000:5000 --name reciperegistry registry:2 + + # Connect the registry to the KinD network so pods can access it + docker network connect kind reciperegistry || true + + # Document the local registry for KinD + kubectl apply -f - </dev/null 2>&1 diff --git a/.github/scripts/deploy-recipe-pack.sh b/.github/scripts/deploy-recipe-pack.sh index 2326faed..ff5d8f25 100755 --- a/.github/scripts/deploy-recipe-pack.sh +++ b/.github/scripts/deploy-recipe-pack.sh @@ -28,18 +28,13 @@ set -euo pipefail BICEP_FILE="${1:-}" RESOURCE_GROUP="${2:-}" SUBSCRIPTION="${3:-}" - +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" echo "Creating bicepconfig.json with published extensions..." -# Create base bicepconfig.json with required experimental features -cat > bicepconfig.json << 'EOF' -{ - "extensions": { - "radius": "br:biceptypes.azurecr.io/radius:latest", - "aws": "br:biceptypes.azurecr.io/aws:latest" - } -} -EOF +# Ensure bicepconfig.json has the base extensions and all published resource-type extensions. +# update-bicepconfig.sh creates the base config only if it doesn't already exist, then merges +# in any *-extension.tgz files that were published during the build step. +"$SCRIPT_DIR/update-bicepconfig.sh" if [[ -z "$BICEP_FILE" ]]; then echo "Error: Bicep file is required" diff --git a/.github/scripts/list-recipe-folders.sh b/.github/scripts/list-recipe-folders.sh index 76b017f4..31f6f98a 100755 --- a/.github/scripts/list-recipe-folders.sh +++ b/.github/scripts/list-recipe-folders.sh @@ -31,8 +31,9 @@ set -euo pipefail ROOT_DIR="${1:-$(pwd)}" -FILTER_TYPE="${2:-all}" -FILTER_TYPE="$(echo "$FILTER_TYPE" | tr '[:upper:]' '[:lower:]')" +RECIPE_TYPE_FILTER="${2:-all}" +RECIPE_TYPE_FILTER="$(echo "$RECIPE_TYPE_FILTER" | tr '[:upper:]' '[:lower:]')" +PLATFORM_FILTER_RAW="${RECIPE_PLATFORM_FILTER:-}" if [[ ! -d "$ROOT_DIR" ]]; then echo "Error: Root directory '$ROOT_DIR' does not exist" >&2 @@ -42,30 +43,96 @@ fi # Convert ROOT_DIR to an absolute path ROOT_DIR="$(cd "$ROOT_DIR" && pwd)" -# Validate filter type -case "$FILTER_TYPE" in +# Validate recipe type filter +case "$RECIPE_TYPE_FILTER" in all|bicep|terraform) ;; *) - echo "Error: Unsupported recipe type filter '$FILTER_TYPE'. Expected 'bicep', 'terraform', or 'all'." >&2 + echo "Error: Unsupported recipe type filter '$RECIPE_TYPE_FILTER'. Expected 'bicep', 'terraform', or 'all'." >&2 exit 1 ;; esac +# Parse optional platform filter (comma or space separated values) +PLATFORM_FILTERS=() +if [[ -n "$PLATFORM_FILTER_RAW" ]]; then + IFS=',' read -ra _raw_filters <<< "$PLATFORM_FILTER_RAW" + for _entry in "${_raw_filters[@]}"; do + _trimmed="$(printf '%s' "$_entry" | xargs)" + if [[ -n "$_trimmed" ]]; then + PLATFORM_FILTERS+=("$(echo "$_trimmed" | tr '[:upper:]' '[:lower:]')") + fi + done +fi + +matches_platform() { + local platform_lower="$1" + if [[ ${#PLATFORM_FILTERS[@]} -eq 0 ]]; then + return 0 + fi + + for filter in "${PLATFORM_FILTERS[@]}"; do + if [[ "$platform_lower" == "$filter" ]]; then + return 0 + fi + done + + return 1 +} + +should_include_type() { + local recipe_type="$1" + case "$RECIPE_TYPE_FILTER" in + all) + return 0 + ;; + bicep) + [[ "$recipe_type" == "bicep" ]] + return + ;; + terraform) + [[ "$recipe_type" == "terraform" ]] + return + ;; + esac +} + +add_recipe_dir() { + local dir="$1" + local recipe_type="$2" + + if ! should_include_type "$recipe_type"; then + return + fi + + local platform="" + if [[ "$dir" =~ /recipes/([^/]+)/ ]]; then + platform="${BASH_REMATCH[1]}" + fi + local platform_lower + platform_lower="$(echo "$platform" | tr '[:upper:]' '[:lower:]')" + + if ! matches_platform "$platform_lower"; then + return + fi + + RECIPE_DIRS+=("$dir") +} + # Use a regular array and sort/uniq instead of associative array for bash 3.x compatibility RECIPE_DIRS=() # Find Bicep recipe directories (directories containing .bicep files under recipes/) -if [[ "$FILTER_TYPE" == "all" || "$FILTER_TYPE" == "bicep" ]]; then +if [[ "$RECIPE_TYPE_FILTER" == "all" || "$RECIPE_TYPE_FILTER" == "bicep" ]]; then while IFS= read -r -d '' matched_path; do - RECIPE_DIRS+=("$(dirname "$matched_path")") + add_recipe_dir "$(dirname "$matched_path")" "bicep" done < <(find "$ROOT_DIR" -type f -path "*/recipes/*/*.bicep" -print0 2>/dev/null) fi # Find Terraform recipe directories (directories containing main.tf under recipes/terraform) -if [[ "$FILTER_TYPE" == "all" || "$FILTER_TYPE" == "terraform" ]]; then +if [[ "$RECIPE_TYPE_FILTER" == "all" || "$RECIPE_TYPE_FILTER" == "terraform" ]]; then while IFS= read -r -d '' matched_path; do - RECIPE_DIRS+=("$(dirname "$matched_path")") + add_recipe_dir "$(dirname "$matched_path")" "terraform" done < <(find "$ROOT_DIR" -type f -path "*/recipes/*/terraform/main.tf" -print0 2>/dev/null) fi diff --git a/.github/scripts/register-recipe.sh b/.github/scripts/register-recipe.sh index 2be81570..86490893 100755 --- a/.github/scripts/register-recipe.sh +++ b/.github/scripts/register-recipe.sh @@ -27,10 +27,12 @@ set -euo pipefail RECIPE_PATH="${1:-}" +ENVIRONMENT_OVERRIDE="${2:-}" +WORKSPACE_OVERRIDE="${3:-}" if [[ -z "$RECIPE_PATH" ]]; then echo "Error: Recipe path is required" - echo "Usage: $0 " + echo "Usage: $0 [environment] [workspace]" exit 1 fi @@ -63,7 +65,49 @@ CATEGORY=$(basename "$(dirname "$RESOURCE_TYPE_PATH")") RESOURCE_NAME=$(basename "$RESOURCE_TYPE_PATH") RESOURCE_TYPE="Radius.$CATEGORY/$RESOURCE_NAME" +# Derive platform from recipe path (first segment after recipes/) +RECIPES_RELATIVE="${RECIPE_PATH#${RESOURCE_TYPE_PATH}/recipes/}" +PLATFORM="${RECIPES_RELATIVE%%/*}" + +# Determine workspace and environment names based on platform +KUBERNETES_WORKSPACE_NAME="${KUBERNETES_WORKSPACE_NAME:-default}" +KUBERNETES_ENVIRONMENT_NAME="${KUBERNETES_ENVIRONMENT_NAME:-default}" +AZURE_WORKSPACE_NAME="${AZURE_WORKSPACE_NAME:-azure}" +AZURE_ENVIRONMENT_NAME="${AZURE_ENVIRONMENT_NAME:-azure}" + +WORKSPACE_NAME="$KUBERNETES_WORKSPACE_NAME" +ENVIRONMENT_NAME="$KUBERNETES_ENVIRONMENT_NAME" + +case "$PLATFORM" in + azure) + WORKSPACE_NAME="$AZURE_WORKSPACE_NAME" + ENVIRONMENT_NAME="$AZURE_ENVIRONMENT_NAME" + ;; + kubernetes) + WORKSPACE_NAME="$KUBERNETES_WORKSPACE_NAME" + ENVIRONMENT_NAME="$KUBERNETES_ENVIRONMENT_NAME" + ;; + "") + : # fall back to defaults + ;; + *) + : # other platforms default to Kubernetes values unless overridden + ;; +esac + +if [[ -n "$ENVIRONMENT_OVERRIDE" ]]; then + ENVIRONMENT_NAME="$ENVIRONMENT_OVERRIDE" +fi + +if [[ -n "$WORKSPACE_OVERRIDE" ]]; then + WORKSPACE_NAME="$WORKSPACE_OVERRIDE" +elif [[ -n "$ENVIRONMENT_OVERRIDE" ]]; then + WORKSPACE_NAME="$ENVIRONMENT_OVERRIDE" +fi + echo "==> Resource type: $RESOURCE_TYPE" +echo "==> Workspace: $WORKSPACE_NAME" +echo "==> Environment: $ENVIRONMENT_NAME" # Determine template path based on recipe type if [[ "$RECIPE_TYPE" == "bicep" ]]; then @@ -86,8 +130,8 @@ if [[ "$RECIPE_TYPE" == "bicep" ]]; then elif [[ "$RECIPE_TYPE" == "terraform" ]]; then # For Terraform, use HTTP module server with format: resourcename-platform.zip - PLATFORM=$(basename "$(dirname "$RECIPE_PATH")") - RECIPE_NAME="${RESOURCE_NAME}-${PLATFORM}" + MODULE_PLATFORM=$(basename "$(dirname "$RECIPE_PATH")") + RECIPE_NAME="${RESOURCE_NAME}-${MODULE_PLATFORM}" TEMPLATE_PATH="http://tf-module-server.radius-test-tf-module-server.svc.cluster.local/${RECIPE_NAME}.zip" fi @@ -95,7 +139,8 @@ echo "==> Registering recipe: $RECIPE_NAME" echo "==> Template path: $TEMPLATE_PATH" rad recipe register default \ - --environment default \ + --workspace "$WORKSPACE_NAME" \ + --environment "$ENVIRONMENT_NAME" \ --resource-type "$RESOURCE_TYPE" \ --template-kind "$TEMPLATE_KIND" \ --template-path "$TEMPLATE_PATH" \ @@ -105,4 +150,4 @@ echo "==> Recipe registered successfully" # Log the registered recipe details echo "==> Verifying registration..." -rad recipe show default --resource-type "$RESOURCE_TYPE" --environment default || echo "Warning: Could not verify recipe registration" \ No newline at end of file +rad recipe show default --resource-type "$RESOURCE_TYPE" --environment "$ENVIRONMENT_NAME" || echo "Warning: Could not verify recipe registration" \ No newline at end of file diff --git a/.github/scripts/test-recipe.sh b/.github/scripts/test-recipe.sh index aab9e33b..ed81d7ff 100755 --- a/.github/scripts/test-recipe.sh +++ b/.github/scripts/test-recipe.sh @@ -17,8 +17,8 @@ # ------------------------------------------------------------ # ============================================================================= -# Test a single Radius recipe by deploying a test app and cleaning up. -# Assumes the recipe has already been registered. +# Test a single Radius recipe by registering it, deploying a test app, and +# cleaning up. Automatically detects whether the recipe is Bicep or Terraform. # # Usage: ./test-recipe.sh # Example: ./test-recipe.sh Security/secrets/recipes/kubernetes/bicep @@ -27,7 +27,8 @@ set -euo pipefail RECIPE_PATH="${1:-}" -ENVIRONMENT_PATH="${2:-/planes/radius/local/resourceGroups/default/providers/Radius.Core/environments/default}" +ENVIRONMENT_NAME_OVERRIDE="${2:-}" +ENVIRONMENT_PATH="" ensure_namespace_ready() { # Ensure the test namespace exists before deploying @@ -36,10 +37,31 @@ ensure_namespace_ready() { fi # Update the env with kubernetes provider - rad env update default --kubernetes-namespace testapp --preview + rad env update "$ENVIRONMENT_NAME" --kubernetes-namespace testapp --preview echo "==> Environment Updated with Kubernetes provider:" - rad env show "$ENVIRONMENT_PATH" -o json --preview || true + rad env show "$ENVIRONMENT_NAME" -o json --preview || true +} + +ensure_workspace_context() { + # Ensure we are operating in the expected workspace context + rad workspace switch "$WORKSPACE_NAME" >/dev/null 2>&1 || true +} + +resolve_environment_path() { + # Resolve the full environment resource ID to avoid hardcoding the provider path + if ! ENVIRONMENT_JSON=$(rad env show "$ENVIRONMENT_NAME" --workspace "$WORKSPACE_NAME" -o json --preview 2>/dev/null); then + echo "Error: Environment '$ENVIRONMENT_NAME' was not found in workspace '$WORKSPACE_NAME'." + exit 1 + fi + + ENVIRONMENT_PATH=$(echo "$ENVIRONMENT_JSON" | jq -r 'if type=="object" then (.id // "") elif type=="array" and length>0 then (.[0].id // "") else "" end') + if [[ -z "$ENVIRONMENT_PATH" ]]; then + echo "Error: Could not determine environment id from rad env show output." + echo "$ENVIRONMENT_JSON" + exit 1 + fi + echo "==> Environment path: $ENVIRONMENT_PATH" } if [[ -z "$RECIPE_PATH" ]]; then @@ -60,8 +82,10 @@ RECIPE_PATH="${RECIPE_PATH#./}" # Detect recipe type based on file presence if [[ -f "$RECIPE_PATH/main.tf" ]]; then RECIPE_TYPE="terraform" + TEMPLATE_KIND="terraform" elif ls "$RECIPE_PATH"/*.bicep &>/dev/null; then RECIPE_TYPE="bicep" + TEMPLATE_KIND="bicep" else echo "Error: Could not detect recipe type in $RECIPE_PATH" exit 1 @@ -75,8 +99,61 @@ CATEGORY=$(basename "$(dirname "$RESOURCE_TYPE_PATH")") RESOURCE_NAME=$(basename "$RESOURCE_TYPE_PATH") RESOURCE_TYPE="Radius.$CATEGORY/$RESOURCE_NAME" -echo "==> Assuming recipe is already registered" +# Derive platform from recipe path (first segment after recipes/) +RECIPES_RELATIVE="${RECIPE_PATH#${RESOURCE_TYPE_PATH}/recipes/}" +PLATFORM="${RECIPES_RELATIVE%%/*}" + +# Determine workspace and environment names based on platform (with overrides) +RADIUS_WORKSPACE_OVERRIDE="${RADIUS_WORKSPACE_OVERRIDE:-}" +RADIUS_ENVIRONMENT_OVERRIDE="${RADIUS_ENVIRONMENT_OVERRIDE:-}" + +KUBERNETES_WORKSPACE_NAME="${KUBERNETES_WORKSPACE_NAME:-default}" +KUBERNETES_ENVIRONMENT_NAME="${KUBERNETES_ENVIRONMENT_NAME:-default}" +AZURE_WORKSPACE_NAME="${AZURE_WORKSPACE_NAME:-azure}" +AZURE_ENVIRONMENT_NAME="${AZURE_ENVIRONMENT_NAME:-azure}" + +WORKSPACE_NAME="$KUBERNETES_WORKSPACE_NAME" +ENVIRONMENT_NAME="$KUBERNETES_ENVIRONMENT_NAME" + +case "$PLATFORM" in + azure) + WORKSPACE_NAME="$AZURE_WORKSPACE_NAME" + ENVIRONMENT_NAME="$AZURE_ENVIRONMENT_NAME" + ;; + kubernetes) + WORKSPACE_NAME="$KUBERNETES_WORKSPACE_NAME" + ENVIRONMENT_NAME="$KUBERNETES_ENVIRONMENT_NAME" + ;; + "") + # Fallback to defaults when the platform segment is missing + WORKSPACE_NAME="$KUBERNETES_WORKSPACE_NAME" + ENVIRONMENT_NAME="$KUBERNETES_ENVIRONMENT_NAME" + ;; + *) + # Additional platforms default to Kubernetes workspace/environment unless overridden + WORKSPACE_NAME="$KUBERNETES_WORKSPACE_NAME" + ENVIRONMENT_NAME="$KUBERNETES_ENVIRONMENT_NAME" + ;; +esac + +if [[ -n "$RADIUS_WORKSPACE_OVERRIDE" ]]; then + WORKSPACE_NAME="$RADIUS_WORKSPACE_OVERRIDE" +fi + +if [[ -n "$RADIUS_ENVIRONMENT_OVERRIDE" ]]; then + ENVIRONMENT_NAME="$RADIUS_ENVIRONMENT_OVERRIDE" +fi + +if [[ -n "$ENVIRONMENT_NAME_OVERRIDE" ]]; then + ENVIRONMENT_NAME="$ENVIRONMENT_NAME_OVERRIDE" +fi + echo "==> Resource type: $RESOURCE_TYPE" +echo "==> Workspace: $WORKSPACE_NAME" +echo "==> Environment: $ENVIRONMENT_NAME" + +ensure_workspace_context +resolve_environment_path # Check if test file exists TEST_FILE="$RESOURCE_TYPE_PATH/test/app.bicep" @@ -92,7 +169,7 @@ APP_NAME="testapp-$(date +%s)" ensure_namespace_ready # Deploy the test app -if rad deploy "$TEST_FILE" --application "$APP_NAME" -e "/planes/radius/local/resourceGroups/default/providers/Radius.Core/environments/default"; then +if rad deploy "$TEST_FILE" --application "$APP_NAME" -e "$ENVIRONMENT_NAME"; then echo "==> Test deployment successful" # Cleanup: delete the app @@ -104,4 +181,4 @@ else exit 1 fi -echo "==> Test completed successfully" +echo "==> Test completed successfully" \ No newline at end of file diff --git a/.github/workflows/validate-azure-recipes.yaml b/.github/workflows/validate-azure-recipes.yaml new file mode 100644 index 00000000..aced1c11 --- /dev/null +++ b/.github/workflows/validate-azure-recipes.yaml @@ -0,0 +1,162 @@ +name: Validate Azure Recipes + +on: + push: + branches: [ main ] + paths: + - '**/azure/**' + - '**/test/app.bicep' + - '.github/workflows/validate-azure-recipes.yaml' + pull_request: + branches: [ main ] + paths: + - '**/azure/**' + - '**/test/app.bicep' + - '.github/workflows/validate-azure-recipes.yaml' + workflow_dispatch: + inputs: + version: + description: 'Radius version number to use (e.g. 0.51.0, 0.51.0-rc1, edge).' + required: false + default: 'edge' + type: string + +permissions: + id-token: write + contents: read + +jobs: + validate-azure-recipes: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + name: Validate Azure ${{ matrix.recipe }} Recipes + environment: azure + strategy: + fail-fast: false + matrix: + recipe: [bicep, terraform] + permissions: + id-token: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 + with: + persist-credentials: false + + - name: Azure Login + uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Set Azure Test Context + id: set-context + run: | + LOCATION="${AZURE_LOCATION:-${{ vars.AZURE_LOCATION }}}" + if [ -z "$LOCATION" ]; then + LOCATION="westus3" + fi + RG="rrttest-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.recipe }}" + echo "location=$LOCATION" >> "$GITHUB_OUTPUT" + echo "AZURE_LOCATION=$LOCATION" >> "$GITHUB_ENV" + echo "AZURE_RESOURCE_GROUP=$RG" >> "$GITHUB_ENV" + echo "AZURE_WORKSPACE_NAME=default" >> "$GITHUB_ENV" + echo "AZURE_ENVIRONMENT_NAME=default" >> "$GITHUB_ENV" + + - name: Create Azure Resource Group + env: + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + run: | + set -euo pipefail + RG="${{ env.AZURE_RESOURCE_GROUP }}" + LOCATION="${{ env.AZURE_LOCATION }}" + current_time=$(date +%s) + az group create \ + --only-show-errors \ + --output none \ + --location "$LOCATION" \ + --name "$RG" \ + --subscription "$AZURE_SUBSCRIPTION_ID" \ + --tags creationTime=$current_time > /dev/null + # Wait for resource group to be fully available + while [[ "$(az group exists --name "$RG" --subscription "$AZURE_SUBSCRIPTION_ID")" != "true" ]]; do + echo "Waiting for resource group '$RG' to be available..." + sleep 5 + done + echo "Resource group '$RG' is ready" + + - name: Setup Node + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 + with: + node-version: 22 + + - name: Set up ORAS + uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v1.2.0 + with: + version: '1.2.0' + + - name: Install Radius CLI + run: make install-radius-cli RAD_VERSION="${{ inputs.version || 'edge' }}" + + - name: Create Radius Cluster + env: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + TEST_AZURE_OIDC_JSON: ${{ secrets.TEST_AZURE_OIDC_JSON }} + run: make create-radius-cluster + + - name: Configure Azure Provider + env: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + run: make configure-azure-provider + + - name: Build Azure Recipes + run: make build-azure-recipes + + - name: Generate Azure Recipe Pack + run: RECIPE_PLATFORM_FILTER=azure make generate-recipe-pack PACK_NAME=azure${{ matrix.recipe }}recipepack OUTPUT_FILE=recipe-pack-azure-${{ matrix.recipe }}.bicep + + - name: Deploy Azure Recipe Pack + env: + RESOURCE_GROUP: ${{ env.AZURE_RESOURCE_GROUP }} + SUBSCRIPTION: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + run: make deploy-recipe-pack BICEP_FILE=recipe-pack-azure-${{ matrix.recipe }}.bicep + + - name: Update Azure Environment with Recipe Pack + run: make update-env-recipe-pack RECIPE_PACK_NAME=azure${{ matrix.recipe }}recipepack ENVIRONMENT=${{ env.AZURE_ENVIRONMENT_NAME }} + + - name: Test Azure ${{ matrix.recipe }} Recipes + run: RECIPE_PLATFORM_FILTER=azure make test ENVIRONMENT=${{ env.AZURE_ENVIRONMENT_NAME }} RECIPE_TYPE=${{ matrix.recipe }} + + - name: Collect Radius pod logs + if: always() + run: | + mkdir -p radius-pod-logs + APP_POD=$(kubectl get pods -n radius-system -o json | jq -r '.items[] | select(.metadata.name | startswith("applications-rp-")) | .metadata.name' | head -n1) + if [ -n "$APP_POD" ]; then + kubectl logs "$APP_POD" -n radius-system --all-containers > radius-pod-logs/applications-rp.log || true + else + echo "applications-rp pod not found" > radius-pod-logs/applications-rp.log + fi + DE_POD=$(kubectl get pods -n radius-system -o json | jq -r '.items[] | select(.metadata.name | test("^(deployment-engine|bicep-de)-")) | .metadata.name' | head -n1) + if [ -n "$DE_POD" ]; then + kubectl logs "$DE_POD" -n radius-system --all-containers > radius-pod-logs/deployment-engine.log || true + else + echo "deployment engine pod not found" > radius-pod-logs/deployment-engine.log + fi + + - name: Upload Radius pod logs + if: always() + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: radius-pod-logs-${{ matrix.recipe }} + path: radius-pod-logs + if-no-files-found: warn + + - name: Cleanup Azure Resources + if: always() + run: make cleanup-azure-resources diff --git a/.github/workflows/validate-resource-types.yaml b/.github/workflows/validate-resource-types.yaml index b034aaa1..3b108178 100644 --- a/.github/workflows/validate-resource-types.yaml +++ b/.github/workflows/validate-resource-types.yaml @@ -5,8 +5,18 @@ name: Validate Resource Types on: push: branches: [main] + paths: + - '**/kubernetes/**' + - '**/test/app.bicep' + - '**/*.yaml' + - '.github/workflows/validate-resource-types.yaml' pull_request: branches: [main] + paths: + - '**/kubernetes/**' + - '**/test/app.bicep' + - '**/*.yaml' + - '.github/workflows/validate-resource-types.yaml' workflow_dispatch: inputs: version: @@ -21,7 +31,7 @@ jobs: validate-resource-types: name: Validate Resource Types and Recipes runs-on: ubuntu-24.04 - timeout-minutes: 5 + timeout-minutes: 30 strategy: fail-fast: false matrix: @@ -39,13 +49,9 @@ jobs: - name: Setup Node uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: - node-version: 16 - - - name: Set up k3d - run: wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash - + node-version: 22 - name: Set up ORAS - uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v1.2.4 + uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v1.2.0 with: version: "1.2.0" @@ -55,10 +61,13 @@ jobs: # this setups a local k3d cluster for testing purposes, creates workspace, group and env preview - name: Create Radius Cluster run: make create-radius-cluster + + - name: Build Recipes + run: RECIPE_PLATFORM_FILTER=kubernetes make build # Generates the bicep definition for recipe pack in recipe-pack-{bicep|terraform}.bicep - name: Generate Recipe Pack - run: make generate-recipe-pack PACK_NAME=${{ matrix.recipe }}recipepack OUTPUT_FILE=recipe-pack-${{ matrix.recipe }}.bicep + run: RECIPE_PLATFORM_FILTER=kubernetes make generate-recipe-pack PACK_NAME=${{ matrix.recipe }}recipepack OUTPUT_FILE=recipe-pack-${{ matrix.recipe }}.bicep # Useful for debugging # - name: Show Recipe Pack Content @@ -69,9 +78,5 @@ jobs: - name: Update Environment with Recipe Pack run: make update-env-recipe-pack RECIPE_PACK_NAME=${{ matrix.recipe }}recipepack ENVIRONMENT="${RAD_ENVIRONMENT_NAME}" - - - name: Build - run: make build - - name: Test ${{ matrix.recipe }} recipes - run: make test ENVIRONMENT="${RAD_ENVIRONMENT_NAME}" RECIPE_TYPE=${{ matrix.recipe }} + run: RECIPE_PLATFORM_FILTER=kubernetes make test ENVIRONMENT=$RAD_ENVIRONMENT_NAME RECIPE_TYPE=${{ matrix.recipe }} diff --git a/Compute/containers/test/app.bicep b/Compute/containers/test/app.bicep index 774126d5..10aa55a6 100644 --- a/Compute/containers/test/app.bicep +++ b/Compute/containers/test/app.bicep @@ -13,7 +13,7 @@ param password string = 'c2VjcmV0cGFzc3dvcmQ=' #disable-next-line secure-parameter-default @secure() param apiKey string = 'abc123xyz' -resource app 'Radius.Core/applications@2025-08-01-preview' = { +resource app 'Applications.Core/applications@2023-10-01-preview' = { name: 'containers-testapp' properties: { environment: environment diff --git a/Makefile b/Makefile index e5995c9c..1cc04548 100644 --- a/Makefile +++ b/Makefile @@ -24,9 +24,9 @@ # make help # Show all available targets # # Environment Setup: -# make install-radius-cli # Install Radius CLI -# make create-radius-cluster # Create a local k3d Kubernetes cluster for testing -# make clean # Delete the local k3d cluster, config, and build artifacts +# make install-radius # Install Radius CLI +# make create-cluster # Create a local kind Kubernetes cluster for testing +# make delete-cluster # Delete the local kind Kubernetes cluster # # Development and Testing: # make build # Build all resource types and recipes diff --git a/docs/contributing/contributing-resource-types-tests.md b/docs/contributing/contributing-resource-types-tests.md index f14741c4..1e75f5dc 100644 --- a/docs/contributing/contributing-resource-types-tests.md +++ b/docs/contributing/contributing-resource-types-tests.md @@ -37,7 +37,7 @@ Repository Root ```bash make install-radius-cli # Install Radius CLI -make create-radius-cluster # Create k3d cluster with Radius and Dapr +make create-radius-cluster # Create kind cluster with Radius make delete-radius-cluster # Delete test cluster ``` @@ -81,7 +81,7 @@ The dev container includes: - Radius CLI (latest version) - Azure CLI - Terraform -- k3d (Kubernetes in Docker) +- kind (Kubernetes in Docker) - kubectl - Bicep - All required VS Code extensions @@ -114,7 +114,7 @@ If you encounter a "Connection refused" error on `localhost:5000` while building 3. Keep this terminal running in the background 4. In a new terminal, retry your build or test command -This creates a port forward that allows the container to access the OCI registry running on your host machine. The registry is created when you run `make create-radius-cluster`, which uses the k3d option to set up a local registry at `localhost:5000`, which is accessible from within the cluster as `reciperegistry:5000`. See the script [`.github/scripts/create-cluster.sh`](../../.github/scripts/create-cluster.sh) for more details on how the `make create-radius-cluster` command sets up the registry. +This creates a port forward that allows the container to access the OCI registry running on your host machine. The registry is created when you run `make create-radius-cluster`, which uses the kind option to set up a local registry at `localhost:5000`, which is accessible from within the cluster as `reciperegistry:5000`. See the script [`.github/scripts/create-cluster.sh`](../../.github/scripts/create-cluster.sh) for more details on how the `make create-radius-cluster` command sets up the registry. **Note**: You may need to run this `socat` command each time you start the dev container if the connection issue persists. diff --git a/docs/contributing/testing-resource-types-recipes.md b/docs/contributing/testing-resource-types-recipes.md index 2cda5250..63ffad3d 100644 --- a/docs/contributing/testing-resource-types-recipes.md +++ b/docs/contributing/testing-resource-types-recipes.md @@ -6,8 +6,7 @@ This guide explains how to test Resource Types and Recipes locally using the sta Before testing, ensure you have: -- Docker installed (for running k3d) -- `k3d` installed +- Docker installed (for running kind) - `kubectl` installed - `helm` installed - `oras` installed @@ -23,7 +22,7 @@ Create a local Kubernetes cluster with Radius (and Dapr) installed: # Install Radius CLI (optional: specify version with RAD_VERSION=0.48.0) make install-radius-cli -# Create k3d cluster with Radius and Dapr configured +# Create kind cluster with Radius configured make create-radius-cluster ``` @@ -257,7 +256,7 @@ make test-recipe RECIPE_PATH=Security/secrets/recipes/kubernetes/bicep ```bash # Environment setup make install-radius-cli # Install Radius CLI -make create-radius-cluster # Create k3d cluster with Radius +make create-radius-cluster # Create kind cluster with Radius make delete-radius-cluster # Delete test cluster # Build commands @@ -322,3 +321,202 @@ Ensure the recipe is built before testing: - **Alpha**: Manual testing using `make test-recipe` is sufficient - **Beta**: Automated testing with `test/app.bicep` files required for all recipes - **Stable**: Full CI/CD integration (see [Contributing Tests](contributing-resource-types-tests.md)) + +## Testing Azure Recipes + +Azure recipes require additional setup since they deploy resources to Azure. + +### Prerequisites for Azure Recipes + +Before testing Azure recipes, ensure you have: + +- Azure CLI installed and authenticated (`az login`) +- An Azure subscription with appropriate permissions (Contributor role recommended) +- A service principal for Radius to use when deploying Azure resources + +### Local Testing + +#### 1. Create a Service Principal + +Radius needs a service principal to deploy Azure resources. Create one with the Azure CLI: + +```bash +az ad sp create-for-rbac --name "radius-test-sp" --role Contributor --scopes /subscriptions/ +``` + +This outputs credentials you'll need: +```json +{ + "appId": "", + "displayName": "radius-test-sp", + "password": "", + "tenant": "" +} +``` + +#### 2. Set Up Azure Environment Variables + +Export the required environment variables for Azure authentication and resource configuration: + +```bash +# Azure authentication (from service principal output) +export AZURE_CLIENT_ID="" +export AZURE_CLIENT_SECRET="" +export AZURE_TENANT_ID="" +export AZURE_SUBSCRIPTION_ID="" + +# Azure resource configuration +export AZURE_LOCATION="westus3" # or your preferred region +export AZURE_RESOURCE_GROUP="rrttest-local" # unique name for testing +``` + +#### 3. Create an Azure Resource Group + +```bash +az group create \ + --name "$AZURE_RESOURCE_GROUP" \ + --location "$AZURE_LOCATION" \ + --subscription "$AZURE_SUBSCRIPTION_ID" +``` + +#### 4. Create a Radius Cluster with Azure Provider + +Azure recipes use **Azure Workload Identity** for authentication, which requires OIDC configuration. When `AZURE_TENANT_ID` is set, the cluster setup script automatically configures the KinD cluster with OIDC support and installs the Azure Workload Identity webhook. + +Follow the [Azure Workload Identity documentation for self-managed clusters](https://azure.github.io/azure-workload-identity/docs/installation/self-managed-clusters.html) to set up OIDC. + +Once OIDC is configured, set the `TEST_AZURE_OIDC_JSON` environment variable with your OIDC configuration: + +```bash +export TEST_AZURE_OIDC_JSON='{ + "AZURE_OIDC_ISSUER": "https://your-oidc-issuer-url", + "AZURE_OIDC_ISSUER_PUBLIC_KEY": "", + "AZURE_OIDC_ISSUER_PRIVATE_KEY": "" +}' +``` + +**Create the cluster:** + +```bash +# Install Radius CLI +make install-radius-cli + +# Create kind cluster with OIDC support and Radius installed +# AZURE_TENANT_ID triggers OIDC/Workload Identity setup +make create-radius-cluster + +# Configure the Azure cloud provider in Radius +make configure-azure-provider +``` + +#### 5. Build and Test Azure Recipes + +```bash +# Build all Azure recipes +make build-azure-recipes + +# Register Azure Bicep recipes +RECIPE_PLATFORM_FILTER=azure make register ENVIRONMENT=default RECIPE_TYPE=bicep + +# Test Azure Bicep recipes +RECIPE_PLATFORM_FILTER=azure make test ENVIRONMENT=default RECIPE_TYPE=bicep + +# Register Azure Terraform recipes +RECIPE_PLATFORM_FILTER=azure make register ENVIRONMENT=default RECIPE_TYPE=terraform + +# Test Azure Terraform recipes +RECIPE_PLATFORM_FILTER=azure make test ENVIRONMENT=default RECIPE_TYPE=terraform +``` + +#### 6. Cleanup + +```bash +# Clean up Azure resources created by recipes +make cleanup-azure-resources + +# Delete the resource group +az group delete --name "$AZURE_RESOURCE_GROUP" --yes --no-wait + +# Delete the local Kubernetes cluster +make delete-radius-cluster +``` + +### Required Environment Variables Reference + +| Variable | Description | Example | +|----------|-------------|---------| +| `AZURE_CLIENT_ID` | Service principal application (client) ID | `12345678-1234-1234-1234-123456789012` | +| `AZURE_CLIENT_SECRET` | Service principal password/secret | `your-secret-value` | +| `AZURE_TENANT_ID` | Azure AD tenant ID | `12345678-1234-1234-1234-123456789012` | +| `AZURE_SUBSCRIPTION_ID` | Azure subscription ID | `12345678-1234-1234-1234-123456789012` | +| `AZURE_LOCATION` | Azure region for resources | `westus3`, `eastus`, `westeurope` | +| `AZURE_RESOURCE_GROUP` | Resource group name for test resources | `rrttest-local` | + +### Azure Recipe Directory Structure + +Azure recipes should follow this structure: + +``` +Data/mySqlDatabases/ +├── mySqlDatabases.yaml +├── README.md +├── recipes/ +│ ├── azure/ +│ │ ├── bicep/ +│ │ │ └── azure-mysql.bicep +│ │ └── terraform/ +│ │ ├── main.tf +│ │ └── var.tf +│ └── kubernetes/ +│ └── ... +└── test/ + └── app.bicep +``` + +### Troubleshooting Azure Recipe Tests + +#### "ResourceGroupNotFound" Error + +Ensure the resource group exists before running tests: +```bash +az group show --name "$AZURE_RESOURCE_GROUP" +``` + +If it doesn't exist, create it: +```bash +az group create --name "$AZURE_RESOURCE_GROUP" --location "$AZURE_LOCATION" +``` + +#### Azure Authentication Fails + +Verify your environment variables are set correctly: +```bash +echo "Client ID: $AZURE_CLIENT_ID" +echo "Tenant ID: $AZURE_TENANT_ID" +echo "Subscription ID: $AZURE_SUBSCRIPTION_ID" +``` + +Test authentication with the Azure CLI: +```bash +az login --service-principal \ + --username "$AZURE_CLIENT_ID" \ + --password "$AZURE_CLIENT_SECRET" \ + --tenant "$AZURE_TENANT_ID" +``` + +#### Service Principal Permission Issues + +Ensure your service principal has `Contributor` role on the subscription or resource group: +```bash +az role assignment create \ + --assignee "$AZURE_CLIENT_ID" \ + --role Contributor \ + --scope "/subscriptions/$AZURE_SUBSCRIPTION_ID" +``` + +#### Recipe Deployment Timeout + +Azure resource provisioning can take longer than Kubernetes. If deployments time out: +- Check the Azure portal for deployment status +- Review Radius pod logs +- Some Azure resources (e.g., databases) may take 10-20 minutes to provision