close
Skip to content

Commit e3dc887

Browse files
howbazaarclaude
andauthored
fix: Migrate off deprecated GraphQL APIs (#118)
## Summary - Migrates `stacklet-admin` off GraphQL fields/mutations marked `@deprecated` in the platform schema, across the repository, account-group, policy-collection, and binding domains. - `repository` commands move from the legacy `repository`/`repositories`/`addRepository`/`removeRepository`/`processRepository` API to `repositoryConfig`/`repositoryConfigs`/`addRepositoryConfig`/`removeRepositoryConfig`/`triggerRepositoryScan`. As a result: - `repository add` drops `--branch-name`, `--policy-file-suffix`, `--policy-directory`, `--deep-import` (no longer accepted by `addRepositoryConfig`). - `repository process`/`scan`/`show`/`remove` take `--uuid` instead of `--url`; `repository scan` drops `--start-rev-spec`. - `account-group` commands move `AccountGroup.items`/`itemCount` to `accountMappings`, and `addAccountGroupItems`/`removeAccountGroupItems` to `upsertAccountGroupMappings`/`removeAccountGroupMappings`. `remove-item` keeps its existing `--uuid`/`--key`/`--provider` interface via an internal mapping-id lookup (paginated, so it works past the first page), since the new mutation needs an opaque mapping id. `add-item` drops `--provider` (unused by the new mutation). - `policy-collection` commands move `PolicyCollection.items`/`itemCount` to `policyMappings` (the `add-item`/`remove-item` mutations themselves were not deprecated, so no CLI interface change there). - `binding` commands move the deprecated top-level `variables` arg/field to the structured `executionConfig { variables }`. - Account and policy commands needed no changes. ## Test plan - [x] `uv run pytest tests/` — 90 passed - [x] `uv run ruff check .` - [x] `uv run ruff format --check .` - [x] `uv run ty check stacklet/` - [x] `uv run deptry .` - [x] **Manual verification against a live local platform deploy** (not just mocked unit tests): - `repository list` / `repository show` — confirmed real `RepositoryConfig` data comes back correctly shaped via `repositoryConfigs`/`repositoryConfig`. - `policy-collection list` — confirmed `policyMappings.pageInfo` shape. - `account-group add` (with `--region`), `add-item`, `show` — confirmed the new `accountMappings` shape end-to-end (create group → add mapping → verify mapping visible). - `account-group remove-item` — confirmed the mapping-id lookup pre-check resolves correctly and the mapping is actually removed (this is the path fixed for pagination per Greptile's review comment on this PR). - `account-group remove` / `account remove` — cleanup succeeded. - `binding list` — query accepted by the schema (no existing bindings locally to exercise the `executionConfig` round-trip on add/update). - All test data created during manual verification was removed afterward; no residue left in the shared local deploy. - Note: `account-group add` requires `--region` for AWS groups — pre-existing server-side validation unrelated to this migration, just something to be aware of when testing. - Not yet exercised manually: `repository add` (needs a reachable git URL) and `binding add`/`update` (no binding fixture available locally). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 697e778 commit e3dc887

8 files changed

Lines changed: 755 additions & 242 deletions

File tree

‎NEWS.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@
44

55
### Changes
66

7+
- **Migrated off deprecated GraphQL APIs**: `stacklet-admin` no longer uses GraphQL
8+
fields/mutations that are deprecated in the platform schema. This includes some
9+
breaking changes:
10+
- `repository add` no longer accepts `--branch-name`, `--policy-file-suffix`,
11+
`--policy-directory`, or `--deep-import` — the platform's replacement
12+
`addRepositoryConfig` mutation no longer supports configuring these at
13+
repository-creation time.
14+
- `repository process`, `repository scan`, `repository remove`, and
15+
`repository show` now take `--uuid` (the repository config UUID) instead of
16+
`--url`. `repository scan` no longer accepts `--start-rev-spec`.
17+
- `account-group add-item` no longer accepts `--provider` (implied by the
18+
target account group).
19+
- `account-group list/show/add/update/remove` and `policy-collection
20+
list/show/add/update/add-item/remove-item/remove` now return account/policy
21+
mappings (with a mapping id and pagination info) instead of the deprecated
22+
flat `items`/`itemCount` fields.
23+
- `binding` commands now return execution variables nested under
24+
`executionConfig` instead of a top-level `variables` field.
25+
726
### Fixes
827

928
---

‎stacklet/client/platform/commands/account_group.py‎

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
# Copyright Stacklet, Inc.
22
# SPDX-License-Identifier: Apache-2.0
33

4+
from typing import Any
5+
46
import click
7+
import jmespath
58

9+
from ..context import StackletContext
10+
from ..exceptions import InvalidInputException
611
from ..graphql.cli import GraphQLCommand, register_graphql_commands
12+
from ..graphql.snippet import GraphQLSnippet
713
from ..graphql.snippets import (
814
AddAccountGroup,
915
AddAccountGroupItem,
@@ -22,6 +28,70 @@ def account_group(*args, **kwargs):
2228
"""
2329

2430

31+
class _FindAccountGroupMapping(GraphQLSnippet):
32+
"""Internal lookup used to resolve a mapping id from an account key/provider."""
33+
34+
name = "_find-account-group-mapping"
35+
snippet = """
36+
query {
37+
accountGroup(uuid: $uuid) {
38+
accountMappings(
39+
first: 1000
40+
after: $after
41+
) {
42+
edges {
43+
node {
44+
id
45+
account {
46+
key
47+
provider
48+
}
49+
}
50+
}
51+
pageInfo {
52+
hasNextPage
53+
endCursor
54+
}
55+
}
56+
}
57+
}
58+
"""
59+
required = {"uuid": "Account group UUID"}
60+
optional = {"after": "Pagination cursor"}
61+
62+
63+
def _remove_item_pre_check(context: StackletContext, cli_args: dict[str, Any]) -> dict[str, Any]:
64+
"""
65+
removeAccountGroupMappings needs the mapping's node id, but the CLI still accepts
66+
the account's key/provider, so look up the id before running the mutation. The
67+
lookup pages through all mappings, since an account group can hold more than a
68+
single page's worth.
69+
"""
70+
group_uuid = cli_args["uuid"]
71+
key = cli_args["key"]
72+
provider = cli_args["provider"]
73+
74+
after = None
75+
while True:
76+
res = context.executor.run_snippet(
77+
_FindAccountGroupMapping, variables={"uuid": group_uuid, "after": after}
78+
)
79+
connection = jmespath.search("data.accountGroup.accountMappings", res) or {}
80+
for edge in connection.get("edges") or []:
81+
account = edge["node"]["account"]
82+
if account["key"] == key and account["provider"].upper() == provider.upper():
83+
return {"mapping_id": edge["node"]["id"]}
84+
85+
page_info = connection.get("pageInfo") or {}
86+
if not page_info.get("hasNextPage"):
87+
break
88+
after = page_info["endCursor"]
89+
90+
raise InvalidInputException(
91+
f"No account with key={key!r} provider={provider!r} found in account group {group_uuid!r}"
92+
)
93+
94+
2595
register_graphql_commands(
2696
account_group,
2797
[
@@ -31,6 +101,11 @@ def account_group(*args, **kwargs):
31101
GraphQLCommand("show", ShowAccountGroup, "Show account group"),
32102
GraphQLCommand("remove", RemoveAccountGroup, "Remove account group"),
33103
GraphQLCommand("add-item", AddAccountGroupItem, "Add account group item"),
34-
GraphQLCommand("remove-item", RemoveAccountGroupItem, "Remove account group item"),
104+
GraphQLCommand(
105+
"remove-item",
106+
RemoveAccountGroupItem,
107+
"Remove account group item",
108+
pre_check=_remove_item_pre_check,
109+
),
35110
],
36111
)

‎stacklet/client/platform/graphql/snippets/account_group.py‎

Lines changed: 81 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ class ListAccountGroups(GraphQLSnippet):
2626
system
2727
variables
2828
priority
29-
itemCount
29+
accountMappings(first: 0) {
30+
pageInfo {
31+
total
32+
}
33+
}
3034
}
3135
}
3236
pageInfo {
@@ -66,13 +70,21 @@ class AddAccountGroup(GraphQLSnippet):
6670
regions
6771
variables
6872
priority
69-
itemCount
70-
items {
71-
uuid
72-
key
73-
provider
74-
name
75-
regions
73+
accountMappings(first: 1000) {
74+
edges {
75+
node {
76+
id
77+
regions
78+
account {
79+
key
80+
provider
81+
name
82+
}
83+
}
84+
}
85+
pageInfo {
86+
total
87+
}
7688
}
7789
}
7890
}
@@ -118,13 +130,21 @@ class UpdateAccountGroup(GraphQLSnippet):
118130
regions
119131
variables
120132
priority
121-
itemCount
122-
items {
123-
uuid
124-
key
125-
provider
126-
name
127-
regions
133+
accountMappings(first: 1000) {
134+
edges {
135+
node {
136+
id
137+
regions
138+
account {
139+
key
140+
provider
141+
name
142+
}
143+
}
144+
}
145+
pageInfo {
146+
total
147+
}
128148
}
129149
}
130150
}
@@ -158,13 +178,21 @@ class ShowAccountGroup(GraphQLSnippet):
158178
system
159179
variables
160180
priority
161-
itemCount
162-
items {
163-
uuid
164-
key
165-
provider
166-
name
167-
regions
181+
accountMappings(first: 1000) {
182+
edges {
183+
node {
184+
id
185+
regions
186+
account {
187+
key
188+
provider
189+
name
190+
}
191+
}
192+
}
193+
pageInfo {
194+
total
195+
}
168196
}
169197
}
170198
}
@@ -189,13 +217,21 @@ class RemoveAccountGroup(GraphQLSnippet):
189217
regions
190218
variables
191219
priority
192-
itemCount
193-
items {
194-
uuid
195-
key
196-
provider
197-
name
198-
regions
220+
accountMappings(first: 1000) {
221+
edges {
222+
node {
223+
id
224+
regions
225+
account {
226+
key
227+
provider
228+
name
229+
}
230+
}
231+
}
232+
pageInfo {
233+
total
234+
}
199235
}
200236
}
201237
}
@@ -208,32 +244,26 @@ class AddAccountGroupItem(GraphQLSnippet):
208244
name = "add-account-group-item"
209245
snippet = """
210246
mutation {
211-
addAccountGroupItems(input:{
212-
uuid: $uuid
213-
items: [
247+
upsertAccountGroupMappings(input:{
248+
mappings: [
214249
{
215-
key: $key
216-
provider: $provider
250+
accountKey: $key
251+
groupUUID: $uuid
252+
regions: $regions
217253
}
218254
]
219255
}) {
220-
group {
221-
name
222-
uuid
256+
mappings {
223257
id
224-
shortName
225-
provider
226-
description
227258
regions
228-
variables
229-
priority
230-
itemCount
231-
items {
232-
uuid
259+
account {
233260
key
234261
provider
235262
name
236-
regions
263+
}
264+
group {
265+
uuid
266+
name
237267
}
238268
}
239269
}
@@ -242,43 +272,19 @@ class AddAccountGroupItem(GraphQLSnippet):
242272
required = {
243273
"uuid": "Account group UUID",
244274
"key": "Account Key",
245-
"provider": "Account Provider",
246275
}
247-
optional = {"regions": "Account Regions"}
248-
parameter_types = {"provider": "CloudProvider!"}
276+
optional = {"regions": {"help": "Account Regions", "multiple": True}}
249277

250278

251279
class RemoveAccountGroupItem(GraphQLSnippet):
252280
name = "remove-account-group-item"
253281
snippet = """
254282
mutation {
255-
removeAccountGroupItems(input:{
256-
uuid: $uuid
257-
items: [
258-
{
259-
key: $key
260-
provider: $provider
261-
}
262-
]
283+
removeAccountGroupMappings(input:{
284+
ids: [$mapping_id]
263285
}) {
264-
group {
265-
name
266-
uuid
286+
removed {
267287
id
268-
shortName
269-
provider
270-
description
271-
regions
272-
variables
273-
priority
274-
itemCount
275-
items {
276-
uuid
277-
key
278-
provider
279-
name
280-
regions
281-
}
282288
}
283289
}
284290
}
@@ -288,4 +294,4 @@ class RemoveAccountGroupItem(GraphQLSnippet):
288294
"key": "Account Key",
289295
"provider": "Account Provider",
290296
}
291-
parameter_types = {"provider": "CloudProvider!"}
297+
parameter_types = {"mapping_id": "ID!"}

0 commit comments

Comments
 (0)