diff --git a/tools/azd-extension/java-on-ai-azd.json b/tools/azd-extension/java-on-ai-azd.json new file mode 100644 index 0000000..3c714ba --- /dev/null +++ b/tools/azd-extension/java-on-ai-azd.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://aka.ms/azd-extension-schema", + "name": "java-on-ai-extension", + "version": "1.1.0", + "description": "Java on AI AZD extension with bash", + "publisher": "Pete Tian", + "commands": [ + { + "name": "collecte user information", + "description": "Before performing any additional actions, you’ll need to store your user info as an environment variable that you can use in subsequent steps.", + "entryPoint": "./lab2/collect-user-info.sh" + }, + { + "name": "Create an Azure Container Apps environment", + "description": "With your user information stored in environment variables, you’re ready to begin the first step in the deployment: creating an Azure Container Apps instance.", + "entryPoint": "./lab-2/create-aca-env.sh" + }, + { + "name": "Create an Azure Database for MySQL instance", + "description": "Create an Azure Database for MySQL - Flexible Server–hosted database.", + "entryPoint": "./lab-2/create-mysql.sh" + }, + { + "name": "Set up a configuration repository", + "description": "Set up the application configuration settings that allow your Spring Boot application to connect with the database.", + "entryPoint": "./lab-2/create-container-registry.sh" + }, + { + "name": "Create the Java components", + "description": "Configure the config, discovery, and admin applications.", + "entryPoint": "./lab-2/create-java-components.sh" + }, + { + "name": "Deploy the component applications to Azure Container Apps", + "description": "Deploy the workload’s component applications to Azure.", + "entryPoint": "./lab-2/deploy-component-apps.sh" + } + ] +} \ No newline at end of file diff --git a/tools/azd-extension/lab-2/collect-user-info.sh b/tools/azd-extension/lab-2/collect-user-info.sh new file mode 100644 index 0000000..03f22ae --- /dev/null +++ b/tools/azd-extension/lab-2/collect-user-info.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Before performing any additional actions, store user info as an environment variable that +# can be used in subsequent steps. + +export USER_NAME=$(az account show --query user.name --output tsv) +echo "Current user:" $USER_NAME + +export AAD_USER_ID=$(az ad signed-in-user show --query id --output tsv) +echo "Object ID:" $AAD_USER_ID diff --git a/tools/azd-extension/lab-2/create-aca-env.sh b/tools/azd-extension/lab-2/create-aca-env.sh new file mode 100644 index 0000000..a177587 --- /dev/null +++ b/tools/azd-extension/lab-2/create-aca-env.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# This script creates an Azure Container Apps environment with a dedicated plan and a virtual network. +# It also creates a resource group and a virtual network with a subnet for the Azure Container Apps environment. + +random_element() { + local array=("$@") + echo "${array[RANDOM % ${#array[@]}]}" +} + +UNIQUEID=$(openssl rand -hex 3) + +export APPNAME=petclinic +export RESOURCE_GROUP=$(az group list --query "[?contains(name, '$APPNAME')].{Name:name}[0]" -o tsv) + +if [ -z "$RESOURCE_GROUP" ]; then + RESOURCE_GROUP=rg-$APPNAME-$UNIQUEID + LOCATION=westus + echo "Creating resource group [$RESOURCE_GROUP] in region [$LOCATION]..." + az group create -g $RESOURCE_GROUP -l $LOCATION +else + LOCATION=$(az group show --name $RESOURCE_GROUP --query "location" -o tsv) + echo "Provisioning in resource group [$RESOURCE_GROUP]..." +fi + +az configure --default group=$RESOURCE_GROUP + +# +# Create VNET for Azure Container Apps Environment +# +VIRTUAL_NETWORK_NAME=vnet-$APPNAME-$UNIQUEID +az network vnet create \ + --resource-group $RESOURCE_GROUP \ + --name $VIRTUAL_NETWORK_NAME \ + --location $LOCATION \ + --address-prefix 10.1.0.0/16 + +ACA_SUBNET_CIDR=10.1.0.0/27 +az network vnet subnet create \ + --resource-group $RESOURCE_GROUP \ + --vnet-name $VIRTUAL_NETWORK_NAME \ + --address-prefixes $ACA_SUBNET_CIDR \ + --name aca-subnet \ + --delegations Microsoft.App/environments + +SUBNET_ID="$(az network vnet subnet show --resource-group $RESOURCE_GROUP --vnet-name $VIRTUAL_NETWORK_NAME --name aca-subnet --query "id" -o tsv)" +echo "Subnet ID: [$SUBNET_ID]" + +# +# Creating the service on an Azure Container Apps Dedicated plan using the workload profiles option. +# This plan gives you more advanced features than the alternative Azure Container Apps Consumption plan type +# +export ACA_ENVIRONMENT=acalab-env-$APPNAME-$UNIQUEID + +# If folder ../config does not exist, create it +if [ ! -d "../config" ]; then + mkdir -p ../config +fi +AZURE_CONFIG_FILE="../config/azure-resource.profile" + +# winty is to accommodate git bash on Windows +# Otherwise, SUBNET_ID will be appended with local drive path +# NO_PATHCOW is for git bash on Windows +export MSYS_NO_PATHCONV=1 + +az containerapp env create \ + -n $ACA_ENVIRONMENT \ + -g $RESOURCE_GROUP \ + --location $LOCATION \ + --enable-workload-profiles true \ + --infrastructure-subnet-resource-id "$SUBNET_ID" \ + --logs-destination none + +export ACA_ENVIRONMENT_ID=$(az containerapp env show -n $ACA_ENVIRONMENT -g $RESOURCE_GROUP --query id -o tsv) + +# Write variables to the azure-resource.profile +{ + echo "RESOURCE_GROUP=$RESOURCE_GROUP" + echo "LOCATION=$LOCATION" + echo "UNIQUEID=$UNIQUEID" + echo "APPNAME=$APPNAME" + echo "ACA_ENVIRONMENT=$ACA_ENVIRONMENT" + echo "ACA_ENVIRONMENT_ID=$ACA_ENVIRONMENT_ID" +} > $AZURE_CONFIG_FILE + +# Verify that the azure-resource.profile file is created properly +if [ -f $AZURE_CONFIG_FILE ]; then + echo "azure-resource.profile file created successfully." +else + echo "Error: azure-resource.profile file not created." + exit 1 +fi diff --git a/tools/azd-extension/lab-2/create-container-registry.sh b/tools/azd-extension/lab-2/create-container-registry.sh new file mode 100644 index 0000000..bc64ae0 --- /dev/null +++ b/tools/azd-extension/lab-2/create-container-registry.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# +# Before deploying applications to the Azure Container Apps environment, an Azure Container Registry instance is needed that allows to build and save application container images. +# Allow the Container Apps environment to pull images from this new container registry. +# +# This script creates an Azure Container Registry (ACR) and a user managed identity for the Azure Container Apps environment. +# The script also assigns the user managed identity to the ACR and grants it the necessary permissions to pull images from the registry. +# The script uses the Azure CLI to create the ACR and user managed identity, and to assign the identity to the ACR. +# The script also checks if the ACR and user managed identity already exist, and skips their creation if they do. +# The script also exports the ACR and user managed identity names and IDs as environment variables, and appends them to the Azure configuration file. +# The script also checks if the Azure Container Apps environment name exists, and exits with an error message if it does not. +# The script also disables path conversion for MSYS (Git Bash) to avoid issues with file paths. + + +AZURE_CONFIG_FILE="../config/azure-resource.profile" +source $AZURE_CONFIG_FILE + +# Check if a container registry containing the string $APPNAME already exists +MYACR=$(az acr list --resource-group $RESOURCE_GROUP --query "[?contains(name, '$APPNAME')].{Name:name}[0]" -o tsv) + +CR_UNIQUEID=$(openssl rand -hex 3) +if [ -n "$MYACR" ]; then + echo "Container registry [$MYACR] already exists. Skipping creation." +else + MYACR=acr$APPNAME$CR_UNIQUEID + echo "Creating new container registry [$MYACR]..." + az acr create \ + -n $MYACR \ + -g $RESOURCE_GROUP \ + --sku Basic \ + --admin-enabled true +fi + +# Check if ACA environment name containing string "acalab-env" exists +if [ -z "$ACA_ENVIRONMENT" ]; then + echo "Error: ACA environment [$ACA_ENVIRONMENT] not found." + exit 1 +fi + +# Disable path conversion for MSYS (Git Bash) +export MSYS_NO_PATHCONV=1 + +# Create the identity that the container apps will use. +export APPS_IDENTITY=uid-petclinic-$CR_UNIQUEID +az identity create --resource-group $RESOURCE_GROUP --name $APPS_IDENTITY --output json + +APPS_IDENTITY_ID=$(az identity show --resource-group $RESOURCE_GROUP --name $APPS_IDENTITY --query id --output tsv) +APPS_IDENTITY_SP_ID=$(az identity show --resource-group $RESOURCE_GROUP --name $APPS_IDENTITY --query principalId --output tsv) + +echo "Assign the user identity to your Azure Container Apps environment." +az containerapp env identity assign -g $RESOURCE_GROUP -n $ACA_ENVIRONMENT --user-assigned $APPS_IDENTITY_ID + +# Grant the identity with the necessary privileges to pull images from the container registry. +export ACR_ID=$(az acr show -n $MYACR -g $RESOURCE_GROUP --query id -o tsv) +az role assignment create --assignee $APPS_IDENTITY_SP_ID --scope $ACR_ID --role acrpull + +# Export the variables +export MYACR +export APPS_IDENTITY +export APPS_IDENTITY_ID +export APPS_IDENTITY_SP_ID + +# Append the variables to the AZURE_CONFIG_FILE +{ + echo "MYACR=$MYACR" + echo "APPS_IDENTITY=$APPS_IDENTITY" + echo "APPS_IDENTITY_ID=$APPS_IDENTITY_ID" + echo "APPS_IDENTITY_SP_ID=$APPS_IDENTITY_SP_ID" + echo "ACR_ID=$ACR_ID" +} >> $AZURE_CONFIG_FILE \ No newline at end of file diff --git a/tools/azd-extension/lab-2/create-java-components.sh b/tools/azd-extension/lab-2/create-java-components.sh new file mode 100644 index 0000000..cf51836 --- /dev/null +++ b/tools/azd-extension/lab-2/create-java-components.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# +# Configure the config, discovery, and admin applications. These are available as built-in components of Azure Container Apps. + +AZURE_CONFIG_FILE="../config/azure-resource.profile" +source $AZURE_CONFIG_FILE + +# Check if ACA environment name exists +if [ -z "$ACA_ENVIRONMENT" ]; then + echo "Error: ACA environment not found." + exit 1 +fi + +JAVA_CONFIG_COMP_NAME=configserver +az containerapp env java-component config-server-for-spring create \ + --environment $ACA_ENVIRONMENT \ + --resource-group $RESOURCE_GROUP \ + --name $JAVA_CONFIG_COMP_NAME \ + --set-configuration spring.cloud.config.server.git.uri=$GIT_URI spring.cloud.config.server.git.search-paths=$SEARCH_PATH spring.cloud.config.server.git.default-label=$LABEL + +# Check the Spring Cloud Config Server Java component to confirm that it was successfully created. +CONFIG_SERVER_EXISTS=$(az containerapp env java-component config-server-for-spring show \ + --environment $ACA_ENVIRONMENT \ + --resource-group $RESOURCE_GROUP \ + --name $JAVA_CONFIG_COMP_NAME --query "name" -o tsv) + +if [ -z "$CONFIG_SERVER_EXISTS" ]; then + echo "Error: Config server was not successfully created." + exit 1 +fi + +# Create the Spring Cloud Eureka Server Java component. This will create a standard Eureka endpoint within +# the Container Apps environment. The Spring Petclinic workload will use this for discovery services. +JAVA_EUREKA_COMP_NAME=eureka +az containerapp env java-component eureka-server-for-spring create \ + --environment $ACA_ENVIRONMENT \ + --resource-group $RESOURCE_GROUP \ + --name $JAVA_EUREKA_COMP_NAME \ + --set-configuration eureka.server.response-cache-update-interval-ms=10000 + +# Create a new Spring Boot Admin application and bind it to the Eureka Server +JAVA_SBA_COMP_NAME=springbootadmin +az containerapp env java-component admin-for-spring create \ + --environment $ACA_ENVIRONMENT \ + --resource-group $RESOURCE_GROUP \ + --name $JAVA_SBA_COMP_NAME \ + --bind $JAVA_EUREKA_COMP_NAME \ + --min-replicas 1 \ + --max-replicas 1 + +# Append the JAVA_EUREKA_COMP_NAME and JAVA_SBA_COMP_NAME to the AZURE_CONFIG_FILE +{ + echo "JAVA_EUREKA_COMP_NAME=$JAVA_EUREKA_COMP_NAME" + echo "JAVA_SBA_COMP_NAME=$JAVA_SBA_COMP_NAME" +} >> $AZURE_CONFIG_FILE + +# Verify that the variables are appended to the azure-resource.profile file +if grep -q "JAVA_EUREKA_COMP_NAME" $AZURE_CONFIG_FILE && grep -q "JAVA_SBA_COMP_NAME" $AZURE_CONFIG_FILE; then + echo "Variables appended to azure-resource.profile file successfully." +else + echo "Error: Variables JAVA_EUREKA_COMP_NAME JAVA_SBA_COMP_NAME not appended to azure-resource.profile file." + exit 1 +fi \ No newline at end of file diff --git a/tools/azd-extension/lab-2/create-mysql.sh b/tools/azd-extension/lab-2/create-mysql.sh new file mode 100644 index 0000000..aef9375 --- /dev/null +++ b/tools/azd-extension/lab-2/create-mysql.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +AZURE_CONFIG_FILE="../config/azure-resource.profile" +source $AZURE_CONFIG_FILE + +APPLICATION_CONFIG_FILE="../config/application-mysql.yml" + +# This script creates an Azure MySQL server and an Azure Container Registry (ACR) in a specified resource group. +MYSQL_SERVER_NAME=mysql-$APPNAME-$UNIQUEID +MYSQL_ADMIN_USERNAME=sqladmin +MYSQL_ADMIN_PASSWORD=$(openssl rand -hex 12) +DATABASE_NAME=petclinic + +az mysql flexible-server create \ + --admin-user "$MYSQL_ADMIN_USERNAME" \ + --admin-password "$MYSQL_ADMIN_PASSWORD" \ + --name "$MYSQL_SERVER_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --public-access none \ + --yes +az mysql flexible-server db create \ + --server-name $MYSQL_SERVER_NAME \ + --resource-group $RESOURCE_GROUP \ + -d $DATABASE_NAME + +# +# Allow public access to the MySQL server +# +az mysql flexible-server firewall-rule create \ + --rule-name allAzureIPs \ + --name $MYSQL_SERVER_NAME \ + --resource-group $RESOURCE_GROUP \ + --start-ip-address 0.0.0.0 --end-ip-address 0.0.0.0 + +# Set up a configuration repository +# set up the application configuration settings that allow Spring Boot applications to connect with the database. +echo " +spring: + datasource: + url: jdbc:mysql://${MYSQL_SERVER_NAME}.mysql.database.azure.com:3306/$DATABASE_NAME?useSSL=true + username: ${MYSQL_ADMIN_USERNAME} + password: ${MYSQL_ADMIN_PASSWORD} + sql: + init: + schema-locations: classpath*:db/mysql/schema.sql + data-locations: classpath*:db/mysql/data.sql + mode: ALWAYS +" > $APPLICATION_CONFIG_FILE + +export GIT_URI="https://github.com/Azure-Samples/java-on-aca.git" +export SEARCH_PATH="config" +export LABEL=main + +{ + echo "GIT_URI=https://github.com/Azure-Samples/java-on-aca.git" + echo "SEARCH_PATH=config" + echo "LABEL=main" +} >> $AZURE_CONFIG_FILE diff --git a/tools/azd-extension/lab-2/deploy-component-apps.sh b/tools/azd-extension/lab-2/deploy-component-apps.sh new file mode 100644 index 0000000..a6757a1 --- /dev/null +++ b/tools/azd-extension/lab-2/deploy-component-apps.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +AZURE_CONFIG_FILE="../config/azure-resource.profile" +source $AZURE_CONFIG_FILE + +SPRING_PETCLINIC_MICROSERIVCES="../../../spring-petclinic-microservices" + +# Check if the spring-petclinic-microservices directory exists +if [ ! -d "$SPRING_PETCLINIC_MICROSERIVCES" ]; then + echo "Error: $SPRING_PETCLINIC_MICROSERIVCES directory does not exist." + # create the directory + mkdir -p $SPRING_PETCLINIC_MICROSERIVCES +fi + +# Get the application code from public upstream repo, and then build the applications. +cd ../../../spring-petclinic-microservices +git submodule update --init +mvn clean package -DskipTests + +# Build the Docker image using the Dockerfile +DOCKERFILE="../tools/Dockerfile" +if [ ! -f $DOCKERFILE ]; then + echo "$DOCKERFILE not found " + exit 1 +fi + +# if ACA environment does not exist, exit +if [ -z "$ACA_ENVIRONMENT" ]; then + echo "Error: ACA environment [$ACA_ENVIRONMENT] not found." + exit 1 +fi + +APP_NAME="api-gateway" +cp -f ../tools/Dockerfile ./spring-petclinic-$APP_NAME/Dockerfile + +# Disable path conversion for MSYS (Git Bash) +export MSYS_NO_PATHCONV=1 + +echo "az containerapp create \ + --name $APP_NAME \ + --resource-group $RESOURCE_GROUP \ + --environment $ACA_ENVIRONMENT \ + --source ./spring-petclinic-$APP_NAME \ + --registry-server $MYACR.azurecr.io \ + --registry-identity $APPS_IDENTITY_ID \ + --ingress external \ + --target-port 8080 \ + --min-replicas 1 \ + --bind $JAVA_CONFIG_COMP_NAME $JAVA_EUREKA_COMP_NAME \ + --runtime java" + +az containerapp create \ + --name $APP_NAME \ + --resource-group $RESOURCE_GROUP \ + --environment $ACA_ENVIRONMENT \ + --source ./spring-petclinic-$APP_NAME \ + --registry-server $MYACR.azurecr.io \ + --registry-identity $APPS_IDENTITY_ID \ + --ingress external \ + --target-port 8080 \ + --min-replicas 1 \ + --bind $JAVA_CONFIG_COMP_NAME $JAVA_EUREKA_COMP_NAME \ + --runtime java + diff --git a/tools/azd-extension/lab-2/lab-2-main.sh b/tools/azd-extension/lab-2/lab-2-main.sh new file mode 100644 index 0000000..1706eff --- /dev/null +++ b/tools/azd-extension/lab-2/lab-2-main.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +echo "Starting Lab 2..." + +echo "Step 1: Collecting user information..." +./collect-user-info.sh + +echo "Step 2: Creating Azure Container Apps environment..." +./create-aca-env.sh + +echo "Step 3: Creating MySql instance..." +./create-mysql.sh + +echo "Step 4: Creating Azure Container Registry..." +./create-container-registry.sh + +echo "Step 5: Creating Java Spring Boot components..." +./create-java-components.sh + +echo "Step 6: Deploy API Gateway..." +./deploy-component-apps.sh + +echo "Lab 2 completed." \ No newline at end of file diff --git a/tools/azd-extension/lab-3/AzureOpenAiConfig.java b/tools/azd-extension/lab-3/AzureOpenAiConfig.java new file mode 100644 index 0000000..b52155b --- /dev/null +++ b/tools/azd-extension/lab-3/AzureOpenAiConfig.java @@ -0,0 +1,25 @@ +package org.springframework.samples.petclinic.chat; + +import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAIClientBuilderCustomizer; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.azure.core.http.HttpClient; +import com.azure.core.util.HttpClientOptions; + +import java.time.Duration; + +@Configuration +public class AzureOpenAiConfig { + + @Bean + public AzureOpenAIClientBuilderCustomizer responseTimeoutCustomizer() { + return openAiClientBuilder -> { + HttpClientOptions clientOptions = new HttpClientOptions() + .setResponseTimeout(Duration.ofMinutes(5)); + openAiClientBuilder.httpClient(HttpClient.createDefault(clientOptions)); + }; + } + +} \ No newline at end of file diff --git a/tools/azd-extension/lab-3/ChatController.java b/tools/azd-extension/lab-3/ChatController.java new file mode 100644 index 0000000..6ed0e59 --- /dev/null +++ b/tools/azd-extension/lab-3/ChatController.java @@ -0,0 +1,35 @@ +// filepath: /workspaces/java-on-aca/tools/spring-petclinic-chat-service/src/main/java/org/springframework/ai/azure/openai/ChatController.java +package org.springframework.samples.petclinic.chat; + +import java.util.Map; + +import org.springframework.ai.azure.openai.AzureOpenAiChatModel; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import reactor.core.publisher.Flux; + +@RestController +public class ChatController { + + private final AzureOpenAiChatModel chatModel; + + @Autowired + public ChatController(AzureOpenAiChatModel chatModel) { + this.chatModel = chatModel; + } + + @GetMapping("/ai/generate") + public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { + return Map.of("generation", this.chatModel.call(message)); + } + + @GetMapping("/ai/generateStream") + public Flux generateStream(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { + Prompt prompt = new Prompt(new UserMessage(message)); + return this.chatModel.stream(prompt); + } +} \ No newline at end of file diff --git a/tools/azd-extension/lab-3/create-ai-account.sh b/tools/azd-extension/lab-3/create-ai-account.sh new file mode 100644 index 0000000..9349caf --- /dev/null +++ b/tools/azd-extension/lab-3/create-ai-account.sh @@ -0,0 +1,49 @@ +#!/bin/bash +AZURE_CONFIG_FILE="../config/azure-resource.profile" +source $AZURE_CONFIG_FILE + +AI_LOCATION=$LOCATION + +OPEN_AI_SERVICE_NAME=open-ai-account-$UNIQUEID + +# Create a new Azure Cognitive Services account +az cognitiveservices account create \ + --resource-group $RESOURCE_GROUP \ + --name $OPEN_AI_SERVICE_NAME \ + --location $AI_LOCATION \ + --kind OpenAI \ + --sku s0 \ + --custom-domain $OPEN_AI_SERVICE_NAME + +az cognitiveservices account deployment create \ + --resource-group $RESOURCE_GROUP \ + --name $OPEN_AI_SERVICE_NAME \ + --deployment-name gpt-4o \ + --model-name gpt-4o \ + --model-version 2024-08-06 \ + --model-format OpenAI \ + --sku-name "GlobalStandard" \ + --sku-capacity 10 + +# Query the Azure OpenAI endpoint +AZURE_OPENAI_ENDPOINT=$(az cognitiveservices account show \ + --resource-group $RESOURCE_GROUP \ + --name $OPEN_AI_SERVICE_NAME \ + --query "properties.endpoint" \ + --output tsv) + +# Query the Azure OpenAI API key +AZURE_OPENAI_API_KEY=$(az cognitiveservices account keys list \ + --resource-group $RESOURCE_GROUP \ + --name $OPEN_AI_SERVICE_NAME \ + --query "key1" \ + --output tsv) + +# Export the endpoint and API key as environment variables +export AZURE_OPENAI_ENDPOINT +export AZURE_OPENAI_API_KEY +{ + echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" + echo "AZURE_OPENAI_API_KEY=$AZURE_OPENAI_API_KEY" +} >> $AZURE_CONFIG_FILE + diff --git a/tools/azd-extension/lab-3/create-spring-boot-ai.sh b/tools/azd-extension/lab-3/create-spring-boot-ai.sh new file mode 100644 index 0000000..ba7ba48 --- /dev/null +++ b/tools/azd-extension/lab-3/create-spring-boot-ai.sh @@ -0,0 +1,101 @@ +#!/bin/bash + +AZURE_CONFIG_FILE="../config/azure-resource.profile" +source $AZURE_CONFIG_FILE + +SPRING_PETCLINIC_MICROSERIVCES="../../../spring-petclinic-microservices" +SPRING_PETCLINIC_CHAT_SERVICE="spring-petclinic-chat-service" + +cd $SPRING_PETCLINIC_MICROSERIVCES +# Check if the spring-petclinic-chat-service directory exists +if [ -d "$SPRING_PETCLINIC_CHAT_SERVICE" ]; then + # remove the directory + echo "Remove directory: $SPRING_PETCLINIC_CHAT_SERVICE." + rm -rf $SPRING_PETCLINIC_CHAT_SERVICE + + echo "Recreate directory: $SPRING_PETCLINIC_CHAT_SERVICE." + # recreate the directory + mkdir -p $SPRING_PETCLINIC_CHAT_SERVICE +fi + + curl https://start.spring.io/starter.tgz \ + -d dependencies=web,cloud-eureka,actuator,lombok,spring-ai-azure-openai \ + -d name=chat-service -d type=maven-project \ + -d jvmVersion=17 -d language=java -d packaging=jar \ + -d groupId=org.springframework.samples.petclinic -d artifactId=chat \ + -d description="Spring Petclinic Chat Service" \ + | tar -xzvf - -C $SPRING_PETCLINIC_CHAT_SERVICE + +POM=$SPRING_PETCLINIC_CHAT_SERVICE/pom.xml +SPRING_AI_VERSION="1.0.0-M6" + +# # update the spring-ai.version in the pom.xml +# if grep -q "" $POM; then +# sed -i "s|.*|$SPRING_AI_VERSION|" $POM +# else +# sed -i "//a \ $SPRING_AI_VERSION" $POM +# fi + +{ + echo "## AI VERSION" + echo "SPRING_AI_VERSION=$SPRING_AI_VERSION" +} >> $AZURE_CONFIG_FILE + +TEST_FILE="https://raw.githubusercontent.com/spring-projects/spring-ai/refs/heads/$SPRING_AI_VERSION/models/spring-ai-azure-openai/src/test/java/org/springframework/ai/azure/openai/AzureOpenAiChatClientIT.java" +curl $TEST_FILE -o $SPRING_PETCLINIC_CHAT_SERVICE/src/main/resources/AzureOpenAiChatClientIT.java + +CHAT_CONFIGURE_JAVA="../tools/azd-extension/lab-3/AzureOpenAiConfig.java" +CHAT_CONTROLLER_JAVA="../tools/azd-extension/lab-3/ChatController.java" +APPLICATION_PROPERTIES="../tools/azd-extension/lab-3/application.properties" +DOCKERFILE="../tools/Dockerfile" + +cp $CHAT_CONFIGURE_JAVA $SPRING_PETCLINIC_CHAT_SERVICE/src/main/java/org/springframework/samples/petclinic/chat +cp $CHAT_CONTROLLER_JAVA $SPRING_PETCLINIC_CHAT_SERVICE/src/main/java/org/springframework/samples/petclinic/chat + +{ + echo "spring.application.name=chat-service" + echo "spring.ai.azure.openai.api-key=$AZURE_OPENAI_API_KEY" + echo "spring.ai.azure.openai.endpoint=$AZURE_OPENAI_ENDPOINT" + echo "spring.ai.azure.openai.chat.options.deployment-name=gpt-4o" + echo "spring.ai.azure.openai.chat.options.temperature=0.7" +} > $APPLICATION_PROPERTIES + +cp $APPLICATION_PROPERTIES $SPRING_PETCLINIC_CHAT_SERVICE/src/main/resources/application.properties + +cd $SPRING_PETCLINIC_CHAT_SERVICE +mvn clean package -DskipTests + +# Export the environment variables +export AZURE_OPENAI_API_KEY="$AZURE_OPENAI_API_KEY" +export AZURE_OPENAI_ENDPOINT="$AZURE_OPENAI_ENDPOINT" + +APP_NAME=chat-service + +cd .. +echo "APPS_IDENTITY_ID: $APPS_IDENTITY_ID" + +cp -f $DOCKERFILE ./spring-petclinic-$APP_NAME/Dockerfile +# Disable path conversion for MSYS (Git Bash) +export MSYS_NO_PATHCONV=1 +az containerapp create \ + --name $APP_NAME \ + --resource-group $RESOURCE_GROUP \ + --environment $ACA_ENVIRONMENT \ + --source ./spring-petclinic-$APP_NAME \ + --registry-server $MYACR.azurecr.io \ + --registry-identity $APPS_IDENTITY_ID \ + --ingress external \ + --target-port 8080 \ + --min-replicas 1 \ + --bind $JAVA_CONFIG_COMP_NAME $JAVA_EUREKA_COMP_NAME \ + --runtime java + +CHAT_URL=$(az containerapp show \ + --resource-group $RESOURCE_GROUP \ + --name $APP_NAME \ + --query properties.configuration.ingress.fqdn \ + -o tsv) + +echo "Chat service URL: $CHAT_URL" + +curl https://$CHAT_URL/ai/generate diff --git a/tools/azd-extension/lab-3/lab-3-main.sh b/tools/azd-extension/lab-3/lab-3-main.sh new file mode 100644 index 0000000..40926bf --- /dev/null +++ b/tools/azd-extension/lab-3/lab-3-main.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +echo "Starting Lab 3..." + +echo "Step 1: Create AI account..." +./create-ai-account.sh +if [ $? -ne 0 ]; then + echo "Error: Step 1 failed. Exiting." + exit 1 +fi + +echo "Step 2: Create Spring Boot AI service..." +./create-spring-boot-ai.sh +if [ $? -ne 0 ]; then + echo "Error: Step 2 failed. Exiting." + exit 1 +fi + +echo "Lab 3 completed successfully." \ No newline at end of file diff --git a/tools/azd-extension/lab-4/config-app-insights.sh b/tools/azd-extension/lab-4/config-app-insights.sh new file mode 100644 index 0000000..2535668 --- /dev/null +++ b/tools/azd-extension/lab-4/config-app-insights.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +AZURE_CONFIG_FILE="../config/azure-resource.profile" +source $AZURE_CONFIG_FILE +export MSYS_NO_PATHCONV=1 + +APP_INSIGHTS_NAME=app-insights-$APPNAME-$UNIQUEID +az monitor app-insights component create \ + --resource-group $RESOURCE_GROUP \ + --app $APP_INSIGHTS_NAME \ + --location $LOCATION \ + --kind web \ + --workspace $WORKSPACE_ID + +export APP_INSIGHTS_CONN=$(az monitor app-insights component show --app $APP_INSIGHTS_NAME -g $RESOURCE_GROUP --query connectionString --output tsv) + +"APP_INSIGHTS_CONN='$APP_INSIGHTS_CONN'" >> $AZURE_CONFIG_FILE + +APP_NAME="api-gateway" +az containerapp update \ + --name $APP_NAME \ + --resource-group $RESOURCE_GROUP \ + --set-env-vars JAVA_TOOL_OPTIONS='-javaagent:/applicationinsights-agent.jar' APPLICATIONINSIGHTS_CONNECTION_STRING="$APP_INSIGHTS_CONN" APPLICATIONINSIGHTS_CONFIGURATION_CONTENT='{"role": {"name": "'$APP_NAME'"}}' + +export RESOURCE_GROUP APP_INSIGHTS_CONN +bash ../../update-apps-appinsights.sh diff --git a/tools/azd-extension/lab-4/enable-log-analytics.sh b/tools/azd-extension/lab-4/enable-log-analytics.sh new file mode 100644 index 0000000..1ee6d88 --- /dev/null +++ b/tools/azd-extension/lab-4/enable-log-analytics.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +AZURE_CONFIG_FILE="../config/azure-resource.profile" +source $AZURE_CONFIG_FILE + +WORKSPACE=la-$APPNAME-$UNIQUEID +az monitor log-analytics workspace create \ + --resource-group $RESOURCE_GROUP \ + --workspace-name $WORKSPACE + +WORKSPACE_ID=$(az monitor log-analytics workspace show -n $WORKSPACE -g $RESOURCE_GROUP --query id -o tsv) +WORKSPACE_CUSTOMER_ID=$(az monitor log-analytics workspace show -n $WORKSPACE -g $RESOURCE_GROUP --query customerId -o tsv) +WORKSPACE_KEY=$(az monitor log-analytics workspace get-shared-keys -n $WORKSPACE -g $RESOURCE_GROUP --query primarySharedKey -o tsv) + +az containerapp env update \ + --name $ACA_ENVIRONMENT \ + --resource-group $RESOURCE_GROUP \ + --logs-destination log-analytics \ + --logs-workspace-id $WORKSPACE_CUSTOMER_ID \ + --logs-workspace-key $WORKSPACE_KEY + +{ + echo "WORKSPACE_ID=$WORKSPACE_ID" + echo "WORKSPACE_CUSTOMER_ID=$WORKSPACE_CUSTOMER_ID" + echo "WORKSPACE_KEY='$WORKSPACE_KEY'" +} >> $AZURE_CONFIG_FILE \ No newline at end of file diff --git a/tools/azure-resource.profile.template b/tools/azure-resource.profile.template index 4d9ccdc..32c2efb 100644 --- a/tools/azure-resource.profile.template +++ b/tools/azure-resource.profile.template @@ -1,4 +1,3 @@ - # Set all the variables with <> and keep the others unchanged # This part is for lab 2 & Lab 3 @@ -6,8 +5,9 @@ # Pay attention to this, use command 'openssl rand -hex 3' to generate a unique id and put value here, don't put command here UNIQUEID= +# To extract the subscription ID, use the following Azure CLI command: +# az account show --query id --output tsv SUBSCRIPTION= -LOCATION= APPNAME=petclinic RESOURCE_GROUP=rg-$APPNAME-$UNIQUEID diff --git a/tools/create-azure-resource.sh b/tools/create-azure-resource.sh index 99f0680..d0efa22 100755 --- a/tools/create-azure-resource.sh +++ b/tools/create-azure-resource.sh @@ -10,6 +10,9 @@ source $DIR/../.devcontainer/funcs.sh # Resource Group +# Random regions for MySQL server +LOCATION=$(random_element australiaeast brazilsouth eastasia eastus2 japaneast southindia swedencentral westus) + az group create -g $RESOURCE_GROUP -l $LOCATION -o table az configure --default group=$RESOURCE_GROUP @@ -17,20 +20,20 @@ az configure --default group=$RESOURCE_GROUP # MySQL server # Random regions for MySQL server -SQL_LOCATION=$(random_element australiaeast brazilsouth eastasia eastus2 japaneast centralindia swedencentral westus) +SQL_LOCATION=$(random_element australiaeast brazilsouth eastasia eastus2 japaneast southindia swedencentral westus) SQL_ID=$(az mysql flexible-server show --resource-group $RESOURCE_GROUP --name $MYSQL_SERVER_NAME -o tsv --query id 2>/dev/null) if [[ -n $SQL_ID ]]; then echo -e "${GREEN}INFO:${NC} MySQL server $MYSQL_SERVER_NAME already exists" else - echo -e "${YELLOW}INFO:${NC} Creating MySQL server $MYSQL_SERVER_NAME in region $SQL_LOCATION ..." + echo -e "${YELLOW}INFO:${NC} Creating MySQL server $MYSQL_SERVER_NAME in region $LOCATION ..." az mysql flexible-server create \ --admin-user $MYSQL_ADMIN_USERNAME \ --admin-password $MYSQL_ADMIN_PASSWORD \ --name $MYSQL_SERVER_NAME \ --resource-group $RESOURCE_GROUP \ - --location $SQL_LOCATION \ + --location $LOCATION \ --public-access none \ --yes fi @@ -53,18 +56,16 @@ fi # Azure OpenAI Service -AI_LOCATION=$(random_element australiaeast brazilsouth eastus2 japaneast southindia swedencentral westus) - AI_ID=$(az cognitiveservices account show --resource-group $RESOURCE_GROUP --name $OPEN_AI_SERVICE_NAME -o tsv --query id 2>/dev/null) if [[ -n $ACR_ID ]]; then echo -e "${GREEN}INFO:${NC} OpenAI instance $OPEN_AI_SERVICE_NAME already exists" else - echo -e "${YELLOW}INFO:${NC} Creating OpenAI instance $OPEN_AI_SERVICE_NAME in region $AI_LOCATION ..." + echo -e "${YELLOW}INFO:${NC} Creating OpenAI instance $OPEN_AI_SERVICE_NAME in region $LOCATION ..." az cognitiveservices account create \ --resource-group $RESOURCE_GROUP \ --name $OPEN_AI_SERVICE_NAME \ - --location $AI_LOCATION \ + --location $LOCATION \ --kind OpenAI \ --sku s0 \ --custom-domain $OPEN_AI_SERVICE_NAME \ @@ -127,13 +128,11 @@ fi # Azure Managed Grafana -GRAFANA_LOCATION=$(random_element australiaeast brazilsouth centralindia eastasia eastus2 japaneast swedencentral westus) - GRAFANA_ID=$(az grafana show --name $GRAFANA_NAME --resource-group $RESOURCE_GROUP -o tsv --query id 2>/dev/null) if [[ -n $GRAFANA_ID ]]; then echo -e "${GREEN}INFO:${NC} Grafana instance $GRAFANA_NAME already exists" else - echo -e "${YELLOW}INFO:${NC} Creating Grafana instance $GRAFANA_NAME in region $GRAFANA_LOCATION ..." + echo -e "${YELLOW}INFO:${NC} Creating Grafana instance $GRAFANA_NAME in region $LOCATION ..." az deployment group create \ --resource-group $RESOURCE_GROUP \