All notable changes to libre-devops-helpers are recorded here. The project follows Semantic Versioning.
- The Message Center and Planner page shows how to register the app their delegated Graph
scopes need (
ServiceMessage.Read.All,Tasks.ReadWrite), and the profile to sign in with.
- GitLab's SAST (Semgrep) has nothing to report: a retry's jitter comes from the system's random source, and reading an analyzer result's XML, which refuses a document type first, is marked a false positive for it as it is for ruff.
ldo terraform sortputs a Terraform module's variables (variables.tf,--inputs) and outputs (outputs.tf,--outputs) in name order, as terraform-docs lists them, then runsterraform fmt(ortofu fmt). It reads blocks as Terraform does, so a}in a heredoc or a string never ends one, one-line blocks sort too, and comments above a block move with it while the rest of the file stays as it was.-rtakes the folders beneath too, and--checkchanges nothing and exits 3 when a file is out of order.ldo terraform docswrites a module's README from itsHEADER.mdand terraform-docs (found onPATH, with the module's own.terraform-docs.ymlwhen it has one);-rtakes each folder beneath with aHEADER.md, and--checkexits 3 when a README is out of date.ldo planner add-rollup PLAN --bucket NAMEraises one task a month summing up that month's Message Center posts (Message Center rollup: 2026-09 (12 messages), with the counts by severity, service and category and a line for each post), and brings one the plan has already up to date. Likeadd-news, it only says what it would do without--write.just rebrand --accent "#RRGGBB"sets the colour of an-o htmlpage's header, so a renamed copy's reports carry its own colour.
ldo planner add-newsraises tasks as Microsoft's own Message Center sync to Planner lays them out: titled[Service] Title [MC1183010], the notes starting with the post's id, published date, category and tags, then its link and text. It counts a task titled that way as the post's, so a board that sync fills gets no second task for a post.--layout shortraises them as 0.7.0 did (MC1183010: Title).
xdr analyzerreads MDE Client Analyzer results, one or many: the zip the analyzer writes on Windows, or the support tool's on Linux and macOS, the folder it unpacks to, or the results XML. Each device's findings come errors first, with--severityto keep the worst,--guidancefor what to do, and--factsfor its OS, versions and services. Nothing is sent anywhere or unpacked to disk. See MDE Client Analyzer results.-o htmlwrites any data command's table as a styled page that stands alone: the command, counts of what is ok, needs attention or failed, the command's notes, and a table that sorts, filters and copies as CSV, in light or dark. In a terminal it is written to a file here and opened in the browser; redirected, to stdout. Nothing is fetched, and it is loaded only when asked for. See HTML reports.jiraandconfluenceread Atlassian Cloud as your own account, with an API token (JIRA_INSTANCE,JIRA_EMAILandJIRA_TOKEN, or[atlassian]profiles): Jira issues by JQL or project, one issue with its description as Markdown, and projects; Confluence spaces, pages, one page as Markdown, and CQL search. See Jira and Confluence.newsreads the Microsoft 365 Message Center: posts by change date (--date today,29/09/2026, a span,last 7d, or--since 7d), by part of a service's name (--service xdr), the security services at once (--security), category and major change, and one post with its text as Markdown. It needsServiceMessage.Read.All, so your own app registration.plannerreads Planner plans, buckets and tasks, andplanner add-news PLAN --bucket NAMEraises a task for each Message Center post the plan has none for yet (by the post's id at the start of a task's title), with its link and text. It only says which without--write, the one change it makes. See Message Center and Planner.
- A Graph service that is not on in the tenant (
TenantDisabled, as Planner answers without a licence that includes it) says so, rather than blaming a missing permission.
azure automation logsandoutputrefused a job a schedule started, whose id isSCH_, two GUIDs and a timestamp joined by underscores, as "not a job id", even when they picked the newest job themselves with--runbook.
azure automation jobs,logsandoutputstopped at the second page of a busy account's jobs, or of a long job's logs, with "refusing to send a token to https://management.azure.com:443": Resource Manager's next links name the port HTTPS uses anyway. A next link now has to match the service's scheme, host and port, the default port being the same as none, and one with a user name before its host is refused.
- The container image builds behind a package proxy:
--build-arg PACKAGE_INDEX=URLtakes every Python package from that index, each checked againstuv.lock's hash, with the index's login as a build secret (--secret id=netrc,src=FILE) that never reaches a layer, and a certificate bundle for an index behind your own authority (--secret id=ca-bundle,src=FILE);--build-arg DEBIAN_UPGRADE=falseleaves out the Debian updates where the mirrors cannot be reached. See Container images.
- A picture in the welcome banner:
just rebrand --banner-picture FILEdraws a logo above the banner's words in its own colours, two pixels to a character with half blocks, and in plain ASCII where there is no colour. See Rebranding. - Colours as
#RRGGBB(and behind the text as well as in front) for anything the tool styles: exact where the terminal shows 24-bit colour, else the nearest of the 256.
just rebrandto a name that sorts elsewhere (one beforefakes, an error class beforeConfigError) sorts the imports and__all__again, so its own checks pass; the prefix on its own (`LDO_`) and hyphenated names (ldo-azure) are renamed too, and shell scripts are rewritten with the rest. Two lines that a longer display name or prefix pushed past the line length have room now.ldo logicapp validatenames its probe workflow after the command, not a fixedldo.
- A MET column in
ldo devices checkandwatch: how many of its checks each device meets,4/4when complete, so--sort met:descputs the complete devices first.
- A warning from the library reads as the command's own do (
warning: ..., in colour on a terminal), without the time and the logger's name;-vshows them again.
- The warning about rows hidden or filtered out in Excel counts each name once, where a name
on several rows counted each time, says
--whereis the way to pick rows, and is not given when--wherepicked them.
- Steering files for Kiro in
.kiro/steering/, one for each section ofAI.md: the product, the stack, the layout, the code, the tests, the docs, releases and the pitfalls, each loaded always or only while a file it covers is open.just aiwrites them fromAI.md, withAGENTS.md, and a test keeps them in step, so there is still one file to edit.
The changes below, and those in the release candidates 0.5.1rc1 to 0.5.1rc3.
- Each release is also published to the GitLab copy of the repository,
gitlab.com/libre-devops/python-helpers:
the wheel and sdist to its package registry, both images (
linux/amd64) to its container registry, and a GitLab release. The repository is mirrored there automatically, and its GitLab CI runs the same checks as GitHub's. --where "COLUMN=VALUE"(or!=) on every command that reads names from a file: only the rows of the CSV or workbook where another column holds that value, e.g.ldo devices watch -f plan.xlsx --column FQDN --where "Scheduled Date=today". Repeatable, one column's values as alternatives. Dates can betoday,tomorrow,yesterday,2026-09-25, or UK or US (25/09/2026,09/25/2026), and spans of them2026-09-01..2026-09-14(either end may be left open),last 7dandnext 7d. A date that could be UK or US is read as its span or its column shows, or refused.keyvault expirytakes-f FILEwith--column,--sheetand--where, as the device commands do.
- A date or time cell in a workbook reads as the day or time it shows (
2026-09-25,09:00:00), where it read as the number Excel keeps it as (46290).
ldo keyvault expiry --all-vaults. It sent a request to every vault in the tenant, and each one you cannot read refuses and logs it, which Defender for Key Vault can take for reconnaissance. Name the vaults you look after instead, as arguments or with-f vaults.txt.ldo self-testno longer sweeps vaults either: it checks one with--vault NAME.
ldo keyvault expirysays when none of the vaults named could be read, and counts them as "checked in 0 of 3 vault(s)", where it said "0 vault(s)" as if there were none.- A window you give shows as you gave it (
30d, not30d 00h) inkeyvault expiry,xdr stale,xdr alerts,entra app-credentialsandentra sign-ins. ldo self-testexplains a command that exited 1 by the warnings it wrote, with their hints, not by its last line (often only its summary), and says how many cases it left out for want of--device,--user,--groupor--workspace.
ldo azure parse-id ID...: split Azure resource ids into their parts, offline: the subscription, resource group, name, type, the resources above a child resource, and what an extension resource is on; management group ids too.-o jsonhas the keys Terraform'sprovider::azurerm::parse_resource_idgives, plusidandmanagement_group_name. Ids come from arguments, stdin or-f.--workspaceonldo logs queryandldo logs ingestiontakes a workspace's resource id or its name as well as its Workspace ID (the GUID), and looks the Workspace ID up, with a note saying which workspace it found. The resource id of anything else is refused, saying what it is the id of.- A profile's
workspacekey, which takes any of the three the same way.workspace_idstill works, for the Workspace ID alone, and now says so when given the resource id. - For library use:
microsoft.resource_ids(parse_resource_id,ResourceId),microsoft.workspaces(workspace_ref),AzureClient.workspace()andLogAnalyticsWorkspace. ldo self-testshows each failure in full, with its hint, after the table, which cuts long details short.
ldo xdr timeline --endpointfailed with HTTP 400: the Defender for Endpoint API has no alert tables. Alerts are now left out there, with a note, and--type alert --endpointis refused with the reason.- A Graph refusal for a missing Entra ID P1 or P2 licence, or for a user without one of the roles an API is limited to (sign-in logs, for one), now says which, not only that a permission is missing.
- Automation account ids, Defender for Cloud assessments and PIM scopes are read with the same resource id parser, so each part is checked the same way everywhere.
ldo xdr timeline DEVICE: a device's events, newest first, as the portal's timeline shows them: processes, network connections, files, registry, logons, image loads, other device events and alerts, with--typeto choose, a window (--since,--today,--fromand--to) and--limit. Defender has no API for the timeline itself, so this is one Advanced Hunting query over the device tables, through Graph or--endpoint; the docs say what that means (30 days, a row limit, the events without the portal's extras).ldo xdr detections list,showandexport: Defender XDR's custom detection rules, the detections Sentinel runs from the Defender portal, read through Graph.listexits 3 when Defender has turned a rule off itself (autoDisabled), the one sign of failing runs left once the legacylastRunDetailsgoes on 2026-10-01.export(andshow --yaml) writes each rule as a YAML file for terraform-msgraph-xdr-custom-detection-rules, in its folder layout, checked against its schema, withTODO(export)comments for what needs review.ldo logs ingestion: which tables a workspace is receiving, from itsUsagetable: when each last got data, for how long it has been quiet, and its GB and billable GB. Quiet tables come first, and it exits 3 when there are any.--fromand--totake a time as well as a day:--from 2026-09-24T09:00 --to 2026-09-24T12:30, local time, or UTC with aZ. Forxdr incidentsas well.-o tsvon every data command: tab-separated values, one row a line, with no header, as the Azure CLI's-o tsv, for shell pipelines. A tab or line break inside a value becomes a space.--colourand--no-colour(or--color,--no-color) before any command: colour, or none, whatever the output is, e.g. forless -R.FORCE_COLORturns colour on too.--sort COLUMN[:desc]and--unique COLUMNon every list, by the column names the table shows:ldo xdr vulns web01 --sort severity:desc --sort cvss:desc, orldo xdr machines -f hosts.txt --sort "last seen:desc" --unique device. Numbers, versions, severities and dates sort as such, names naturally (web2beforeweb10), blanks last. For the table, CSV and TSV; JSON is left forjq.core.sorting(sort_records,unique,natural_key) andcore.colour(the colour decision, ANSI styles, coloured JSON), for library use as well as the CLI.- For library use:
GraphServiceClientandArmServiceClient(microsoft.api_clients) andcore.http.ServiceClient, which every client now builds on, so each hascreate,for_profileandwithalike (ServiceNow'sTableClientgainswith);core.fieldsfor reading API JSON;core.util.require_guid.ApiClient(network_settings=...)gives one client its own proxy and certificates;EntraClient.look_up_deviceslooks names up and checks group membership in one call;core.probeholds the network test's probe;core.auth.Pkcethe proof key a browser sign-in uses.
ldo network testprinted a proxy address's password (http://user:password@proxy, asHTTPS_PROXYoften holds) in its table, its JSON and its hints. Proxy addresses are now shown asuser:***@everywhere; the real one is still what calls go through.
- Two commands signing in at once (two terminals, say) could lose one's kept sign-in, or fail on a shared temporary file. The token cache is now changed under a lock file.
- Names and ids checked before they go into a URL (machine ids, job and stream ids, vault and Logic App names, profile names) let a trailing line break through. They no longer do.
entra deviceslists a name's devices most recently signed in first, so the ones marked as older records are older.network testreads a TLS-inspecting proxy's certificate through a proxy that wants a user and password, as the calls themselves do.
pim settings -o jsonwrites snake_case keys with real values ("requires_mfa": true,"max_activation": "PT8H"), as every other command's JSON does. It wrote the table's labels ("Needs MFA": "yes"). Every command's JSON shape is now held by a test.- Something given that cannot be used (a malformed id, an empty query, an unknown
severity or resource) is an
InputErroreverywhere, for library callers catching it; a profile that cannot do what was asked is aConfigError. - On a terminal, a table's last column is cut to fit the window, with an ellipsis, so a long message (an error's detail, say) no longer wraps across the table. Piped, redirected, CSV, TSV and JSON output keep every character.
self-test's progress lines line up, whatever the count.- The code is checked harder: mypy in strict mode, ruff's security rules, a complexity limit of 10 per function (the device checker, the token checks and several commands are split into named steps), and a docstring on every public module, class and function. The API clients share their setup, models read API JSON one way, and the devices, entra, logicapp and xdr commands are split into a module per area. None of it changes what a command does, and a test now proves that for every command's JSON.
ldo self-test(hidden from--help): runs every read-only command against a device, user and group you name, discards their output, and reports each as ok, attention, refused, usage or CRASH, a crash with the lines ofldoit came through.--reportwrites it all to a file. For trying a build in a real tenant before a release.
xdr vulns(and anything showing a date) crashed withOverflowErroron Defender's "not known" date,0001-01-01. Such dates now show as-, and a date the platform cannot convert to local time is shown in UTC rather than failing.- A short name found nothing when Defender (or Entra) knows the device by its FQDN. It is
now looked for as the first label of one:
web01findsweb01.corp.example, neverweb010.corp.example.xdr machinesmarks such a matchprefix.
- Corporate proxy support. Every HTTPS call, and every
azldoruns, follow one set of rules: loopback and the metadata endpoint always direct, thenno_proxyandNO_PROXY, thenLDO_PROXY_ADDRESS, the config file'sproxy,HTTPS_PROXY/HTTP_PROXY/ALL_PROXY, and the operating system's setting. An address without a scheme meanshttp://, as for cntlm and Px, which sign in to an NTLM or Kerberos proxy for you. - The operating system's certificate store is trusted by default, with the public roots
and the config file's
ca_bundle, in one bundle the Azure CLI is handed asREQUESTS_CA_BUNDLEtoo, so both work behind a TLS-inspecting proxy whose root IT has installed.LDO_CA_BUNDLE,REQUESTS_CA_BUNDLEorCURL_CA_BUNDLEnames a bundle to use exactly instead. No new dependency. ldo network test: asks Entra ID, Graph, Azure Resource Manager, Defender and each ServiceNow instance (and any--url) for an unsigned answer, and says which proxy each call used, which certificates are trusted, and what to try when one fails: a proxy's NTLM sign-in (cntlm or Px), a proxy that is not running, a TLS-inspecting proxy's certificate (with its issuer named), or a local proxy listening on 3128 or 3129.AI.md: instructions for AI coding assistants, read by Claude Code (throughCLAUDE.md), GitHub Copilot (.github/copilot-instructions.md, andAGENTS.mdfor its coding agent) and Codex (AGENTS.md).AGENTS.mdis written from it byjust ai, and a test fails when it falls behind.
- The README installs from PyPI (
uv tool install,pipx,uv piporpip), with a PyPI badge; installing a tag from GitHub is still shown.
ca_bundlereplaced the public roots rather than adding to them, so a host a proxy does not inspect (sign-in often is not) failed to verify, and it never reached the Azure CLI. It now adds to the public roots and the OS store, for both.
- Releases can publish to PyPI through trusted publishing, once the repository variable
PUBLISH_PYPIistrue: the same wheel and sdist, last, after the images and the GitHub release. The README PyPI shows has its relative links pointed at GitHub.
- The package metadata links to the docs and the changelog, and its keywords name Graph, Sentinel, PIM, Logic Apps and ServiceNow.
-
ldo entra devices: look devices up in Entra ID by name (arguments, stdin, or-fwith a file or workbook column), and with--groupcheck each is in an Entra group, named by its object id or display name. Each group's members are fetched once; nested membership counts unless--direct. Exits 3 when a device is missing, or not in every group. -
Defender device groups:
xdr machinesshows each machine's device group, anddevices checkandwatchtake--device-group NAMEas an expectation. -
ldo azure automation:accounts,jobs(a runbook's recent runs, with--runbook,--status,--failedand--since),logs(a job's output, warning, error and other streams, oldest first, with why it failed; the newest job by default) andoutput(the job's output as text, for piping). An account is named by name or resource id.jobsandlogsexit 3 on a failed job. -
-o jsonis coloured on a terminal (keys, strings, numbers, booleans, and brackets in the banner's rainbow by depth); piped, it stays plain JSON. -
ldo json: pretty-print JSON from stdin or a file, one document or JSON Lines, in colour on a terminal, with--sort-keys,--compact,--indentand--colour.--yamlwrites YAML instead, with the standard library alone: strings a YAML reader could misread are quoted, and multi-line strings become|blocks. -
OTLP logs take the same variables as
Write-LdoLog:LDO_SERVICE_NAME,LDO_SERVICE_VERSIONandLDO_DEPLOYMENT_ENVIRONMENTfor the resource, andLDO_TRACE_ID,LDO_SPAN_IDandLDO_CORRELATION_IDto put every record in a trace (an id that is not valid hex is left out).OTEL_SERVICE_NAMEandOTEL_RESOURCE_ATTRIBUTESare read too.
- With
--log-format otlporjson, stderr holds only log records: the notes, warnings and errorsldoprints become records (an error's hint an attribute), so the stream is clean JSON Lines for a collector. Tested against OpenTelemetry Collector 0.161 through theotlp_json_filereceiver, andfile_logwith theotlp_jsonconnector. - OTLP records use the semantic conventions' current attribute names,
code.function.name(fully qualified) andcode.line.number, in place of the deprecatedcode.functionandcode.lineno.
- Help text keeps its paragraphs together rather than breaking where the source lines did, and a test keeps help text free of what markdown would swallow.
- The README is a short front page: what
ldodoes, a table of every command group, install, a quickstart and links. The detail moved todocs/, one page per area, with its prose cut down, andldo --helplinks there. - Hints that said "see the README" link to the page they mean.
- The justfile groups its recipes, drops the ones that only wrapped
just run(use,token,config-init,check-devices,watch-devices), and addsaudit,secrets,build,lock,ci(everything CI checks) andrelease(checks, then tags and pushes). - The descriptions in
ldo --help, the package metadata and the image labels name what the tool now covers.
- A test runs every
ldoexample in the README and docs with--help, checks everyjustexample is a recipe, follows every link and anchor, and keeps the pinned install version in step with the release.
- The 0.2.0 release stopped before publishing the default image and the GitHub release:
its arm64 build compiled the Azure CLI's 12,000 files under emulation and ran out of
time. The Azure CLI's bytecode is now compiled on the build machine's own platform (it
is the same on every architecture), in seconds. Only
0.2.0-slimwas published. - Every push and pull request now builds both architectures, so a build that fails only under emulation is caught before a release.
- The default image's Azure CLI now runs cryptography 50.0.1 (CVE-2026-69247, CVE-2026-69248 and CVE-2026-69249). Azure CLI 2.90.0 pins msal 1.36.0, which caps cryptography below 49, so msal 1.39.0 is forced as well until Microsoft moves the pin.
config initcreates the config file readable only by you, rather than narrowing it after writing.
- The container scan reports findings that have a fix to the Security tab. The full list,
unfixed Debian findings included, is kept with each run as the
trivy-<variant>artifact. - Code scanning findings in the tests and scripts are fixed: URL checks compare the host, calls with side effects are made before they are asserted, protocol methods have docstrings rather than empty bodies, and the ServiceNow runtime no longer imports the runtime that imports it.
- Names from Excel workbooks:
-freads.xlsx,.xlsm,.xltxand.xltmas well as text and CSV, for exampleldo devices check -f plan.xlsx --column FQDN.--sheetpicks the tab; without it, the one visible sheet with the column is used. A header may sit below title rows, in CSV files too. Hidden and filtered rows are read, with a warning. The reader is the standard library alone: values as Excel saved them, no formulas recalculated, no macros run, and each part size-capped with document type declarations refused. Legacy.xls,.xlsb,.odsand password-protected files get a hint to save as.xlsxor.csv. - Container images on GitHub Container Registry, for
linux/amd64andlinux/arm64: the tool with the Azure CLI (latest), for signing in as yourself, and the tool alone (slim), on a digest-pinnedpython:3.14-slimbase, as an unprivileged user. Floating, exact and immutable stamped tags, with build provenance and SBOM attestations. - A patching pipeline for the images: every build is smoke tested and scanned with Trivy
(fixed high or critical findings fail the
slimimage; fixed critical ones fail the default image), a weekly run rebuilds the latest release with the newest Debian security updates and republishes only when a package changed, and Dependabot keeps the base images and the Azure CLI's lock file current. just image,image-slim,image-runandimage-scan, andjust coverage.- Line and branch coverage in CI, failing below a floor set in
pyproject.toml. The README's coverage badge comes from CI too:badges.ymlpublishes the total to abadgesbranch after each passing run on main, with no third-party service. - Signing in again when a sign-in lapses. When the Azure CLI's refresh token has run out
(a sign-in frequency policy, 90 days unused, a password change, a revoked session, new
MFA), the error now names the cause (
ReauthRequired, with the Entra ID code explained), and on a terminal the tool offers to sign the Azure CLI back in to that tenant, then carries on and restores the active account.devices checkandwatchask on the main thread and repeat a pass a lapse interrupted.LDO_REAUTH=device-codeoroffchanges or stops the question; scripts and CI never see it. - An
interactiveordevice-codeprofile now keeps its refresh token between commands, so you are not asked to sign in for each one.token_cachepicks where:file(the default: plaintext, 0600, refused if other accounts can read it, and it works headless),keychain(macOS Keychain or the Linux Secret Service through the new optionalkeychainextra, and DPAPI on Windows) ormemory(nowhere, as before).ldo entra sign-outforgets it. Access tokens are never kept. ldo graph: a fast way to read Microsoft Graph with a profile's sign-in.whoami(who the token is for, its scopes or roles, its expiry),token(asentra token graph),getfor any path or pasted Graph URL (paging with--alland--limit,--select,--filter,--search,--count,--orderby,--expand,--top,--beta, andConsistencyLevelwhen an advanced query needs it),get-user,get-device,get-group,get-appandget-spby name or id, andhunt. Read-only throughout.ldo devices av-signature(andldo device av-signature): the Defender Antivirus signature, engine and platform versions of devices, their antivirus mode and Defender's definitions check, from a built-in hunting query, through Graph or with--endpoint. Names come as fordevices check,-fworkbooks included.--at-leastflags older signatures,--show-queryprints the KQL, and it exits 3 for a device not found, out of date or behind.ldo xdr huntnow runs through Graph'srunHuntingQuery, covering every Defender XDR table (email, identity, cloud apps and alerts as well as devices), with--timespan;--endpointkeeps the Defender for Endpoint API, which the Azure CLI's sign-in can use.ldo logicapp, ported from the LibreDevOpsHelpers LogicApps module, for Consumption Logic App workflows (Sentinel playbooks among them):check(the offline contract checks, a gate with--strict),params,references(connection keys used and whether wired),connections,order(deploy tiers from dispatch actions),diff(across shapes),defaultsandrewrite(lifting between estates),export(deployed workflows to files) andvalidate(the resource provider's verdict, deploying nothing). Templates with unrendered Terraform tokens are read as they are.ldo xdr incidents top,latest,list,summaryandshow: Defender XDR's incident queue through the Graph security API, Sentinel's incidents included in the unified platform. Built-in windows (--today,--yesterday,--since, and--from/--tofor between days, taken whole in local time, or--updatedfor last updates), with--status,--severity,--source sentinel(and the other Defender services) and-n.showgives the alerts, devices, users and the portal link. NeedsSecurityIncident.Read.All, whichentra tokennow checks for.- ServiceNow:
ldo snow sign-in,sign-out,whoami,token,instanceandapps, and a[servicenow]config section (or justSNOW_INSTANCE_URLand friends). Sign-in is OAuth through an application registry entry: in a browser, pasting back where it lands (single sign-on, MFA and headless machines included), or with a password once; the refresh token, and a client secret typed in at sign-in, are kept in the private file.auth = "basic"remains for instances that allow it, and a 401 explains ServiceNow's new block on basic sign-in for interactive accounts. - An
interactiveprofile signs in with a device code when no browser can be opened (a headless machine), as the Azure CLI does. - A 401 is retried once with a new token, and a claims challenge (continuous access
evaluation) gets a hint to sign in again.
interactiveanddevice-codeprofiles say why they are signing in again.
- The tests now mirror the package (
tests/core,tests/microsoft/<feature>,tests/cli,tests/cli/commands), with the shared fakes split into afakespackage, one module per concern; a test keeps the two trees in step. Many more CLI paths are tested. - A release now publishes the container images before the GitHub release, so a release never exists without them.
- The README opens with the Libre DevOps logo and centred badges, as the organisation's profile does, with a rule between sections.
just useandjust tokenran commands that had moved underazandentra.- Four CLI tests failed in GitHub Actions, where Typer styles and wraps usage errors; they now compare the message's plain text.
- A sheet test's 70,000 character id failed on Windows, which caps an environment
variable (pytest's
PYTEST_CURRENT_TEST) at 32,767 characters; its cases have short ids.
- CI's dependency audit now covers the optional extras too.
First public release.
ldocommand line tool, installed as a console script, with command groups foraz,entra,xdr,intune,azure,keyvault,logsanddevices.- A config file at
~/.config/ldo/config.tomlwith a section per vendor. Microsoft profiles live under[microsoft.profiles.<name>]: named tenants, optionally pinned to a subscription, in thepublic,usgovorchinacloud.config initwrites a template andconfig pathshows where it is. - Credentials: the Azure CLI (the default), client secret, workload identity (a federated token file, or GitHub Actions OIDC) and managed identity (App Service and IMDS). Secrets come from the environment, never the config file.
profilesto list every profile;az useandaz whoamito switch the Azure CLI between profiles and show the active account.entra tokenandentra inspect-tokento get or decode an access token and check its expiry, issuer, audience and tenant, and which features its scopes or roles cover.devices check, a fast parallel check of many devices against expectations (in Entra, onboarded and active in Defender, tagged, in groups, enrolled and compliant in Intune);devices watch, which repeats it until complete within an interval, timeout and pass limit you set; anddevices show, one device across services with what looks wrong.entra device-groups,group-devices,group-members,user-groups,user-roles(active and PIM-eligible),sign-ins,app-credentialsandca-policies.xdr machines,stale,alerts,vulns,indicatorsandhunt(Advanced Hunting).intune devices.azure subscriptions,resource-graph,rbac,secure-score,recommendationsanddefender-plans.keyvault expiry, reading metadata only, for named vaults or every vault found through Resource Graph.logs queryfor Log Analytics and Sentinel workspaces.pim eligible,active(with--permanent-onlyfor standing access),requests,approvalsandsettings, across Azure resource roles, Entra roles and PIM for Groups, for the signed-in user or a named one. An area that cannot be read is a warning, not a failure. Everything reads; nothing activates or approves.interactive(browser, with PKCE) anddevice-codesign-in for your own public client app registration, for delegated scopes the Azure CLI's token lacks. Tokens and refresh tokens stay in memory for the one command.-o table|json|csvon every data command; names from arguments, stdin, text files or a CSV column;--log-format text|json|otlpand--log-level, which read the sameLDO_LOG_FORMATandLDO_LOG_LEVELvariables as LibreDevOpsHelpers.- Exit code 3 when a command ran and found something that needs attention, so scheduled jobs can alert on findings.
welcome, and a Libre DevOps unicorn banner (traced from the logo, coloured in diagonal bands) on bareldoandconfig init, shown only on a terminal.just rebrand, which renames the command, package, distribution, environment variable prefix, error class, repository links and banner for use under another organisation's name, with every name a parameter.- CI that scans every commit for secrets (gitleaks), audits the locked dependencies (pip-audit), tests on Python 3.11 to 3.14 and on Windows and macOS, and builds once; and a release workflow that publishes that build as a GitHub release when a version tag is pushed.
- Importable packages laid out by vendor (
core, thenmicrosoftand its features), with the layering between them enforced by a test.