Skip to content

chore: added design-docs for account management api#152

Draft
christian-kreuzberger-dtx wants to merge 5 commits intomainfrom
feature/implement-account-management
Draft

chore: added design-docs for account management api#152
christian-kreuzberger-dtx wants to merge 5 commits intomainfrom
feature/implement-account-management

Conversation

@christian-kreuzberger-dtx
Copy link
Copy Markdown
Collaborator

@christian-kreuzberger-dtx christian-kreuzberger-dtx commented Apr 15, 2026

Description

New design docs for dtctl iam and dtctl account, based on https://github.com/dynatrace-oss/dtctl/blob/docs/account-namespace-design/docs/dev/ACCOUNT_NAMESPACE_DESIGN.md from @discostu105

Related Issues

Closes #

Testing

  • Tests pass (make test)
  • Linting passes (make lint)

@christian-kreuzberger-dtx christian-kreuzberger-dtx force-pushed the feature/implement-account-management branch from 3e247f4 to b8208fa Compare April 16, 2026 10:11
@dynatrace-oss dynatrace-oss deleted a comment from Copilot AI Apr 16, 2026
@christian-kreuzberger-dtx christian-kreuzberger-dtx force-pushed the feature/implement-account-management branch 2 times, most recently from 9a5b042 to d136516 Compare April 22, 2026 06:52
@christian-kreuzberger-dtx christian-kreuzberger-dtx force-pushed the feature/implement-account-management branch from d136516 to d7eae72 Compare April 22, 2026 07:30
Comment thread docs/dev/ACCOUNT_MANAGEMENT_DESIGN.md Outdated

```go
// iamBaseURLForTier returns the IAM service URL for the detected tier
func iamBaseURLForTier(env Environment) string {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We're currently in the process of switching to new authz subdomains for service that exposes /access-info endpoint. We'll keep existing one available for now, but at some point in time we would like to sunset those listed in this function.

Up to date list of new subdomains for this service can be found here: https://dt-rnd.atlassian.net/wiki/spaces/PIET/pages/71338308/Domain+Schema#Authorization-services-domains-(authz)

dtctl iam get policies --level global
dtctl iam get bindings --level environment [--level-id <envID>] [--group NAME]
dtctl iam get boundaries --level environment [--level-id <envID>]
dtctl iam get environments
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not sure if there is a case for making environments subcommand IAM specific from user point of view.

Comment thread docs/dev/ACCOUNT_MANAGEMENT_DESIGN.md Outdated
**dtctl focuses on the policy bindings system** as it is the current platform
approach. The key concept:

1. **Policies** are created at a **level** (`account` or `environment`) and
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actually environment level policies are deprecated and aren't fully supported in UI. Users are encouraged to create policies on account level, so that they can be used for every environment within that account.

I would need to double check if we blocked that kind of operation on API level.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agree, will update to account level only.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated: default to account-level, allow overrides on environment-level. If one of the calls doesn't work with environment-level, please let me know.

Comment thread docs/dev/ACCOUNT_MANAGEMENT_DESIGN.md Outdated

### IAM API Endpoints

All under `https://api.dynatrace.com/iam/v1/accounts/{accountUUID}/`:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I wish our API would be consistent with the table below 😅 Or at least just "consistent".

Unfortunately iam/v1/accounts/{accountUUID}/ as prefix doesn't work. Would it be useful to have more explicit reference to OpenAPI spec while working on the design? see: https://api.dynatrace.com/spec-json

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'll reference the spec and will verify endpoints

Comment thread docs/dev/ACCOUNT_MANAGEMENT_DESIGN.md Outdated
# Uses the same --level as the policy.
dtctl iam create binding \
--policy "event-writer" \
--group "Event Writers" \
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is there a pattern in dtctl that users work with friendly names of Dynatrace entities (documents, dashboards, etc.)?

Binding API endpoints definitely require raw group uuid in payload or path, so to make it work with friendly name dtctl would need to do a lookup for group uuid.

Copy link
Copy Markdown
Collaborator Author

@christian-kreuzberger-dtx christian-kreuzberger-dtx Apr 24, 2026

Choose a reason for hiding this comment

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

yes, e.g., for Dashboards and for Workflows.

dtctl iam create binding --policy "viewer" --group "Team" [--boundary "prod"] # account level (default)
dtctl iam create binding --policy "viewer" --group "Team" --level environment [--level-id <envID>] [--boundary "prod"]
dtctl iam create boundary --name "prod" --zones "Production,Staging" # account level (default)
dtctl iam create boundary --name "prod" --zones "Production,Staging" --level environment [--level-id <envID>]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

--zones sounds wrong, both as a parameter name and suggested content of that parameter.

I think that this was meant as a parameter similar to --statement for policies. I'd go for something like --conditions, because that's what you put into boundary definition. You can get some samples here:

|--------|-----------|------------|---------|
| **IAM** | `/iam/v1/accounts/{uuid}/...` | `account-idm-read/write` | Users, groups, policies, bindings, boundaries |
| **Subscriptions** | `/sub/v2/accounts/{uuid}/...` | `account-uac-read` | DPS subscriptions, usage, cost |
| **Cost allocation** | `/v1/accounts/{uuid}/settings/costcenters`, `/v1/accounts/{uuid}/settings/products` | `account-uac-read` | Cost center and product field values |
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
| **Cost allocation** | `/v1/accounts/{uuid}/settings/costcenters`, `/v1/accounts/{uuid}/settings/products` | `account-uac-read` | Cost center and product field values |
| **Cost allocation** | `/v1/accounts/{uuid}/settings/costcenters`, `/v1/accounts/{uuid}/settings/products` | `account-uac-read` | Costcenter and product field values |

The API path uses costcenters (one word). This rename should be applied throughout:

  • L76: "Costcenter and product CRUD"
  • L1060: dtctl account get costcenters
  • L1228: "Costcenters/products" in pagination table
  • L1249: GetCostcenters in package layout
  • L1408: "costcenter/product write operations"
  • L1425: "costcenters, products" in scope table
  • L1432: "costcenter/product management"
  • L1484: "Costcenter/product management"
  • L1538: "List costcenters" in appendix

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Why would we not just call it "Cost center"? That's also the official wording in Dynatrace Docs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Oh, my bad, seems like this got renamed in the docs, somehow remembered that "costcenters" was used everywhere ... its just important that the query-parameters + url-paths are always written as one word.

# Note: subscription-level `dtctl account get usage` (without --per-environment) is
# deferred to Phase 2a pending API time-filter support (see Phase 2).
dtctl account get cost --subscription <uuid> [--granularity daily|weekly|monthly]
dtctl account get cost --subscription <uuid> --per-environment --from 2026-01-01 --to 2026-03-31
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replace lines 1038--1043 with:

# Usage & Cost
dtctl account get usage              [--subscription <uuid>] [--capability <key>]
dtctl account get usage --per-environment [--subscription <uuid>] [--from <date>] [--to <date>] [--env <id>] [--capability <key>] [--cluster <id>]
dtctl account get cost               [--subscription <uuid>] [--capability <key>]
dtctl account get cost  --per-environment [--subscription <uuid>] [--from <date>] [--to <date>] [--env <id>] [--capability <key>] [--cluster <id>]

Ship both subscription-level and per-environment in Phase 1. Subscription-level is pass-through — no --granularity, no --from/--to (the API doesn't support time filters on these endpoints).

dtctl has zero precedent for client-side aggregation — all existing resource handlers are strict pass-through. Adding domain-specific rollup logic would break the "No Leaky Abstractions" principle from API_DESIGN.md.

Having dtctl account get usage --per-environment work but not dtctl account get usage would also be a UX problem — users expect the broad command before narrowing.

Also adds missing --cluster <id> to get cost --per-environment — the v3 cost endpoint supports clusterIds (Managed only) just like usage.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

An alternative worth considering: defer all 4 cost/usage commands to Phase 2 (both subscription-level and per-environment). This avoids the warning UX and keeps Phase 1 focused on subscriptions, forecast, and rate-card.

The tradeoff:

  • Option A (current suggestion): Ship per-environment (server-side filters) + subscription-level (pass-through with warning) in Phase 1. Full cost/usage coverage from day one, but the subscription-level warning is a bit noisy.
  • Option B: Defer all 4 to Phase 2, ship them together once the API has startTime/endTime. Cleaner UX, no warnings, but Phase 1 has no cost/usage commands at all.

Both options avoid client-side aggregation.

dtctl account get products
dtctl account get cost-allocation --subscription <uuid> --env <id> --field COSTCENTER|PRODUCT [--from <date>] [--to <date>]
# Note: --env is REQUIRED (no account-wide mode in Phase 1); --from/--to default to current month.
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replace lines 1059--1064 with:

# Cost Allocation (Phase 3)
dtctl account get costcenters
dtctl account get products
dtctl account get cost-allocation --subscription <uuid> --env <id> --field COSTCENTER|PRODUCT [--from <date>] [--to <date>]
# --env is REQUIRED; --from/--to default to current month. See Cost Allocation Guardrails.

Rename cost-centerscostcenters. Mark as Phase 3. Simplify env note — constraint is permanent.

| Get usage | GET | `/sub/v2/accounts/{uuid}/subscriptions/{subUuid}/usage` | 2a |
| Get usage/env | GET | `/sub/v3/accounts/{uuid}/subscriptions/{subUuid}/environments/usage` | 2a |
| Get cost | GET | `/sub/v2/accounts/{uuid}/subscriptions/{subUuid}/cost` | 1 |
| Get cost/env | GET | `/sub/v3/accounts/{uuid}/subscriptions/{subUuid}/environments/cost` | 1 |
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replace lines 1097--1100 with:

| Get usage | GET | `/sub/v2/accounts/{uuid}/subscriptions/{subUuid}/usage` | 1 |
| Get usage/env | GET | `/sub/v3/accounts/{uuid}/subscriptions/{subUuid}/environments/usage` | 1 |
| Get cost | GET | `/sub/v2/accounts/{uuid}/subscriptions/{subUuid}/cost` | 1 |
| Get cost/env | GET | `/sub/v3/accounts/{uuid}/subscriptions/{subUuid}/environments/cost` | 1 |

Move usage to Phase 1 (pass-through). Note: the latest PR already fixed the v3 path for usage/env — good.

dtctl adds `--granularity daily|weekly|monthly` (default `monthly`) and
performs the rollup client-side before printing. This is safe because the
response size is bounded -- subscriptions are capped at 1 year, so the
daily record count is bounded at roughly 365.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replace lines 1112--1119 with:

**Subscription-level cost and usage (pass-through with warning).** Both
subscription-level endpoints return the full subscription history in a single GET
with no server-side time filter or aggregation. dtctl passes the response through
as-is (no client-side rollup or trimming) but warns on stderr:

    Warning: subscription-level usage returns the full history (1+ MB).
    Use --per-environment with --from/--to for narrower results,
    or --capability <key> to filter server-side.

The cost endpoint response is small enough (~50 KB) that no warning is needed.
The usage warning is triggered unconditionally on the subscription-level path.

When the API adds `startTime`/`endTime` parameters, dtctl will expose them as
`--from`/`--to` passthrough flags (see Phase 2a).

> **Design note:** dtctl follows a pass-through design — resource handlers return
> API responses without client-side aggregation or transformation. Adding
> `--granularity` rollup or `--from`/`--to` client-side trimming would be the only
> instance of domain-specific business logic in the CLI. See API_DESIGN.md,
> "No Leaky Abstractions" rule.

Replace --granularity client-side rollup with pass-through + stderr warning. The design note should live in the doc itself so future contributors understand why we chose not to aggregate client-side.

subscription history is always returned, which makes it unsafe to ship as
a CLI default. When the API adds time filtering, the subscription-level
`UsageHandler` methods are added and `dtctl account get usage` (without
`--per-environment`) is enabled.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replace lines 1391--1399 with:

**Phase 2a (waiting on API time filters).** Subscription-level cost and usage
currently download the full history (cost: ~50 KB, usage: 1+ MB) because the API
lacks `startTime`/`endTime` parameters. Phase 1 ships these as pass-through with
a stderr warning on usage. When the API adds server-side time filtering, dtctl
exposes `--from`/`--to` as passthrough flags on both subscription-level commands,
eliminating the unnecessary data transfer and the warning.

Phase 2a is now an optimization, not a blocker.

- `account get cost-allocation` (per-environment, required `--env`)
- Effective permissions analysis (uses `iam:effective-permissions:read`
scope, added in this phase only)
- (Optional) cost center/product write operations
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replace lines 1404--1408 with:

- `account get costcenters`, `get products`, `get cost-allocation`
  (per-environment, required `--env`; see Cost Allocation Guardrails for
  page-cap behavior and scale limitations pending API granularity support)
- `CostcentersHandler`, `ProductHandler`, `CostAllocationHandler`
- Effective permissions analysis (uses `iam:effective-permissions:read`
  scope, added in this phase only)
- (Optional) costcenter/product write operations

Move costcenters/products/cost-allocation to Phase 3.

| Get usage | GET | `/sub/v2/accounts/{uuid}/subscriptions/{subUuid}/usage` | No | 2a (needs `startTime`/`endTime`) |
| Get usage/env | GET | `/sub/v3/accounts/{uuid}/subscriptions/{subUuid}/environments/usage` | Yes (cursor) | 2a (needs `startTime`/`endTime`) |
| Get cost | GET | `/sub/v2/accounts/{uuid}/subscriptions/{subUuid}/cost` | No | 1 |
| Get cost/env | GET | `/sub/v3/accounts/{uuid}/subscriptions/{subUuid}/environments/cost` | Yes (cursor) | 1 |
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replace lines 1527--1530 with:

| Get usage | GET | `/sub/v2/accounts/{uuid}/subscriptions/{subUuid}/usage` | No | 1 (pass-through, warns on large response) |
| Get usage/env | GET | `/sub/v3/accounts/{uuid}/subscriptions/{subUuid}/environments/usage` | Yes (cursor) | 1 |
| Get cost | GET | `/sub/v2/accounts/{uuid}/subscriptions/{subUuid}/cost` | No | 1 (pass-through) |
| Get cost/env | GET | `/sub/v3/accounts/{uuid}/subscriptions/{subUuid}/environments/cost` | Yes (cursor) | 1 |

Move to Phase 1 as pass-through.

| Access info (IAM svc) | GET | `{iamBaseURL}/api/public/environment-access/access-info` | No | 1 |
| Get cost allocation | GET | `/v1/accounts/{uuid}/settings/costcenters` (per field) | Yes (cursor) | 3 |
| List cost centers | GET | `/v1/accounts/{uuid}/settings/costcenters` | Yes (offset) | 1 |
| List products | GET | `/v1/accounts/{uuid}/settings/products` | Yes (offset) | 1 |
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replace lines 1538--1539 with:

| List costcenters | GET | `/v1/accounts/{uuid}/settings/costcenters` | Yes (offset) | 3 |
| List products | GET | `/v1/accounts/{uuid}/settings/products` | Yes (offset) | 3 |

Move to Phase 3.

auto-discovery must be disabled for client-credentials contexts and an
explicit `account-uuid` required instead.

3. **Rate limiting.** The account management API may have stricter rate limits
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

current configuration:

  • /iam/.* endpoints: 600 requests/min per user
  • all others: 200 requests/min per user

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants