Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/azure-cli/azure/cli/command_modules/appservice/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -2597,12 +2597,16 @@

helps['webapp sitecontainers convert'] = """
type: command
short-summary: Convert a webapp from sitecontainers to a classic custom container and vice versa.
short-summary: Convert a webapp from sitecontainers to a classic custom container and vice versa. Supports both single-container (DOCKER|) and multi-container (COMPOSE|) apps.
examples:
- name: Convert a webapp to classic custom container (docker) from sitecontainers
text: az webapp sitecontainers convert --mode docker --name MyWebApp --resource-group MyResourceGroup
- name: Convert a webapp to sitecontainers from classic custom container (docker)
- name: Convert a single-container webapp (DOCKER|) to sitecontainers
text: az webapp sitecontainers convert --mode sitecontainers --name MyWebApp --resource-group MyResourceGroup
- name: Convert a multi-container webapp (COMPOSE|) to sitecontainers
text: az webapp sitecontainers convert --mode sitecontainers --name MyWebApp --resource-group MyResourceGroup
- name: Convert a COMPOSE app to sitecontainers specifying which service is the main container
text: az webapp sitecontainers convert --mode sitecontainers --name MyWebApp --resource-group MyResourceGroup --main-container-name web
"""


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@ def load_arguments(self, _):
with self.argument_context("webapp sitecontainers convert") as c:
c.argument('mode', options_list=['--mode'], help='Mode for conversion.',
arg_type=get_enum_type(['docker', 'sitecontainers']))
c.argument('main_container_name', options_list=['--main-container-name'],
help='For COMPOSE to sitecontainers conversion, specifies which '
'compose service should be the main container. If not provided, '
'the service with a port mapping is auto-detected.')

with self.argument_context('webapp show') as c:
c.argument('name', arg_type=webapp_name_arg_type)
Expand Down
692 changes: 684 additions & 8 deletions src/azure-cli/azure/cli/command_modules/appservice/custom.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Basic multi-container: web app + redis sidecar
# Tests: auto-detect main (web has ports), simple image extraction, no env/volumes
version: '3'
services:
web:
image: "myacr.azurecr.io/webapp:v1"
ports:
- "8080:8080"
redis:
image: "redis:7-alpine"
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Entrypoint and command in various forms
# Tests: entrypoint+command merging, scalar vs sequence formats, startUpCommand generation
version: '3'
services:
web:
image: "myacr.azurecr.io/flask-app:latest"
ports:
- "5000:5000"
# Entrypoint as string, command as string → both merged
entrypoint: "gunicorn"
command: "--bind 0.0.0.0:5000 app:app --workers 4"
worker:
image: "myacr.azurecr.io/celery-worker:latest"
# Entrypoint as list, command as list
entrypoint: ["celery", "-A", "tasks"]
command: ["worker", "--loglevel=info", "--concurrency=2"]
scheduler:
image: "myacr.azurecr.io/celery-worker:latest"
# Only command (no entrypoint)
command: "celery -A tasks beat --loglevel=info"
sidecar:
image: "myacr.azurecr.io/monitoring:v1"
# Only entrypoint (no command)
entrypoint: ["/usr/local/bin/monitor", "--port", "9090"]
ports:
- "9090:9090"
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Environment variables in mapping format (dict)
# Tests: env var extraction, inline values → app settings, mapping format parsing
version: '3'
services:
api:
image: "myacr.azurecr.io/api-server:latest"
ports:
- "3000:3000"
environment:
NODE_ENV: production
DB_HOST: localhost
DB_PORT: "5432"
API_KEY: "s3cret-key-value"
db:
image: "postgres:15-alpine"
ports:
- "5432:5432"
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: "p@ssw0rd"
Comment on lines +13 to +20
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These fixtures include password/API-key-like literal values (e.g., API_KEY, POSTGRES_PASSWORD) which can trigger repository secret scanning and fail CI. Consider replacing with clearly fake placeholders (e.g., not-a-real-password) or otherwise aligning with the repo’s secret-scan suppression approach for test data.

Suggested change
API_KEY: "s3cret-key-value"
db:
image: "postgres:15-alpine"
ports:
- "5432:5432"
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: "p@ssw0rd"
API_KEY: "not-a-real-api-key"
db:
image: "postgres:15-alpine"
ports:
- "5432:5432"
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: "not-a-real-password"

Copilot uses AI. Check for mistakes.
POSTGRES_DB: myapp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Environment variables in sequence (list) format
# Tests: env var sequence parsing, value-less entries (app setting reference)
version: '3'
services:
app:
image: "myacr.azurecr.io/myapp:2.0"
ports:
- "80:80"
environment:
- REDIS_URL=redis://localhost:6379
- APP_SECRET=mysecretvalue
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APP_SECRET=mysecretvalue and similar literals in fixtures can trigger secret scanning (even though they’re test-only). Consider using clearly fake placeholders that won’t match common secret patterns.

Suggested change
- APP_SECRET=mysecretvalue
- APP_SECRET=not-a-real-secret

Copilot uses AI. Check for mistakes.
- EXISTING_SETTING
- ANOTHER_SETTING=
cache:
image: "redis:7-alpine"
environment:
- REDIS_MAXMEMORY=256mb
- REDIS_MAXMEMORY_POLICY=allkeys-lru
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Full realistic scenario: WordPress + MariaDB + Redis + Nginx reverse proxy
# Tests: multiple services, all field types, volume mapping, env vars, main detection
version: '3.8'
services:
nginx:
image: "myacr.azurecr.io/nginx-proxy:latest"
ports:
- "80:80"
- "443:443"
volumes:
- "${WEBAPP_STORAGE_HOME}/nginx/nginx.conf:/etc/nginx/nginx.conf"
- "${WEBAPP_STORAGE_HOME}/nginx/certs:/etc/nginx/certs"
entrypoint: ["nginx"]
command: ["-g", "daemon off;"]
restart: always

wordpress:
image: "wordpress:6.4-php8.2-fpm"
ports:
- "9000:9000"
volumes:
- "${WEBAPP_STORAGE_HOME}/site/wwwroot:/var/www/html"
- type: bind
source: "${WEBAPP_STORAGE_HOME}/wordpress/uploads"
target: /var/www/html/wp-content/uploads
environment:
WORDPRESS_DB_HOST: "localhost:3306"
WORDPRESS_DB_USER: wp_user
WORDPRESS_DB_PASSWORD: "wp_s3cret"
WORDPRESS_DB_NAME: wordpress
WORDPRESS_TABLE_PREFIX: wp_
WORDPRESS_CONFIG_EXTRA: |
define('WP_REDIS_HOST', 'localhost');
define('WP_REDIS_PORT', 6379);
restart: always

mariadb:
image: "mariadb:11"
ports:
- "3306:3306"
volumes:
- "${WEBAPP_STORAGE_HOME}/mysql/data:/var/lib/mysql"
environment:
- MYSQL_ROOT_PASSWORD=rootpass123
- MYSQL_DATABASE=wordpress
- MYSQL_USER=wp_user
- MYSQL_PASSWORD=wp_s3cret
Comment on lines +26 to +47
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sample compose file contains password-like values (e.g., WORDPRESS_DB_PASSWORD, MYSQL_ROOT_PASSWORD, MYSQL_PASSWORD). These strings may be flagged by automated secret scanners in CI. Consider swapping them to obvious non-secret placeholders to avoid false positives in security tooling.

Copilot uses AI. Check for mistakes.
command: "--default-authentication-plugin=mysql_native_password"
restart: always

redis:
image: "redis:7-alpine"
command: "redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru"
restart: always
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Multiple port mappings per service
# Tests: only first container port used as targetPort, multi-port warning
version: '3'
services:
app:
image: "myacr.azurecr.io/fullstack:latest"
ports:
- "80:80"
- "443:443"
- "8080:8080"
metrics:
image: "prom/prometheus:v2.48.0"
ports:
- "9090:9090"
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# No services have port mappings
# Tests: main container fallback to first service, warning about missing targetPort
version: '3'
services:
processor:
image: "myacr.azurecr.io/bg-processor:latest"
command: "python worker.py"
environment:
QUEUE_URL: "amqp://localhost:5672"
rabbitmq:
image: "rabbitmq:3-management-alpine"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Port conflicts: two services mapping different host ports to same container port
# Tests: CRITICAL port conflict warning, host≠container port warning
version: '3'
services:
frontend:
image: "myacr.azurecr.io/frontend:latest"
ports:
- "80:8080"
backend:
image: "myacr.azurecr.io/backend:latest"
ports:
- "8080:8080"
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Service names with underscores, dots, special characters
# Tests: _sanitize_container_name() converting to valid sitecontainer names
version: '3'
services:
my_web_app:
image: "myacr.azurecr.io/web:latest"
ports:
- "80:80"
background.worker:
image: "myacr.azurecr.io/worker:latest"
command: "python worker.py"
data-processor:
image: "myacr.azurecr.io/processor:latest"
UPPERCASE_SVC:
image: "redis:alpine"
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Unsupported bind mounts (not using ${WEBAPP_STORAGE_HOME})
# Tests: UNSUPPORTED bind mount warning, mixed supported/unsupported volumes
version: '3'
services:
app:
image: "myacr.azurecr.io/app:latest"
ports:
- "8080:8080"
volumes:
# Supported: uses ${WEBAPP_STORAGE_HOME}
- "${WEBAPP_STORAGE_HOME}/site/wwwroot:/app/public"
# Unsupported: absolute host path
- "/var/data:/app/data"
# Unsupported: relative path
- "./config:/app/config"
# Supported: another valid bind mount
- "${WEBAPP_STORAGE_HOME}/logs:/app/logs"
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Volumes: bind mounts using ${WEBAPP_STORAGE_HOME}
# Tests: bind mount → /home volumeSubPath mapping, multiple mounts per service
version: '3'
services:
wordpress:
image: "wordpress:6.4-php8.2-apache"
ports:
- "80:80"
volumes:
- "${WEBAPP_STORAGE_HOME}/site/wwwroot:/var/www/html"
- "${WEBAPP_STORAGE_HOME}/wordpress/uploads:/var/www/html/wp-content/uploads"
environment:
WORDPRESS_DB_HOST: localhost
WORDPRESS_DB_NAME: wordpress
mysql:
image: "mysql:8.0"
ports:
- "3306:3306"
volumes:
- "${WEBAPP_STORAGE_HOME}/mysql/data:/var/lib/mysql"
environment:
MYSQL_ROOT_PASSWORD: rootpass
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MYSQL_ROOT_PASSWORD: rootpass looks like a real secret and may be flagged by CI secret scanning despite being test data. Consider replacing with an explicit placeholder value (e.g., example-password) that won’t match common password heuristics.

Suggested change
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_ROOT_PASSWORD: example-password

Copilot uses AI. Check for mistakes.
MYSQL_DATABASE: wordpress
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Volumes: long syntax (mapping with type/source/target)
# Tests: long syntax bind mount parsing, long syntax volume parsing
version: '3.2'
services:
web:
image: "nginx:1.25-alpine"
ports:
- "80:80"
volumes:
- type: bind
source: "${WEBAPP_STORAGE_HOME}/site/wwwroot"
target: /usr/share/nginx/html
- type: bind
source: "${WEBAPP_STORAGE_HOME}/nginx/conf.d"
target: /etc/nginx/conf.d
read_only: true
api:
image: "myacr.azurecr.io/api:v3"
ports:
- "8080:8080"
volumes:
- type: volume
source: api-cache
target: /tmp/cache

volumes:
api-cache:
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Volumes: named volumes (top-level volumes section)
# Tests: named volume → /compose/volumes/<name> mapping (local ephemeral), top-level volumes parsing, warnings
version: '3'
services:
app:
image: "myacr.azurecr.io/nodeapp:latest"
ports:
- "3000:3000"
volumes:
- app-data:/app/data
- app-logs:/app/logs
mongo:
image: "mongo:7"
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db

volumes:
app-data:
app-logs:
mongo-data:
Loading
Loading