Skip to content

Commit d6e405e

Browse files
authored
Merge pull request #56 from Azure-Samples/azure-ppeline
Add pipeline to deploy to Azure and smoke test the app
2 parents eaf2c69 + 67e48b9 commit d6e405e

18 files changed

Lines changed: 561 additions & 90 deletions

.github/workflows/azure-dev.yaml

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
name: Deploy to Azure
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
# Run when commits are pushed to mainline branch
7+
branches:
8+
- main
9+
pull_request:
10+
# Run when a pull request is created targeting the mainline branch
11+
branches:
12+
- main
13+
14+
# GitHub Actions workflow to deploy to Azure using azd
15+
# To configure required secrets for connecting to Azure, simply run `azd pipeline config`
16+
17+
# Set up permissions for deploying with secretless Azure federated credentials
18+
# https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#set-up-azure-login-with-openid-connect-authentication
19+
permissions:
20+
id-token: write
21+
contents: read
22+
23+
jobs:
24+
build:
25+
runs-on: ubuntu-latest
26+
outputs:
27+
uri: ${{ steps.output.outputs.uri }}
28+
env:
29+
AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
30+
AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
31+
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
32+
AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
33+
# Project-specific variables
34+
AZURE_SEARCH_SERVICE_LOCATION: ${{ vars.AZURE_SEARCH_SERVICE_LOCATION }}
35+
AZURE_SEARCH_SERVICE: ${{ vars.AZURE_SEARCH_SERVICE }}
36+
AZURE_SEARCH_INDEX: ${{ vars.AZURE_SEARCH_INDEX }}
37+
AZURE_SEARCH_SERVICE_RESOURCE_GROUP: ${{ vars.AZURE_SEARCH_SERVICE_RESOURCE_GROUP }}
38+
AZURE_SEARCH_SERVICE_SKU: ${{ vars.AZURE_SEARCH_SERVICE_SKU }}
39+
AZURE_SEARCH_SEMANTIC_SEARCH_SKU: ${{ vars.AZURE_SEARCH_SEMANTIC_SEARCH_SKU }}
40+
AZURE_STORAGE_ACCOUNT_LOCATION: ${{ vars.AZURE_STORAGE_ACCOUNT_LOCATION }}
41+
AZURE_STORAGE_ACCOUNT_RESOURCE_GROUP: ${{ vars.AZURE_STORAGE_ACCOUNT_RESOURCE_GROUP }}
42+
AZURE_STORAGE_ACCOUNT: ${{ vars.AZURE_STORAGE_ACCOUNT }}
43+
AZURE_API_SERVICE_LOCATION: ${{ vars.AZURE_API_SERVICE_LOCATION }}
44+
AZURE_API_SERVICE_RESOURCE_GROUP: ${{ vars.AZURE_API_SERVICE_RESOURCE_GROUP }}
45+
AZURE_LOG_ANALYTICS: ${{ vars.AZURE_LOG_ANALYTICS }}
46+
AZURE_APPINSIGHTS: ${{ vars.AZURE_APPINSIGHTS }}
47+
AZURE_COMPUTER_VISION_ACCOUNT: ${{ vars.AZURE_COMPUTER_VISION_ACCOUNT }}
48+
AZURE_COMPUTER_VISION_RESOURCE_GROUP: ${{ vars.AZURE_COMPUTER_VISION_RESOURCE_GROUP }}
49+
AZURE_COMPUTER_VISION_LOCATION: ${{ vars.AZURE_COMPUTER_VISION_LOCATION }}
50+
AZURE_COMPUTER_VISION_SKU_NAME: ${{ vars.AZURE_COMPUTER_VISION_SKU_NAME }}
51+
SERVICE_ACA_RESOURCE_EXISTS: ${{ vars.SERVICE_ACA_RESOURCE_EXISTS }}
52+
steps:
53+
- name: Checkout
54+
uses: actions/checkout@v5
55+
56+
- name: Install azd
57+
uses: Azure/setup-azd@v2.2.0
58+
59+
- name: Log in with Azure (Federated Credentials)
60+
run: |
61+
azd auth login `
62+
--client-id "$Env:AZURE_CLIENT_ID" `
63+
--federated-credential-provider "github" `
64+
--tenant-id "$Env:AZURE_TENANT_ID"
65+
shell: pwsh
66+
67+
# Workaround for https://github.com/Azure/azure-dev/issues/3920
68+
- name: Create azd env
69+
run: azd env new $AZURE_ENV_NAME
70+
env:
71+
AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
72+
73+
- name: Prepackage Frontend
74+
run: |
75+
azd hooks run prepackage --no-prompt
76+
env:
77+
AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
78+
79+
- name: Provision Infrastructure
80+
run: |
81+
azd provision --no-prompt
82+
env:
83+
AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
84+
AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
85+
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
86+
87+
- name: Deploy Application
88+
run: azd deploy --no-prompt
89+
env:
90+
AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
91+
AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
92+
AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
93+
94+
- name: Output Deployment URI
95+
id: output
96+
run: |
97+
azd env get-values > .env
98+
source .env
99+
echo "uri=$SERVICE_ACA_URI" >> "$GITHUB_OUTPUT"
100+
101+
102+
smoketests:
103+
runs-on: ubuntu-latest
104+
needs: build
105+
steps:
106+
107+
- name: Checkout
108+
uses: actions/checkout@v5
109+
110+
- name: Set up Python (for Playwright test)
111+
uses: actions/setup-python@v5
112+
with:
113+
python-version: '3.12'
114+
115+
- name: Install Playwright (Python) and Browsers
116+
run: |
117+
pip install --no-cache-dir playwright
118+
python -m playwright install --with-deps chromium
119+
120+
- name: End-to-End UI Test (Playwright)
121+
shell: bash
122+
env:
123+
URI: ${{needs.build.outputs.uri}}
124+
run: |
125+
python scripts/smoke_test.py "$URI"

.github/workflows/python.yaml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Python check
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test_package:
11+
name: Test ${{ matrix.os }} Python ${{ matrix.python_version }}
12+
runs-on: ${{ matrix.os }}
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
os: ["ubuntu-latest"]
17+
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
18+
steps:
19+
- uses: actions/checkout@v4
20+
- name: Install uv
21+
uses: astral-sh/setup-uv@v6
22+
with:
23+
enable-cache: true
24+
version: "0.4.20"
25+
cache-dependency-glob: "requirements**.txt"
26+
python-version: ${{ matrix.python_version }}
27+
activate-environment: true
28+
- name: Setup node
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version: "22"
32+
- name: Build frontend
33+
run: |
34+
cd ./app/frontend
35+
npm install
36+
npm run build
37+
- name: Install dependencies
38+
run: |
39+
uv pip install -r requirements-dev.txt
40+
- name: Lint with ruff
41+
run: ruff check .
42+
- name: Check formatting with ruff
43+
run: ruff format --check .
44+
- name: Run tests with pytest
45+
run: pytest

app/backend/app.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,23 @@
2828
mimetypes.add_type("application/javascript", ".js")
2929
mimetypes.add_type("text/css", ".css")
3030

31+
3132
@bp.route("/")
3233
async def index():
3334
return await bp.send_static_file("index.html")
3435

36+
3537
@bp.route("/favicon.ico")
3638
async def favicon():
3739
return await bp.send_static_file("favicon.ico")
3840

41+
3942
@bp.route("/assets/<path:path>")
4043
async def assets(path):
41-
return await send_from_directory(Path(__file__).resolve().parent / "static" / "assets", path)
44+
return await send_from_directory(
45+
Path(__file__).resolve().parent / "static" / "assets", path
46+
)
47+
4248

4349
@bp.route("/search", methods=["POST"])
4450
async def search():
@@ -52,40 +58,41 @@ async def search():
5258
search_text=None,
5359
top=size,
5460
vector_queries=[
55-
VectorizableTextQuery(
56-
k=size,
57-
fields="embedding",
58-
text=search_text
59-
)
61+
VectorizableTextQuery(k=size, fields="embedding", text=search_text)
6062
],
61-
select="metadata_storage_path"
63+
select="metadata_storage_path",
6264
)
6365
response_results = []
6466
async for result in results:
65-
response_results.append({
66-
"score": result["@search.score"],
67-
"url": result["metadata_storage_path"]
68-
})
67+
response_results.append(
68+
{"score": result["@search.score"], "url": result["metadata_storage_path"]}
69+
)
6970
return jsonify(response_results)
7071

72+
7173
@bp.before_app_serving
7274
def setup_clients():
7375
AZURE_SEARCH_SERVICE = os.environ["AZURE_SEARCH_SERVICE"]
7476
AZURE_SEARCH_INDEX = os.environ["AZURE_SEARCH_INDEX"]
7577
if os.getenv("RUNNING_IN_PRODUCTION"):
7678
credential = ManagedIdentityCredential(client_id=os.environ["AZURE_CLIENT_ID"])
7779
else:
78-
credential = AzureDeveloperCliCredential(tenant_id=os.environ["AZURE_TENANT_ID"])
80+
credential = AzureDeveloperCliCredential(
81+
tenant_id=os.environ["AZURE_TENANT_ID"]
82+
)
7983
search_client = SearchClient(
8084
endpoint=f"https://{AZURE_SEARCH_SERVICE}.search.windows.net",
8185
index_name=AZURE_SEARCH_INDEX,
82-
credential=credential
86+
credential=credential,
8387
)
8488
current_app.config[CONFIG_SEARCH_CLIENT] = search_client
8589

90+
8691
def load_azd_env():
8792
"""Get path to current azd env file and load file using python-dotenv"""
88-
result = subprocess.run("azd env list -o json", shell=True, capture_output=True, text=True)
93+
result = subprocess.run(
94+
"azd env list -o json", shell=True, capture_output=True, text=True
95+
)
8996
if result.returncode != 0:
9097
raise Exception("Error loading azd env")
9198
env_json = json.loads(result.stdout)
@@ -98,6 +105,7 @@ def load_azd_env():
98105
logger.info(f"Loading azd env from {env_file_path}")
99106
load_dotenv(env_file_path, override=True)
100107

108+
101109
def create_app():
102110
app = Quart(__name__)
103111
app.register_blueprint(bp)
@@ -110,5 +118,5 @@ def create_app():
110118

111119
if not os.getenv("RUNNING_IN_PRODUCTION"):
112120
load_azd_env()
113-
114-
return app
121+
122+
return app

app/backend/gunicorn.conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
workers = (num_cpus * 2) + 1
1414
worker_class = "uvicorn.workers.UvicornWorker"
1515

16-
timeout = 120
16+
timeout = 120

app/backend/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from app import create_app
22

3-
app = create_app()
3+
app = create_app()

app/backend/requirements.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ aiohttp==3.11.0b0
33
azure-common==1.1.28
44
azure-core==1.29.7
55
azure-identity==1.16.1
6+
azure-mgmt-core==1.4.0
7+
azure-mgmt-web==7.2.0
8+
azure-mgmt-storage==21.2.1
69
azure-search-documents==11.7.0b1
710
azure-storage-blob==12.19.0
811
blinker==1.7.0
912
certifi==2024.7.4
10-
cffi==1.16.0
13+
cffi==2.0.0
1114
charset-normalizer==3.3.2
1215
click==8.1.7
1316
colorama==0.4.6
@@ -33,7 +36,7 @@ PyJWT==2.8.0
3336
Quart==0.20.0
3437
requests==2.32.4
3538
six==1.16.0
36-
typing_extensions==4.9.0
39+
typing_extensions>=4.12.2
3740
urllib3==2.5.0
3841
Werkzeug==3.0.6
3942
wsproto==1.2.0

0 commit comments

Comments
 (0)