From 870e4e171e42895b646bf0fce1fb5b5d6abc1f5d Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 13:16:19 +1100 Subject: [PATCH 01/33] lab_setup first draft --- docs/workshop/lab_setup.py | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 docs/workshop/lab_setup.py diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py new file mode 100755 index 00000000..d48c3bb6 --- /dev/null +++ b/docs/workshop/lab_setup.py @@ -0,0 +1,59 @@ +#!/bin/env python + +import click +import subprocess +import os + +@click.command() +@click.option('--username', required=True, help='Azure username/email') +@click.option('--password', required=True, help='Azure password') +@click.option('--azure-env-name', required=True, help='Azure environment name') +@click.option('--subscription', required=True, help='Azure subscription ID') +def setup(username, password, azure_env_name, subscription): + """Automates Azure environment setup and configuration.""" + try: + # Azure CLI login + click.echo("Logging into Azure CLI...") + subprocess.run(['az', 'login', '-u', username, '-p', password], check=True) + + # Azure Developer CLI environment setup + click.echo("Creating new AZD environment...") + subprocess.run([ + 'azd', 'env', 'new', azure_env_name, + '--location', 'canadaeast', + '--subscription', subscription + ], check=True) + + # Refresh AZD environment + click.echo("Refreshing AZD environment...") + subprocess.run([ + 'azd', 'env', 'refresh', + '-e', azure_env_name, + '--no-prompt' + ], check=True) + + # Export environment variables + click.echo("Exporting environment variables...") + with open('../../.env', 'w') as env_file: + subprocess.run(['azd', 'env', 'get-values'], stdout=env_file, check=True) + + # Run roles script + click.echo("Running roles script...") + subprocess.run(['../../infra/hooks/roles.sh'], check=True) + + # Run postprovision hook + click.echo("Running postprovision hook...") + process = subprocess.Popen(['azd', 'hooks', 'run', 'postprovision', '-e', azure_env_name], + stdin=subprocess.PIPE, + text=True) + process.communicate(input='1\n') + + click.echo("Setup completed successfully!") + + except subprocess.CalledProcessError as e: + click.echo(f"Error during setup: {str(e)}", err=True) + raise click.Abort() + +if __name__ == '__main__': + setup() + From 938e235091f74e02d63ca8370af30a93ed504f26 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 13:20:08 +1100 Subject: [PATCH 02/33] Add an optional --tenant param for the az step --- docs/workshop/lab_setup.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index d48c3bb6..e326f342 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -9,20 +9,27 @@ @click.option('--password', required=True, help='Azure password') @click.option('--azure-env-name', required=True, help='Azure environment name') @click.option('--subscription', required=True, help='Azure subscription ID') -def setup(username, password, azure_env_name, subscription): +@click.option('--tenant', help='Azure tenant ID (optional)') +def setup(username, password, azure_env_name, subscription, tenant): """Automates Azure environment setup and configuration.""" try: # Azure CLI login click.echo("Logging into Azure CLI...") - subprocess.run(['az', 'login', '-u', username, '-p', password], check=True) + login_cmd = ['az', 'login', '-u', username, '-p', password] + if tenant: + login_cmd.extend(['--tenant', tenant]) + subprocess.run(login_cmd, check=True) # Azure Developer CLI environment setup click.echo("Creating new AZD environment...") - subprocess.run([ + azd_cmd = [ 'azd', 'env', 'new', azure_env_name, '--location', 'canadaeast', '--subscription', subscription - ], check=True) + ] + if tenant: + azd_cmd.extend(['--tenant', tenant]) + subprocess.run(azd_cmd, check=True) # Refresh AZD environment click.echo("Refreshing AZD environment...") From b12981dc1e9ad73b3eb623c78931ce1d0057a574 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 13:27:23 +1100 Subject: [PATCH 03/33] Using rich-click Reverse Reverse --- docs/workshop/lab_setup.py | 24 +++++++++++++++++------- requirements.txt | 2 ++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index e326f342..06866ea2 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -1,17 +1,27 @@ #!/bin/env python -import click +import rich_click as click import subprocess import os @click.command() -@click.option('--username', required=True, help='Azure username/email') -@click.option('--password', required=True, help='Azure password') -@click.option('--azure-env-name', required=True, help='Azure environment name') -@click.option('--subscription', required=True, help='Azure subscription ID') -@click.option('--tenant', help='Azure tenant ID (optional)') +@click.option('--username', required=True, help='Azure username/email for authentication') +@click.option('--password', required=True, help='Azure password for authentication', hide_input=True) +@click.option('--azure-env-name', required=True, help='Name for the new Azure environment') +@click.option('--subscription', required=True, help='Azure subscription ID to use') +@click.option('--tenant', help='Optional Azure tenant ID for specific directory') def setup(username, password, azure_env_name, subscription, tenant): - """Automates Azure environment setup and configuration.""" + """ + Automates Azure environment setup and configuration. + + This command will: + * Log into Azure CLI + * Create a new AZD environment + * Refresh the environment + * Export environment variables + * Run roles script + * Execute postprovision hook + """ try: # Azure CLI login click.echo("Logging into Azure CLI...") diff --git a/requirements.txt b/requirements.txt index e69de29b..a3e83105 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,2 @@ +click +rich-click From 2f8d7edae4f86cf2a6887f15b4120ba860931d4f Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 13:51:06 +1100 Subject: [PATCH 04/33] Steps as functions --- docs/workshop/lab_setup.py | 127 +++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 42 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 06866ea2..c32733dc 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -3,6 +3,85 @@ import rich_click as click import subprocess import os +from functools import wraps +from typing import List, Callable + +# Rich-click configuration remains the same... + +# Step registration +steps: List[tuple[Callable, str]] = [] + +def blue(text: str): + return click.style(text, fg="blue") + +def bold(text: str): + return click.style(text, fg="bright_white", bold=True) + +def step(label: str): + """Decorator to register and label setup steps""" + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + step_number = len([s for s in steps if s[0] == func]) + 1 + click.echo(f"\n{bold(f'Step {step_number}')}: {blue(label)}") + click.echo() + return func(*args, **kwargs) + steps.append((wrapper, label)) + return wrapper + return decorator + +class SetupContext: + """Context object to hold setup parameters""" + def __init__(self, username, password, azure_env_name, subscription, tenant): + self.username = username + self.password = password + self.azure_env_name = azure_env_name + self.subscription = subscription + self.tenant = tenant + +@step("Azure CLI Authentication") +def azure_login(ctx: SetupContext): + login_cmd = ['az', 'login', '-u', ctx.username, '-p', ctx.password] + if ctx.tenant: + login_cmd.extend(['--tenant', ctx.tenant]) + subprocess.run(login_cmd, check=True) + +@step("Azure Developer CLI Environment Setup") +def create_azd_environment(ctx: SetupContext): + azd_cmd = [ + 'azd', 'env', 'new', ctx.azure_env_name, + '--location', 'canadaeast', + '--subscription', ctx.subscription + ] + if ctx.tenant: + azd_cmd.extend(['--tenant', ctx.tenant]) + subprocess.run(azd_cmd, check=True) + +@step("Refresh AZD Environment") +def refresh_environment(ctx: SetupContext): + subprocess.run([ + 'azd', 'env', 'refresh', + '-e', ctx.azure_env_name, + '--no-prompt' + ], check=True) + +@step("Export Environment Variables") +def export_variables(ctx: SetupContext): + with open('../../.env', 'w') as env_file: + subprocess.run(['azd', 'env', 'get-values'], stdout=env_file, check=True) + +@step("Run Roles Script") +def run_roles(ctx: SetupContext): + subprocess.run(['../../infra/hooks/roles.sh'], check=True) + +@step("Execute Postprovision Hook") +def run_postprovision(ctx: SetupContext): + process = subprocess.Popen( + ['azd', 'hooks', 'run', 'postprovision', '-e', ctx.azure_env_name], + stdin=subprocess.PIPE, + text=True + ) + process.communicate(input='1\n') @click.command() @click.option('--username', required=True, help='Azure username/email for authentication') @@ -23,49 +102,13 @@ def setup(username, password, azure_env_name, subscription, tenant): * Execute postprovision hook """ try: - # Azure CLI login - click.echo("Logging into Azure CLI...") - login_cmd = ['az', 'login', '-u', username, '-p', password] - if tenant: - login_cmd.extend(['--tenant', tenant]) - subprocess.run(login_cmd, check=True) - - # Azure Developer CLI environment setup - click.echo("Creating new AZD environment...") - azd_cmd = [ - 'azd', 'env', 'new', azure_env_name, - '--location', 'canadaeast', - '--subscription', subscription - ] - if tenant: - azd_cmd.extend(['--tenant', tenant]) - subprocess.run(azd_cmd, check=True) - - # Refresh AZD environment - click.echo("Refreshing AZD environment...") - subprocess.run([ - 'azd', 'env', 'refresh', - '-e', azure_env_name, - '--no-prompt' - ], check=True) - - # Export environment variables - click.echo("Exporting environment variables...") - with open('../../.env', 'w') as env_file: - subprocess.run(['azd', 'env', 'get-values'], stdout=env_file, check=True) - - # Run roles script - click.echo("Running roles script...") - subprocess.run(['../../infra/hooks/roles.sh'], check=True) - - # Run postprovision hook - click.echo("Running postprovision hook...") - process = subprocess.Popen(['azd', 'hooks', 'run', 'postprovision', '-e', azure_env_name], - stdin=subprocess.PIPE, - text=True) - process.communicate(input='1\n') + ctx = SetupContext(username, password, azure_env_name, subscription, tenant) - click.echo("Setup completed successfully!") + # Execute all registered steps + for step_func, _ in steps: + step_func(ctx) + + click.echo("\nSetup completed successfully!") except subprocess.CalledProcessError as e: click.echo(f"Error during setup: {str(e)}", err=True) From 22dc3f9c93e1ff345f8403b2c2a05e6c64ddd8f3 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 13:55:48 +1100 Subject: [PATCH 05/33] Each step function takes context variables as kw parameters --- docs/workshop/lab_setup.py | 61 +++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index c32733dc..61e2f1d1 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -6,8 +6,6 @@ from functools import wraps from typing import List, Callable -# Rich-click configuration remains the same... - # Step registration steps: List[tuple[Callable, str]] = [] @@ -30,54 +28,45 @@ def wrapper(*args, **kwargs): return wrapper return decorator -class SetupContext: - """Context object to hold setup parameters""" - def __init__(self, username, password, azure_env_name, subscription, tenant): - self.username = username - self.password = password - self.azure_env_name = azure_env_name - self.subscription = subscription - self.tenant = tenant - @step("Azure CLI Authentication") -def azure_login(ctx: SetupContext): - login_cmd = ['az', 'login', '-u', ctx.username, '-p', ctx.password] - if ctx.tenant: - login_cmd.extend(['--tenant', ctx.tenant]) +def azure_login(*, username: str, password: str, tenant: str = None): + login_cmd = ['az', 'login', '-u', username, '-p', password] + if tenant: + login_cmd.extend(['--tenant', tenant]) subprocess.run(login_cmd, check=True) @step("Azure Developer CLI Environment Setup") -def create_azd_environment(ctx: SetupContext): +def create_azd_environment(*, azure_env_name: str, subscription: str, tenant: str = None): azd_cmd = [ - 'azd', 'env', 'new', ctx.azure_env_name, + 'azd', 'env', 'new', azure_env_name, '--location', 'canadaeast', - '--subscription', ctx.subscription + '--subscription', subscription ] - if ctx.tenant: - azd_cmd.extend(['--tenant', ctx.tenant]) + if tenant: + azd_cmd.extend(['--tenant', tenant]) subprocess.run(azd_cmd, check=True) @step("Refresh AZD Environment") -def refresh_environment(ctx: SetupContext): +def refresh_environment(*, azure_env_name: str): subprocess.run([ 'azd', 'env', 'refresh', - '-e', ctx.azure_env_name, + '-e', azure_env_name, '--no-prompt' ], check=True) @step("Export Environment Variables") -def export_variables(ctx: SetupContext): +def export_variables(): with open('../../.env', 'w') as env_file: subprocess.run(['azd', 'env', 'get-values'], stdout=env_file, check=True) @step("Run Roles Script") -def run_roles(ctx: SetupContext): +def run_roles(): subprocess.run(['../../infra/hooks/roles.sh'], check=True) @step("Execute Postprovision Hook") -def run_postprovision(ctx: SetupContext): +def run_postprovision(*, azure_env_name: str): process = subprocess.Popen( - ['azd', 'hooks', 'run', 'postprovision', '-e', ctx.azure_env_name], + ['azd', 'hooks', 'run', 'postprovision', '-e', azure_env_name], stdin=subprocess.PIPE, text=True ) @@ -102,11 +91,27 @@ def setup(username, password, azure_env_name, subscription, tenant): * Execute postprovision hook """ try: - ctx = SetupContext(username, password, azure_env_name, subscription, tenant) + # Create parameters dictionary + params = { + 'username': username, + 'password': password, + 'azure_env_name': azure_env_name, + 'subscription': subscription, + 'tenant': tenant + } # Execute all registered steps for step_func, _ in steps: - step_func(ctx) + # Get the parameter names for this function + from inspect import signature + sig = signature(step_func.__wrapped__) + # Filter params to only include what the function needs + step_params = { + name: params[name] + for name in sig.parameters + if name in params + } + step_func(**step_params) click.echo("\nSetup completed successfully!") From f07c0fc958bfc2c826d9144432c7fc849a3373f6 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 13:59:35 +1100 Subject: [PATCH 06/33] if a step returns a dict then merge it into the params --- docs/workshop/lab_setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 61e2f1d1..251748fe 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -111,7 +111,10 @@ def setup(username, password, azure_env_name, subscription, tenant): for name in sig.parameters if name in params } - step_func(**step_params) + # Execute step and merge any returned dict into params + result = step_func(**step_params) + if isinstance(result, dict): + params.update(result) click.echo("\nSetup completed successfully!") From d2d85ff23734d2027e47aea0232591e4b3d8965a Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:04:29 +1100 Subject: [PATCH 07/33] username and password optional to ease dev in normal tenant --- docs/workshop/lab_setup.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 251748fe..b4136bc4 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -29,8 +29,10 @@ def wrapper(*args, **kwargs): return decorator @step("Azure CLI Authentication") -def azure_login(*, username: str, password: str, tenant: str = None): - login_cmd = ['az', 'login', '-u', username, '-p', password] +def azure_login(*, username: str = None, password: str = None, tenant: str = None): + login_cmd = ['az', 'login'] + if username and password: + login_cmd.extend(['-u', username, '-p', password]) if tenant: login_cmd.extend(['--tenant', tenant]) subprocess.run(login_cmd, check=True) @@ -73,8 +75,8 @@ def run_postprovision(*, azure_env_name: str): process.communicate(input='1\n') @click.command() -@click.option('--username', required=True, help='Azure username/email for authentication') -@click.option('--password', required=True, help='Azure password for authentication', hide_input=True) +@click.option('--username', help='Azure username/email for authentication') +@click.option('--password', help='Azure password for authentication', hide_input=True) @click.option('--azure-env-name', required=True, help='Name for the new Azure environment') @click.option('--subscription', required=True, help='Azure subscription ID to use') @click.option('--tenant', help='Optional Azure tenant ID for specific directory') @@ -83,7 +85,7 @@ def setup(username, password, azure_env_name, subscription, tenant): Automates Azure environment setup and configuration. This command will: - * Log into Azure CLI + * Log into Azure CLI (interactive if no credentials provided) * Create a new AZD environment * Refresh the environment * Export environment variables From f0ada692ebbaeae48e548ab4d8427a334728ae2c Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:08:14 +1100 Subject: [PATCH 08/33] GitHub Authentication step --- docs/workshop/lab_setup.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index b4136bc4..13d2de03 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -28,6 +28,21 @@ def wrapper(*args, **kwargs): return wrapper return decorator +@step("GitHub Authentication") +def github_auth(): + """Authenticate with GitHub using the gh CLI tool""" + process = subprocess.Popen( + ['gh', 'auth', 'login', + '--hostname', 'github.com', + '--git-protocol', 'https', + '--web', + '--scopes', 'workflow'], + stdin=subprocess.PIPE, + env={**os.environ, 'GITHUB_TOKEN': ''}, + text=True + ) + process.communicate(input='Y\n') + @step("Azure CLI Authentication") def azure_login(*, username: str = None, password: str = None, tenant: str = None): login_cmd = ['az', 'login'] From aff75fd4e87cdca77913b7721434de12023904a4 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:26:06 +1100 Subject: [PATCH 09/33] Fixed step number --- docs/workshop/lab_setup.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 13d2de03..79b2eff0 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -16,11 +16,11 @@ def bold(text: str): return click.style(text, fg="bright_white", bold=True) def step(label: str): + """Decorator to register and label setup steps""" def decorator(func): @wraps(func) - def wrapper(*args, **kwargs): - step_number = len([s for s in steps if s[0] == func]) + 1 + def wrapper(*args, step_number, **kwargs): click.echo(f"\n{bold(f'Step {step_number}')}: {blue(label)}") click.echo() return func(*args, **kwargs) @@ -118,9 +118,11 @@ def setup(username, password, azure_env_name, subscription, tenant): } # Execute all registered steps - for step_func, _ in steps: - # Get the parameter names for this function + for index, entry in enumerate(steps): from inspect import signature + step_func, _ = entry + + # Get the parameter names for this function sig = signature(step_func.__wrapped__) # Filter params to only include what the function needs step_params = { @@ -129,7 +131,7 @@ def setup(username, password, azure_env_name, subscription, tenant): if name in params } # Execute step and merge any returned dict into params - result = step_func(**step_params) + result = step_func(step_number=index + 1, **step_params) if isinstance(result, dict): params.update(result) From e5f87385802eaec9cfe4cac6312774c4783c68c1 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:27:07 +1100 Subject: [PATCH 10/33] Display total steps --- docs/workshop/lab_setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 79b2eff0..a3b0ab70 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -20,8 +20,8 @@ def step(label: str): """Decorator to register and label setup steps""" def decorator(func): @wraps(func) - def wrapper(*args, step_number, **kwargs): - click.echo(f"\n{bold(f'Step {step_number}')}: {blue(label)}") + def wrapper(*args, step_number, total_steps, **kwargs): + click.echo(f"\n{bold(f'Step {step_number}/{total_steps}')}: {blue(label)}") click.echo() return func(*args, **kwargs) steps.append((wrapper, label)) @@ -131,7 +131,7 @@ def setup(username, password, azure_env_name, subscription, tenant): if name in params } # Execute step and merge any returned dict into params - result = step_func(step_number=index + 1, **step_params) + result = step_func(step_number=index + 1, total_steps=len(steps), **step_params) if isinstance(result, dict): params.update(result) From 65bdf6d66fca0596e96cc032c63228836dd43453 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:30:55 +1100 Subject: [PATCH 11/33] Fork repo step --- docs/workshop/lab_setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index a3b0ab70..dc0444b4 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -43,6 +43,11 @@ def github_auth(): ) process.communicate(input='Y\n') +@step("Fork GitHub Repository") +def fork_repository(): + """Fork the current repository using the gh CLI tool""" + subprocess.run(['gh', 'repo', 'fork', '--remote'], check=True) + @step("Azure CLI Authentication") def azure_login(*, username: str = None, password: str = None, tenant: str = None): login_cmd = ['az', 'login'] From 2e4e0f6d0b31b18a7af69806d1e2733a3650c4e5 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:33:20 +1100 Subject: [PATCH 12/33] Skip Github auth if already authenticated --- docs/workshop/lab_setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index dc0444b4..4f6add4f 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -31,6 +31,16 @@ def wrapper(*args, step_number, total_steps, **kwargs): @step("GitHub Authentication") def github_auth(): """Authenticate with GitHub using the gh CLI tool""" + # Check if already authenticated + result = subprocess.run(['gh', 'auth', 'status'], + capture_output=True, + text=True, + check=False) + if result.returncode == 0: + click.echo("Already authenticated with GitHub") + return + + # Proceed with authentication if not already authenticated process = subprocess.Popen( ['gh', 'auth', 'login', '--hostname', 'github.com', From 4741c9eb88579669808ec095256a38cd3eacbcd2 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:39:07 +1100 Subject: [PATCH 13/33] not need to call gh auth status if force is true --- docs/workshop/lab_setup.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 4f6add4f..3de15c1d 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -29,18 +29,19 @@ def wrapper(*args, step_number, total_steps, **kwargs): return decorator @step("GitHub Authentication") -def github_auth(): +def github_auth(*, force: bool = False): """Authenticate with GitHub using the gh CLI tool""" - # Check if already authenticated - result = subprocess.run(['gh', 'auth', 'status'], - capture_output=True, - text=True, - check=False) - if result.returncode == 0: - click.echo("Already authenticated with GitHub") - return - - # Proceed with authentication if not already authenticated + # Only check authentication status if not forcing re-auth + if not force: + result = subprocess.run(['gh', 'auth', 'status'], + capture_output=True, + text=True, + check=False) + if result.returncode == 0: + click.echo("Already authenticated with GitHub") + return + + # Proceed with authentication process = subprocess.Popen( ['gh', 'auth', 'login', '--hostname', 'github.com', @@ -158,4 +159,3 @@ def setup(username, password, azure_env_name, subscription, tenant): if __name__ == '__main__': setup() - From 459ea2261b8cdf7d41e7e0505876c8d9ab161a40 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:40:25 +1100 Subject: [PATCH 14/33] Skip Azure CLI authentication if we're already authenticated --- docs/workshop/lab_setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 3de15c1d..36dc0db2 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -61,6 +61,16 @@ def fork_repository(): @step("Azure CLI Authentication") def azure_login(*, username: str = None, password: str = None, tenant: str = None): + # Check if already logged in + result = subprocess.run(['az', 'account', 'show'], + capture_output=True, + text=True, + check=False) + if result.returncode == 0: + click.echo("Already authenticated with Azure CLI") + return + + # Proceed with login if not authenticated login_cmd = ['az', 'login'] if username and password: login_cmd.extend(['-u', username, '-p', password]) From d73bbd8f23df4004bc9baf6b6a00fd263241d59f Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:42:06 +1100 Subject: [PATCH 15/33] Force Azure CLI authentication if force is true --- docs/workshop/lab_setup.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 36dc0db2..bb49d1ac 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -60,17 +60,18 @@ def fork_repository(): subprocess.run(['gh', 'repo', 'fork', '--remote'], check=True) @step("Azure CLI Authentication") -def azure_login(*, username: str = None, password: str = None, tenant: str = None): - # Check if already logged in - result = subprocess.run(['az', 'account', 'show'], - capture_output=True, - text=True, - check=False) - if result.returncode == 0: - click.echo("Already authenticated with Azure CLI") - return - - # Proceed with login if not authenticated +def azure_login(*, username: str = None, password: str = None, tenant: str = None, force: bool = False): + # Only check authentication status if not forcing re-auth + if not force: + result = subprocess.run(['az', 'account', 'show'], + capture_output=True, + text=True, + check=False) + if result.returncode == 0: + click.echo("Already authenticated with Azure CLI") + return + + # Proceed with login if not authenticated or force=True login_cmd = ['az', 'login'] if username and password: login_cmd.extend(['-u', username, '-p', password]) From c58490a4a2ffe8721d63d573cdde8c01d24d71db Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:48:56 +1100 Subject: [PATCH 16/33] --force --- docs/workshop/lab_setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index bb49d1ac..90b66ee1 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -80,14 +80,12 @@ def azure_login(*, username: str = None, password: str = None, tenant: str = Non subprocess.run(login_cmd, check=True) @step("Azure Developer CLI Environment Setup") -def create_azd_environment(*, azure_env_name: str, subscription: str, tenant: str = None): +def create_azd_environment(*, azure_env_name: str, subscription: str): azd_cmd = [ 'azd', 'env', 'new', azure_env_name, '--location', 'canadaeast', '--subscription', subscription ] - if tenant: - azd_cmd.extend(['--tenant', tenant]) subprocess.run(azd_cmd, check=True) @step("Refresh AZD Environment") @@ -122,7 +120,8 @@ def run_postprovision(*, azure_env_name: str): @click.option('--azure-env-name', required=True, help='Name for the new Azure environment') @click.option('--subscription', required=True, help='Azure subscription ID to use') @click.option('--tenant', help='Optional Azure tenant ID for specific directory') -def setup(username, password, azure_env_name, subscription, tenant): +@click.option('--force', is_flag=True, help='Force re-authentication and re-provisioning') +def setup(username, password, azure_env_name, subscription, tenant, force): """ Automates Azure environment setup and configuration. @@ -141,7 +140,8 @@ def setup(username, password, azure_env_name, subscription, tenant): 'password': password, 'azure_env_name': azure_env_name, 'subscription': subscription, - 'tenant': tenant + 'tenant': tenant, + 'force': force } # Execute all registered steps From 7f7d105680cd761624fd3d916f39936308df8adc Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 14:52:49 +1100 Subject: [PATCH 17/33] Azd auth step --- docs/workshop/lab_setup.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 90b66ee1..d3822b57 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -79,6 +79,26 @@ def azure_login(*, username: str = None, password: str = None, tenant: str = Non login_cmd.extend(['--tenant', tenant]) subprocess.run(login_cmd, check=True) +@step("Azure Developer CLI Authentication") +def azd_login(*, force: bool = False): + """Authenticate with Azure Developer CLI using device code""" + # Only check authentication status if not forcing re-auth + if not force: + result = subprocess.run(['azd', 'auth', 'status'], + capture_output=True, + text=True, + check=False) + if result.returncode == 0: + click.echo("Already authenticated with Azure Developer CLI") + return + + # Proceed with authentication + subprocess.run([ + 'azd', 'auth', 'login', + '--use-device-code', + '--no-prompt' + ], check=True) + @step("Azure Developer CLI Environment Setup") def create_azd_environment(*, azure_env_name: str, subscription: str): azd_cmd = [ From 7dadc0d409dc94574ec7433c22463a68201a7b57 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 15:11:29 +1100 Subject: [PATCH 18/33] Insist not to use personal credentials for the azd auth step --- docs/workshop/lab_setup.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index d3822b57..c6bdab5a 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -5,15 +5,16 @@ import os from functools import wraps from typing import List, Callable +from click import style # Step registration steps: List[tuple[Callable, str]] = [] def blue(text: str): - return click.style(text, fg="blue") + return style(text, fg="blue") def bold(text: str): - return click.style(text, fg="bright_white", bold=True) + return style(text, fg="bright_white", bold=True) def step(label: str): @@ -80,7 +81,7 @@ def azure_login(*, username: str = None, password: str = None, tenant: str = Non subprocess.run(login_cmd, check=True) @step("Azure Developer CLI Authentication") -def azd_login(*, force: bool = False): +def azd_login(*, username: str = None, password: str = None, force: bool = False): """Authenticate with Azure Developer CLI using device code""" # Only check authentication status if not forcing re-auth if not force: @@ -92,6 +93,17 @@ def azd_login(*, force: bool = False): click.echo("Already authenticated with Azure Developer CLI") return + # Display credentials if provided + if username and password: + click.echo(f"Enter the following credentials to authenticate with Azure Developer CLI:") + click.echo(f"Username: {username}") + click.echo(f"Password: {password}") + click.echo() + click.echo(f"{style('IMPORTANT', fg='red', reverse=True)}: {style('Do not use your personal credentials for this step!', underline=True)}") + + # Wait for user to press Enter + input("\nPress Enter to start the azd login process...") + # Proceed with authentication subprocess.run([ 'azd', 'auth', 'login', From 62f8a22a7fe37e5c1e02cd9861a5a0bb923135eb Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 15:18:32 +1100 Subject: [PATCH 19/33] Only create the new azd env if it doesn't exist already --- docs/workshop/lab_setup.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index c6bdab5a..24e7bc8e 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -113,6 +113,19 @@ def azd_login(*, username: str = None, password: str = None, force: bool = False @step("Azure Developer CLI Environment Setup") def create_azd_environment(*, azure_env_name: str, subscription: str): + # Check if environment already exists + result = subprocess.run( + ['azd', 'env', 'list'], + capture_output=True, + text=True, + check=True + ) + + if azure_env_name in result.stdout: + click.echo(f"Environment '{azure_env_name}' already exists") + return + + # Create new environment if it doesn't exist azd_cmd = [ 'azd', 'env', 'new', azure_env_name, '--location', 'canadaeast', @@ -135,7 +148,7 @@ def export_variables(): @step("Run Roles Script") def run_roles(): - subprocess.run(['../../infra/hooks/roles.sh'], check=True) + subprocess.run(['bash', '../../infra/hooks/roles.sh'], check=True) @step("Execute Postprovision Hook") def run_postprovision(*, azure_env_name: str): From ef4a12b2da3e6e4e5bf92cd8c263724a2dabc3d0 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 15:23:54 +1100 Subject: [PATCH 20/33] resolve the .env file relatively to the script --- docs/workshop/lab_setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 24e7bc8e..6a9bd9f2 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -6,6 +6,7 @@ from functools import wraps from typing import List, Callable from click import style +from pathlib import Path # Step registration steps: List[tuple[Callable, str]] = [] @@ -143,7 +144,10 @@ def refresh_environment(*, azure_env_name: str): @step("Export Environment Variables") def export_variables(): - with open('../../.env', 'w') as env_file: + # Get the directory where the script is located and resolve .env path + env_path = Path(__file__).parent.parent.parent / '.env' + + with open(env_path, 'w') as env_file: subprocess.run(['azd', 'env', 'get-values'], stdout=env_file, check=True) @step("Run Roles Script") From a9c30f88718bcb301f2da5122c57910729a4b67e Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 15:42:10 +1100 Subject: [PATCH 21/33] resolve the roles.sh script relatively to the current script --- docs/workshop/lab_setup.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 6a9bd9f2..54b1010b 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -152,7 +152,10 @@ def export_variables(): @step("Run Roles Script") def run_roles(): - subprocess.run(['bash', '../../infra/hooks/roles.sh'], check=True) + # Get the directory where the script is located + script_dir = Path(__file__).parent + roles_script = script_dir.parent.parent / 'infra' / 'hooks' / 'roles.sh' + subprocess.run(['bash', str(roles_script)], check=True) @step("Execute Postprovision Hook") def run_postprovision(*, azure_env_name: str): @@ -168,8 +171,8 @@ def run_postprovision(*, azure_env_name: str): @click.option('--password', help='Azure password for authentication', hide_input=True) @click.option('--azure-env-name', required=True, help='Name for the new Azure environment') @click.option('--subscription', required=True, help='Azure subscription ID to use') -@click.option('--tenant', help='Optional Azure tenant ID for specific directory') -@click.option('--force', is_flag=True, help='Force re-authentication and re-provisioning') +@click.option('--tenant', help='Azure tenant ID') +@click.option('--force', is_flag=True, help='Force re-authentication') def setup(username, password, azure_env_name, subscription, tenant, force): """ Automates Azure environment setup and configuration. From f57719031fe41e7ee10825ef4841fb5639941cb2 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 15:46:37 +1100 Subject: [PATCH 22/33] Fix bang comment --- docs/workshop/lab_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 54b1010b..9e912e11 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -1,4 +1,4 @@ -#!/bin/env python +#!/usr/bin/env python import rich_click as click import subprocess From 84731d51049ec9b285e1f8025ffceabfc1dae2ed Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sat, 7 Dec 2024 05:58:07 +0000 Subject: [PATCH 23/33] Update lab manual --- docs/workshop/LAB-MANUAL.md | 39 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/docs/workshop/LAB-MANUAL.md b/docs/workshop/LAB-MANUAL.md index dccceb9d..750df23d 100644 --- a/docs/workshop/LAB-MANUAL.md +++ b/docs/workshop/LAB-MANUAL.md @@ -1,22 +1,4 @@ -## Azure Credentials: - -# CREDENTIALS - -++Username = "@lab.CloudPortalCredential(User1).Username" -Password = "@lab.CloudPortalCredential(User1).Password" -AzureEnvName = "AITOUR@lab.LabInstance.Id" -Subscription = "@lab.CloudSubscription.Id"++ - - -**If you are viewing this from the Skillable lab page** the above are your unique azure credentials. - -> **Note**: You will be asked to copy the above block in the lab later so keep this information readily available. - -**If you are viewing this from Github:** The above are not your credentials. They are placeholders. Your actual credentials can be seen on the Skillable lab page. - -*** - -### Welcome to this Microsoft workshop! +### Welcome to the AI Tour and workshop WRK551! In this session, you will learn how to build the app, **Contoso Creative Writer**. This app will assist the marketing team at Contoso Outdoors in creating trendy, well-researched articles to promote the company’s products. @@ -43,7 +25,24 @@ To participate in this workshop, you will need: 3. Click the green **<> Create codespace** button at the bottom of the page. * This will open a pre-built Codespace on main. -4. Once your Codespace is ready: + > **🚧 IMPORTANT**: Do not open the GitHub Codespace on a fork of the repository, this would prevent you from using the prebuilt Codespace container image. Don't worry, you'll have the possibility to fork the repository later. + +4. Once your Codespace is ready, **run the following command**: + +``` +./docs/workshop/lab_setup.py \ + --username "@lab.CloudPortalCredential(User1).Username" \ + --password "@lab.CloudPortalCredential(User1).Password" \ + --azure-env-name "AITOUR@lab.LabInstance.Id" \ + --subscription "@lab.CloudSubscription.Id" +``` + +> [!IMPORTANT] +> - **If you are viewing this from the Skillable lab page**: The above are your unique azure credentials. +> - **If you are viewing this from Github**: The above are not your credentials. They are placeholders. Your actual credentials can be seen on the Skillable lab page. + + +5. Once the previous script is complete: * In the file explorer look for the **docs** folder and in it open the **workshop** folder. * Open the **LAB-SETUP.ipynb** file. * Follow the instructions to get going! From e50c4f20401da63c4cf62081087c206aff998990 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Mon, 9 Dec 2024 09:11:48 +1100 Subject: [PATCH 24/33] Only fork repo if we don't already have a remote called upstream --- docs/workshop/lab_setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 9e912e11..18441bd3 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -59,6 +59,16 @@ def github_auth(*, force: bool = False): @step("Fork GitHub Repository") def fork_repository(): """Fork the current repository using the gh CLI tool""" + # Check if upstream remote already exists + result = subprocess.run(['git', 'remote', 'get-url', 'upstream'], + capture_output=True, + text=True, + check=False) + if result.returncode == 0: + click.echo("Repository already has an upstream remote") + return + + # Proceed with fork if no upstream remote exists subprocess.run(['gh', 'repo', 'fork', '--remote'], check=True) @step("Azure CLI Authentication") From 30659380d806cbe4782440d002d694a350581270 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Mon, 9 Dec 2024 09:18:16 +1100 Subject: [PATCH 25/33] Removed azd auth status check, not reliable --- docs/workshop/lab_setup.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 18441bd3..c9c38bc6 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -92,17 +92,8 @@ def azure_login(*, username: str = None, password: str = None, tenant: str = Non subprocess.run(login_cmd, check=True) @step("Azure Developer CLI Authentication") -def azd_login(*, username: str = None, password: str = None, force: bool = False): +def azd_login(*, username: str = None, password: str = None, tenant: str = None, force: bool = False): """Authenticate with Azure Developer CLI using device code""" - # Only check authentication status if not forcing re-auth - if not force: - result = subprocess.run(['azd', 'auth', 'status'], - capture_output=True, - text=True, - check=False) - if result.returncode == 0: - click.echo("Already authenticated with Azure Developer CLI") - return # Display credentials if provided if username and password: @@ -116,11 +107,10 @@ def azd_login(*, username: str = None, password: str = None, force: bool = False input("\nPress Enter to start the azd login process...") # Proceed with authentication - subprocess.run([ - 'azd', 'auth', 'login', - '--use-device-code', - '--no-prompt' - ], check=True) + login_cmd = ['azd', 'auth', 'login', '--use-device-code', '--no-prompt'] + if tenant: + login_cmd.extend(['--tenant-id', tenant]) + subprocess.run(login_cmd, check=True) @step("Azure Developer CLI Environment Setup") def create_azd_environment(*, azure_env_name: str, subscription: str): From b3e2c7131c06bb9b36b3a1a3789ac3e043764e92 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Mon, 9 Dec 2024 09:28:29 +1100 Subject: [PATCH 26/33] Save step progress --- docs/workshop/lab_setup.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index c9c38bc6..200e4001 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -8,6 +8,9 @@ from click import style from pathlib import Path +# Add these constants near the top +TEMP_FILE = Path.home() / '.lab_setup_progress' + # Step registration steps: List[tuple[Callable, str]] = [] @@ -172,7 +175,7 @@ def run_postprovision(*, azure_env_name: str): @click.option('--azure-env-name', required=True, help='Name for the new Azure environment') @click.option('--subscription', required=True, help='Azure subscription ID to use') @click.option('--tenant', help='Azure tenant ID') -@click.option('--force', is_flag=True, help='Force re-authentication') +@click.option('--force', is_flag=True, help='Force re-authentication and start from beginning') def setup(username, password, azure_env_name, subscription, tenant, force): """ Automates Azure environment setup and configuration. @@ -196,9 +199,19 @@ def setup(username, password, azure_env_name, subscription, tenant, force): 'force': force } + # Determine starting step + start_step = 0 + if not force and TEMP_FILE.exists(): + start_step = int(TEMP_FILE.read_text().strip()) + click.echo(f"\nResuming from step {start_step + 1}") + # Execute all registered steps for index, entry in enumerate(steps): from inspect import signature + # Skip steps that were already completed + if index < start_step: + continue + step_func, _ = entry # Get the parameter names for this function @@ -214,6 +227,13 @@ def setup(username, password, azure_env_name, subscription, tenant, force): if isinstance(result, dict): params.update(result) + # Save progress after each successful step + TEMP_FILE.write_text(str(index + 1)) + + # Clean up temp file on successful completion + if TEMP_FILE.exists(): + TEMP_FILE.unlink() + click.echo("\nSetup completed successfully!") except subprocess.CalledProcessError as e: From d984c81c6409871669ff8c999516b728e821e021 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Mon, 9 Dec 2024 09:34:46 +1100 Subject: [PATCH 27/33] Display a message when all steps have already been executed --- docs/workshop/lab_setup.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 200e4001..9d0da4c0 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -7,6 +7,7 @@ from typing import List, Callable from click import style from pathlib import Path +from inspect import signature # Add these constants near the top TEMP_FILE = Path.home() / '.lab_setup_progress' @@ -203,6 +204,10 @@ def setup(username, password, azure_env_name, subscription, tenant, force): start_step = 0 if not force and TEMP_FILE.exists(): start_step = int(TEMP_FILE.read_text().strip()) + if start_step >= len(steps): + click.echo("\nAll steps were already successfully executed!") + click.echo("Use --force to execute all steps from the beginning if needed.") + return click.echo(f"\nResuming from step {start_step + 1}") # Execute all registered steps From ac86696fb93d53ad0625f8fd025e483552f644bc Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Mon, 9 Dec 2024 09:35:42 +1100 Subject: [PATCH 28/33] Add a --step param that allows to set which step to resume from --- docs/workshop/lab_setup.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 9d0da4c0..6adf5b27 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -177,7 +177,8 @@ def run_postprovision(*, azure_env_name: str): @click.option('--subscription', required=True, help='Azure subscription ID to use') @click.option('--tenant', help='Azure tenant ID') @click.option('--force', is_flag=True, help='Force re-authentication and start from beginning') -def setup(username, password, azure_env_name, subscription, tenant, force): +@click.option('--step', type=int, help='Resume from a specific step number (1-based)') +def setup(username, password, azure_env_name, subscription, tenant, force, step): """ Automates Azure environment setup and configuration. @@ -202,7 +203,11 @@ def setup(username, password, azure_env_name, subscription, tenant, force): # Determine starting step start_step = 0 - if not force and TEMP_FILE.exists(): + if step is not None: + if not 1 <= step <= len(steps): + raise click.BadParameter(f"Step must be between 1 and {len(steps)}") + start_step = step - 1 + elif not force and TEMP_FILE.exists(): start_step = int(TEMP_FILE.read_text().strip()) if start_step >= len(steps): click.echo("\nAll steps were already successfully executed!") From f0f9acf3564f9fb6de347d0a3a102cb51bc2eb65 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Mon, 9 Dec 2024 09:40:22 +1100 Subject: [PATCH 29/33] Hightlight step number --- docs/workshop/lab_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 6adf5b27..c45c4033 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -213,7 +213,7 @@ def setup(username, password, azure_env_name, subscription, tenant, force, step) click.echo("\nAll steps were already successfully executed!") click.echo("Use --force to execute all steps from the beginning if needed.") return - click.echo(f"\nResuming from step {start_step + 1}") + click.echo(f"\nResuming from step {blue(start_step + 1)}") # Execute all registered steps for index, entry in enumerate(steps): From 6ab18095349712ea1afe1567814237fd6f18bd22 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Sun, 8 Dec 2024 22:54:44 +0000 Subject: [PATCH 30/33] Updated docstring --- docs/workshop/lab_setup.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index c45c4033..a876c916 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -183,12 +183,15 @@ def setup(username, password, azure_env_name, subscription, tenant, force, step) Automates Azure environment setup and configuration. This command will: - * Log into Azure CLI (interactive if no credentials provided) - * Create a new AZD environment - * Refresh the environment - * Export environment variables - * Run roles script - * Execute postprovision hook + 1. GitHub Authentication + 2. Fork GitHub Repository + 3. Azure CLI Authentication + 4. Azure Developer CLI Authentication + 5. Azure Developer CLI Environment Setup + 6. Refresh AZD Environment + 7. Export Environment Variables + 8. Run Roles Script + 9. Execute Postprovision Hook """ try: # Create parameters dictionary From 1cd26f67a25b71d7a3d34a4c5f673d311bf19223 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Mon, 9 Dec 2024 10:00:17 +1100 Subject: [PATCH 31/33] Better azd login instructions --- docs/workshop/lab_setup.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index a876c916..7dcf0985 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -101,14 +101,12 @@ def azd_login(*, username: str = None, password: str = None, tenant: str = None, # Display credentials if provided if username and password: - click.echo(f"Enter the following credentials to authenticate with Azure Developer CLI:") - click.echo(f"Username: {username}") - click.echo(f"Password: {password}") + click.echo(f"{style('When asked for Azure credentials, enter the following:', underline=True)}") + click.echo(f"Username: {style(username, fg='blue', bold=True)}") + click.echo(f"Password: {style(password, fg='blue', bold=True)}") + click.echo() + click.echo(f"{style('IMPORTANT', fg='red', reverse=True)}: {style('DO NOT use your personal credentials for this step!', fg='red', underline=True)}") click.echo() - click.echo(f"{style('IMPORTANT', fg='red', reverse=True)}: {style('Do not use your personal credentials for this step!', underline=True)}") - - # Wait for user to press Enter - input("\nPress Enter to start the azd login process...") # Proceed with authentication login_cmd = ['azd', 'auth', 'login', '--use-device-code', '--no-prompt'] From 4d36b580ab24ea9ec45fb0916c1b6d12fde94219 Mon Sep 17 00:00:00 2001 From: Cedric Vidal Date: Mon, 9 Dec 2024 10:06:22 +1100 Subject: [PATCH 32/33] More styling --- docs/workshop/lab_setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/workshop/lab_setup.py b/docs/workshop/lab_setup.py index 7dcf0985..7c83ab3e 100755 --- a/docs/workshop/lab_setup.py +++ b/docs/workshop/lab_setup.py @@ -101,7 +101,8 @@ def azd_login(*, username: str = None, password: str = None, tenant: str = None, # Display credentials if provided if username and password: - click.echo(f"{style('When asked for Azure credentials, enter the following:', underline=True)}") + opts = {'underline': True} + click.echo(f"{style('When asked to ', **opts)}{style('Pick an account', **opts, bold=True)}{style(', hit the ', **opts)}{style('Use another account', **opts, bold=True)}{style(' button and enter the following:', **opts)}") click.echo(f"Username: {style(username, fg='blue', bold=True)}") click.echo(f"Password: {style(password, fg='blue', bold=True)}") click.echo() From b32bf78d19ebae5060cd8b8f9a916b40eb6d11ed Mon Sep 17 00:00:00 2001 From: Marlene <57748216+marlenezw@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:19:04 -0500 Subject: [PATCH 33/33] Update file reference in LAB-MANUAL.md --- docs/workshop/LAB-MANUAL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/workshop/LAB-MANUAL.md b/docs/workshop/LAB-MANUAL.md index 750df23d..d710f3c9 100644 --- a/docs/workshop/LAB-MANUAL.md +++ b/docs/workshop/LAB-MANUAL.md @@ -44,7 +44,7 @@ To participate in this workshop, you will need: 5. Once the previous script is complete: * In the file explorer look for the **docs** folder and in it open the **workshop** folder. - * Open the **LAB-SETUP.ipynb** file. + * Open the **workshop-1-intro.ipynb** file. * Follow the instructions to get going! Have fun building!🎉