Skip to content

Cloudflare CCF 3.1.1: add per-instance storage deployment mode for multi-instance CCF connectors - #14792

Draft
MitchellGulledge3 wants to merge 5 commits into
Azure:masterfrom
MitchellGulledge3:reference/cloudflare-ccf-multi-instance-hardening
Draft

Cloudflare CCF 3.1.1: add per-instance storage deployment mode for multi-instance CCF connectors#14792
MitchellGulledge3 wants to merge 5 commits into
Azure:masterfrom
MitchellGulledge3:reference/cloudflare-ccf-multi-instance-hardening

Conversation

@MitchellGulledge3

@MitchellGulledge3 MitchellGulledge3 commented Jul 29, 2026

Copy link
Copy Markdown

TL;DR

A CCF connector that uses a StorageAccountBlobContainer poller generates an ARM
nested deployment to create its storage-side plumbing (queues, Event Grid
subscription, role assignments). That nested deployment is generated with ARM's
default "outer" expression scope, and its internal dependsOn IDs are built
with the 3-argument resourceId(resourceGroupName, type, …) overload.

That overload overrides only the resource group and silently inherits the
subscription of the deployment currently running. So when a customer points the
connector at a storage account in a different subscription, the dependsOn IDs
name a resource that does not exist in the template, and ARM rejects the whole
deployment before creating anything:

InvalidTemplate — Deployment template validation failed:
'The resource 'Microsoft.Storage/storageAccounts/<acct>/queueServices/default/queues/<queue>'
 is not defined in the template.'

This PR adds a new per-connector-instance storage generator that does not have
this defect (inner expression scope + explicitly-scoped resource IDs), and adopts
it in the Cloudflare CCF solution. I have reproduced the failure and verified the
fix against real Azure ARM — see Evidence.

Scope correction, stated up front: this reproduces only when the storage
account is in a different subscription. A different resource group in the
same subscription works fine. Earlier drafts of this description overstated the
blast radius; the matrix below is measured, not inferred.


Decisions requested from maintainers

  1. Is connectionDeployment.mode: "PerInstance" an acceptable opt-in contract
    for a solution's connector definition, or would you prefer a different
    configuration surface?
  2. Should the per-instance generator be a separate file (as here) or should the
    existing storageAccountDeploymentTemplate.ps1 be fixed in place and
    parameterised?
  3. Do you want the suspected same defect in the single-instance generator fixed in
    this PR, or tracked separately?
    (See Known related issue.
    I have deliberately not touched it here.)
  4. Are the two incidental shared-generator fixes acceptable in this PR (context-pane
    traversal, earlier parameter merge), or should they be split out? They are not
    opt-in and do change behaviour for other solutions — see the change matrix.

Glossary (this PR assumes a lot; here is the vocabulary)

Term Meaning
CCF Codeless Connector Framework — Sentinel data connectors defined by JSON rather than code.
CCP The connector-platform generator in Tools/Create-Azure-Sentinel-Solution that turns that JSON into ARM.
StorageAccountBlobContainer poller A CCF ingestion mode where the customer drops logs into a blob container; Event Grid publishes "blob created" events to a storage queue, and Sentinel drains that queue.
Storage-side topology The resources that live next to the customer's storage account: notification queue, dead-letter queue, Event Grid system topic + event subscription, and the role assignments that let Sentinel read them.
DCR / DCE Data Collection Rule / Data Collection Endpoint — the ingestion pipeline that shapes rows and routes them into a table.
Nested deployment A Microsoft.Resources/deployments resource embedded inside a template, used here to deploy into a different resource group/subscription than the parent.
Outer vs inner scope ARM's expressionEvaluationOptions. Outer (the default) evaluates the nested template's expressions in the parent's context. Inner evaluates them in the nested deployment's own context, so it only sees parameters explicitly passed to it.

Customer scenario

An organisation with more than one upstream account at a log source (for example,
several Cloudflare accounts, each exporting to its own storage account) wants each
one ingested independently into one Sentinel workspace.

Today the generator emits one storage-side topology per solution, so a second
account cannot be wired up without colliding with the first. This PR makes the
generated connector deployable once per upstream account, with deterministically
derived per-instance names for the queues, Event Grid resources, DCR/DCE and role
assignments.

While building that, the nested-deployment scope defect described above surfaced,
because per-instance deployments are precisely the case where the storage account
tends to live somewhere other than the Sentinel workspace.


Root cause, minimally

Parent template computes the dependency ID:

// 3-arg overload: overrides RG only, inherits the CURRENT deployment's subscription
"notificationQueueResourceId":
  "[resourceId(parameters('StorageAccountResourceGroupName'),
               'Microsoft.Storage/storageAccounts/queueServices/queues',
               variables('storageAccountName'), 'default', variables('queueName'))]"

The nested deployment then targets a different subscription, but has no
expressionEvaluationOptions, so it defaults to outer scope and its dependsOn
uses that parent-computed (wrong-subscription) ID:

{
  "type": "Microsoft.Resources/deployments",
  "subscriptionId": "[parameters('StorageAccountSubscription')]",
  "resourceGroup":  "[parameters('StorageAccountResourceGroupName')]",
  "properties": {
    // no expressionEvaluationOptions  -> outer scope
    "template": { "resources": [
      { "dependsOn": [ "[variables('notificationQueueResourceId')]" ] }
    ]}
  }
}

subscriptionId/resourceGroup control where resources are placed. They never
change expression context. So the queue is created in subscription B while the
dependsOn string names subscription A. No resource in the template has that ID →
InvalidTemplate.

Two things are required to fix it, and only doing one is not enough (I verified
this the hard way — see Evidence):

  1. Set expressionEvaluationOptions: { "scope": "inner" } and pass primitives
    (names, GUIDs) as parameters, then recompute the resource IDs inside the
    nested template so they bind to the nested deployment's own subscription/RG.
    Passing the parent's already-computed ID in as a parameter still fails — it is
    the same wrong string.
  2. In the parent, use the full-form resourceId(subscriptionId, resourceGroup, …)
    and subscriptionResourceId(subscriptionId, …) overloads wherever a
    storage-side ID is built.

Both are implemented in perInstanceStorageDeploymentTemplate.ps1.


Evidence

Verified against live Azure ARM using the actual packaged artifacts from
Solutions/Cloudflare CCF/Package/*.zip — not a hand-written model. The connector's
generated contentTemplate.mainTemplate was extracted from each package and deployed
for real into a Sentinel workspace's resource group, with the storage account in a
different resource group.

Packaged artifact nested deployment scope az deployment group create result
Pre-fix package (absent — defaults to outer) InvalidTemplate — nothing created
This PR's package {"scope": "inner"} ✅ queues + role assignments created

Exact error from the pre-fix artifact:

InvalidTemplate — Deployment template validation failed: 'The resource
'Microsoft.Storage/storageAccounts/<acct>/queueServices/default/queues/cf-<hash>-notification'
is not defined in the template.'

This matches the failure signature reported from the field, including the derived
cf-<hash>-notification queue-name shape.

Important for anyone trying to reproduce this: az deployment group validate
passes on the broken template. ARM does not expand the nested deployment during
validation, so the defect only surfaces on an actual create. Any CI or pre-flight
check based on validate will not catch it.

With this PR's artifact, the same deployment creates the queues
(cf-<hash>-notification, cf-<hash>-dlq) and their Storage Queue Data Contributor
role assignments at correct fully-qualified scopes. The only residual errors in my run
were artifacts of my test harness (I passed a User object ID where the template
correctly requires principalType: ServicePrincipal, and my harness stubbed the
Event Grid system-topic condition) — neither is a template defect.

Scope of the underlying ARM behaviour

Separately, minimal templates were used to isolate the mechanism. The failure occurs
when the dependsOn expression and the nested resource's own implicit resource ID
resolve to different scopes. For the existing single-instance generator's pattern
(3-argument resourceId(resourceGroupName, type, …), which overrides only the resource
group and inherits the running deployment's subscription), the mismatch appears only
across subscriptions; different-resource-group-same-subscription resolves
consistently and works.

Also run: arm-ttk via the repo's .arm-ttk, the solution package build, and the
solution's Python package tests (10/10), including a structural regression check
asserting the nested deployment is inner-scoped and self-contained.


Change matrix — what is opt-in and what is not

Please read this instead of a blanket "nothing else changes" claim.

Change File Opt-in? Can affect other solutions?
New per-instance storage generator perInstanceStorageDeploymentTemplate.ps1 (new) ✅ only when connectionDeployment.mode = "PerInstance" ❌ No
Dispatch to per-instance vs existing generator createCCPConnector.ps1 ❌ No — else branch is the untouched existing path
Suppress the shared content-template DCR when per-instance is active createCCPConnector.ps1 ❌ No
Context-pane traversal fix — generate parameters for all instructionSteps, not just the first createCCPConnector.ps1 No ⚠️ Yes — solutions with multi-step context panes will now get parameters that were previously dropped. This is an intentional bug fix, but it is a behaviour change.
Earlier merge of connector-definition inputs so poller generators can see them createCCPConnector.ps1 No ⚠️ Possibly — merge is additive and guarded (if (-not …exists)), so existing entries are left intact, but ordering changed.

The existing storageAccountDeploymentTemplate.ps1 is not modified by this PR.


Known related issue

storageAccountDeploymentTemplate.ps1 (the single-instance generator, unchanged
here) exhibits the same two ingredients: its nested deployment has no
expressionEvaluationOptions, and its parent builds notificationQueueResourceId
with the 3-argument resourceId overload. My reproduction template was modelled
directly on it, and that template fails cross-subscription.

I have not changed it in this PR, to keep the blast radius small and because the
right fix (patch in place vs. converge on the new generator) is a maintainer call —
see Decision 3 above. Happy to send a follow-up PR, or fold it in here if you prefer.


Files changed

  • Tools/Create-Azure-Sentinel-Solution/common/perInstanceStorageDeploymentTemplate.ps1new, +454
  • Tools/Create-Azure-Sentinel-Solution/common/createCCPConnector.ps1 — +42/−4
  • Solutions/Cloudflare CCF/** — adopts connectionDeployment.mode: "PerInstance", regenerated package, version bump, tests

The large line counts are dominated by regenerated package artifacts
(mainTemplate.json, createUiDefinition.json). The hand-authored change is the two
tooling files above plus the Cloudflare connector definition and tests.

Mitchell Gulledge and others added 2 commits July 23, 2026 13:27
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a35de7b3-6bf5-4412-b87e-48b6fbce713d
The per-instance storage nested deployment generated by the V3 packaging
tool omitted expressionEvaluationOptions, so ARM defaulted to OUTER scope.
Under outer scope, resourceId() inside the nested template is evaluated
against the PARENT deployment's subscription/resource group rather than the
storage account's. Every intra-template dependsOn therefore resolved to an
identifier that does not exist in the nested deployment and ARM failed with:

  Deployment template validation failed: 'The resource
  'Microsoft.Storage/storageAccounts/<acct>/queueServices/default/queues/
  cf-<hash>-notification' is not defined in the template.'

This only reproduces when the storage account lives in a different resource
group or subscription from the Sentinel workspace - i.e. the multi-instance
scenario - which is why it passed packaging validation but failed on deploy.

Changes to Tools/Create-Azure-Sentinel-Solution/common/perInstanceStorageDeploymentTemplate.ps1:
  - Set expressionEvaluationOptions.scope = "inner" on the nested deployment.
  - Make the inner template self-contained: 14 declared parameters plus a
    matching properties.parameters value-passing block; all variables('x')
    and outer parameters('x') references rewritten to inner parameters('x').
  - Add outer variable createEventGridSystemTopic so arm-ttk does not type
    compare the bool against the securestring EGSystemTopicName.
  - Declare inner principalId as securestring to match the parent type.
  - Replace the deprecated
    Microsoft.Storage/storageAccounts/queueServices/queues/providers/roleAssignments
    form with Microsoft.Authorization/roleAssignments + scope and a bare GUID name.
  - Refresh apiVersions: queues 2021-04-01 -> 2025-06-01, EventGrid
    systemTopics 2022-06-15 -> 2025-02-15, eventSubscriptions
    2023-12-15-preview -> 2025-02-15.
  - eventSubscription dependsOn now uses resourceId() instead of format().

Adds a regression guard to Solutions/Cloudflare CCF/Tests/validate_cloudflare_package.py
that asserts the nested deployment is inner-scoped and self-contained
(declared params == passed params, no variables() leakage, no undeclared
parameters). Verified to fail when the fix is reverted.

Validation: arm-ttk 48 passed / 0 failed (baseline had 1 pre-existing
failure); build-and-validate.ps1 BUILD SUCCESSFUL, 12 passed / 0 failed.
Repackaged as Cloudflare CCF 3.1.1.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c1fae0f8-8534-46d8-8630-384001b1dda7
@MitchellGulledge3
MitchellGulledge3 requested review from a team as code owners July 29, 2026 18:40
@MitchellGulledge3
MitchellGulledge3 marked this pull request as draft July 29, 2026 21:17
@MitchellGulledge3

Copy link
Copy Markdown
Author

🚧 DO NOT MERGE — WIP. Converting to draft while we finish corrections. Will mark ready for review once validated.

Mitchell Gulledge and others added 3 commits July 29, 2026 14:17
CloudflareAccountId and CloudflareAccountName were declared on the DCR input
stream and listed in the transform's project operator, but nothing ever assigned
them. Cloudflare blob payloads do not carry either field, and addOnAttributes is
stored on the connector ARM resource only - it is never injected into the DCR or
the destination table (PIF-2026-0042). Both columns therefore ingested as null,
which is exactly what Sean Stark observed in his lab on 10 June.

The connector grid was unaffected because it reads addOnAttributes off the ARM
resource directly.

Because each connection owns its own DCR, the values are now baked into that
DCR's transform at packaging time:

  - CloudflareLog_DCR.json: drop both columns from streamDeclarations (they are
    synthesized, not ingested) and seed them as empty placeholders in the
    transform so the authoritative source stays valid standalone.
  - CloudflareLog_PollerConfig.json: declare connectionDeployment.attributionColumns
    mapping each column to the connection parameter that supplies it.
  - perInstanceStorageDeploymentTemplate.ps1: rewrite each placeholder into an ARM
    concat() that injects the parameter value. Single quotes and backslashes are
    stripped from the value because they are the only characters that can escape
    or terminate a KQL string literal. Packaging now fails if a mapped parameter
    or table column is missing, or if a placeholder is absent or duplicated.

The regression suite only asserted that the columns were declared and projected,
so it passed while both were null. It now asserts they are absent from the input
stream, present as placeholders in the source transform, and injected with
scrubbing in the packaged template.

Blob lifecycle management remains deliberately deferred: managementPolicies is a
single per-storage-account resource, so a per-connection rule would overwrite any
existing policy on a shared storage account.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 12bd8fa3-5c6b-4c24-b27f-2e4c3273bbc4
The nested storage deployment sets subscriptionId + resourceGroup to
the storage account's scope, so resourceGroup().location resolves to
the storage RG (where the storage account lives). The prior
reference(storageAccountId, ..., 'Full').location call was rejected by
ARM validation with 'template function reference is not expected at
this location' inside a scope='inner' cross-subscription nested
deployment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 05a71fba-60ed-42e9-8e00-d987b81955e6
Two deployment-blocking defects found during end-to-end validation in a
live subscription (storage in eastus, workspace in westus2 — exercising
the cross-subscription/cross-resource-group path this solution targets).

1. Event Grid system topic location
   The storage-side nested deployment is inner-scoped
   (expressionEvaluationOptions.scope = inner) and targets the storage
   account's subscription and resource group. It set the system topic's
   location via reference(storageAccountId, ..., 'Full').location, which
   ARM rejects during validation:
     'The template function reference is not expected at this location.'
   Because the nested deployment already runs in the storage account's
   resource group, resourceGroup().location yields the same region
   without a runtime reference. (Committed earlier as 28e70cd.)

2. Empty location parameter from the portal UI
   The 'location' parameter is documented as unused — it exists solely to
   satisfy the arm-ttk Location-Should-Not-Be-Hardcoded check — yet it
   declared minLength: 1. When createUiDefinition's getLAWorkspace filter
   did not resolve, the portal submitted an empty string and ARM failed:
     'The provided value for the template parameter location is not
      valid. Length of the value should be greater than or equal to 1.'
   minLength is removed, since arm-ttk only inspects defaultValue.

   The load-bearing 'workspace-location' parameter had the same exposure:
   it defaults to an empty string and was consumed directly by 57
   resource location fields, so an empty value would have produced
   resources with no region. Those now resolve through a new
   workspaceLocationEffective variable that falls back to
   resourceGroup().location when the parameter is empty. The
   createUiDefinition location output also falls back to the selected
   workspace's region.

Validation:
- az deployment group validate with location='' and
  workspace-location='' (the exact failing input) now succeeds.
- az deployment group create with normal parameters succeeds.
- Full end-to-end ingestion confirmed: 250 Cloudflare Logpush events
  flowed blob -> Event Grid -> Storage Queue -> DCE/DCR ->
  CloudflareV2_CL, with CloudflareAccountId/CloudflareAccountName
  attribution populated, zero dead-letter messages and no DCRLogErrors.
- All 10 checks in Tests/validate_cloudflare_package.py pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 05a71fba-60ed-42e9-8e00-d987b81955e6
@MitchellGulledge3

Copy link
Copy Markdown
Author

Validation update — v3.1.3

Deployed and validated end-to-end in a live subscription, deliberately exercising the cross-subscription / cross-resource-group path this change targets (storage account in eastus, Sentinel workspace in westus2).

Two deployment-blocking defects found and fixed

1. Event Grid system topic location
The storage-side nested deployment is inner-scoped and targets the storage account's subscription/RG, but set the system topic location via reference(storageAccountId, ..., 'Full').location. ARM rejects this during validation:

The template function 'reference' is not expected at this location.

Since the nested deployment already runs in the storage account's resource group, resourceGroup().location resolves to the same region without a runtime reference.

2. Empty location parameter from the portal UI
location is documented as unused (it exists only to satisfy the arm-ttk Location-Should-Not-Be-Hardcoded check) but declared minLength: 1. When createUiDefinition's getLAWorkspace filter did not resolve, the portal submitted an empty string:

The provided value for the template parameter 'location' is not valid.
Length of the value should be greater than or equal to '1'.

minLength is removed (arm-ttk only inspects defaultValue).

The load-bearing workspace-location parameter had the same exposure — it defaults to "" and was consumed directly by 57 resource location fields, which would have produced region-less resources. These now resolve through a workspaceLocationEffective variable that falls back to resourceGroup().location.

Evidence

Check Result
validate with location='' + workspace-location='' (exact failing input) ✅ Succeeded
create with normal parameters ✅ Succeeded
Connector "Connect" action (previously failed) ✅ Succeeded
End-to-end ingestion ✅ 250 events → CloudflareV2_CL
Attribution columns populated CloudflareAccountId / CloudflareAccountName
Dead-letter queue ✅ 0 messages
DCRLogErrors ✅ 0 rows
Tests/validate_cloudflare_package.py ✅ 10/10 checks pass

Pipeline proven: blob → Event Grid → Storage Queue → DCE/DCR → CloudflareV2_CL

Still marked draft / do not merge pending review.

@microsoft-github-policy-service

Copy link
Copy Markdown

@MitchellGulledge3 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"
Contributor License Agreement

Contribution License Agreement

This Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
and conveys certain license rights to Microsoft Corporation and its affiliates (“Microsoft”) for Your
contributions to Microsoft open source projects. This Agreement is effective as of the latest signature
date below.

  1. Definitions.
    “Code” means the computer software code, whether in human-readable or machine-executable form,
    that is delivered by You to Microsoft under this Agreement.
    “Project” means any of the projects owned or managed by Microsoft and offered under a license
    approved by the Open Source Initiative (www.opensource.org).
    “Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any
    Project, including but not limited to communication on electronic mailing lists, source code control
    systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
    discussing and improving that Project, but excluding communication that is conspicuously marked or
    otherwise designated in writing by You as “Not a Submission.”
    “Submission” means the Code and any other copyrightable material Submitted by You, including any
    associated comments and documentation.
  2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any
    Project. This Agreement covers any and all Submissions that You, now or in the future (except as
    described in Section 4 below), Submit to any Project.
  3. Originality of Work. You represent that each of Your Submissions is entirely Your original work.
    Should You wish to Submit materials that are not Your original work, You may Submit them separately
    to the Project if You (a) retain all copyright and license information that was in the materials as You
    received them, (b) in the description accompanying Your Submission, include the phrase “Submission
    containing materials of a third party:” followed by the names of the third party and any licenses or other
    restrictions of which You are aware, and (c) follow any other instructions in the Project’s written
    guidelines concerning Submissions.
  4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else
    for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
    Submission is made in the course of Your work for an employer or Your employer has intellectual
    property rights in Your Submission by contract or applicable law, You must secure permission from Your
    employer to make the Submission before signing this Agreement. In that case, the term “You” in this
    Agreement will refer to You and the employer collectively. If You change employers in the future and
    desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
    and secure permission from the new employer before Submitting those Submissions.
  5. Licenses.
  • Copyright License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license in the
    Submission to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute
    the Submission and such derivative works, and to sublicense any or all of the foregoing rights to third
    parties.
  • Patent License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license under
    Your patent claims that are necessarily infringed by the Submission or the combination of the
    Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
    import or otherwise dispose of the Submission alone or with the Project.
  • Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement.
    No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
    granted by implication, exhaustion, estoppel or otherwise.
  1. Representations and Warranties. You represent that You are legally entitled to grant the above
    licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
    have disclosed under Section 3). You represent that You have secured permission from Your employer to
    make the Submission in cases where Your Submission is made in the course of Your work for Your
    employer or Your employer has intellectual property rights in Your Submission by contract or applicable
    law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
    have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
    You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
    REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
    EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
    PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
    NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
  2. Notice to Microsoft. You agree to notify Microsoft in writing of any facts or circumstances of which
    You later become aware that would make Your representations in this Agreement inaccurate in any
    respect.
  3. Information about Submissions. You agree that contributions to Projects and information about
    contributions may be maintained indefinitely and disclosed publicly, including Your name and other
    information that You submit with Your Submission.
  4. Governing Law/Jurisdiction. This Agreement is governed by the laws of the State of Washington, and
    the parties consent to exclusive jurisdiction and venue in the federal courts sitting in King County,
    Washington, unless no federal subject matter jurisdiction exists, in which case the parties consent to
    exclusive jurisdiction and venue in the Superior Court of King County, Washington. The parties waive all
    defenses of lack of personal jurisdiction and forum non-conveniens.
  5. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and
    supersedes any and all prior agreements, understandings or communications, written or oral, between
    the parties relating to the subject matter hereof. This Agreement may be assigned by Microsoft.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds an opt-in per-connector-instance (“PerInstance”) storage-side deployment generator for CCF StorageAccountBlobContainer pollers to fix cross-scope nested deployment dependency resolution, and adopts it in the Cloudflare CCF solution.

Changes:

  • Introduces a new PowerShell generator for per-instance storage/Event Grid/RBAC topology with inner-scoped nested deployments.
  • Updates createCCPConnector.ps1 to dispatch to the new generator when connectionDeployment.mode == "PerInstance" and skips generating the shared DCR in that mode.
  • Updates the Cloudflare CCF solution artifacts (poller config, table/DCR, UI definition, release notes) and adds targeted Python package validation.

Reviewed changes

Copilot reviewed 11 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Tools/Create-Azure-Sentinel-Solution/common/perInstanceStorageDeploymentTemplate.ps1 New per-instance storage deployment generator that emits inner-scoped nested deployments and deterministic per-instance resources.
Tools/Create-Azure-Sentinel-Solution/common/createCCPConnector.ps1 Adds PerInstance dispatch, fixes ContextPane parameter traversal, and adjusts parameter merge / shared DCR behavior.
Solutions/Cloudflare CCF/Tests/validate_cloudflare_package.py Adds targeted regression/structure checks for the generated package (inner-scope nested deployment, deterministic naming, attribution injection).
Solutions/Cloudflare CCF/ReleaseNotes.md Bumps solution version and documents fixes/behavior changes.
Solutions/Cloudflare CCF/Package/testParameters.json Removes unused parameters from test parameters.
Solutions/Cloudflare CCF/Package/createUiDefinition.json Updates solution description and changes the location output expression.
Solutions/Cloudflare CCF/Data/Solution_Cloudflare.json Updates solution description and version.
Solutions/Cloudflare CCF/Data Connectors/CloudflareLog_CCF/CloudflareLog_Table.json Adds Cloudflare account attribution columns to the table schema.
Solutions/Cloudflare CCF/Data Connectors/CloudflareLog_CCF/CloudflareLog_PollerConfig.json Opts into PerInstance mode and switches to deterministic connector naming / per-instance DCR wiring.
Solutions/Cloudflare CCF/Data Connectors/CloudflareLog_CCF/CloudflareLog_DCR.json Adds attribution placeholders to the authoritative transform.
Solutions/Cloudflare CCF/Data Connectors/CloudflareLog_CCF/CloudflareLog_ConnectorDefinition.json Updates connector UX text/instructions to align with multi-instance/per-instance behavior.

Comment on lines +247 to +263
type = "Microsoft.Resources/deploymentScripts"
apiVersion = "2023-08-01"
name = "[[variables('rbacPropagationScriptName')]"
location = "[parameters('workspace-location')]"
kind = "AzurePowerShell"
dependsOn = @(
"[[variables('storageNestedDeploymentId')]"
)
properties = [ordered]@{
azPowerShellVersion = "11.5"
forceUpdateTag = "[[parameters('forceUpdateTag')]"
retentionInterval = "PT1H"
cleanupPreference = "Always"
timeout = "PT5M"
scriptContent = $scriptContent
}
}
"outputs": {
"workspace-location": "[first(map(filter(basics('getLAWorkspace').value, (filter) => and(contains(toLower(filter.id), toLower(resourceGroup().name)),equals(filter.name,basics('workspace')))), (item) => item.location))]",
"location": "[location()]",
"location": "[if(empty(location()), first(map(filter(basics('getLAWorkspace').value, (filter) => and(contains(toLower(filter.id), toLower(resourceGroup().name)),equals(filter.name,basics('workspace')))), (item) => item.location)), location())]",
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants