This is the complete list. Nothing else is created, changed, or deleted.
| When | Operation | Target | Performed by |
|---|---|---|---|
| Deployment | Create Container Apps environment, VNet + subnet, Log Analytics workspace, Key Vault | The resource group you chose | ARM template |
| Deployment | Create one Container Apps job and one user-assigned managed identity per copy job | The resource group you chose | ARM template |
| Deployment | Create one placeholder Key Vault secret per copy job | The new Key Vault | ARM template |
| Deployment | Assign Key Vault Secrets User on one secret |
The new Key Vault | ARM template |
| Deployment | Assign a read-only role on your source | Your existing source storage account | ARM template |
| Deployment | Assign AcrPull, only when containerRegistryResourceId is set |
Your existing container registry | ARM template |
set-secret |
PUT one secret value |
The new Key Vault | copyctl.py |
grant-source |
Add the copy subnet to networkAcls.virtualNetworkRules |
Your existing source storage account | copyctl.py |
apply, go-live, dry-run, enable, disable |
Update job environment variables, cron expression, or replica timeout | The Container Apps job | copyctl.py |
start |
Start one job execution | The Container Apps job | copyctl.py |
revoke-source |
Remove the copy subnet from networkAcls.virtualNetworkRules |
Your existing source storage account | copyctl.py |
check-source |
Nothing - read-only diagnostic queries; writes nothing anywhere | (nothing) | copyctl.py |
Only four rows touch resources you already own: three on the source storage
account, and one optional AcrPull grant if you chose to host the image in your
own registry. The two network-rule changes are separate, explicit commands
rather than a side effect of installing: nothing modifies your existing storage
account's firewall unless you run grant-source. Neither command changes the
account's default firewall action, and neither adds an allow-list entry for a
laptop or Cloud Shell IP.
Each job makes outbound connections to Azure Storage, Microsoft Entra, Microsoft Graph/SharePoint, the container registry holding the published image, Key Vault, and Azure Monitor. It has no inbound endpoint and no external control plane.
The source storage account must have public network access enabled so its firewall and service endpoint can be used. Accounts reachable only through an existing private endpoint are outside this package's network model.
Each copy job gets its own user-assigned managed identity, holding exactly two roles:
Key Vault Secrets User, scoped to that job's single secret.- A read-only role on that job's source.
ADLS Gen2 sources use Storage Blob Data Reader scoped to the single container.
Azure Files sources use Storage File Data Privileged Reader at storage-account
scope. The role is read-only, but Azure Files OAuth backup semantics bypass
per-file and per-directory ACLs, and Azure offers no narrower scope for this
access path. Use a source-dedicated storage account if account-wide read is too
broad.
No identity in this package has any data-write role in Azure. Writing happens only into SharePoint.
Each SharePoint application must have Microsoft Graph Sites.Selected with
admin consent, then a write grant on one specific site. Sites.Selected grants
nothing on its own, so an application with no site grant can read nothing.
One application may serve several jobs that target the same site. Using one
application for two different sites is rejected by copyctl.py validate, because
it would widen each job's reach beyond what it needs.
At run time the job resolves the site and document library through Graph and copies only into the resolved library and folder. Resolution happens on every execution rather than being pinned at deployment, so a renamed or replaced library fails loudly instead of copying into the wrong place.
Job configuration files contain identifiers only. They must never contain the
Entra client secret, storage keys, SAS tokens, or passwords. copyctl.py validate rejects any unknown field, which includes anything named like a
secret.
copyctl.py set-secret reads the secret without terminal echo and sends it
straight to Azure Resource Manager over TLS. The value is never written to disk,
never placed on a command line where another local process could read it via
/proc, and never passed as a deployment parameter, so it does not appear in
deployment history. The job reads it from Key Vault through its managed identity.
The deployment template creates a non-credential placeholder secret so the job's secret reference resolves before you store the real value. A job that runs against the placeholder fails authentication; it does not run unauthenticated.
Do not enable shell tracing while running copyctl.py. Rotate with
copyctl.py set-secret before the Entra credential expires, then remove the old
credential from the application registration.
The runtime wrapper contains no destination-delete operation and never calls
rclone sync. scripts/validate.sh fails the build if one appears.
existingFiles: skipadds--ignore-existing.existingFiles: replace_if_changedletsrclone copyreplace a changed file.dryRun: trueadds--dry-run.includePathsbecomes--files-from-raw; unsafe relative paths are rejected.modifiedOnOrAfterbecomes--max-age, based on last-modified time.--ignore-size --ignore-checksumare always applied. These are rclone's documented SharePoint compatibility settings: SharePoint rewrites some uploaded files, mainly Microsoft Office formats, so destination size and hash legitimately differ from the source. Without them every such file would look changed and be re-uploaded on every run. Comparison falls back to modification time, and a successful copy is therefore not a byte-for-byte or file-size attestation.
Every job deploys in dry-run mode with its schedule parked. Going live requires
typing LIVE COPY <job> at a prompt, and activating a schedule is a separate
command again.
The application image is built and published once, then deployed by SHA-256 digest. Customers do not build it, so no deployment depends on package repositories or download endpoints being reachable or unchanged.
- The Dockerfile frontend and Alpine base are pinned by digest.
- rclone 1.74.4 is pinned, and its archive checksum is verified at build time.
- The runtime is non-root and contains only Alpine, CA certificates, rclone, jq, curl, and the constrained wrapper.
If your organization does not permit pulling from public registries, import the
same digest into your own registry with az acr import, then pass that
reference as containerImage and the registry's resource ID as
containerRegistryResourceId. The second parameter grants each job identity
AcrPull on that registry and adds the matching registries entry; without it
job creation fails with an unauthorized pull, because Container Apps resolves
the image at create time rather than at first execution. The bytes are
identical; the digest proves it.
rclone emits JSON logs including per-file copy decisions and transfer statistics. Container Apps sends stdout and stderr to a Log Analytics workspace with 90-day retention.
copyctl.py preview starts one dry-run execution and reads that execution's
statistics back out of the workspace, so it needs read access there. No other
command reads it. Preview refuses to run against a job in live mode rather than
switching the mode itself: Container Apps has no per-execution override that
preserves the job's Key Vault secret reference, so forcing dry run for a single
execution cannot be done safely. Switch the job with dry-run, preview, then
go-live.
Because the entire job configuration lives in the Container Apps job's
environment variables, az containerapp job show and the Azure portal are a
complete, current record of what a job will copy and where. Configuration changes
are ordinary ARM operations and appear in the Azure Activity Log alongside
deployments, role assignments, network-rule changes, and manual starts.
Log Analytics alone is not a tamper-evident archive. Apply your own Azure Monitor/SIEM retention and access controls where logs are compliance evidence.
- A VNet-integrated workload-profile environment adds Azure-managed networking resources and their associated cost.
- Key Vault public network access remains enabled and authenticated.
copyctl.py validaterejects a schedule whose interval is shorter thantimeoutMinutesfor the common cron shapes, but unusual expressions are not modelled and could still overlap.- SharePoint path length and character restrictions still apply, and file names are normalized by SharePoint.
- SharePoint may rewrite uploaded Office files, so this package cannot guarantee that destination bytes or size match the source even when the document is functionally equivalent. Verify those formats by opening them, not by comparing hashes.
- Azure Files NTFS ACLs are not preserved in SharePoint.
modifiedOnOrAfteruses modification time; portable creation-time filtering is not available across both supported source types.- The portal form supports one source storage account in the deployment subscription. Multiple source accounts, or a source in another subscription, require the CLI path.