investing-algorithm-framework 7.19.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +197 -0
- investing_algorithm_framework/app/__init__.py +47 -0
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +2204 -0
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1667 -0
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/__init__.py +35 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
- investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
- investing_algorithm_framework/app/strategy.py +675 -0
- investing_algorithm_framework/app/task.py +41 -0
- investing_algorithm_framework/app/web/__init__.py +5 -0
- investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
- investing_algorithm_framework/app/web/controllers/orders.py +20 -0
- investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
- investing_algorithm_framework/app/web/controllers/positions.py +18 -0
- investing_algorithm_framework/app/web/create_app.py +20 -0
- investing_algorithm_framework/app/web/error_handler.py +59 -0
- investing_algorithm_framework/app/web/responses.py +20 -0
- investing_algorithm_framework/app/web/run_strategies.py +4 -0
- investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
- investing_algorithm_framework/app/web/schemas/order.py +12 -0
- investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
- investing_algorithm_framework/app/web/schemas/position.py +15 -0
- investing_algorithm_framework/app/web/setup_cors.py +6 -0
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +207 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/create_app.py +54 -0
- investing_algorithm_framework/dependency_container.py +155 -0
- investing_algorithm_framework/domain/__init__.py +148 -0
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +435 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +111 -0
- investing_algorithm_framework/domain/constants.py +83 -0
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +42 -0
- investing_algorithm_framework/domain/decimal_parsing.py +40 -0
- investing_algorithm_framework/domain/exceptions.py +112 -0
- investing_algorithm_framework/domain/models/__init__.py +43 -0
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/base_model.py +25 -0
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/__init__.py +5 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
- investing_algorithm_framework/domain/models/order/__init__.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +384 -0
- investing_algorithm_framework/domain/models/order/order_side.py +36 -0
- investing_algorithm_framework/domain/models/order/order_status.py +37 -0
- investing_algorithm_framework/domain/models/order/order_type.py +30 -0
- investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
- investing_algorithm_framework/domain/models/position/__init__.py +4 -0
- investing_algorithm_framework/domain/models/position/position.py +68 -0
- investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
- investing_algorithm_framework/domain/models/time_frame.py +153 -0
- investing_algorithm_framework/domain/models/time_interval.py +124 -0
- investing_algorithm_framework/domain/models/time_unit.py +149 -0
- investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
- investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
- investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
- investing_algorithm_framework/domain/models/trade/trade.py +388 -0
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
- investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/positions/__init__.py +4 -0
- investing_algorithm_framework/domain/positions/position_size.py +41 -0
- investing_algorithm_framework/domain/services/__init__.py +11 -0
- investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
- investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
- investing_algorithm_framework/domain/services/rounding_service.py +27 -0
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/stateless_actions.py +7 -0
- investing_algorithm_framework/domain/strategy.py +44 -0
- investing_algorithm_framework/domain/utils/__init__.py +27 -0
- investing_algorithm_framework/domain/utils/csv.py +104 -0
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +41 -0
- investing_algorithm_framework/domain/utils/signatures.py +17 -0
- investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
- investing_algorithm_framework/domain/utils/synchronized.py +12 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +50 -0
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +10 -0
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
- investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
- investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
- investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
- investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
- investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
- investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +40 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
- investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/services/__init__.py +132 -0
- investing_algorithm_framework/services/backtesting/__init__.py +5 -0
- investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
- investing_algorithm_framework/services/configuration_service.py +96 -0
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/services/market_credential_service.py +40 -0
- investing_algorithm_framework/services/metrics/__init__.py +114 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +181 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
- investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +9 -0
- investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +826 -0
- investing_algorithm_framework/services/portfolios/__init__.py +16 -0
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
- investing_algorithm_framework/services/repository_service.py +40 -0
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
- investing_algorithm_framework/services/trade_service/__init__.py +3 -0
- investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
- investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
- investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
- investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
- investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
- investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import re
|
|
4
|
+
import random
|
|
5
|
+
import string
|
|
6
|
+
import asyncio
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from azure.identity import DefaultAzureCredential
|
|
10
|
+
from azure.mgmt.resource import ResourceManagementClient
|
|
11
|
+
from azure.mgmt.storage import StorageManagementClient
|
|
12
|
+
from azure.mgmt.web import WebSiteManagementClient
|
|
13
|
+
|
|
14
|
+
STORAGE_ACCOUNT_NAME_PREFIX = "iafstorageaccount"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def generate_unique_resource_name(base_name):
|
|
18
|
+
"""
|
|
19
|
+
Function to generate a unique resource name by appending a random suffix.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
base_name (str): The base name for the resource.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
str: The unique resource name.
|
|
26
|
+
"""
|
|
27
|
+
unique_suffix = ''.join(
|
|
28
|
+
random.choices(string.ascii_lowercase + string.digits, k=6)
|
|
29
|
+
)
|
|
30
|
+
return f"{base_name}{unique_suffix}".lower()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def ensure_azure_functools():
|
|
34
|
+
"""
|
|
35
|
+
Function to ensure that the Azure Functions Core Tools are installed.
|
|
36
|
+
If not, it will prompt the user to install it.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
result = subprocess.run(
|
|
41
|
+
["func", "--version"],
|
|
42
|
+
stdout=subprocess.PIPE,
|
|
43
|
+
stderr=subprocess.PIPE,
|
|
44
|
+
text=True,
|
|
45
|
+
)
|
|
46
|
+
if result.returncode != 0:
|
|
47
|
+
raise FileNotFoundError("Azure Functions Core Tools not found.")
|
|
48
|
+
except FileNotFoundError:
|
|
49
|
+
print("Azure Functions Core Tools not found. Please install it.")
|
|
50
|
+
print("You can install it using the following command:")
|
|
51
|
+
print("npm install -g azure-functions-core-tools@4 --unsafe-perm true")
|
|
52
|
+
exit(1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def read_env_file_and_set_function_env_variables(
|
|
56
|
+
function_app_name,
|
|
57
|
+
storage_connection_string,
|
|
58
|
+
storage_container_name,
|
|
59
|
+
resource_group_name
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
Function to read the .env file in the working directory
|
|
63
|
+
and set the environment variables for the Function App.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
None
|
|
67
|
+
"""
|
|
68
|
+
env_file_path = os.path.join(os.getcwd(), ".env")
|
|
69
|
+
entries = {}
|
|
70
|
+
if os.path.exists(env_file_path):
|
|
71
|
+
with open(env_file_path, "r") as file:
|
|
72
|
+
for line in file:
|
|
73
|
+
if "=" in line:
|
|
74
|
+
key, value = line.strip().split("=", 1)
|
|
75
|
+
entries[key] = value
|
|
76
|
+
|
|
77
|
+
# Convert dictionary to CLI format
|
|
78
|
+
settings = [f"{key}={value}" for key, value in entries.items()]
|
|
79
|
+
|
|
80
|
+
# Construct the command
|
|
81
|
+
command = [
|
|
82
|
+
"az", "functionapp", "config", "appsettings", "set",
|
|
83
|
+
"--name", function_app_name,
|
|
84
|
+
"--resource-group", resource_group_name,
|
|
85
|
+
"--settings"
|
|
86
|
+
] + settings # Append all settings
|
|
87
|
+
|
|
88
|
+
# Run the Azure CLI command asynchronously
|
|
89
|
+
process = await asyncio.create_subprocess_exec(
|
|
90
|
+
*command,
|
|
91
|
+
stdout=asyncio.subprocess.PIPE,
|
|
92
|
+
stderr=asyncio.subprocess.PIPE
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
stdout, stderr = await process.communicate()
|
|
96
|
+
|
|
97
|
+
if process.returncode == 0:
|
|
98
|
+
print(
|
|
99
|
+
"Environment variables successfully set for the function app."
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
print(
|
|
103
|
+
"Error setting environment variables: " +
|
|
104
|
+
f"{stderr.decode().strip()}"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
else:
|
|
108
|
+
print(f".env file not found at {env_file_path}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
async def publish_function_app(
|
|
112
|
+
function_app_name,
|
|
113
|
+
storage_connection_string,
|
|
114
|
+
storage_container_name,
|
|
115
|
+
resource_group_name
|
|
116
|
+
):
|
|
117
|
+
"""
|
|
118
|
+
Function to publish the Function App using Azure Functions Core Tools.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
function_app_name (str): Name of the Function App to publish.
|
|
122
|
+
storage_connection_string (str): Azure Storage Connection String.
|
|
123
|
+
storage_container_name (str): Azure Storage Container Name.
|
|
124
|
+
resource_group_name (str): Resource Group Name.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
None
|
|
128
|
+
"""
|
|
129
|
+
print(f"Publishing Function App {function_app_name}")
|
|
130
|
+
|
|
131
|
+
# Wait for 60 seconds to ensure the Function App is ready
|
|
132
|
+
time.sleep(60)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
# Step 1: Publish the Azure Function App
|
|
136
|
+
process = await asyncio.create_subprocess_exec(
|
|
137
|
+
"func", "azure", "functionapp", "publish", function_app_name
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Wait for the subprocess to finish
|
|
141
|
+
_, stderr = await process.communicate()
|
|
142
|
+
|
|
143
|
+
# Check the return code
|
|
144
|
+
if process.returncode != 0:
|
|
145
|
+
|
|
146
|
+
if stderr is not None:
|
|
147
|
+
raise Exception(
|
|
148
|
+
f"Error publishing Function App: {stderr.decode().strip()}"
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
raise Exception("Error publishing Function App")
|
|
152
|
+
|
|
153
|
+
print(f"Function App {function_app_name} published successfully.")
|
|
154
|
+
|
|
155
|
+
# Step 2: Add app settings
|
|
156
|
+
add_settings_process = await asyncio.create_subprocess_exec(
|
|
157
|
+
"az", "functionapp", "config", "appsettings", "set",
|
|
158
|
+
"--name", function_app_name,
|
|
159
|
+
"--settings",
|
|
160
|
+
f"AZURE_STORAGE_CONNECTION_STRING={storage_connection_string}",
|
|
161
|
+
f"AZURE_STORAGE_CONTAINER_NAME={storage_container_name}",
|
|
162
|
+
"--resource-group", resource_group_name
|
|
163
|
+
)
|
|
164
|
+
_, stderr1 = await add_settings_process.communicate()
|
|
165
|
+
|
|
166
|
+
if add_settings_process.returncode != 0:
|
|
167
|
+
|
|
168
|
+
if stderr1 is not None:
|
|
169
|
+
raise Exception(
|
|
170
|
+
f"Error adding App settings: {stderr1.decode().strip()}"
|
|
171
|
+
)
|
|
172
|
+
else:
|
|
173
|
+
raise Exception("Error adding App settings")
|
|
174
|
+
|
|
175
|
+
print(
|
|
176
|
+
"Added app settings to the Function App successfully"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Step 3: Update the cors settings
|
|
180
|
+
cors_process = await asyncio.create_subprocess_exec(
|
|
181
|
+
"az", "functionapp", "cors", "add",
|
|
182
|
+
"--name", function_app_name,
|
|
183
|
+
"--allowed-origins", "*",
|
|
184
|
+
"--resource-group", resource_group_name
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
_, stderr1 = await add_settings_process.communicate()
|
|
188
|
+
|
|
189
|
+
if cors_process.returncode != 0:
|
|
190
|
+
|
|
191
|
+
if stderr1 is not None:
|
|
192
|
+
raise Exception(
|
|
193
|
+
f"Error adding cors settings: {stderr1.decode().strip()}"
|
|
194
|
+
)
|
|
195
|
+
else:
|
|
196
|
+
raise Exception("Error adding cors settings")
|
|
197
|
+
|
|
198
|
+
print("All app settings have been added successfully.")
|
|
199
|
+
print("Function App creation completed successfully.")
|
|
200
|
+
except Exception as e:
|
|
201
|
+
print(f"Error publishing Function App: {e}")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
async def create_function_app(
|
|
205
|
+
resource_group_name,
|
|
206
|
+
deployment_name,
|
|
207
|
+
storage_account_name,
|
|
208
|
+
region
|
|
209
|
+
):
|
|
210
|
+
"""
|
|
211
|
+
Creates an Azure Function App in a Consumption Plan and deploys
|
|
212
|
+
a Python Function.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
resource_group_name (str): Resource group name.
|
|
216
|
+
deployment_name (str): Name of the Function App to create.
|
|
217
|
+
storage_account_name (str): Name of the associated Storage Account.
|
|
218
|
+
region (str): Azure region (e.g., "eastus").
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
dict: Details of the created or existing Function App.
|
|
222
|
+
"""
|
|
223
|
+
# Check if the Function App already exists
|
|
224
|
+
print(f"Checking if Function App '{deployment_name}' exists...")
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
# Check for the Function App
|
|
228
|
+
check_process = await asyncio.create_subprocess_exec(
|
|
229
|
+
"az",
|
|
230
|
+
"functionapp",
|
|
231
|
+
"show",
|
|
232
|
+
"--name",
|
|
233
|
+
deployment_name,
|
|
234
|
+
"--resource-group",
|
|
235
|
+
resource_group_name,
|
|
236
|
+
stdout=asyncio.subprocess.PIPE,
|
|
237
|
+
stderr=asyncio.subprocess.PIPE
|
|
238
|
+
)
|
|
239
|
+
stdout, stderr = await check_process.communicate()
|
|
240
|
+
|
|
241
|
+
if check_process.returncode == 0:
|
|
242
|
+
# The Function App exists, return details
|
|
243
|
+
print(f"Function App '{deployment_name}' already exists.")
|
|
244
|
+
return stdout.decode()
|
|
245
|
+
|
|
246
|
+
# If the return code is non-zero, and the error indicates
|
|
247
|
+
# the Function App doesn't exist, proceed to create it
|
|
248
|
+
if "ResourceNotFound" in stderr.decode():
|
|
249
|
+
print(
|
|
250
|
+
f"Function App '{deployment_name}' does not exist." +
|
|
251
|
+
" Proceeding to create it..."
|
|
252
|
+
)
|
|
253
|
+
else:
|
|
254
|
+
# If the error is something else, raise it
|
|
255
|
+
print(f"Error checking for Function App: {stderr.decode()}")
|
|
256
|
+
raise Exception(stderr.decode())
|
|
257
|
+
|
|
258
|
+
# Create the Function App
|
|
259
|
+
print(f"Creating Function App '{deployment_name}'...")
|
|
260
|
+
create_process = await asyncio.create_subprocess_exec(
|
|
261
|
+
"az",
|
|
262
|
+
"functionapp",
|
|
263
|
+
"create",
|
|
264
|
+
"--resource-group",
|
|
265
|
+
resource_group_name,
|
|
266
|
+
"--consumption-plan-location",
|
|
267
|
+
region,
|
|
268
|
+
"--runtime",
|
|
269
|
+
"python",
|
|
270
|
+
"--runtime-version",
|
|
271
|
+
"3.10",
|
|
272
|
+
"--functions-version",
|
|
273
|
+
"4",
|
|
274
|
+
"--name",
|
|
275
|
+
deployment_name,
|
|
276
|
+
"--os-type",
|
|
277
|
+
"linux",
|
|
278
|
+
"--storage-account",
|
|
279
|
+
storage_account_name
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Wait for the subprocess to finish
|
|
283
|
+
_, create_stderr = await create_process.communicate()
|
|
284
|
+
|
|
285
|
+
# Check the return code for the create command
|
|
286
|
+
if create_process.returncode != 0:
|
|
287
|
+
print(
|
|
288
|
+
"Error creating Function App: " +
|
|
289
|
+
f"{create_stderr.decode().strip()}"
|
|
290
|
+
)
|
|
291
|
+
raise Exception(
|
|
292
|
+
"Error creating Function App: " +
|
|
293
|
+
f"{create_stderr.decode().strip()}"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
print(f"Function App '{deployment_name}' created successfully.")
|
|
297
|
+
return {"status": "created"}
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
print(f"Error creating Function App: {e}")
|
|
301
|
+
raise e
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def create_file_from_template(template_path, output_path):
|
|
305
|
+
"""
|
|
306
|
+
Creates a new file by replacing placeholders in a template file.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
template_path (str): The path to the template file.
|
|
310
|
+
output_path (str): The path to the output file.
|
|
311
|
+
replacements (dict): A dictionary of placeholder
|
|
312
|
+
keys and their replacements.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
None
|
|
316
|
+
"""
|
|
317
|
+
with open(template_path, "r") as file:
|
|
318
|
+
template = file.read()
|
|
319
|
+
|
|
320
|
+
with open(output_path, "w") as file:
|
|
321
|
+
file.write(template)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def ensure_consumption_plan(
|
|
325
|
+
resource_group_name,
|
|
326
|
+
plan_name,
|
|
327
|
+
region,
|
|
328
|
+
subscription_id,
|
|
329
|
+
credential
|
|
330
|
+
):
|
|
331
|
+
"""
|
|
332
|
+
Ensures that an App Service Plan with the Consumption Plan exists.
|
|
333
|
+
If not, creates it.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
resource_group_name (str): The name of the resource group.
|
|
337
|
+
plan_name (str): The name of the App Service Plan.
|
|
338
|
+
region (str): The Azure region for the resources.
|
|
339
|
+
subscription_id (str): The Azure subscription ID.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
object: The App Service Plan object.
|
|
343
|
+
"""
|
|
344
|
+
web_client = WebSiteManagementClient(credential, subscription_id)
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
print(
|
|
348
|
+
f"Checking if App Service Plan '{plan_name}' exists" +
|
|
349
|
+
f" in resource group '{resource_group_name}'..."
|
|
350
|
+
)
|
|
351
|
+
plan = web_client.app_service_plans.get(resource_group_name, plan_name)
|
|
352
|
+
print(f"App Service Plan '{plan_name}' already exists.")
|
|
353
|
+
except Exception: # Plan does not exist
|
|
354
|
+
print(
|
|
355
|
+
f"App Service Plan '{plan_name}' not found. " +
|
|
356
|
+
"Creating it as a Consumption Plan..."
|
|
357
|
+
)
|
|
358
|
+
plan = web_client.app_service_plans.begin_create_or_update(
|
|
359
|
+
resource_group_name,
|
|
360
|
+
plan_name,
|
|
361
|
+
{
|
|
362
|
+
"location": region,
|
|
363
|
+
"sku": {"name": "Y1", "tier": "Dynamic"},
|
|
364
|
+
"kind": "functionapp", # Mark this as for Function Apps
|
|
365
|
+
"properties": {}
|
|
366
|
+
},
|
|
367
|
+
).result()
|
|
368
|
+
print(f"App Service Plan '{plan_name}' created successfully.")
|
|
369
|
+
return plan
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def ensure_storage_account(
|
|
373
|
+
storage_account_name,
|
|
374
|
+
resource_group_name,
|
|
375
|
+
region,
|
|
376
|
+
subscription_id,
|
|
377
|
+
credential,
|
|
378
|
+
):
|
|
379
|
+
"""
|
|
380
|
+
Checks if a storage account exists. If it doesn't, creates it.
|
|
381
|
+
|
|
382
|
+
If no storage account name is provided, a unique name will
|
|
383
|
+
be generated. However, before we create a new
|
|
384
|
+
storage account, we check if there a storage account exists
|
|
385
|
+
with the prefix 'iafstorageaccount'. If it exists, we use
|
|
386
|
+
that storage account.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
storage_account_name (str): The name of the storage account.
|
|
390
|
+
resource_group_name (str): The name of the resource group.
|
|
391
|
+
region (str): The Azure region for the resources.
|
|
392
|
+
subscription_id (str): The Azure subscription ID.
|
|
393
|
+
credential: Azure credentials object.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
StorageAccount: The created storage account object.
|
|
397
|
+
"""
|
|
398
|
+
# Create Storage Management Client
|
|
399
|
+
storage_client = StorageManagementClient(credential, subscription_id)
|
|
400
|
+
|
|
401
|
+
# Check if the storage account exists
|
|
402
|
+
try:
|
|
403
|
+
|
|
404
|
+
# Check if provided storage account name has prefix 'iafstorageaccount'
|
|
405
|
+
if storage_account_name.startswith(STORAGE_ACCOUNT_NAME_PREFIX):
|
|
406
|
+
# List all storage accounts in the resource group
|
|
407
|
+
storage_accounts = storage_client\
|
|
408
|
+
.storage_accounts.list_by_resource_group(
|
|
409
|
+
resource_group_name
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
for account in storage_accounts:
|
|
413
|
+
if account.name.startswith(STORAGE_ACCOUNT_NAME_PREFIX):
|
|
414
|
+
storage_account_name = account.name
|
|
415
|
+
break
|
|
416
|
+
|
|
417
|
+
storage_client.storage_accounts.get_properties(
|
|
418
|
+
resource_group_name,
|
|
419
|
+
storage_account_name,
|
|
420
|
+
)
|
|
421
|
+
print(f"Storage account '{storage_account_name}' already exists.")
|
|
422
|
+
account_key = storage_client.storage_accounts.list_keys(
|
|
423
|
+
resource_group_name,
|
|
424
|
+
storage_account_name,
|
|
425
|
+
).keys[1].value
|
|
426
|
+
connection_string = "DefaultEndpointsProtocol=https;" + \
|
|
427
|
+
f"AccountName={storage_account_name};" + \
|
|
428
|
+
f"AccountKey={account_key};EndpointSuffix=core.windows.net"
|
|
429
|
+
return connection_string, storage_account_name
|
|
430
|
+
except Exception: # If the storage account does not exist
|
|
431
|
+
print("Creating storage account ...")
|
|
432
|
+
|
|
433
|
+
# Create storage account
|
|
434
|
+
storage_async_operation = storage_client.storage_accounts.begin_create(
|
|
435
|
+
resource_group_name,
|
|
436
|
+
storage_account_name,
|
|
437
|
+
{
|
|
438
|
+
"location": region,
|
|
439
|
+
"sku": {"name": "Standard_LRS"},
|
|
440
|
+
"kind": "StorageV2",
|
|
441
|
+
},
|
|
442
|
+
)
|
|
443
|
+
storage_async_operation.result()
|
|
444
|
+
|
|
445
|
+
if storage_async_operation.status() == "Succeeded":
|
|
446
|
+
print(
|
|
447
|
+
f"Storage account '{storage_account_name}'" +
|
|
448
|
+
"created successfully."
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
account_key = storage_client.storage_accounts\
|
|
452
|
+
.list_keys(
|
|
453
|
+
resource_group_name,
|
|
454
|
+
storage_account_name,
|
|
455
|
+
).keys[1].value
|
|
456
|
+
connection_string = f"DefaultEndpointsProtocol=https;"\
|
|
457
|
+
f"AccountName={storage_account_name};" + \
|
|
458
|
+
f"AccountKey={account_key};" + \
|
|
459
|
+
"EndpointSuffix=core.windows.net"
|
|
460
|
+
return connection_string, storage_account_name
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def ensure_az_login(skip_check=False):
|
|
464
|
+
"""
|
|
465
|
+
Ensures the user is logged into Azure using `az login`.
|
|
466
|
+
If not logged in, it will prompt the user to log in.
|
|
467
|
+
|
|
468
|
+
Raises:
|
|
469
|
+
Exception: An error occurred during the login process.
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
if skip_check:
|
|
473
|
+
return
|
|
474
|
+
|
|
475
|
+
result = subprocess.run(["az", "login"], check=True)
|
|
476
|
+
|
|
477
|
+
if result.returncode != 0:
|
|
478
|
+
raise Exception("An error occurred during 'az login'.")
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def get_default_subscription_id():
|
|
482
|
+
"""
|
|
483
|
+
Fetches the default subscription ID using Azure CLI.
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
str: The default subscription ID.
|
|
487
|
+
"""
|
|
488
|
+
print("Fetching default subscription ID...")
|
|
489
|
+
|
|
490
|
+
# Check if an default subscription ID is set in the environment
|
|
491
|
+
if "AZURE_SUBSCRIPTION_ID" in os.environ:
|
|
492
|
+
return os.environ["AZURE_SUBSCRIPTION_ID"]
|
|
493
|
+
|
|
494
|
+
try:
|
|
495
|
+
print(
|
|
496
|
+
"If you want to use a different subscription, please provide the"
|
|
497
|
+
" subscription ID with the '--subscription_id' option or"
|
|
498
|
+
" by setting the 'AZURE_SUBSCRIPTION_ID' environment variable."
|
|
499
|
+
)
|
|
500
|
+
result = subprocess.run(
|
|
501
|
+
["az", "account", "show", "--query", "id", "-o", "tsv"],
|
|
502
|
+
stdout=subprocess.PIPE,
|
|
503
|
+
stderr=subprocess.PIPE,
|
|
504
|
+
text=True,
|
|
505
|
+
check=True,
|
|
506
|
+
)
|
|
507
|
+
subscription_id = result.stdout.strip()
|
|
508
|
+
print(f"Default subscription ID: {subscription_id}")
|
|
509
|
+
return subscription_id
|
|
510
|
+
except subprocess.CalledProcessError:
|
|
511
|
+
print(
|
|
512
|
+
"Error fetching default subscription ID." +
|
|
513
|
+
" Please log in with 'az login'."
|
|
514
|
+
)
|
|
515
|
+
raise
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def ensure_resource_group(
|
|
519
|
+
resource_group_name,
|
|
520
|
+
region,
|
|
521
|
+
subscription_id,
|
|
522
|
+
create_if_not_exists
|
|
523
|
+
):
|
|
524
|
+
"""
|
|
525
|
+
Checks if a resource group exists. If it doesn't,
|
|
526
|
+
creates it if `create_if_not_exists` is True.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
resource_group_name (str): The name of the resource group.
|
|
530
|
+
region (str): The Azure region for the resources.
|
|
531
|
+
subscription_id (str): The Azure subscription ID.
|
|
532
|
+
create_if_not_exists (bool): Flag to create the
|
|
533
|
+
resource group if it does not exist.
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
None
|
|
537
|
+
"""
|
|
538
|
+
credential = DefaultAzureCredential()
|
|
539
|
+
resource_client = ResourceManagementClient(credential, subscription_id)
|
|
540
|
+
|
|
541
|
+
print(f"Checking if resource group '{resource_group_name}' exists...")
|
|
542
|
+
try:
|
|
543
|
+
resource_client.resource_groups.get(resource_group_name)
|
|
544
|
+
print(f"Resource group '{resource_group_name}' already exists.")
|
|
545
|
+
except Exception: # If the resource group does not exist
|
|
546
|
+
|
|
547
|
+
try:
|
|
548
|
+
if create_if_not_exists:
|
|
549
|
+
print(
|
|
550
|
+
f"Resource group '{resource_group_name}' not" +
|
|
551
|
+
" found. Creating it..."
|
|
552
|
+
)
|
|
553
|
+
resource_client.resource_groups.create_or_update(
|
|
554
|
+
resource_group_name,
|
|
555
|
+
{"location": region},
|
|
556
|
+
)
|
|
557
|
+
print(
|
|
558
|
+
f"Resource group '{resource_group_name}'" +
|
|
559
|
+
" created successfully."
|
|
560
|
+
)
|
|
561
|
+
else:
|
|
562
|
+
print(
|
|
563
|
+
f"Resource group '{resource_group_name}' does" +
|
|
564
|
+
" not exist, and 'create_if_not_exists' is False."
|
|
565
|
+
)
|
|
566
|
+
raise ValueError(
|
|
567
|
+
f"Resource group '{resource_group_name}' does not exist."
|
|
568
|
+
)
|
|
569
|
+
except Exception as e:
|
|
570
|
+
raise Exception(f"Error creating resource group: {e}")
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def create_storage_and_function(
|
|
574
|
+
resource_group_name,
|
|
575
|
+
storage_account_name,
|
|
576
|
+
container_name,
|
|
577
|
+
deployment_name,
|
|
578
|
+
region,
|
|
579
|
+
subscription_id=None,
|
|
580
|
+
create_resource_group_if_not_exists=False,
|
|
581
|
+
skip_login=False
|
|
582
|
+
):
|
|
583
|
+
|
|
584
|
+
# Make sure that the deployment name only contains lowercase letters, and
|
|
585
|
+
# uppercase letters
|
|
586
|
+
regex = r"^[a-zA-Z0-9]+$"
|
|
587
|
+
if not re.match(regex, deployment_name):
|
|
588
|
+
raise ValueError(
|
|
589
|
+
"--deployment_name can only contain " +
|
|
590
|
+
"letters (uppercase and lowercase)."
|
|
591
|
+
)
|
|
592
|
+
# Get current working directory
|
|
593
|
+
cwd = os.getcwd()
|
|
594
|
+
|
|
595
|
+
# Get the path of this script (command.py)
|
|
596
|
+
current_script_path = os.path.abspath(__file__)
|
|
597
|
+
|
|
598
|
+
# Construct the path to the template file
|
|
599
|
+
template_host_file_path = os.path.join(
|
|
600
|
+
os.path.dirname(current_script_path),
|
|
601
|
+
"templates",
|
|
602
|
+
"azure_function_host.json.template"
|
|
603
|
+
)
|
|
604
|
+
template_settings_path = os.path.join(
|
|
605
|
+
os.path.dirname(current_script_path),
|
|
606
|
+
"templates",
|
|
607
|
+
"azure_function_local.settings.json.template"
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
create_file_from_template(
|
|
611
|
+
template_host_file_path, os.path.join(cwd, "host.json")
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
create_file_from_template(
|
|
615
|
+
template_settings_path, os.path.join(cwd, "local.settings.json")
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
# Fetch default subscription ID if not provided
|
|
619
|
+
if not subscription_id:
|
|
620
|
+
subscription_id = get_default_subscription_id()
|
|
621
|
+
|
|
622
|
+
# Authenticate using DefaultAzureCredential
|
|
623
|
+
# (requires environment variables or Azure CLI login)
|
|
624
|
+
credential = DefaultAzureCredential()
|
|
625
|
+
|
|
626
|
+
# Check if the resource group exists
|
|
627
|
+
ensure_resource_group(
|
|
628
|
+
resource_group_name,
|
|
629
|
+
region,
|
|
630
|
+
subscription_id,
|
|
631
|
+
create_resource_group_if_not_exists
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
if storage_account_name is None:
|
|
635
|
+
storage_account_name = \
|
|
636
|
+
generate_unique_resource_name(STORAGE_ACCOUNT_NAME_PREFIX)
|
|
637
|
+
|
|
638
|
+
# Ensure storage account exists
|
|
639
|
+
storage_account_connection_string, storage_account_name = \
|
|
640
|
+
ensure_storage_account(
|
|
641
|
+
storage_account_name,
|
|
642
|
+
resource_group_name,
|
|
643
|
+
region,
|
|
644
|
+
subscription_id,
|
|
645
|
+
credential
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
# Create Function App
|
|
649
|
+
asyncio.run(
|
|
650
|
+
create_function_app(
|
|
651
|
+
resource_group_name=resource_group_name,
|
|
652
|
+
deployment_name=deployment_name,
|
|
653
|
+
region=region,
|
|
654
|
+
storage_account_name=storage_account_name
|
|
655
|
+
)
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
# Publish Function App
|
|
659
|
+
asyncio.run(
|
|
660
|
+
publish_function_app(
|
|
661
|
+
function_app_name=deployment_name,
|
|
662
|
+
storage_connection_string=storage_account_connection_string,
|
|
663
|
+
storage_container_name=container_name,
|
|
664
|
+
resource_group_name=resource_group_name
|
|
665
|
+
)
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
print(
|
|
669
|
+
f"Function App '{deployment_name}' deployment" +
|
|
670
|
+
"completed successfully."
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def command(
|
|
675
|
+
resource_group,
|
|
676
|
+
subscription_id,
|
|
677
|
+
storage_account_name,
|
|
678
|
+
container_name,
|
|
679
|
+
deployment_name,
|
|
680
|
+
region,
|
|
681
|
+
create_resource_group_if_not_exists,
|
|
682
|
+
skip_login
|
|
683
|
+
):
|
|
684
|
+
"""
|
|
685
|
+
Command-line tool for creating an Azure storage account,
|
|
686
|
+
blob container, and Function App.
|
|
687
|
+
|
|
688
|
+
Args:
|
|
689
|
+
resource_group (str): The name of the resource group.
|
|
690
|
+
subscription_id (str): The Azure subscription ID.
|
|
691
|
+
storage_account_name (str): The name of the storage account.
|
|
692
|
+
container_name (str): The name of the blob container.
|
|
693
|
+
function_app (str): The name of the Azure Function App.
|
|
694
|
+
region (str): The Azure region for the resources.
|
|
695
|
+
create_resource_group_if_not_exists (bool): Flag to create
|
|
696
|
+
the resource group if it does not exist.
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
None
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
print("logging in to Azure...")
|
|
703
|
+
# Ensure the user is logged in
|
|
704
|
+
ensure_az_login(skip_check=skip_login)
|
|
705
|
+
|
|
706
|
+
print("Checking functools...")
|
|
707
|
+
# Ensure azure functions core tools are installed
|
|
708
|
+
ensure_azure_functools()
|
|
709
|
+
create_storage_and_function(
|
|
710
|
+
resource_group_name=resource_group,
|
|
711
|
+
storage_account_name=storage_account_name,
|
|
712
|
+
container_name=container_name,
|
|
713
|
+
deployment_name=deployment_name,
|
|
714
|
+
region=region,
|
|
715
|
+
subscription_id=subscription_id,
|
|
716
|
+
skip_login=skip_login,
|
|
717
|
+
create_resource_group_if_not_exists=create_resource_group_if_not_exists
|
|
718
|
+
)
|