Compare commits
11 Commits
c6349e2577
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ff0a7205f | |||
| 5162b183bf | |||
| b42c659560 | |||
| 55971f7d89 | |||
| c3a632ed4b | |||
| f6061fb9a0 | |||
| 218db54a08 | |||
| 2c50e4ea17 | |||
| 023de7e88d | |||
| a240e62e75 | |||
| 6e60dc7199 |
34
README.md
34
README.md
@@ -2,20 +2,40 @@
|
|||||||
|
|
||||||
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
|
## 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
|
```shell
|
||||||
# Find the certificate name
|
# Find the certificate name
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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 .
|
COPY templates/ templates/
|
||||||
COPY templates.json .
|
COPY templates.json .
|
||||||
COPY ./entrypoint.sh /
|
COPY ./entrypoint.sh /
|
||||||
|
|
||||||
|
|||||||
@@ -33,19 +33,15 @@ def clear_image_versions():
|
|||||||
|
|
||||||
def on_publisher_changed():
|
def on_publisher_changed():
|
||||||
clear_offers()
|
clear_offers()
|
||||||
# st.rerun()
|
|
||||||
|
|
||||||
def on_offer_changed():
|
def on_offer_changed():
|
||||||
clear_skus()
|
clear_skus()
|
||||||
# st.rerun()
|
|
||||||
|
|
||||||
def on_sku_changed():
|
def on_sku_changed():
|
||||||
clear_skus()
|
clear_skus()
|
||||||
# st.rerun()
|
|
||||||
|
|
||||||
def on_image_version_changed():
|
def on_image_version_changed():
|
||||||
clear_image_versions()
|
clear_image_versions()
|
||||||
# st.rerun()
|
|
||||||
|
|
||||||
def version_key(v):
|
def version_key(v):
|
||||||
return [int(x) for x in v.split('.')]
|
return [int(x) for x in v.split('.')]
|
||||||
@@ -75,8 +71,11 @@ def get_locations():
|
|||||||
if loc.metadata.region_type == 'Physical'
|
if loc.metadata.region_type == 'Physical'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def usage_scenario_label(item):
|
||||||
|
return item['label']
|
||||||
|
|
||||||
subscription_id = getenv("AZURE_SUBSCRIPTION_ID")
|
subscription_id = getenv("AZURE_SUBSCRIPTION_ID")
|
||||||
default_location = getenv("AZURE_LOCATION")
|
default_location = getenv("AZURE_LOCATION", "westeurope")
|
||||||
|
|
||||||
credential = DefaultAzureCredential()
|
credential = DefaultAzureCredential()
|
||||||
|
|
||||||
@@ -107,7 +106,7 @@ 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)
|
st.session_state.selected_publisher = publisher_col.selectbox('Select Publisher', options=st.session_state.publishers, on_change=on_publisher_changed, index=None)
|
||||||
else:
|
else:
|
||||||
st.error("No publishers found. Please check your Azure subscription and location.")
|
st.error("No publishers found. Please check your Azure subscription and location.")
|
||||||
st.stop()
|
# st.stop()
|
||||||
|
|
||||||
# Offers
|
# Offers
|
||||||
if 'offers' not in st.session_state and is_valid('selected_publisher'):
|
if 'offers' not in st.session_state and is_valid('selected_publisher'):
|
||||||
@@ -117,9 +116,9 @@ 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)
|
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'):
|
elif is_valid('selected_publisher'):
|
||||||
st.info("No offers found for the selected publisher. Please select a different publisher.")
|
st.info("No offers found for the selected publisher. Please select a different publisher.")
|
||||||
st.stop()
|
# st.stop()
|
||||||
else:
|
# else:
|
||||||
st.stop()
|
# st.stop()
|
||||||
|
|
||||||
# SKUs
|
# SKUs
|
||||||
if 'skus' not in st.session_state and is_valid('selected_publisher') and is_valid('selected_offer'):
|
if 'skus' not in st.session_state and is_valid('selected_publisher') and is_valid('selected_offer'):
|
||||||
@@ -129,9 +128,9 @@ 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)
|
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'):
|
elif is_valid('selected_offer'):
|
||||||
st.info("No SKUs found for the selected offer. Please select a different offer.")
|
st.info("No SKUs found for the selected offer. Please select a different offer.")
|
||||||
st.stop()
|
# st.stop()
|
||||||
else:
|
# else:
|
||||||
st.stop()
|
# st.stop()
|
||||||
|
|
||||||
# Image versions
|
# Image versions
|
||||||
if 'image_versions' not in st.session_state and is_valid('selected_publisher') and is_valid('selected_offer') and is_valid('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'):
|
||||||
@@ -148,9 +147,9 @@ if is_valid('image_versions'):
|
|||||||
st.session_state.selected_image_version = version_col.selectbox('Select Image Version', options=st.session_state.image_versions, index=None)
|
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'):
|
elif is_valid('selected_sku'):
|
||||||
st.info("No image versions found for the selected SKU. Please select a different SKU.")
|
st.info("No image versions found for the selected SKU. Please select a different SKU.")
|
||||||
st.stop()
|
# st.stop()
|
||||||
else:
|
# else:
|
||||||
st.stop()
|
# st.stop()
|
||||||
|
|
||||||
if is_valid('selected_image_version'):
|
if is_valid('selected_image_version'):
|
||||||
st.subheader("Usage example")
|
st.subheader("Usage example")
|
||||||
@@ -158,9 +157,6 @@ if is_valid('selected_image_version'):
|
|||||||
with open("templates.json") as f:
|
with open("templates.json") as f:
|
||||||
templates = json.load(f)
|
templates = json.load(f)
|
||||||
|
|
||||||
def usage_scenario_label(item):
|
|
||||||
return item['label']
|
|
||||||
|
|
||||||
layout = st.columns(4)
|
layout = st.columns(4)
|
||||||
|
|
||||||
selected_file = layout[0].selectbox('Select usage scenario:', options=templates, format_func=usage_scenario_label)
|
selected_file = layout[0].selectbox('Select usage scenario:', options=templates, format_func=usage_scenario_label)
|
||||||
@@ -175,3 +171,9 @@ if is_valid('selected_image_version'):
|
|||||||
)
|
)
|
||||||
|
|
||||||
st.code(rendered, language=selected_file['language'])
|
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)
|
||||||
|
|||||||
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
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ data "azuread_user" "az_lab_admin" {
|
|||||||
|
|
||||||
locals {
|
locals {
|
||||||
kv_secret_name = "azure-client-secret"
|
kv_secret_name = "azure-client-secret"
|
||||||
app_name = "${var.project_name}-app"
|
app_name = "${var.project_name}-app"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_resource_group" "rg" {
|
resource "azurerm_resource_group" "rg" {
|
||||||
@@ -52,7 +52,6 @@ resource "azurerm_key_vault" "kv" {
|
|||||||
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
|
||||||
enable_rbac_authorization = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_role_assignment" "app_assignment" {
|
resource "azurerm_role_assignment" "app_assignment" {
|
||||||
@@ -120,6 +119,11 @@ resource "azurerm_container_app" "app" {
|
|||||||
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,19 +188,19 @@ resource "azurerm_dns_txt_record" "domain_verification" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_dns_cname_record" "app_record" {
|
resource "azurerm_dns_cname_record" "app_record" {
|
||||||
name = var.project_name
|
name = var.project_name
|
||||||
zone_name = var.dns_zone_name
|
zone_name = var.dns_zone_name
|
||||||
resource_group_name = var.dns_zone_resource_group_name
|
resource_group_name = var.dns_zone_resource_group_name
|
||||||
ttl = 300
|
ttl = 300
|
||||||
|
|
||||||
record = "${local.app_name}.${azurerm_container_app_environment.env.default_domain}"
|
record = "${local.app_name}.${azurerm_container_app_environment.env.default_domain}"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "azurerm_container_app_custom_domain" "custom_domain" {
|
resource "azurerm_container_app_custom_domain" "custom_domain" {
|
||||||
name = trimsuffix(trimprefix(azurerm_dns_txt_record.domain_verification.fqdn, "asuid."), ".")
|
name = trimsuffix(trimprefix(azurerm_dns_txt_record.domain_verification.fqdn, "asuid."), ".")
|
||||||
container_app_id = azurerm_container_app.app.id
|
container_app_id = azurerm_container_app.app.id
|
||||||
|
|
||||||
lifecycle {
|
lifecycle {
|
||||||
ignore_changes = [ certificate_binding_type, container_app_environment_certificate_id ]
|
ignore_changes = [certificate_binding_type, container_app_environment_certificate_id]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user