Skip to content

Azure IPAM v4.0.0 Release #377

Open
DCMattyG wants to merge 131 commits intomainfrom
ipam-3.7.0
Open

Azure IPAM v4.0.0 Release #377
DCMattyG wants to merge 131 commits intomainfrom
ipam-3.7.0

Conversation

@DCMattyG
Copy link
Copy Markdown
Contributor

@DCMattyG DCMattyG commented Mar 11, 2026

Azure IPAM v4.0.0

This is a major release that delivers significant framework upgrades, a data grid migration, authentication modernization, comprehensive documentation overhaul, revamped examples, and numerous bug fixes.


Major Framework & Dependency Upgrades

  • React 18 → 19: Full migration including removal of forwardRef wrappers (ref-as-prop), removal of PropTypes, explicit null for useRef() calls, and migration of LoadingButton to Button
  • MSAL v4 → v5 (@azure/msal-browser 4.x → 5.x, @azure/msal-react 3.x → 5.x): Removed obsolete config, consolidated event types, fixed silent token timeout recovery (timed_out error code), and prevented iframe fallback timeout loops
  • Inovua React Data Grid → AG Grid (ag-grid-community / ag-grid-react 35.x): Complete migration to AG Grid including centralized DataGrid component, custom styling, column state persistence, unified data loading overlays, and custom cell renderers (drill-down, info, progress)
  • Vite 7 → 8 (vite 7.x → 8.x, @vitejs/plugin-react 5.x → 6.x): Migrated to Vite 8 which replaces Rollup with Rolldown and esbuild with Oxc for bundling, transforms, and minification. Removed vite-plugin-eslint2 (redundant with editor-based linting and incompatible with Vite 8)
  • ESLint 10: Upgraded from ESLint 9 to 10 with modern React linting plugins (@eslint-react/eslint-plugin, eslint-plugin-react-compiler), replacing eslint-plugin-react and eslint-plugin-react-hooks. Added dist/ ignore, fixed no-useless-assignment violations, and removed unused eslint-plugin-jest
  • Updated NPM packages across the board (Vite 7.3.x, React Router 7.13.x, MUI 7.3.x, etc.)

Engine & Backend

  • Azure Function Blueprints: Implemented Blueprint-based function naming for improved clarity
  • Python dependency cleanup: Removed msal, azure-common, azure-keyvault-secrets, and six; added azure-mgmt-resource-subscriptions to address Azure SDK module separation
  • Python linting: Added pyproject.toml with Ruff linter configuration (pycodestyle, pyflakes, isort). Resolved all lint violations across 18 engine files — bare except clauses, wildcard imports replaced with explicit names, unused imports/variables, invalid escape sequences, import sorting and grouping
  • Reservation logic hardening: Fixed CIDR overlap detection during auto-fulfillment, added validation for all in-block vNet prefix overlap checks, and made auto-fulfillment idempotent by deduping existing block vNet associations
  • Endpoint fix: Standalone NICs are now included in the Endpoint list (fixes Standalone NICs not listed in Endpoint node #371)
  • Network associations fix: Resolved improper handling of missing vNETs and vHUBs (fixes Error fetching available IP Block networks #350)

UI & UX Improvements

  • Drill-down navigation in Discover (fixes Navigation Improvements #183): Added hierarchical drill-down with multi-filter pass-through and hidden column auto-reveal when filters are active
  • Centralized authentication handling: New AuthHandler for MSAL error handling, centralized token acquisition via tokenService
  • Custom DraggablePaper component: Replaced react-draggable package with a purpose-built component
  • Associations UX: Shifted API work to Redux thunks, optimized data refresh, fixed infinite update loops by separating initial grid selection from user selection state
  • Planner data loading: Addressed issue where Planner would not load when data was incomplete (fixes Planner will not load #366)
  • Reservation UX: Fixed view reversion after cancelling a Reservation, fixed column sorting, and streamlined the interface
  • Unified grid experience: Centralized DataGrid and ConfigureGrid components with shared filter utilities, consistent loading overlays, and AG Grid custom styling (brightness filter for row hover)
  • Search bar: Fixed messaging when no resources are found in Azure IPAM scope

Deployment, Build & Infrastructure

  • RHEL images updated to UBI9
  • Dockerfiles optimized: Improved layering, removed unneeded steps across all container images. Resolved Hadolint lint violations (ADDCOPY, JSON notation for CMD/ENTRYPOINT, pipefail for piped RUN commands). Added centralized .hadolint.yaml for rule suppressions
  • KeyVault Soft Delete added to deployment and migration Bicep templates (fixes Enable soft delete for Key Vault #373)
  • Build flexibility: Added support for building with either current or latest NPM/Python packages
  • CI/CD path exclusions: Added exclusions to avoid unnecessary test/build runs for documentation-only changes
  • Azure PowerShell SDK v14 compatibility: Fixed Get-AzAccessToken breaking changes (fixes Breaking changes to Get-AzAccessToken #343); updated deploy & migrate scripts

Documentation Overhaul

  • Massively revamped How-To docs: authentication, exclusions, Discover, Reservations, External Networks, and Virtual Network Associations sections
  • New documentation: Comprehensive automation docs, API docs for vNet Associations, initial External Networks docs, detailed Reservations feature docs
  • 50+ screenshots added or replaced to reflect the current UI
  • Fixed all markdown warnings/errors and added .markdownlint.json configuration
  • Doc cleanup: Fixed stale links, grammar, spelling issues, deprecated folder descriptions, and undocumented switches across all sections

Examples

  • Terraform example revamped: Migrated from Shell scripts to the official Azure IPAM Terraform provider
  • Azure ESLZ example modernized: Updated resource API references, modern coding standards, and improved parameterization
  • Script examples reorganized: PowerShell and Shell scripts moved into a dedicated examples/scripts/ folder with new helper scripts and README
  • Token helper function: Standardized access token generation; removed legacy Microsoft Graph SDK v1 support

Testing

  • Added tests for Virtual Network Association permutations
  • Expanded overall testing coverage with numerous additional Pester tests
  • Updated test expectations to align with additionally created resources
  • CI lint gate: Added pre-deployment lint job to the testing workflow (ESLint, Vite build verification, Ruff for Python, Bicep template validation, Hadolint for Dockerfiles). Deploy is skipped if any check fails, preventing wasted Azure resources

Bug Fixes

[major]

…that were not being accounted for properly
…ue to improper handling of missing vNETs and vHUBs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 128 out of 188 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

ui/src/features/analysis/visualize/visualize.jsx:567

  • Search is no longer wrapped in React.forwardRef, but it still calls useImperativeHandle(ref, ...) and the parent uses <Search ref={searchRef} />. In React, ref is not passed as a normal prop, so ref here will be undefined and searchRef.current will stay null (breaking getValue()/hasValue() calls and potentially crashing effects that dereference it). Convert Search back to React.forwardRef((props, ref) => ...) (or rename to an explicit prop like innerRef and update callers) so the imperative API works.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 129 out of 189 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

ui/src/features/analysis/visualize/visualize.jsx:553

  • Search is no longer wrapped in React.forwardRef, but it still tries to receive ref via props and uses useImperativeHandle(ref, ...). In React, ref is not passed as a normal prop to function components, so searchRef will never be wired up and the imperative API (getValue, hasValue, etc.) will be broken (and can also cause warnings like “Function components cannot be given refs”). Convert Search back to React.forwardRef (accepting ref as the 2nd arg) and remove ref from the props destructuring.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 129 out of 189 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

ui/src/features/analysis/visualize/visualize.jsx:553

  • Search is no longer wrapped in React.forwardRef, but it still relies on a ref argument (useImperativeHandle(ref, ...)). In React, ref={searchRef} is not passed as a normal prop, so ref will be undefined here and searchRef.current will never get the imperative API (breaking calls like searchRef.current.hasValue() / getValue()). Convert Search back to React.forwardRef((props, ref) => ...) (or rename to an explicit prop like innerRef and pass it manually).
    ui/src/features/analysis/peering/peering.jsx:654
  • Search is defined as a normal component that destructures { ref, ... }, but it's used as <Search ref={searchRef} /> and calls useImperativeHandle(ref, ...). React does not pass ref through props unless the component uses React.forwardRef, so searchRef.current.setValue/clearSearch/... will break at runtime. Restore React.forwardRef for Search (or pass a differently named prop like innerRef).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- Update package version from ^2.13.0 to ^3.0.0
- Disable rules-of-hooks and exhaustive-deps (covered by react-compiler)
- Downgrade component-hook-factories to warn (duplicates no-nested-component-definitions)
- Disable set-state-in-effect (widespread pattern, to address incrementally)
- Bump @eslint-react/eslint-plugin from ^3.0.0 to ^4.0.0
- Rename rsc/function-definition to rsc-function-definition (v4 prefix flattening)
- Move key before spread props in Autocomplete renderOption callbacks (11 sites)
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 130 out of 190 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

DCMattyG added 7 commits April 1, 2026 11:57
The bare `exit` in the `finally` block was overriding `exit 1` from
the `catch` block, causing deployment failures to report exit code 0.
This prevented GitHub Actions from detecting failed deployments.
…ngine

- Add pyproject.toml with ruff config (E4, E7, E9, F, I, W291, W293, W605)
- Replace bare except clauses with except Exception across 8 files
- Replace star imports (from app.models import *) with explicit imports in 8 files
- Remove unused imports and variables in main.py, space.py, dependencies.py
- Fix invalid escape sequences with raw strings in space.py and argquery.py
- Fix None comparisons, not-in operators, trailing semicolons
- Rename duplicate function names (read_index, get_admins, get_block_reservations, delete_block_reservations)
- Sort and organize all imports via isort across 14 files
- Remove trailing whitespace in globals.py and status.py
- Consolidate UUID imports in admin.py
- Replace ADD with COPY for local file operations (DL3020)
- Convert CMD/ENTRYPOINT to JSON notation (DL3025)
- Add SHELL pipefail directive before piped RUN commands (DL4006)
- Add --no-cache-dir to pip upgrade commands (DL3042)
- Add explicit WORKDIR in engine/Dockerfile.func (DL3045)
- Add .hadolint.yaml for centralized rule suppressions
- Add lint job gating deploy with ESLint, Vite build, Ruff, Bicep, and Hadolint checks
- Gate deploy job on lint success to avoid wasting Azure resources on bad code
- Use hadolint-action for all Dockerfile variants (deb, rhel, func, dev, lb)
@DCMattyG DCMattyG requested a review from Copilot April 2, 2026 01:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 147 out of 207 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 4 to 6
| where isnotnull(tags["ipam-res-id"])
| extend prefixes = properties.addressSpace.addressPrefixes
| project id, prefixes, resv = tags["ipam-res-id"]
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The reservation settlement workflow has multiple sources in this PR (docs/examples) standardizing on the X-IPAM-RES-ID tag key, but the Resource Graph query still filters/projects tags["ipam-res-id"]. This will prevent reservations from being detected/settled if the deployed VNets are tagged with the new key. Update the query (and any downstream handling that depends on resv) to use X-IPAM-RES-ID, or support both keys (e.g., prefer X-IPAM-RES-ID but fall back to ipam-res-id) for backward compatibility.

Suggested change
| where isnotnull(tags["ipam-res-id"])
| extend prefixes = properties.addressSpace.addressPrefixes
| project id, prefixes, resv = tags["ipam-res-id"]
| extend resv = coalesce(tostring(tags["X-IPAM-RES-ID"]), tostring(tags["ipam-res-id"]))
| where isnotnull(resv)
| extend prefixes = properties.addressSpace.addressPrefixes
| project id, prefixes, resv

Copilot uses AI. Check for mistakes.
} else {
newOptions.title.show = true;
newOptions.legend.selected = Object.fromEntries(
newOptions.series.map(s => [s.name, false])
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

In the else branch (no selection), legend.selected is being set to false for every series. In ECharts, legend.selected[name] = false hides that series, so this will likely render an empty chart when no search selection is active. For the “no selection” state, you probably want either (a) all series selected (true) or (b) to remove/reset legend.selected entirely so ECharts defaults apply.

Suggested change
newOptions.series.map(s => [s.name, false])
newOptions.series.map(s => [s.name, true])

Copilot uses AI. Check for mistakes.
Comment on lines 694 to 697
disabled={ showSubnets ? (!selectedSubscription || !selectedNetwork || !selectedMask) : (!selectedSpace || !selectedBlock || !selectedMask) }
variant="contained"
loading={sending}
onClick={onSubmit}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

loading={sending} was carried over from LoadingButton, but MUI Button does not consistently support a loading prop (depending on MUI version/component APIs), which can result in no spinner and/or an invalid DOM attribute warning. If you still need loading behavior, switch back to LoadingButton (lab) or implement loading UX explicitly (disable + show CircularProgress via startIcon/endIcon).

Suggested change
disabled={ showSubnets ? (!selectedSubscription || !selectedNetwork || !selectedMask) : (!selectedSpace || !selectedBlock || !selectedMask) }
variant="contained"
loading={sending}
onClick={onSubmit}
disabled={
sending ||
(showSubnets
? (!selectedSubscription || !selectedNetwork || !selectedMask)
: (!selectedSpace || !selectedBlock || !selectedMask))
}
variant="contained"
onClick={onSubmit}
startIcon={sending ? <CircularProgress size={16} /> : null}

Copilot uses AI. Check for mistakes.
Comment on lines +186 to +188
<Button onClick={onSubmit} loading={sending} disabled={!changed}>
Apply
</LoadingButton>
</Button>
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Same issue as elsewhere: loading={sending} is being passed to Button. If Button doesn’t support loading in your MUI version, this will silently drop the loading UI and may leak an invalid attribute to the DOM. Prefer a loading-capable component (e.g., LoadingButton) or explicitly render a spinner while sending is true.

Copilot uses AI. Check for mistakes.

# Execute Startup Script
ENTRYPOINT ./init.sh ${PORT}
ENTRYPOINT ["/bin/sh", "-c", "./init.sh ${PORT}"]
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Using sh -c without exec can interfere with proper signal forwarding (SIGTERM/SIGINT) and graceful shutdown inside containers because PID 1 remains the shell rather than your process tree. Consider using exec ./init.sh ${PORT} in the shell command (as you already do in the repo root Dockerfiles) so the init script becomes PID 1 and receives signals reliably.

Suggested change
ENTRYPOINT ["/bin/sh", "-c", "./init.sh ${PORT}"]
ENTRYPOINT ["/bin/sh", "-c", "exec ./init.sh ${PORT}"]

Copilot uses AI. Check for mistakes.
</Provider>
</MsalProvider>
</React.StrictMode>
);
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

msalInstance.initialize() is awaited via .then(...), but there’s no .catch(...) handler. If initialization fails (misconfig, storage issue, blocked third-party cookies, etc.), the app will never render and the failure may surface only as an unhandled promise rejection. Add a catch path that logs the error and renders a minimal fallback (or triggers a controlled redirect), so failures are diagnosable and don’t leave a blank page.

Suggested change
);
);
}).catch((error) => {
// Log initialization failures so they are diagnosable.
// Without this, users may see only a blank page.
console.error("MSAL initialization failed:", error);
// Do not attempt to render UI inside MSAL's hidden iframe.
if (isInHiddenIframe) {
return;
}
// Render a minimal fallback so the user is not left with a blank screen.
root.render(
<React.StrictMode>
<div style={{ padding: "1rem", fontFamily: "sans-serif" }}>
<h1>Authentication initialization failed</h1>
<p>
We were unable to start the authentication service. Please refresh the
page or try again later. If the problem persists, contact support.
</p>
</div>
</React.StrictMode>
);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants