acex-cli 4.5.1__tar.gz → 5.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {acex_cli-4.5.1 → acex_cli-5.0.0}/PKG-INFO +9 -8
- {acex_cli-4.5.1 → acex_cli-5.0.0}/pyproject.toml +15 -8
- acex_cli-5.0.0/src/acex_cli/__init__.py +5 -0
- acex_cli-5.0.0/src/acex_cli/changelog.json +498 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/commands/node.py +20 -12
- acex_cli-4.5.1/src/acex_cli/__init__.py +0 -3
- {acex_cli-4.5.1 → acex_cli-5.0.0}/README.md +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/commands/__init__.py +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/commands/asset.py +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/commands/context.py +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/commands/logical_node.py +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/commands/ned.py +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/context.py +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/diff_formatters.py +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/main.py +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/output.py +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/print_utils.py +0 -0
- {acex_cli-4.5.1 → acex_cli-5.0.0}/src/acex_cli/sdk.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: acex-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0
|
|
4
4
|
Summary: ACE-X CLI - Command-line interface for ACE-X
|
|
5
5
|
License: AGPL-3.0
|
|
6
6
|
Keywords: automation,cli,control
|
|
@@ -11,13 +11,14 @@ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
-
|
|
15
|
-
Requires-Dist: acex-
|
|
16
|
-
Requires-Dist: acex-
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist:
|
|
20
|
-
Requires-Dist:
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
15
|
+
Requires-Dist: acex-client (>=4.12.0,<5.0.0)
|
|
16
|
+
Requires-Dist: acex-devkit (>=3.0.0,<4.0.0)
|
|
17
|
+
Requires-Dist: acex-driver-cisco-ioscli (>=5.1.0,<6.0.0)
|
|
18
|
+
Requires-Dist: click (>=8.1.7)
|
|
19
|
+
Requires-Dist: ntc-templates (>=8.1.0)
|
|
20
|
+
Requires-Dist: rich (>=13.0.0)
|
|
21
|
+
Requires-Dist: typer (>=0.12.0)
|
|
21
22
|
Project-URL: Homepage, https://github.com/acex-labs/acex
|
|
22
23
|
Project-URL: Repository, https://github.com/acex-labs/acex
|
|
23
24
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "acex-cli"
|
|
3
|
-
version = "
|
|
3
|
+
version = "5.0.0"
|
|
4
4
|
description = "ACE-X CLI - Command-line interface for ACE-X"
|
|
5
5
|
authors = ["Johan Lahti <johan.lahti@acebit.se>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -14,13 +14,13 @@ packages = [
|
|
|
14
14
|
|
|
15
15
|
[tool.poetry.dependencies]
|
|
16
16
|
python = "^3.13"
|
|
17
|
-
typer = "
|
|
18
|
-
rich = "
|
|
19
|
-
click = "8.1.7"
|
|
20
|
-
acex-client = "^4.
|
|
21
|
-
acex-devkit = "^
|
|
22
|
-
acex-driver-cisco-ioscli = "^
|
|
23
|
-
ntc-templates = "
|
|
17
|
+
typer = ">=0.12.0"
|
|
18
|
+
rich = ">=13.0.0"
|
|
19
|
+
click = ">=8.1.7"
|
|
20
|
+
acex-client = "^4.12.0" # Changes to real dep. by switch_acex_client_dep for publishing
|
|
21
|
+
acex-devkit = "^3.0.0"
|
|
22
|
+
acex-driver-cisco-ioscli = "^5.1.0"
|
|
23
|
+
ntc-templates = ">=8.1.0"
|
|
24
24
|
|
|
25
25
|
[tool.poetry.scripts]
|
|
26
26
|
acex = "acex_cli.main:app"
|
|
@@ -29,6 +29,13 @@ acex = "acex_cli.main:app"
|
|
|
29
29
|
pytest = "^8.0.0"
|
|
30
30
|
pytest-asyncio = "^0.23.0"
|
|
31
31
|
|
|
32
|
+
[tool.poetry.plugins."acex.changelog"]
|
|
33
|
+
acex-cli = "acex_cli"
|
|
34
|
+
|
|
35
|
+
# Shown as the component's name in Settings > What's new.
|
|
36
|
+
[tool.acex.changelog]
|
|
37
|
+
name = "ACEX CLI"
|
|
38
|
+
|
|
32
39
|
[build-system]
|
|
33
40
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
34
41
|
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"component": "acex-cli",
|
|
4
|
+
"name": "ACEX CLI",
|
|
5
|
+
"releases": [
|
|
6
|
+
{
|
|
7
|
+
"version": "5.0.0",
|
|
8
|
+
"date": "2026-11-06",
|
|
9
|
+
"entries": [
|
|
10
|
+
{
|
|
11
|
+
"id": "20261013-node-status-is-split-into-admin-status-a",
|
|
12
|
+
"kind": "changed",
|
|
13
|
+
"title": "Node `status` is split into `admin_status` and `provision_status`",
|
|
14
|
+
"description": "`acex node list` no longer has `--status`; use `--admin-status` (planned, active, decommissioned) and `--provision-status` instead. The node table, `acex node show`, and JSON/CSV output now carry `admin_status` and `provision_status` instead of `status`, so scripts that read `status` or pass `--columns status` must be updated. Needs a server that has the same change.",
|
|
15
|
+
"breaking": true,
|
|
16
|
+
"audience": "user",
|
|
17
|
+
"refs": [
|
|
18
|
+
{
|
|
19
|
+
"label": "#292",
|
|
20
|
+
"url": null
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "4.5.1",
|
|
28
|
+
"date": "2026-09-02",
|
|
29
|
+
"summary": "The compact and flat diff views work with changes, and `acex version` prints a version again.",
|
|
30
|
+
"entries": [
|
|
31
|
+
{
|
|
32
|
+
"id": "01-acex-node-config-diff-plan-f-compact-and",
|
|
33
|
+
"kind": "fixed",
|
|
34
|
+
"title": "`acex node config diff plan -f compact` and `-f flat` work with changes",
|
|
35
|
+
"description": "Both views used a field name that does not exist on the diff objects, so they should have stopped with a Python error whenever the diff was not empty. They now read the right field and list added, removed and changed components.",
|
|
36
|
+
"breaking": false,
|
|
37
|
+
"audience": "user",
|
|
38
|
+
"refs": [
|
|
39
|
+
{
|
|
40
|
+
"label": "#207",
|
|
41
|
+
"url": null
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "02-acex-version-prints-a-version-again-but-",
|
|
47
|
+
"kind": "fixed",
|
|
48
|
+
"title": "`acex version` prints a version again, but it still says 0.2.0",
|
|
49
|
+
"description": "The command no longer fails on a missing output helper. The number it prints comes from a constant that was never updated, so it reports 0.2.0 whatever release is installed.",
|
|
50
|
+
"breaking": false,
|
|
51
|
+
"audience": "user",
|
|
52
|
+
"refs": [
|
|
53
|
+
{
|
|
54
|
+
"label": "#207",
|
|
55
|
+
"url": null
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "03-management-connection-lookups-use-the-ap",
|
|
61
|
+
"kind": "fixed",
|
|
62
|
+
"title": "Management connection lookups use the API path without a trailing slash",
|
|
63
|
+
"description": "`acex node show` (management address, port and protocol), `acex node connect` and `acex node config diff apply` now request the node's management connection from the path without a trailing slash, matching the server change in this release. Against a server that expects the old path, these commands would fail to find the connection.",
|
|
64
|
+
"breaking": false,
|
|
65
|
+
"audience": "user",
|
|
66
|
+
"refs": [
|
|
67
|
+
{
|
|
68
|
+
"label": "#251",
|
|
69
|
+
"url": null
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"version": "4.5.0",
|
|
77
|
+
"date": "2026-04-22",
|
|
78
|
+
"entries": [
|
|
79
|
+
{
|
|
80
|
+
"id": "01-acex-node-connect-node-opens-an-ssh-sess",
|
|
81
|
+
"kind": "added",
|
|
82
|
+
"title": "`acex node connect NODE` opens an SSH session to the node",
|
|
83
|
+
"description": "Looks up the node's management connection and starts `ssh` to its address, replacing the `acex` process. It uses your local `ssh` and login name, and exits with status 1 if there is no connection, it is not SSH, or it has no address.",
|
|
84
|
+
"breaking": false,
|
|
85
|
+
"audience": "user",
|
|
86
|
+
"refs": [
|
|
87
|
+
{
|
|
88
|
+
"label": "#126",
|
|
89
|
+
"url": null
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"version": "4.4.1",
|
|
97
|
+
"date": "2026-04-22",
|
|
98
|
+
"entries": [
|
|
99
|
+
{
|
|
100
|
+
"id": "01-acex-node-show-also-shows-the-management",
|
|
101
|
+
"kind": "added",
|
|
102
|
+
"title": "`acex node show` also shows the management address, port and protocol",
|
|
103
|
+
"description": "The node details now include `mgmt_target_ip`, `mgmt_target_port` and `mgmt_protocol` from the node's first management connection. If the connection cannot be fetched, the command still prints the rest. The `id` field is filled in even when the API response leaves it out.",
|
|
104
|
+
"breaking": false,
|
|
105
|
+
"audience": "user",
|
|
106
|
+
"refs": [
|
|
107
|
+
{
|
|
108
|
+
"label": "#122",
|
|
109
|
+
"url": null
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"version": "4.3.1",
|
|
117
|
+
"date": "2026-04-21",
|
|
118
|
+
"entries": [
|
|
119
|
+
{
|
|
120
|
+
"id": "01-acex-node-config-show-desired-no-longer-",
|
|
121
|
+
"kind": "fixed",
|
|
122
|
+
"title": "`acex node config show desired` no longer prints a debug timing line",
|
|
123
|
+
"description": "The \"[debug] sdk init: ...\" line that 4.3.0 printed after the output is gone, and `--render` no longer ends with a Python error.",
|
|
124
|
+
"breaking": false,
|
|
125
|
+
"audience": "user",
|
|
126
|
+
"refs": [
|
|
127
|
+
{
|
|
128
|
+
"label": "#119",
|
|
129
|
+
"url": null
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"version": "4.3.0",
|
|
137
|
+
"date": "2026-04-21",
|
|
138
|
+
"entries": [
|
|
139
|
+
{
|
|
140
|
+
"id": "01-acex-logical-node-and-acex-ned-list-and-",
|
|
141
|
+
"kind": "added",
|
|
142
|
+
"title": "`acex logical-node` and `acex ned` list and show commands",
|
|
143
|
+
"description": "`acex logical-node list|show LOGICAL_NODE_ID` and `acex ned list|show NED_ID` print logical nodes and network element drivers. Command groups with underscores in their code name are typed with hyphens.",
|
|
144
|
+
"breaking": false,
|
|
145
|
+
"audience": "user",
|
|
146
|
+
"refs": [
|
|
147
|
+
{
|
|
148
|
+
"label": "#115",
|
|
149
|
+
"url": null
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "06-acex-node-config-show-local-node-dir-dir",
|
|
155
|
+
"kind": "added",
|
|
156
|
+
"title": "`acex node config show local NODE --dir DIR` compiles config from local files",
|
|
157
|
+
"description": "Builds the configuration for a node from the config map files in a local directory, without publishing them, and shows it like `show desired` (`--render`, `--path`, `--format` work the same). It needs the `acex` backend package installed and reports which config maps were found and matched.",
|
|
158
|
+
"breaking": false,
|
|
159
|
+
"audience": "user",
|
|
160
|
+
"refs": [
|
|
161
|
+
{
|
|
162
|
+
"label": "#115",
|
|
163
|
+
"url": null
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"id": "03-filter-and-page-asset-list-node-list-and",
|
|
169
|
+
"kind": "added",
|
|
170
|
+
"title": "Filter and page `asset list`, `node list` and `logical-node list`",
|
|
171
|
+
"description": "`asset list` filters on `--vendor`, `--os`, `--hardware-model`, `--ned-id`, `--serial-number` and `--assigned`; `node list` on `--site`, `--hostname`, `--logical-node-id`, `--asset-ref-id` and `--status`; `logical-node list` on `--role`, `--site`, `--hostname`, `--sequence` and `--assigned`. All three return at most 100 rows unless `--limit` (`-l`) and `--offset` say otherwise, and the table shows \"Showing 1-100 of N\" when more exist.",
|
|
172
|
+
"breaking": false,
|
|
173
|
+
"audience": "user",
|
|
174
|
+
"refs": [
|
|
175
|
+
{
|
|
176
|
+
"label": "#115",
|
|
177
|
+
"url": null
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"id": "02-output-as-table-json-or-csv-with-format-",
|
|
183
|
+
"kind": "added",
|
|
184
|
+
"title": "Output as table, JSON or CSV with `--format`, `--columns` and `--no-header`",
|
|
185
|
+
"description": "All `list` and `show` commands for assets, nodes, logical nodes and NEDs take `--format` (`-f`) `table`, `json` or `csv`, plus `--columns` (`-c`) with a comma-separated list of columns. `list` commands also take `--no-header`, which makes the output easier to use in scripts.",
|
|
186
|
+
"breaking": false,
|
|
187
|
+
"audience": "user",
|
|
188
|
+
"refs": [
|
|
189
|
+
{
|
|
190
|
+
"label": "#115",
|
|
191
|
+
"url": null
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"id": "05-acex-node-config-show-desired-observed-g",
|
|
197
|
+
"kind": "changed",
|
|
198
|
+
"title": "`acex node config show desired|observed` give a section overview and `--path`",
|
|
199
|
+
"description": "Without `--render` they now print a table of configuration sections with component counts; `--path system.ntp` (or another section) drills in, and `--format json` dumps everything. `--render` prints the device configuration with line numbers and syntax highlighting.",
|
|
200
|
+
"breaking": false,
|
|
201
|
+
"audience": "user",
|
|
202
|
+
"refs": [
|
|
203
|
+
{
|
|
204
|
+
"label": "#115",
|
|
205
|
+
"url": null
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"id": "07-node-and-configuration-lookups-that-find",
|
|
211
|
+
"kind": "changed",
|
|
212
|
+
"title": "Node and configuration lookups that find nothing now exit with status 1",
|
|
213
|
+
"description": "`acex node show`, `config show observed`, `config diff plan` and `config diff apply` exit with status 1 for an unknown node or a missing configuration, with \"Node 'X' not found.\", \"No observed config found.\" or \"No desired configuration found.\". Earlier versions printed the message and exited with 0.",
|
|
214
|
+
"breaking": false,
|
|
215
|
+
"audience": "user",
|
|
216
|
+
"refs": [
|
|
217
|
+
{
|
|
218
|
+
"label": "#115",
|
|
219
|
+
"url": null
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"id": "04-node-commands-accept-a-hostname-as-well-",
|
|
225
|
+
"kind": "changed",
|
|
226
|
+
"title": "Node commands accept a hostname as well as an ID",
|
|
227
|
+
"description": "`acex node show`, `acex node config show ...` and `acex node config diff ...` take `NODE` as a numeric ID or a hostname. If several nodes share the hostname, they are listed and the command asks for the ID.",
|
|
228
|
+
"breaking": false,
|
|
229
|
+
"audience": "user",
|
|
230
|
+
"refs": [
|
|
231
|
+
{
|
|
232
|
+
"label": "#115",
|
|
233
|
+
"url": null
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"id": "09-the-could-not-connect-to-api-message-is-",
|
|
239
|
+
"kind": "changed",
|
|
240
|
+
"title": "The \"Could not connect to API\" message is no longer shown",
|
|
241
|
+
"description": "Commands no longer test the API address first, so an unreachable or untrusted server is no longer reported with that message. What appears instead comes from the client library, and its wording is not confirmed.",
|
|
242
|
+
"breaking": false,
|
|
243
|
+
"audience": "user",
|
|
244
|
+
"refs": [
|
|
245
|
+
{
|
|
246
|
+
"label": "#115",
|
|
247
|
+
"url": null
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"version": "4.1.3",
|
|
255
|
+
"date": "2026-03-18",
|
|
256
|
+
"entries": [
|
|
257
|
+
{
|
|
258
|
+
"id": "01-acex-node-config-diff-apply-node-id-push",
|
|
259
|
+
"kind": "added",
|
|
260
|
+
"title": "`acex node config diff apply NODE_ID` pushes the planned changes to a device",
|
|
261
|
+
"description": "It works out the difference between observed and desired configuration, prints the device commands in a box and asks \"Are you sure you want to apply the config above?\". On yes it sends them over the node's first management connection; on no it exits without changes (exit status 0).",
|
|
262
|
+
"breaking": false,
|
|
263
|
+
"audience": "user",
|
|
264
|
+
"refs": [
|
|
265
|
+
{
|
|
266
|
+
"label": "#72",
|
|
267
|
+
"url": null
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"id": "03-acex-node-config-diff-plan-f-commands-pr",
|
|
273
|
+
"kind": "changed",
|
|
274
|
+
"title": "`acex node config diff plan -f commands` prints driver-rendered patch commands",
|
|
275
|
+
"description": "The `commands` format now asks the device driver to render the patch for the node, the same text that `diff apply` shows before asking for confirmation.",
|
|
276
|
+
"breaking": false,
|
|
277
|
+
"audience": "user",
|
|
278
|
+
"refs": [
|
|
279
|
+
{
|
|
280
|
+
"label": "#72",
|
|
281
|
+
"url": null
|
|
282
|
+
}
|
|
283
|
+
]
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"id": "02-acex-node-config-diff-plan-shows-changes",
|
|
287
|
+
"kind": "changed",
|
|
288
|
+
"title": "`acex node config diff plan` shows changes per component and attribute",
|
|
289
|
+
"description": "The `tree`, `compact`, `flat` and `summary` views now list added, removed and changed components, with before and after values per changed attribute; `flat` is a table of Type, Component, Attribute, Before and After. Output from earlier versions looked different, so scripts that parse it need to be checked.",
|
|
290
|
+
"breaking": false,
|
|
291
|
+
"audience": "user",
|
|
292
|
+
"refs": [
|
|
293
|
+
{
|
|
294
|
+
"label": "#70",
|
|
295
|
+
"url": null
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"label": "#81",
|
|
299
|
+
"url": null
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"version": "4.1.0",
|
|
307
|
+
"date": "2026-02-13",
|
|
308
|
+
"entries": [
|
|
309
|
+
{
|
|
310
|
+
"id": "03-acex-node-config-diff-plan-node-id-shows",
|
|
311
|
+
"kind": "added",
|
|
312
|
+
"title": "`acex node config diff plan NODE_ID` shows what would change on a node",
|
|
313
|
+
"description": "Compares the latest observed configuration with the desired one. `--format` (`-f`) selects `tree` (default), `compact`, `flat`, `summary` or `json`; `--max-depth` (`-d`) and `--no-values` shorten the tree view. A `commands` format is also accepted, but is not listed in the help; it appears to print device commands.",
|
|
314
|
+
"breaking": false,
|
|
315
|
+
"audience": "user",
|
|
316
|
+
"refs": [
|
|
317
|
+
{
|
|
318
|
+
"label": "#49",
|
|
319
|
+
"url": null
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
"label": "#52",
|
|
323
|
+
"url": null
|
|
324
|
+
}
|
|
325
|
+
]
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"id": "01-acex-node-config-show-desired-node-id-pr",
|
|
329
|
+
"kind": "added",
|
|
330
|
+
"title": "`acex node config show desired NODE_ID` prints the desired configuration",
|
|
331
|
+
"description": "Prints the configuration defined for the node as JSON, or as device configuration text with `--render`. A `--path` option is accepted but has no effect yet.",
|
|
332
|
+
"breaking": false,
|
|
333
|
+
"audience": "user",
|
|
334
|
+
"refs": [
|
|
335
|
+
{
|
|
336
|
+
"label": "#48",
|
|
337
|
+
"url": null
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"label": "#49",
|
|
341
|
+
"url": null
|
|
342
|
+
}
|
|
343
|
+
]
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
"id": "02-acex-node-config-show-observed-node-id-p",
|
|
347
|
+
"kind": "added",
|
|
348
|
+
"title": "`acex node config show observed NODE_ID` prints the latest backed-up config",
|
|
349
|
+
"description": "Prints the last configuration read from the device as parsed JSON, or as device configuration text with `--render`. Prints \"No observed config found.\" when there is none.",
|
|
350
|
+
"breaking": false,
|
|
351
|
+
"audience": "user",
|
|
352
|
+
"refs": [
|
|
353
|
+
{
|
|
354
|
+
"label": "#48",
|
|
355
|
+
"url": null
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"label": "#49",
|
|
359
|
+
"url": null
|
|
360
|
+
}
|
|
361
|
+
]
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"id": "04-acex-starts-again-after-the-start-up-fai",
|
|
365
|
+
"kind": "fixed",
|
|
366
|
+
"title": "`acex` starts again after the start-up failure in 0.2.5",
|
|
367
|
+
"description": "The undefined option type in the node commands is gone, so the `acex` command loads normally.",
|
|
368
|
+
"breaking": false,
|
|
369
|
+
"audience": "user",
|
|
370
|
+
"refs": [
|
|
371
|
+
{
|
|
372
|
+
"label": "#47",
|
|
373
|
+
"url": null
|
|
374
|
+
}
|
|
375
|
+
]
|
|
376
|
+
}
|
|
377
|
+
]
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"version": "0.2.5",
|
|
381
|
+
"date": "2026-02-10",
|
|
382
|
+
"entries": [
|
|
383
|
+
{
|
|
384
|
+
"id": "02-acex-node-config-observed-node-id-prints",
|
|
385
|
+
"kind": "added",
|
|
386
|
+
"title": "`acex node config observed NODE_ID` prints the latest backed-up configuration",
|
|
387
|
+
"description": "Prints the last configuration stored for a node, as device text by default or as JSON with `--format json` (`-f`), or a specific backup with `--hash`. In this release it cannot be used, because `acex` itself fails to start (see the next entry).",
|
|
388
|
+
"breaking": false,
|
|
389
|
+
"audience": "user",
|
|
390
|
+
"refs": [
|
|
391
|
+
{
|
|
392
|
+
"label": "#44",
|
|
393
|
+
"url": null
|
|
394
|
+
}
|
|
395
|
+
]
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"id": "01-clear-message-when-the-acex-api-cannot-b",
|
|
399
|
+
"kind": "added",
|
|
400
|
+
"title": "Clear message when the ACEX API cannot be reached",
|
|
401
|
+
"description": "Before running a command, `acex` now contacts the API address of the active context. If the connection fails it prints \"Could not connect to API!\" plus the reason, and for a certificate problem \"Could not connect to API due to untrusted certificate!\"; it then stops with exit status 0, so scripts cannot detect the failure from the status.",
|
|
402
|
+
"breaking": false,
|
|
403
|
+
"audience": "user",
|
|
404
|
+
"refs": [
|
|
405
|
+
{
|
|
406
|
+
"label": "#44",
|
|
407
|
+
"url": null
|
|
408
|
+
}
|
|
409
|
+
]
|
|
410
|
+
}
|
|
411
|
+
]
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"version": "0.2.1",
|
|
415
|
+
"date": "2026-01-29",
|
|
416
|
+
"entries": [
|
|
417
|
+
{
|
|
418
|
+
"id": "03-placeholder-commands-acex-run-acex-list-",
|
|
419
|
+
"kind": "removed",
|
|
420
|
+
"title": "Placeholder commands `acex run`, `acex list` and `acex status` are gone",
|
|
421
|
+
"description": "These three commands did nothing but print a message and have been removed. Use `acex node list`, `acex asset list` and the `acex context` commands instead.",
|
|
422
|
+
"breaking": false,
|
|
423
|
+
"audience": "user",
|
|
424
|
+
"refs": [
|
|
425
|
+
{
|
|
426
|
+
"label": "#35",
|
|
427
|
+
"url": null
|
|
428
|
+
}
|
|
429
|
+
]
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
"id": "02-choose-which-acex-api-to-talk-to-with-ac",
|
|
433
|
+
"kind": "added",
|
|
434
|
+
"title": "Choose which ACEX API to talk to with `acex context`",
|
|
435
|
+
"description": "`acex context add --name NAME --url URL` saves and activates an API address (`--no-verify-ssl` skips certificate checking). `acex context list`, `use NAME`, `show` and `verify-ssl true|false` manage saved contexts, which are kept in `~/.acex/.acex_cli_context.json`. Without an active context, node and asset commands report that none is selected.",
|
|
436
|
+
"breaking": false,
|
|
437
|
+
"audience": "user",
|
|
438
|
+
"refs": [
|
|
439
|
+
{
|
|
440
|
+
"label": "#35",
|
|
441
|
+
"url": null
|
|
442
|
+
}
|
|
443
|
+
]
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"id": "01-list-and-show-nodes-and-assets-with-acex",
|
|
447
|
+
"kind": "added",
|
|
448
|
+
"title": "List and show nodes and assets with `acex node` and `acex asset`",
|
|
449
|
+
"description": "`acex node list`, `acex node show NODE_ID`, `acex asset list` and `acex asset show ASSET_ID` fetch data from the ACEX API and print it as a table (one row per item, or a Field/Value table for `show`). An empty list prints \"No nodes found.\" or \"No assets found.\".",
|
|
450
|
+
"breaking": false,
|
|
451
|
+
"audience": "user",
|
|
452
|
+
"refs": [
|
|
453
|
+
{
|
|
454
|
+
"label": "#35",
|
|
455
|
+
"url": null
|
|
456
|
+
}
|
|
457
|
+
]
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"id": "04-shell-completion-is-enabled-for-acex",
|
|
461
|
+
"kind": "changed",
|
|
462
|
+
"title": "Shell completion is enabled for `acex`",
|
|
463
|
+
"description": "The command now offers the standard `--install-completion` and `--show-completion` options. Running `acex` with no sub-command prints the help text.",
|
|
464
|
+
"breaking": false,
|
|
465
|
+
"audience": "user",
|
|
466
|
+
"refs": [
|
|
467
|
+
{
|
|
468
|
+
"label": "#35",
|
|
469
|
+
"url": null
|
|
470
|
+
}
|
|
471
|
+
]
|
|
472
|
+
}
|
|
473
|
+
]
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
"version": "0.2.0",
|
|
477
|
+
"date": "2025-11-04",
|
|
478
|
+
"entries": [
|
|
479
|
+
{
|
|
480
|
+
"id": "01-first-acex-command-with-version-run-list",
|
|
481
|
+
"kind": "added",
|
|
482
|
+
"title": "First `acex` command with `version`, `run`, `list` and `status`",
|
|
483
|
+
"description": "The `acex` command exists, with `acex version`, `acex run FILE`, `acex list` and `acex status`.",
|
|
484
|
+
"breaking": false,
|
|
485
|
+
"audience": "user",
|
|
486
|
+
"refs": [
|
|
487
|
+
{
|
|
488
|
+
"label": "83efd0b",
|
|
489
|
+
"url": null
|
|
490
|
+
}
|
|
491
|
+
]
|
|
492
|
+
}
|
|
493
|
+
]
|
|
494
|
+
}
|
|
495
|
+
],
|
|
496
|
+
"coming": [],
|
|
497
|
+
"highlights": []
|
|
498
|
+
}
|
|
@@ -45,7 +45,7 @@ def _decode_config_content(content: str) -> str:
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def _fetch_node_instance(sdk, identifier: str):
|
|
48
|
-
"""Resolve identifier to full
|
|
48
|
+
"""Resolve identifier to full NodeInstance. Used by show/apply that need asset info."""
|
|
49
49
|
if identifier.isdigit():
|
|
50
50
|
node = sdk.node_instances.get(identifier)
|
|
51
51
|
else:
|
|
@@ -57,7 +57,7 @@ def _fetch_node_instance(sdk, identifier: str):
|
|
|
57
57
|
typer.echo(f"Multiple nodes match '{identifier}':")
|
|
58
58
|
from acex_cli.output import display_list
|
|
59
59
|
|
|
60
|
-
display_list(result, columns=["id", "hostname", "site", "
|
|
60
|
+
display_list(result, columns=["id", "hostname", "site", "admin_status", "provision_status"], title="")
|
|
61
61
|
raise typer.Exit(1)
|
|
62
62
|
node = sdk.node_instances.get(result.items[0].id)
|
|
63
63
|
if not node:
|
|
@@ -84,7 +84,7 @@ def _resolve_node_quick(sdk, identifier: str):
|
|
|
84
84
|
typer.echo(f"Multiple nodes match '{identifier}':")
|
|
85
85
|
from acex_cli.output import display_list
|
|
86
86
|
|
|
87
|
-
display_list(result, columns=["id", "hostname", "site", "
|
|
87
|
+
display_list(result, columns=["id", "hostname", "site", "admin_status", "provision_status"], title="")
|
|
88
88
|
raise typer.Exit(1)
|
|
89
89
|
return result.items[0]
|
|
90
90
|
|
|
@@ -177,7 +177,7 @@ def _fetch_desired_config(sdk, identifier: str):
|
|
|
177
177
|
typer.echo("No desired configuration found.")
|
|
178
178
|
raise typer.Exit(1)
|
|
179
179
|
|
|
180
|
-
# Return quick node + config. Config commands that need full
|
|
180
|
+
# Return quick node + config. Config commands that need full NodeInstance
|
|
181
181
|
# (e.g. render, apply) will call _fetch_node_instance separately.
|
|
182
182
|
return quick, config
|
|
183
183
|
|
|
@@ -205,7 +205,11 @@ def list_cmd(
|
|
|
205
205
|
hostname: str | None = typer.Option(None, help="Filter by hostname (prefix match)"),
|
|
206
206
|
logical_node_id: int | None = typer.Option(None, help="Filter by logical node ID"),
|
|
207
207
|
asset_ref_id: int | None = typer.Option(None, help="Filter by asset ref ID"),
|
|
208
|
-
|
|
208
|
+
admin_status: str | None = typer.Option(None, help="Filter by admin status (planned, active, decommissioned)"),
|
|
209
|
+
provision_status: str | None = typer.Option(
|
|
210
|
+
None,
|
|
211
|
+
help=("Filter by provision status (unprovisioned, adopted, enrolled, provisioning, provisioned, failed)"),
|
|
212
|
+
),
|
|
209
213
|
# Pagination
|
|
210
214
|
limit: int = typer.Option(100, "--limit", "-l", help="Max items to return"),
|
|
211
215
|
offset: int = typer.Option(0, "--offset", help="Items to skip"),
|
|
@@ -221,13 +225,16 @@ def list_cmd(
|
|
|
221
225
|
hostname=hostname,
|
|
222
226
|
logical_node_id=logical_node_id,
|
|
223
227
|
asset_ref_id=asset_ref_id,
|
|
224
|
-
|
|
228
|
+
admin_status=admin_status,
|
|
229
|
+
provision_status=provision_status,
|
|
225
230
|
)
|
|
226
231
|
result = sdk.node_instances.query(limit=limit, offset=offset, **filters)
|
|
227
232
|
display_list(
|
|
228
233
|
result,
|
|
229
234
|
format=format,
|
|
230
|
-
columns=columns.split(",")
|
|
235
|
+
columns=columns.split(",")
|
|
236
|
+
if columns
|
|
237
|
+
else ["id", "hostname", "site", "admin_status", "provision_status", "vendor", "os", "ned_id"],
|
|
231
238
|
no_header=no_header,
|
|
232
239
|
model=NodeListItem,
|
|
233
240
|
title="Node Instances",
|
|
@@ -631,7 +638,7 @@ def _print_config_section(console, data: dict, path: str):
|
|
|
631
638
|
|
|
632
639
|
|
|
633
640
|
def _flatten_node(node, node_id: str = None) -> dict:
|
|
634
|
-
"""Flatten
|
|
641
|
+
"""Flatten NodeInstance into a single-level dict for display."""
|
|
635
642
|
data = node.model_dump() if hasattr(node, "model_dump") else dict(node)
|
|
636
643
|
asset = data.pop("asset", {}) or {}
|
|
637
644
|
logical_node = data.pop("logical_node", {}) or {}
|
|
@@ -641,7 +648,8 @@ def _flatten_node(node, node_id: str = None) -> dict:
|
|
|
641
648
|
"hostname": logical_node.get("hostname"),
|
|
642
649
|
"site": logical_node.get("site"),
|
|
643
650
|
"role": logical_node.get("role"),
|
|
644
|
-
"
|
|
651
|
+
"admin_status": data.get("admin_status"),
|
|
652
|
+
"provision_status": data.get("provision_status"),
|
|
645
653
|
"vendor": asset.get("vendor"),
|
|
646
654
|
"os": asset.get("os"),
|
|
647
655
|
"os_version": asset.get("os_version"),
|
|
@@ -658,7 +666,7 @@ def _flatten_node(node, node_id: str = None) -> dict:
|
|
|
658
666
|
|
|
659
667
|
|
|
660
668
|
def _resolve_node(sdk, identifier: str):
|
|
661
|
-
"""Resolve a node by ID or hostname. Returns
|
|
669
|
+
"""Resolve a node by ID or hostname. Returns NodeInstance or exits."""
|
|
662
670
|
# Try as numeric ID first
|
|
663
671
|
if identifier.isdigit():
|
|
664
672
|
node = sdk.node_instances.get(identifier)
|
|
@@ -673,7 +681,7 @@ def _resolve_node(sdk, identifier: str):
|
|
|
673
681
|
typer.echo(f"Multiple nodes match hostname '{identifier}':")
|
|
674
682
|
from acex_cli.output import display_list
|
|
675
683
|
|
|
676
|
-
display_list(result, columns=["id", "hostname", "site", "
|
|
684
|
+
display_list(result, columns=["id", "hostname", "site", "admin_status", "provision_status"], title="")
|
|
677
685
|
typer.echo("Use the ID to specify which node.")
|
|
678
686
|
raise typer.Exit(1)
|
|
679
687
|
|
|
@@ -693,7 +701,7 @@ def _resolve_node_id(sdk, identifier: str) -> str:
|
|
|
693
701
|
typer.echo(f"Multiple nodes match hostname '{identifier}':")
|
|
694
702
|
from acex_cli.output import display_list
|
|
695
703
|
|
|
696
|
-
display_list(result, columns=["id", "hostname", "site", "
|
|
704
|
+
display_list(result, columns=["id", "hostname", "site", "admin_status", "provision_status"], title="")
|
|
697
705
|
typer.echo("Use the ID to specify which node.")
|
|
698
706
|
raise typer.Exit(1)
|
|
699
707
|
typer.echo(f"Node '{identifier}' not found.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|