@@ -10,19 +10,41 @@ Back up and restore NHP Azure Table Storage (ATS) via JSON snapshots in Blob Sto
1010
1111## Configuration
1212
13- Create a ` .env ` file in the repo root:
13+ Create a ` .env ` file in the repo root by copying and filling in the values below :
1414
1515``` bash
16- export AZURE_STORAGE_ACCOUNT_NAME=< storage_name>
17- export PROD_TABLE_NAME=< ats_name>
18- export BACKUP_CONTAINER_NAME=< backup_container_name>
19- export DEV_TABLE_NAME=< ats_dev_name>
16+ # Azure resources
17+ AZURE_STORAGE_ACCOUNT_NAME=< storage-name> # Globally unique, lowercase letters+numbers, 3-24 chars
18+ AZURE_RESOURCE_GROUP_NAME=< resource-group> # Existing resource group where resources are created
19+ AZURE_LOCATION=< region> # e.g. australiaeast; pick one near your users
20+
21+ # Storage configuration
22+ AZURE_SKU=Standard_LRS # Cheapest; fine for dev/test and small prod workloads
23+ AZURE_STORAGE_KIND=StorageV2 # General-purpose v2; supports blobs, queues, tables
24+
25+ # Backup targets
26+ PROD_TABLE_NAME=< ats-name> # Production Azure Table Storage table to back up
27+ BACKUP_CONTAINER_NAME=< backup-container-name> # Blob container for JSON snapshots
28+ DEV_TABLE_NAME=< ats-dev-name> # Dev/test table for safe restore experiments
29+
30+ # Function App
31+ AZURE_FUNCTION_APP_NAME=< function-app-name> # Globally unique, used in URLs and deployment
32+ AZURE_PYTHON_VERSION=3.13 # Max version supported by Azure Functions; 3.14 is still in preview
33+ AZURE_FUNCTION_INSTANCE_MEMORY=2048 # Flex Consumption memory in MB; 2048 is a good default
2034```
2135
2236Windows users should omit ` export ` or use ` set ` instead.
37+ Notice that ` AZURE_SKU ` and ` AZURE_STORAGE_KIND ` default to ` Standard_LRS ` and ` StorageV2 ` if omitted.
2338
2439## Quick deploy
2540
41+
42+ ### Automated deploy
43+
44+ Run ` uv run deploy.py ` to interactively create the resource group, storage account,
45+ Function App, backup container, app settings, and publish the functions.
46+ Add ` --yes ` to skip confirmation prompts.
47+
2648This project uses ** Azure Functions on Flex Consumption** (Python 3.13).
2749
2850### 1. Generate ` requirements.txt `
@@ -33,7 +55,7 @@ Azure's remote build uses pip, not uv. Compile a lockfile before every deploy:
3355uv pip compile pyproject.toml -o requirements.txt
3456```
3557
36- ### 1.5 . Prepare ` .funcignore `
58+ ### 2 . Prepare ` .funcignore `
3759
3860Ensure ` .funcignore ` exists so the publish step skips your local venv and build artefacts:
3961
@@ -51,10 +73,64 @@ tests/
5173.github/
5274```
5375
54- ### 2. Publish
76+ ### 3. Azure resources and app settings
77+
78+ <details >
79+ <summary ><b >Click to expand: create storage account, Function App, and configure app settings</b ></summary >
80+
81+ Create the storage account. The name must be globally unique, lowercase letters and numbers only, 3–24 characters.
82+
83+ ``` bash
84+ az storage account create \
85+ --name " $AZURE_STORAGE_ACCOUNT_NAME " \
86+ --resource-group " $AZURE_RESOURCE_GROUP_NAME " \
87+ --location " $AZURE_LOCATION " \
88+ --sku " $AZURE_SKU " \
89+ --kind " $AZURE_STORAGE_KIND "
90+ ```
91+
92+ Create the backup container:
5593
5694``` bash
57- func azure functionapp publish < function-app-name>
95+ az storage container create \
96+ --name " $BACKUP_CONTAINER_NAME " \
97+ --account-name " $AZURE_STORAGE_ACCOUNT_NAME " \
98+ --auth-mode login
99+ ```
100+
101+ Create the Function App:
102+
103+ ``` bash
104+ az functionapp create \
105+ --resource-group " $AZURE_RESOURCE_GROUP_NAME " \
106+ --name " $AZURE_FUNCTION_APP_NAME " \
107+ --storage-account " $AZURE_STORAGE_ACCOUNT_NAME " \
108+ --flexconsumption-location " $AZURE_LOCATION " \
109+ --runtime python \
110+ --runtime-version " $AZURE_PYTHON_VERSION " \
111+ --functions-version 4 \
112+ --instance-memory " $AZURE_FUNCTION_INSTANCE_MEMORY "
113+ ```
114+
115+ Configure app settings:
116+
117+ ``` bash
118+ az functionapp config appsettings set \
119+ --name " $AZURE_FUNCTION_APP_NAME " \
120+ --resource-group " $AZURE_RESOURCE_GROUP_NAME " \
121+ --settings \
122+ " AZURE_STORAGE_ACCOUNT_NAME=$AZURE_STORAGE_ACCOUNT_NAME " \
123+ " PROD_TABLE_NAME=$PROD_TABLE_NAME " \
124+ " BACKUP_CONTAINER_NAME=$BACKUP_CONTAINER_NAME " \
125+ " DEV_TABLE_NAME=$DEV_TABLE_NAME "
126+ ```
127+
128+ </details >
129+
130+ ### 4. Publish
131+
132+ ``` bash
133+ func azure functionapp publish " $AZURE_FUNCTION_APP_NAME "
58134```
59135
60136The app contains two functions:
@@ -107,54 +183,45 @@ az storage blob download --account-name "$AZURE_STORAGE_ACCOUNT_NAME" \
107183uv run --env-file .env python -m backup.core --restore snapshot.json --target-table < table_name>
108184```
109185
110- ## Details
111-
112- <details >
113- <summary ><b >Azure Function setup (first time)</b ></summary >
114-
115- ``` bash
116- az functionapp create \
117- --resource-group < your-rg> \
118- --name < new-app-name> \
119- --storage-account < your-storage> \
120- --flexconsumption-location < region> \
121- --runtime python \
122- --runtime-version 3.13 \
123- --functions-version 4 \
124- --instance-memory 2048
125-
126- az functionapp config appsettings set \
127- --name < your-app-name> \
128- --resource-group < your-rg> \
129- --settings \
130- ' AZURE_STORAGE_ACCOUNT_NAME=<your-account>' \
131- ' PROD_TABLE_NAME=<your-table>' \
132- ' BACKUP_CONTAINER_NAME=<backup-container-name>' \
133- ' DEV_TABLE_NAME=<your-dev-table>'
134- ```
135-
136- </details >
186+ ## Local testing
137187
138188<details >
139- <summary ><b >Local testing </b ></summary >
189+ <summary ><b >Click to expand </b ></summary >
140190
141191Create ` local.settings.json ` :
142192
143- ``` json
144- {
145- "IsEncrypted" : false ,
146- "Values" : {
147- "AzureWebJobsStorage" : " UseDevelopmentStorage=true" ,
148- "FUNCTIONS_WORKER_RUNTIME" : " python" ,
149- "AZURE_FUNCTIONS_ENVIRONMENT" : " Development" ,
150- "AZURE_STORAGE_ACCOUNT_NAME" : " <your-account>" ,
151- "PROD_TABLE_NAME" : " <your-table>" ,
152- "BACKUP_CONTAINER_NAME" : " <your-container>" ,
153- "DEV_TABLE_NAME" : " <your-dev-table>"
154- }
193+ ``` python
194+ import json
195+ import re
196+
197+ env = {}
198+ with open (" .env" ) as f:
199+ for line in f:
200+ line = line.strip()
201+ if line and not line.startswith(" #" ):
202+ key, _, value = line.partition(" =" )
203+ env[key.strip()] = value.strip().strip(" '\" " )
204+
205+ settings = {
206+ " IsEncrypted" : False ,
207+ " Values" : {
208+ " AzureWebJobsStorage" : " UseDevelopmentStorage=true" ,
209+ " FUNCTIONS_WORKER_RUNTIME" : " python" ,
210+ " AZURE_FUNCTIONS_ENVIRONMENT" : " Development" ,
211+ " AZURE_STORAGE_ACCOUNT_NAME" : env[" AZURE_STORAGE_ACCOUNT_NAME" ],
212+ " PROD_TABLE_NAME" : env[" PROD_TABLE_NAME" ],
213+ " BACKUP_CONTAINER_NAME" : env[" BACKUP_CONTAINER_NAME" ],
214+ " DEV_TABLE_NAME" : env[" DEV_TABLE_NAME" ],
215+ }
155216}
217+
218+ with open (" local.settings.json" , " w" ) as f:
219+ json.dump(settings, f, indent = 2 )
220+ f.write(" \n " )
156221```
157222
223+ Save the above Python code in ` local_settings.py ` and run it: ` uv run local_settings.py `
224+
158225Run the host:
159226
160227``` bash
@@ -169,8 +236,10 @@ curl http://localhost:7071/admin/functions/nhp_ats_backup -X POST -d '{}'
169236
170237</details >
171238
239+ ## Developer notes
240+
172241<details >
173- <summary ><b >Developer notes </b ></summary >
242+ <summary ><b >Click to expand </b ></summary >
174243
175244- ` backup/core.py ` — library functions and non-interactive backup/restore
176245- ` backup/cli.py ` — interactive workflow and snapshot resolution
0 commit comments