Compare commits
19 Commits
a9ac5f2549
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ff0a7205f | |||
| 5162b183bf | |||
| b42c659560 | |||
| 55971f7d89 | |||
| c3a632ed4b | |||
| f6061fb9a0 | |||
| 218db54a08 | |||
| 2c50e4ea17 | |||
| 023de7e88d | |||
| a240e62e75 | |||
| 6e60dc7199 | |||
| c6349e2577 | |||
| 4b86f2bd57 | |||
| 2f465a8217 | |||
| 86281742da | |||
| d7fb56eb41 | |||
| f21739c250 | |||
| a75743e4e0 | |||
| 319410fbcc |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
# Terraform
|
# Terraform
|
||||||
**/.terraform
|
**/.terraform
|
||||||
**/*.tfplan
|
**/*tfplan
|
||||||
**/*.tfstate*
|
**/*.tfstate*
|
||||||
**/*.tfvars
|
**/*.tfvars
|
||||||
**/!*auto.tfvars
|
**/!*auto.tfvars
|
||||||
@@ -8,7 +8,12 @@
|
|||||||
|
|
||||||
# Python
|
# Python
|
||||||
**/.venv
|
**/.venv
|
||||||
|
**/.env
|
||||||
|
**/playground.py
|
||||||
|
|
||||||
# Azure Secrets and Configuration.
|
# Azure Secrets and Configuration.
|
||||||
/.acr-pat
|
/.acr-pat
|
||||||
/azure.env
|
/azure.env
|
||||||
|
|
||||||
|
# MacOS Finder files
|
||||||
|
**/.DS_Store
|
||||||
|
|||||||
44
README.md
44
README.md
@@ -1,16 +1,46 @@
|
|||||||
## Azure Image Chooser
|
# Azure Image Chooser
|
||||||
|
|
||||||
Azure Image Chooser is a [Streamlit](https://streamlit.io) application that allows users to select Azure VM images from the Azure Marketplace.
|
Azure Image Chooser is a [Streamlit](https://streamlit.io) application that allows users to select Azure VM images from the Azure Marketplace.
|
||||||
|
|
||||||
It is in its preliminary version and is subject to development and change. It is provided here for you convenience and may not include all features or functionality of the final product.
|
Azure Image Chooser is written in Python and requires Python interpreter. At the time of writing this, Python 3.13 is the latest.
|
||||||
|
|
||||||
Azure Image Chooser is written in Python and requires Python interpreter. At the time of writing this, Python 3.13 is the latest. Execute the following commands to run the app:
|
You can run it on your local machine or deploy to any platform that runs containers. A Docker file and Terraform code to deploy to the Azure are provided.
|
||||||
|
|
||||||
|
## Running on a local machine
|
||||||
|
|
||||||
|
Create a Python development environment file `.env`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
AZURE_SUBSCRIPTION_ID="subscription_id"
|
||||||
|
AZURE_CLIENT_ID="client_id"
|
||||||
|
AZURE_CLIENT_SECRET="client_secret"
|
||||||
|
AZURE_TENANT_ID="tenant_id"
|
||||||
|
AZURE_LOCATION="westeurope"
|
||||||
|
```
|
||||||
|
|
||||||
|
> NOTE: Replace the placeholder values with your actual values. Omit `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, and `AZURE_TENANT_ID` if you are using Azure CLI authentication.
|
||||||
|
|
||||||
|
Execute the following commands to run the app:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
.venv/bin/python -m pip install pip --upgrade
|
source .venv/bin/activate
|
||||||
.venv/bin/pip install streamlit azure-identity azure-mgmt-compute
|
python -m pip install pip --upgrade
|
||||||
.venv/bin/streamlit run image-chooser.py
|
pip install -r requirements.txt
|
||||||
|
cd app
|
||||||
|
streamlit run image-chooser.py
|
||||||
```
|
```
|
||||||
|
|
||||||
You have to be authenticated in Azure CLI. The app will block terminal and start a web server. Follow the instructions in the terminal to access the app.
|
The app will block terminal and start a web server. Follow the instructions in the terminal to access the app.
|
||||||
|
|
||||||
|
## Add Certificate Binding for Azure Deployment
|
||||||
|
|
||||||
|
Unfortunately, as of now, the Terraform Azure Resource Manager Provider does not support binding certificates to container apps. You can still bind the certificate using the Azure CLI.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Find the certificate name
|
||||||
|
CERTIFICATE_LOWERCASE_NAME=$(az containerapp env certificate list -g $RESOURCE_GROUP -n $ENVIRONMENT --query '[].name' -o tsv)
|
||||||
|
|
||||||
|
# Bind the certificate to the container app
|
||||||
|
az containerapp hostname bind --hostname $DOMAIN_NAME -g $RESOURCE_GROUP -n $CONTAINER_APP --environment $ENVIRONMENT --certificate $CERTIFICATE_LOWERCASE_NAME --validation-method CNAME
|
||||||
|
```
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ COPY requirements.txt .
|
|||||||
RUN pip install --root-user-action=ignore --no-cache-dir -r requirements.txt
|
RUN pip install --root-user-action=ignore --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY image-chooser.py .
|
COPY image-chooser.py .
|
||||||
|
COPY templates/ templates/
|
||||||
|
COPY templates.json .
|
||||||
COPY ./entrypoint.sh /
|
COPY ./entrypoint.sh /
|
||||||
|
|
||||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||||
|
|||||||
@@ -2,19 +2,49 @@ import streamlit as st
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
import re
|
import re
|
||||||
from azure.identity import DefaultAzureCredential
|
from azure.identity import DefaultAzureCredential
|
||||||
|
from azure.mgmt.resource import SubscriptionClient
|
||||||
from azure.mgmt.compute import ComputeManagementClient
|
from azure.mgmt.compute import ComputeManagementClient
|
||||||
|
from jinja2 import Template, Environment, FileSystemLoader
|
||||||
|
import json
|
||||||
|
from os import getenv
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
def is_valid(key_name: str):
|
||||||
|
return key_name in st.session_state and st.session_state[key_name] is not None and len(st.session_state[key_name]) > 0
|
||||||
|
|
||||||
|
def is_valid_and_not_equal(key_name: str, value: str):
|
||||||
|
return key_name in st.session_state and st.session_state[key_name] != value
|
||||||
|
|
||||||
def clear_offers():
|
def clear_offers():
|
||||||
if 'offers' in st.session_state: del st.session_state.offers
|
if 'offers' in st.session_state: del st.session_state.offers
|
||||||
if 'selected_offer' in st.session_state: del st.session_state.selected_offer
|
if 'selected_offer' in st.session_state: del st.session_state.selected_offer
|
||||||
clear_skus()
|
clear_skus()
|
||||||
|
|
||||||
def clear_selected_sku():
|
|
||||||
if 'selected_sku' in st.session_state: del st.session_state.selected_sku
|
|
||||||
|
|
||||||
def clear_skus():
|
def clear_skus():
|
||||||
if 'skus' in st.session_state: del st.session_state.skus
|
if 'skus' in st.session_state: del st.session_state.skus
|
||||||
clear_selected_sku()
|
if 'selected_sku' in st.session_state: del st.session_state.selected_sku
|
||||||
|
clear_image_versions()
|
||||||
|
|
||||||
|
def clear_image_versions():
|
||||||
|
if 'image_versions' in st.session_state: del st.session_state.image_versions
|
||||||
|
if 'selected_image_version' in st.session_state: del st.session_state.selected_image_version
|
||||||
|
|
||||||
|
def on_publisher_changed():
|
||||||
|
clear_offers()
|
||||||
|
|
||||||
|
def on_offer_changed():
|
||||||
|
clear_skus()
|
||||||
|
|
||||||
|
def on_sku_changed():
|
||||||
|
clear_skus()
|
||||||
|
|
||||||
|
def on_image_version_changed():
|
||||||
|
clear_image_versions()
|
||||||
|
|
||||||
|
def version_key(v):
|
||||||
|
return [int(x) for x in v.split('.')]
|
||||||
|
|
||||||
@st.cache_data
|
@st.cache_data
|
||||||
def get_publishers(location: str):
|
def get_publishers(location: str):
|
||||||
@@ -32,57 +62,118 @@ def get_skus(location: str, publisher: str, offer: str):
|
|||||||
def get_image_versions(location: str, publisher: str, offer: str, sku: str):
|
def get_image_versions(location: str, publisher: str, offer: str, sku: str):
|
||||||
return [version.name for version in compute_client.virtual_machine_images.list(location, publisher, offer, sku)]
|
return [version.name for version in compute_client.virtual_machine_images.list(location, publisher, offer, sku)]
|
||||||
|
|
||||||
subscription_id = "c885a276-c882-483f-b216-42f73715161d"
|
@st.cache_data
|
||||||
location = "westeurope"
|
def get_locations():
|
||||||
|
subscription_client = SubscriptionClient(credential)
|
||||||
|
locations = sorted(subscription_client.subscriptions.list_locations(getenv('AZURE_SUBSCRIPTION_ID')), key=lambda l: l.name)
|
||||||
|
return [
|
||||||
|
loc for loc in locations
|
||||||
|
if loc.metadata.region_type == 'Physical'
|
||||||
|
]
|
||||||
|
|
||||||
|
def usage_scenario_label(item):
|
||||||
|
return item['label']
|
||||||
|
|
||||||
|
subscription_id = getenv("AZURE_SUBSCRIPTION_ID")
|
||||||
|
default_location = getenv("AZURE_LOCATION", "westeurope")
|
||||||
|
|
||||||
credential = DefaultAzureCredential()
|
credential = DefaultAzureCredential()
|
||||||
|
|
||||||
|
locations = get_locations()
|
||||||
|
|
||||||
compute_client = ComputeManagementClient(credential, subscription_id)
|
compute_client = ComputeManagementClient(credential, subscription_id)
|
||||||
|
|
||||||
st.set_page_config(page_title='Azure Image Chooser', layout='wide')
|
st.set_page_config(page_title='Azure Image Chooser', layout='wide')
|
||||||
|
|
||||||
st.title('Azure Image Chooser')
|
st.title('Azure Image Chooser')
|
||||||
|
|
||||||
left_col, middle_col, right_col = st.columns(3)
|
location_cols = st.columns(4)
|
||||||
|
|
||||||
|
location = location_cols[0].selectbox('Select Azure Location',
|
||||||
|
options=[{"name": loc.name, "display_name": loc.display_name} for loc in locations],
|
||||||
|
index=locations.index(next((l for l in locations if l.name == default_location), default_location)),
|
||||||
|
format_func=lambda loc: loc['display_name']
|
||||||
|
)['name']
|
||||||
|
|
||||||
|
publisher_col, offer_col, sku_col, version_col = st.columns(4)
|
||||||
|
|
||||||
|
# Publishers
|
||||||
if 'publishers' not in st.session_state:
|
if 'publishers' not in st.session_state:
|
||||||
st.session_state.publishers = get_publishers(location)
|
st.session_state.publishers = get_publishers(location)
|
||||||
clear_offers()
|
clear_offers()
|
||||||
|
|
||||||
selected_publisher = left_col.selectbox('Select Publisher', options=st.session_state.publishers)
|
if is_valid('publishers'):
|
||||||
|
st.session_state.selected_publisher = publisher_col.selectbox('Select Publisher', options=st.session_state.publishers, on_change=on_publisher_changed, index=None)
|
||||||
|
else:
|
||||||
|
st.error("No publishers found. Please check your Azure subscription and location.")
|
||||||
|
# st.stop()
|
||||||
|
|
||||||
if 'selected_publisher' not in st.session_state or selected_publisher != st.session_state.selected_publisher:
|
# Offers
|
||||||
st.session_state.selected_publisher = selected_publisher
|
if 'offers' not in st.session_state and is_valid('selected_publisher'):
|
||||||
clear_offers()
|
|
||||||
|
|
||||||
if 'offers' not in st.session_state:
|
|
||||||
st.session_state.offers = get_offers(location, st.session_state.selected_publisher)
|
st.session_state.offers = get_offers(location, st.session_state.selected_publisher)
|
||||||
clear_skus()
|
|
||||||
|
|
||||||
selected_offer = middle_col.selectbox('Select Offer', options=st.session_state.offers)
|
if is_valid('offers'):
|
||||||
|
st.session_state.selected_offer = offer_col.selectbox('Select Offer', options=st.session_state.offers, on_change=on_offer_changed, index=None)
|
||||||
|
elif is_valid('selected_publisher'):
|
||||||
|
st.info("No offers found for the selected publisher. Please select a different publisher.")
|
||||||
|
# st.stop()
|
||||||
|
# else:
|
||||||
|
# st.stop()
|
||||||
|
|
||||||
if 'selected_offer' not in st.session_state or selected_offer != st.session_state.selected_offer:
|
# SKUs
|
||||||
st.session_state.selected_offer = selected_offer
|
if 'skus' not in st.session_state and is_valid('selected_publisher') and is_valid('selected_offer'):
|
||||||
clear_skus()
|
|
||||||
|
|
||||||
if 'skus' not in st.session_state:
|
|
||||||
st.session_state.skus = get_skus(location, st.session_state.selected_publisher, st.session_state.selected_offer)
|
st.session_state.skus = get_skus(location, st.session_state.selected_publisher, st.session_state.selected_offer)
|
||||||
clear_selected_sku()
|
|
||||||
|
|
||||||
selected_sku = right_col.selectbox('Select SKU', options=st.session_state.skus)
|
if is_valid('skus'):
|
||||||
|
st.session_state.selected_sku = sku_col.selectbox('Select SKU', options=st.session_state.skus, on_change=on_sku_changed, index=None)
|
||||||
|
elif is_valid('selected_offer'):
|
||||||
|
st.info("No SKUs found for the selected offer. Please select a different offer.")
|
||||||
|
# st.stop()
|
||||||
|
# else:
|
||||||
|
# st.stop()
|
||||||
|
|
||||||
if 'selected_sku' not in st.session_state or selected_sku != st.session_state.selected_sku:
|
# Image versions
|
||||||
st.session_state.selected_sku = selected_sku
|
if 'image_versions' not in st.session_state and is_valid('selected_publisher') and is_valid('selected_offer') and is_valid('selected_sku'):
|
||||||
|
image_versions = get_image_versions(location, st.session_state.selected_publisher, st.session_state.selected_offer, st.session_state.selected_sku)
|
||||||
|
# Check if all image version string match the re.
|
||||||
|
regex = re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+$')
|
||||||
|
|
||||||
def version_key(v):
|
if all(regex.match(version) for version in image_versions):
|
||||||
return [int(x) for x in v.split('.')]
|
image_versions = sorted(image_versions, key=version_key)
|
||||||
|
|
||||||
regex = re.compile(r'^[0-9]+\.[0-9]+\.[0-9]+$')
|
st.session_state.image_versions = image_versions
|
||||||
|
|
||||||
# Display available image versions
|
if is_valid('image_versions'):
|
||||||
images_versions = get_image_versions(location, st.session_state.selected_publisher, st.session_state.selected_offer, st.session_state.selected_sku)
|
st.session_state.selected_image_version = version_col.selectbox('Select Image Version', options=st.session_state.image_versions, index=None)
|
||||||
|
elif is_valid('selected_sku'):
|
||||||
|
st.info("No image versions found for the selected SKU. Please select a different SKU.")
|
||||||
|
# st.stop()
|
||||||
|
# else:
|
||||||
|
# st.stop()
|
||||||
|
|
||||||
# Check if all image version string match the re.
|
if is_valid('selected_image_version'):
|
||||||
if all(regex.match(version) for version in images_versions):
|
st.subheader("Usage example")
|
||||||
images_versions = sorted(images_versions, key=version_key)
|
|
||||||
|
|
||||||
st.dataframe(images_versions, hide_index=True, column_config={"value": "Image Version"})
|
with open("templates.json") as f:
|
||||||
|
templates = json.load(f)
|
||||||
|
|
||||||
|
layout = st.columns(4)
|
||||||
|
|
||||||
|
selected_file = layout[0].selectbox('Select usage scenario:', options=templates, format_func=usage_scenario_label)
|
||||||
|
|
||||||
|
env = Environment(loader=FileSystemLoader("templates"))
|
||||||
|
tpl = env.get_template(selected_file['file'])
|
||||||
|
rendered = tpl.render(
|
||||||
|
publisher=st.session_state.selected_publisher,
|
||||||
|
offer=st.session_state.selected_offer,
|
||||||
|
sku=st.session_state.selected_sku,
|
||||||
|
version=st.session_state.selected_image_version
|
||||||
|
)
|
||||||
|
|
||||||
|
st.code(rendered, language=selected_file['language'])
|
||||||
|
|
||||||
|
if is_valid('selected_publisher') and is_valid('selected_offer') and is_valid('skus'):
|
||||||
|
sku_list = '[\n' + ',\n'.join(f'\t"{sku}"' for sku in st.session_state['skus']) +'\n]'
|
||||||
|
st.subheader('Available SKUs')
|
||||||
|
st.markdown('The below HCL code is suitable to be used as SKU validation set.')
|
||||||
|
st.code(sku_list)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
streamlit
|
streamlit
|
||||||
azure-identity
|
azure-identity
|
||||||
|
azure.mgmt.resource
|
||||||
azure-mgmt-compute
|
azure-mgmt-compute
|
||||||
|
python-dotenv
|
||||||
|
|||||||
17
app/templates.json
Normal file
17
app/templates.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "Terraform VM image reference",
|
||||||
|
"language": "hcl",
|
||||||
|
"file": "azurerm_hcl.tpl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Azure CLI",
|
||||||
|
"language": "shell",
|
||||||
|
"file": "shell.tpl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Azure Resource Manager Template",
|
||||||
|
"language": "json",
|
||||||
|
"file": "arm_vm.jsonc"
|
||||||
|
}
|
||||||
|
]
|
||||||
17
app/templates/arm_vm.jsonc
Normal file
17
app/templates/arm_vm.jsonc
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
// This is a partial Azure virtual machine resource template.
|
||||||
|
"type": "Microsoft.Compute/virtualMachines",
|
||||||
|
"apiVersion": "2022-03-01",
|
||||||
|
"name": "example-vm",
|
||||||
|
"location": "westeurope",
|
||||||
|
"properties": {
|
||||||
|
"storageProfile": {
|
||||||
|
"imageReference": {
|
||||||
|
"publisher": "{{ publisher }}",
|
||||||
|
"offer": "{{ offer }}",
|
||||||
|
"sku": "{{ sku }}",
|
||||||
|
"version": "{{ version }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
app/templates/azurerm_hcl.tpl
Normal file
6
app/templates/azurerm_hcl.tpl
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
source_image_reference = {
|
||||||
|
publisher = "{{ publisher }}"
|
||||||
|
offer = "{{ offer }}"
|
||||||
|
sku = "{{ sku }}"
|
||||||
|
version = "{{ version }}"
|
||||||
|
}
|
||||||
1
app/templates/shell.tpl
Normal file
1
app/templates/shell.tpl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
az create -n MyVM -g MyResourceGroup --image {{ publisher }}:{{ offer }}:{{ sku }}:{{ version }}
|
||||||
17
build.sh
17
build.sh
@@ -1,17 +1,14 @@
|
|||||||
#!/bin/bash
|
#!//usr/bin/env bash
|
||||||
|
|
||||||
IMAGE_NAME="azure-image-chooser"
|
IMAGE_NAME="azure-image-chooser"
|
||||||
#IMAGE="docker.io/skoszewski/$IMAGE_NAME"
|
IMAGE="docker.io/skoszewski/$IMAGE_NAME:latest"
|
||||||
IMAGE="skdomlab.azurecr.io/$IMAGE_NAME"
|
# IMAGE="skdomlab.azurecr.io/$IMAGE_NAME"
|
||||||
|
|
||||||
if [ "$(basename $(command -v docker))" = "docker" ]; then
|
if command -v docker > /dev/null; then
|
||||||
CMD="docker"
|
docker buildx build -t $IMAGE app
|
||||||
elif [ "$(basename $(command -v podman))" = "podman" ]; then
|
elif command -v container > /dev/null; then
|
||||||
CMD="podman"
|
container build -t $IMAGE app
|
||||||
else
|
else
|
||||||
echo "No suitable container tool found"
|
echo "No suitable container tool found"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$CMD build -t $IMAGE app
|
|
||||||
$CMD push $IMAGE
|
|
||||||
|
|||||||
@@ -1,24 +1,37 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
if [ -z "$AZURE_CLIENT_ID" ] || [ -z "$AZURE_TENANT_ID" ] || [ -z "$AZURE_CLIENT_SECRET" ] || [ -z "$AZURE_SUBSCRIPTION_ID" ]; then
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
echo "One or more environment variables are not set."
|
|
||||||
exit 1
|
if [ -f "$SCRIPT_DIR/azure.env" ]; then
|
||||||
|
source "$SCRIPT_DIR/azure.env"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$(basename $(command -v docker))" = "docker" ]; then
|
REQUIRED_VARS=("AZURE_CLIENT_ID" "AZURE_TENANT_ID" "AZURE_CLIENT_SECRET" "AZURE_SUBSCRIPTION_ID")
|
||||||
|
for VAR in "${REQUIRED_VARS[@]}"; do
|
||||||
|
if [ -z "${!VAR}" ]; then
|
||||||
|
echo "Environment variable $VAR is not set."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
IMAGE_NAME="azure-image-chooser"
|
||||||
|
IMAGE="docker.io/skoszewski/$IMAGE_NAME:latest"
|
||||||
|
|
||||||
|
RUN_ARGS=(
|
||||||
|
"--env" "AZURE_CLIENT_ID=$AZURE_CLIENT_ID"
|
||||||
|
"--env" "AZURE_TENANT_ID=$AZURE_TENANT_ID"
|
||||||
|
"--env" "AZURE_CLIENT_SECRET=$AZURE_CLIENT_SECRET"
|
||||||
|
"--env" "AZURE_SUBSCRIPTION_ID=$AZURE_SUBSCRIPTION_ID"
|
||||||
|
"-p" "8501:8501"
|
||||||
|
)
|
||||||
|
|
||||||
|
if command -v docker > /dev/null; then
|
||||||
CMD="docker"
|
CMD="docker"
|
||||||
elif [ "$(basename $(command -v podman))" = "podman" ]; then
|
elif command -v container > /dev/null; then
|
||||||
CMD="podman"
|
CMD="container"
|
||||||
else
|
else
|
||||||
echo "No suitable container tool found"
|
echo "No suitable container tool found"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$CMD run --rm \
|
$CMD run --rm -it "${RUN_ARGS[@]}" $IMAGE
|
||||||
-it \
|
|
||||||
-e AZURE_CLIENT_ID="$AZURE_CLIENT_ID" \
|
|
||||||
-e AZURE_TENANT_ID="$AZURE_TENANT_ID" \
|
|
||||||
-e AZURE_CLIENT_SECRET="$AZURE_CLIENT_SECRET" \
|
|
||||||
-e AZURE_SUBSCRIPTION_ID="$AZURE_SUBSCRIPTION_ID" \
|
|
||||||
-p 8501:8501 \
|
|
||||||
azure-image-chooser
|
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ terraform {
|
|||||||
source = "hashicorp/azurerm"
|
source = "hashicorp/azurerm"
|
||||||
version = ">= 4.0.0"
|
version = ">= 4.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
azuread = {
|
||||||
|
source = "hashicorp/azuread"
|
||||||
|
version = ">= 3.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
backend "local" {
|
backend "local" {
|
||||||
@@ -19,8 +24,13 @@ provider "azurerm" {
|
|||||||
|
|
||||||
data "azurerm_client_config" "current" {}
|
data "azurerm_client_config" "current" {}
|
||||||
|
|
||||||
|
data "azuread_user" "az_lab_admin" {
|
||||||
|
user_principal_name = "az-lab-admin@lab.koszewscy.waw.pl"
|
||||||
|
}
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
kv_secret_name = "azure-client-secret"
|
kv_secret_name = "azure-client-secret"
|
||||||
|
app_name = "${var.project_name}-app"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_resource_group" "rg" {
|
resource "azurerm_resource_group" "rg" {
|
||||||
@@ -37,11 +47,11 @@ resource "azurerm_log_analytics_workspace" "logaws" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_key_vault" "kv" {
|
resource "azurerm_key_vault" "kv" {
|
||||||
name = "${var.project_name}-kv"
|
name = "${var.project_name}-kv"
|
||||||
location = azurerm_resource_group.rg.location
|
location = azurerm_resource_group.rg.location
|
||||||
resource_group_name = azurerm_resource_group.rg.name
|
resource_group_name = azurerm_resource_group.rg.name
|
||||||
sku_name = "standard"
|
sku_name = "standard"
|
||||||
tenant_id = data.azurerm_client_config.current.tenant_id
|
tenant_id = data.azurerm_client_config.current.tenant_id
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_role_assignment" "app_assignment" {
|
resource "azurerm_role_assignment" "app_assignment" {
|
||||||
@@ -50,10 +60,18 @@ resource "azurerm_role_assignment" "app_assignment" {
|
|||||||
role_definition_name = "Key Vault Secrets User"
|
role_definition_name = "Key Vault Secrets User"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "azurerm_role_assignment" "az_lab_admin_assignment" {
|
||||||
|
scope = azurerm_key_vault.kv.id
|
||||||
|
principal_id = data.azuread_user.az_lab_admin.object_id
|
||||||
|
role_definition_name = "Key Vault Secrets Officer"
|
||||||
|
}
|
||||||
|
|
||||||
resource "azurerm_key_vault_secret" "azure_client_secret" {
|
resource "azurerm_key_vault_secret" "azure_client_secret" {
|
||||||
key_vault_id = azurerm_key_vault.kv.id
|
key_vault_id = azurerm_key_vault.kv.id
|
||||||
name = local.kv_secret_name
|
name = local.kv_secret_name
|
||||||
value = var.azure_client_secret
|
value = var.azure_client_secret
|
||||||
|
|
||||||
|
depends_on = [azurerm_role_assignment.az_lab_admin_assignment]
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_container_app_environment" "env" {
|
resource "azurerm_container_app_environment" "env" {
|
||||||
@@ -64,7 +82,7 @@ resource "azurerm_container_app_environment" "env" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_container_app" "app" {
|
resource "azurerm_container_app" "app" {
|
||||||
name = "${var.project_name}-app"
|
name = local.app_name
|
||||||
container_app_environment_id = azurerm_container_app_environment.env.id
|
container_app_environment_id = azurerm_container_app_environment.env.id
|
||||||
resource_group_name = azurerm_resource_group.rg.name
|
resource_group_name = azurerm_resource_group.rg.name
|
||||||
revision_mode = "Single"
|
revision_mode = "Single"
|
||||||
@@ -94,21 +112,21 @@ resource "azurerm_container_app" "app" {
|
|||||||
|
|
||||||
env {
|
env {
|
||||||
name = "AZURE_CLIENT_SECRET"
|
name = "AZURE_CLIENT_SECRET"
|
||||||
secret_name = "azure_client_secret"
|
secret_name = local.kv_secret_name
|
||||||
}
|
}
|
||||||
|
|
||||||
env {
|
env {
|
||||||
name = "AZURE_SUBSCRIPTION_ID"
|
name = "AZURE_SUBSCRIPTION_ID"
|
||||||
value = var.subscription_id
|
value = var.subscription_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "AZURE_LOCATION"
|
||||||
|
value = azurerm_resource_group.rg.location
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
min_replicas = 1
|
|
||||||
max_replicas = 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
workload_profile_name = "Consumption"
|
|
||||||
|
|
||||||
ingress {
|
ingress {
|
||||||
target_port = 8501
|
target_port = 8501
|
||||||
external_enabled = true
|
external_enabled = true
|
||||||
@@ -119,10 +137,21 @@ resource "azurerm_container_app" "app" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
identity {
|
||||||
|
type = "UserAssigned"
|
||||||
|
identity_ids = [azurerm_user_assigned_identity.uai.id]
|
||||||
|
}
|
||||||
|
|
||||||
registry {
|
registry {
|
||||||
server = "skdomlab.azurecr.io"
|
server = "skdomlab.azurecr.io"
|
||||||
identity = azurerm_user_assigned_identity.uai.id
|
identity = azurerm_user_assigned_identity.uai.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
azurerm_key_vault.kv,
|
||||||
|
azurerm_key_vault_secret.azure_client_secret,
|
||||||
|
azurerm_role_assignment.app_assignment
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_user_assigned_identity" "uai" {
|
resource "azurerm_user_assigned_identity" "uai" {
|
||||||
@@ -141,3 +170,37 @@ data "azurerm_container_registry" "acr" {
|
|||||||
name = "skdomlab"
|
name = "skdomlab"
|
||||||
resource_group_name = "dom-lab-common"
|
resource_group_name = "dom-lab-common"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data "azurerm_dns_zone" "lab_dns_zone" {
|
||||||
|
name = var.dns_zone_name
|
||||||
|
resource_group_name = var.dns_zone_resource_group_name
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_dns_txt_record" "domain_verification" {
|
||||||
|
name = "asuid.${var.project_name}"
|
||||||
|
resource_group_name = data.azurerm_dns_zone.lab_dns_zone.resource_group_name
|
||||||
|
zone_name = data.azurerm_dns_zone.lab_dns_zone.name
|
||||||
|
ttl = 300
|
||||||
|
|
||||||
|
record {
|
||||||
|
value = azurerm_container_app.app.custom_domain_verification_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_dns_cname_record" "app_record" {
|
||||||
|
name = var.project_name
|
||||||
|
zone_name = var.dns_zone_name
|
||||||
|
resource_group_name = var.dns_zone_resource_group_name
|
||||||
|
ttl = 300
|
||||||
|
|
||||||
|
record = "${local.app_name}.${azurerm_container_app_environment.env.default_domain}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "azurerm_container_app_custom_domain" "custom_domain" {
|
||||||
|
name = trimsuffix(trimprefix(azurerm_dns_txt_record.domain_verification.fqdn, "asuid."), ".")
|
||||||
|
container_app_id = azurerm_container_app.app.id
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
ignore_changes = [certificate_binding_type, container_app_environment_certificate_id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,3 +25,15 @@ variable "project_name" {
|
|||||||
description = "The name used to construct Azure resource names."
|
description = "The name used to construct Azure resource names."
|
||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "dns_zone_name" {
|
||||||
|
description = "The name of the DNS zone for domain verification."
|
||||||
|
type = string
|
||||||
|
default = "lab.koszewscy.waw.pl"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "dns_zone_resource_group_name" {
|
||||||
|
description = "The name of the resource group containing the DNS zone."
|
||||||
|
type = string
|
||||||
|
default = "dom-lab-zones"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user