argocd-axi 0.1.3 → 0.1.5
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.
- package/README.md +40 -37
- package/ROADMAP.md +14 -40
- package/dist/src/argocd.js +24 -2
- package/dist/src/cli.js +8 -0
- package/dist/src/commands/apps.js +410 -27
- package/dist/src/commands/auth.js +49 -0
- package/dist/src/commands/common.js +3 -3
- package/dist/src/commands/ctx.js +118 -2
- package/dist/src/config.js +50 -2
- package/dist/src/errors.js +29 -3
- package/dist/src/gate.js +4 -5
- package/dist/src/help.js +7 -2
- package/dist/src/model.js +33 -14
- package/dist/src/redact.js +2 -0
- package/dist/src/registry.js +24 -28
- package/dist/src/skill.js +7 -4
- package/dist/src/toon.js +4 -10
- package/dist/src/version.js +1 -1
- package/package.json +2 -2
- package/skills/argocd-axi/SKILL.md +14 -6
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<a href="https://x.com/dematom1"><img alt="X" src="https://img.shields.io/badge/X-@dematom1-black?style=flat-square" /></a>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Argo CD for agents, read-only except opt-in writes on writable contexts. `argocd-axi` wraps the `argocd` CLI and answers
|
|
12
12
|
the questions an agent asks during a deployment problem (which apps need
|
|
13
13
|
attention and why, what differs from Git, what was deployed when, what the
|
|
14
14
|
pods are logging, whether a sync is allowed right now) in compact
|
|
@@ -18,7 +18,7 @@ pods are logging, whether a sync is allowed right now) in compact
|
|
|
18
18
|
```
|
|
19
19
|
$ argocd-axi
|
|
20
20
|
bin: ~/.local/bin/argocd-axi
|
|
21
|
-
description:
|
|
21
|
+
description: Argo CD for agents: read operations plus opt-in writes on writable contexts
|
|
22
22
|
context: prod
|
|
23
23
|
server: argocd.example.com
|
|
24
24
|
auth: argocd CLI session for this context
|
|
@@ -31,9 +31,7 @@ attention[3]{app,project,sync,health,why}:
|
|
|
31
31
|
checkout,shop,Synced,Progressing,Progressing for 25m
|
|
32
32
|
operations[1]{app,phase,started}:
|
|
33
33
|
checkout,Running,25m ago
|
|
34
|
-
help[2]:
|
|
35
|
-
Run `argocd-axi apps view <app>` to diagnose an application
|
|
36
|
-
Run `argocd-axi apps` to list every application
|
|
34
|
+
help[2]: Run `argocd-axi apps view <app>` to diagnose an application,Run `argocd-axi apps` to list every application
|
|
37
35
|
```
|
|
38
36
|
|
|
39
37
|
## Why
|
|
@@ -48,9 +46,10 @@ argocd-axi turns that into small, definitive answers:
|
|
|
48
46
|
top and says why, in one line, with `count: N of M total`.
|
|
49
47
|
- **Summary first.** Diffs and manifests come back as a per-resource list;
|
|
50
48
|
the raw text is one `--full` away.
|
|
51
|
-
- **
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
- **Opt-in writes.** `apps sync <app>` previews the per-resource diff and
|
|
50
|
+
makes no change unless `--confirm` is passed. It never prunes unless
|
|
51
|
+
`--prune` is explicit, and execution is limited to configured writable
|
|
52
|
+
contexts. Other writes remain refused.
|
|
54
53
|
- **Safe output.** Secret data, passwords, tokens and sensitive-looking Helm
|
|
55
54
|
parameters are redacted and counted. No flag reveals them.
|
|
56
55
|
- **Never hangs.** Nothing streams or follows; waits are bounded; no prompt can
|
|
@@ -75,15 +74,22 @@ Requirements:
|
|
|
75
74
|
v3.3.6. There is no hard minimum; `ctx` and the home view warn when the CLI
|
|
76
75
|
and server differ by a major version.
|
|
77
76
|
|
|
78
|
-
##
|
|
77
|
+
## Setup
|
|
79
78
|
|
|
80
79
|
argocd-axi adds no auth of its own. It uses whatever the `argocd` CLI already
|
|
81
80
|
has: the current context's session, or `ARGOCD_SERVER` and
|
|
82
81
|
`ARGOCD_AUTH_TOKEN` from the environment. It never starts a login, never
|
|
83
82
|
prompts, and never reads the argocd config file (`~/.config/argocd/config`,
|
|
84
|
-
which holds clear-text tokens).
|
|
85
|
-
answers `
|
|
86
|
-
|
|
83
|
+
which holds clear-text tokens). A missing session answers `AUTH_REQUIRED`; an
|
|
84
|
+
expired or rejected login answers `AUTH_EXPIRED`. When the target server is
|
|
85
|
+
known, help includes the exact operator login command; otherwise it shows the
|
|
86
|
+
login syntax and asks for the server address. Run
|
|
87
|
+
`argocd-axi auth guide` for offline setup instructions. Human operators run
|
|
88
|
+
`argocd login <server> --sso --grpc-web` interactively themselves. Agents and
|
|
89
|
+
CI should set `ARGOCD_SERVER` and `ARGOCD_AUTH_TOKEN` from a scoped account
|
|
90
|
+
token. Verify the selected context with `argocd-axi ctx`. The tool never logs in.
|
|
91
|
+
|
|
92
|
+
### Scoped agent credentials
|
|
87
93
|
|
|
88
94
|
An agent that uses your own SSO session has all of your Argo CD permissions.
|
|
89
95
|
The recommended agent credential is a **short-lived project-role token**,
|
|
@@ -139,9 +145,10 @@ argocd-axi apps [--problems --project --selector --repo --cluster --sync --healt
|
|
|
139
145
|
argocd-axi apps view <app> [--full]
|
|
140
146
|
argocd-axi apps resources <app> [--problems | --orphaned]
|
|
141
147
|
argocd-axi apps diff <app> [--revision <rev>] [--full]
|
|
148
|
+
argocd-axi apps sync <app> [--confirm] [--prune] [--timeout <s>] --context <c>
|
|
142
149
|
argocd-axi apps history <app> [--limit <n>]
|
|
143
150
|
argocd-axi apps manifests <app> [--source git|live] [--revision <rev>] [--kind <k>] [--full]
|
|
144
|
-
argocd-axi apps logs <app> [--kind <k> --name <n>] [--tail 100] [--since 15m] [--filter <s>] [-p]
|
|
151
|
+
argocd-axi apps logs <app> [--kind <k> --name <n>] [--tail 100] [--since 15m] [--filter <s>] [--full] [-p]
|
|
145
152
|
argocd-axi apps wait <app> [--sync --health --operation] [--timeout 120]
|
|
146
153
|
argocd-axi apps actions <app> [--kind <k>]
|
|
147
154
|
argocd-axi projects | projects view <p> | projects windows <p>
|
|
@@ -187,20 +194,21 @@ specific app.
|
|
|
187
194
|
Unknown flags are rejected before any argocd call, with the valid flags
|
|
188
195
|
listed.
|
|
189
196
|
|
|
190
|
-
| Code | Meaning
|
|
191
|
-
| --------------------------------- |
|
|
192
|
-
| `VALIDATION_ERROR` | Bad flag or argument (exit 2)
|
|
193
|
-
| `AUTH_REQUIRED` | No session
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
203
|
-
| `
|
|
197
|
+
| Code | Meaning |
|
|
198
|
+
| --------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
199
|
+
| `VALIDATION_ERROR` | Bad flag or argument (exit 2) |
|
|
200
|
+
| `AUTH_REQUIRED` | No session is configured |
|
|
201
|
+
| `AUTH_EXPIRED` | Argo CD rejected or expired the login; help prints the exact command when the server is known |
|
|
202
|
+
| `FORBIDDEN` | Argo CD RBAC denied it (Argo CD also says this for objects in projects you cannot see) |
|
|
203
|
+
| `NOT_FOUND` | No such app, project or ApplicationSet |
|
|
204
|
+
| `RATE_LIMITED` | The server asked to slow down |
|
|
205
|
+
| `SERVER_UNREACHABLE` | Network or TLS failure reaching the server |
|
|
206
|
+
| `REFUSED` | A verb argocd-axi does not run, with the reason and the human route |
|
|
207
|
+
| `NO_CONTEXT`, `CONTEXT_NOT_FOUND` | No current context, or `--context` names one that is not configured |
|
|
208
|
+
| `TIMEOUT` | argocd did not answer in time |
|
|
209
|
+
| `ARGOCD_NOT_INSTALLED` | No `argocd` on `PATH` |
|
|
210
|
+
| `CONFIG_INVALID` | argocd-axi's own config file is malformed |
|
|
211
|
+
| `UPSTREAM_ERROR` | Any other argocd failure, with its cleaned message |
|
|
204
212
|
|
|
205
213
|
## Safety model
|
|
206
214
|
|
|
@@ -211,9 +219,7 @@ specific app.
|
|
|
211
219
|
local-file and delete flags whatever a command passes, gives argocd no
|
|
212
220
|
stdin, and enforces a timeout and an output cap. Tests fail if a new
|
|
213
221
|
operation becomes reachable without updating the registry.
|
|
214
|
-
- **
|
|
215
|
-
`REFUSED` with a reason and the raw command a human can run instead of
|
|
216
|
-
failing blind.
|
|
222
|
+
- **Opt-in Application writes.** Sync, rollback, refresh and terminate-op require an explicit context, preview and `--confirm`; execution is limited by `writable-contexts` and Argo CD RBAC. Other writes and credential commands answer `REFUSED`.
|
|
217
223
|
- **Redaction.** Kubernetes Secret `data`, `stringData` and their
|
|
218
224
|
last-applied annotation, values under sensitive-looking keys, and env-var or
|
|
219
225
|
Helm-parameter pairs with sensitive-looking names are replaced with
|
|
@@ -224,13 +230,10 @@ specific app.
|
|
|
224
230
|
reach them.
|
|
225
231
|
- **Writable contexts.** argocd-axi's own config file,
|
|
226
232
|
`~/.config/argocd-axi/config` (or `$XDG_CONFIG_HOME/argocd-axi/config`),
|
|
227
|
-
holds the allowlist of contexts where
|
|
228
|
-
It defaults to none
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
writable-contexts:
|
|
232
|
-
- staging
|
|
233
|
-
```
|
|
233
|
+
holds the allowlist of contexts where Application writes may execute.
|
|
234
|
+
It defaults to none. To allow writes on a context, a human runs
|
|
235
|
+
`argocd-axi ctx writable add <context>` in a terminal and confirms the exact
|
|
236
|
+
context name. Use `argocd-axi ctx writable remove <context>` to revoke access.
|
|
234
237
|
|
|
235
238
|
- **Version cache.** The server version is checked at most once every 12
|
|
236
239
|
hours per context and server, and cached in `~/.cache/argocd-axi/`
|
package/ROADMAP.md
CHANGED
|
@@ -1,54 +1,29 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
-
argocd-axi
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
argocd-axi supports confirmed app sync, rollback, refresh and terminate-op on
|
|
4
|
+
contexts explicitly listed as writable. This file records remaining write
|
|
5
|
+
tier work and shared safety rules.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Remaining write tier
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
| Command | Wraps | Notes |
|
|
13
|
-
| ------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
14
|
-
| `apps sync <app> --context <c> [--prune] [--revision <rev>] [--resource <group:kind:name>]` | `argocd app sync` | Preview from `--dry-run`, the per-resource diff, the sync-window verdict and an RBAC check (`account can-i sync`) |
|
|
15
|
-
| `apps rollback <app> <history-id> --context <c>` | `argocd app rollback` | No native dry run, so the preview is synthesized: target history entry against the current one. Refused while auto-sync is enabled, as Argo CD requires |
|
|
16
|
-
| `apps terminate-op <app> --context <c>` | `argocd app terminate-op` | No-op with exit 0 when no operation is running |
|
|
17
|
-
| `apps refresh <app> [--hard] --context <c>` | `argocd app get --refresh` | Triggers a reconcile, so it is a write, not a read |
|
|
18
|
-
|
|
19
|
-
Later, a third tier: `apps action run <app> <action> --kind <kind>` for
|
|
20
|
-
resource actions such as restart, and Argo Rollouts promote and abort
|
|
21
|
-
through the same path.
|
|
9
|
+
Resource actions such as restart, and Argo Rollouts promote and abort, remain
|
|
10
|
+
future work. The supported Application writes are listed in [README.md](README.md).
|
|
22
11
|
|
|
23
12
|
### Rules every write follows
|
|
24
13
|
|
|
25
|
-
The gate is
|
|
26
|
-
|
|
27
|
-
and calls the gate.
|
|
28
|
-
|
|
29
|
-
1. **Preview by default.** Without the command's execute flag, nothing is
|
|
30
|
-
sent. The preview shows the target context and server, the object, the
|
|
31
|
-
change set and the preflight checks, plus the exact command that executes
|
|
32
|
-
it. Previews work against any context.
|
|
33
|
-
2. **A local allowlist of writable contexts, default none.** Executing needs
|
|
34
|
-
the context listed in argocd-axi's own config file,
|
|
35
|
-
`~/.config/argocd-axi/config` (or `$XDG_CONFIG_HOME/argocd-axi/config`):
|
|
36
|
-
|
|
37
|
-
```yaml
|
|
38
|
-
writable-contexts:
|
|
39
|
-
- staging
|
|
40
|
-
```
|
|
14
|
+
The gate is built in `src/gate.ts`, with its allowlist loaded from
|
|
15
|
+
`src/config.ts`. New writes must use the gate and the registered operation path.
|
|
41
16
|
|
|
42
|
-
|
|
43
|
-
|
|
17
|
+
1. **Preview by default.** Without `--confirm`, no write is sent. Each write
|
|
18
|
+
previews the target action before execution.
|
|
19
|
+
2. **A local allowlist of writable contexts, default none.** See [README.md](README.md)
|
|
20
|
+
for how a human manages the allowlist.
|
|
44
21
|
|
|
45
22
|
3. **An explicit target.** Every write names `--context <name>`; the current
|
|
46
23
|
context is never used implicitly, and the result echoes the context and
|
|
47
24
|
server.
|
|
48
|
-
4. **
|
|
49
|
-
|
|
50
|
-
vocabulary. The exact execute-flag spelling is settled when each command is
|
|
51
|
-
built.
|
|
25
|
+
4. **Explicit confirmation.** Every supported Application write requires
|
|
26
|
+
`--confirm` to execute.
|
|
52
27
|
5. **Idempotent.** Asking for a state that already exists (syncing an app
|
|
53
28
|
that is already synced to the target revision, terminating when nothing
|
|
54
29
|
runs) is a no-op with exit 0. Exit 1 only when the intent cannot be met:
|
|
@@ -94,4 +69,3 @@ instances (version, SSO provider, app count) are known.
|
|
|
94
69
|
|
|
95
70
|
Submission to the [AXI community catalog](https://github.com/kunchenguid/axi)
|
|
96
71
|
comes after v1. npm releases are automated; see the Releases section in
|
|
97
|
-
[README.md](README.md).
|
package/dist/src/argocd.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { resolveTarget } from "./context.js";
|
|
2
3
|
import { CODES, axiError, mapArgocdFailure, } from "./errors.js";
|
|
3
4
|
import { FORBIDDEN_ARGS, OPERATIONS } from "./registry.js";
|
|
4
5
|
/**
|
|
@@ -13,7 +14,10 @@ export function buildArgv(op, args, context) {
|
|
|
13
14
|
const operation = OPERATIONS[op];
|
|
14
15
|
for (const arg of args) {
|
|
15
16
|
const name = arg.startsWith("--") ? arg.split("=")[0] : arg;
|
|
16
|
-
if (name !== undefined &&
|
|
17
|
+
if (name !== undefined &&
|
|
18
|
+
FORBIDDEN_ARGS.includes(name) &&
|
|
19
|
+
!(op === "app refresh" &&
|
|
20
|
+
(name === "--refresh" || name === "--hard-refresh"))) {
|
|
17
21
|
throw axiError(`internal guard: argument ${name} is never passed to argocd`, CODES.REFUSED);
|
|
18
22
|
}
|
|
19
23
|
}
|
|
@@ -37,10 +41,28 @@ export async function runArgocd(op, args, options = {}) {
|
|
|
37
41
|
]);
|
|
38
42
|
}
|
|
39
43
|
if (result.exitCode !== 0 && !options.allowFailure) {
|
|
40
|
-
|
|
44
|
+
const failure = options.failure ?? {};
|
|
45
|
+
const cleaned = result.stderr || result.stdout;
|
|
46
|
+
if (!failure.loginCommand && isAuthenticationFailure(cleaned)) {
|
|
47
|
+
failure.agentCredentials = Boolean(process.env.ARGOCD_AUTH_TOKEN?.trim());
|
|
48
|
+
try {
|
|
49
|
+
const { target } = await resolveTarget(options.context);
|
|
50
|
+
const server = target.envServer ?? target.server;
|
|
51
|
+
failure.loginCommand = server
|
|
52
|
+
? `argocd login '${server.replaceAll("'", "'\\''")}' --sso --grpc-web`
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
failure.loginCommand = undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw mapArgocdFailure(cleaned, failure);
|
|
41
60
|
}
|
|
42
61
|
return result;
|
|
43
62
|
}
|
|
63
|
+
function isAuthenticationFailure(message) {
|
|
64
|
+
return /rpc error: code = Unauthenticated|invalid session|token is expired|token has expired|no session information|unauthenticated|please login|login required|failed to get token|session (?:has )?expired/i.test(message);
|
|
65
|
+
}
|
|
44
66
|
/** Run an operation whose stdout is JSON and parse it. */
|
|
45
67
|
export async function argocdJson(op, args, options = {}) {
|
|
46
68
|
const result = await runArgocd(op, args, options);
|
package/dist/src/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { appsCommand } from "./commands/apps.js";
|
|
|
5
5
|
import { appsetsCommand } from "./commands/appsets.js";
|
|
6
6
|
import { canICommand, whoamiCommand } from "./commands/account.js";
|
|
7
7
|
import { ctxCommand } from "./commands/ctx.js";
|
|
8
|
+
import { authCommand } from "./commands/auth.js";
|
|
8
9
|
import { homeCommand } from "./commands/home.js";
|
|
9
10
|
import { clustersCommand, reposCommand } from "./commands/inventory.js";
|
|
10
11
|
import { projectsCommand } from "./commands/projects.js";
|
|
@@ -45,6 +46,7 @@ const refusalCommand = (key) => async () => refuse(key);
|
|
|
45
46
|
const TOP_LEVEL_REFUSALS = Object.keys(REFUSALS).filter((key) => !key.includes(" "));
|
|
46
47
|
export const COMMANDS = Object.assign(Object.create(null), {
|
|
47
48
|
ctx: ctxCommand,
|
|
49
|
+
auth: authCommand,
|
|
48
50
|
whoami: whoamiCommand,
|
|
49
51
|
"can-i": canICommand,
|
|
50
52
|
apps: appsCommand,
|
|
@@ -56,6 +58,12 @@ export const COMMANDS = Object.assign(Object.create(null), {
|
|
|
56
58
|
...Object.fromEntries(TOP_LEVEL_REFUSALS.map((key) => [key, refusalCommand(key)])),
|
|
57
59
|
});
|
|
58
60
|
export async function main(argv = process.argv.slice(2)) {
|
|
61
|
+
if (argv.length === 1 &&
|
|
62
|
+
argv[0] !== undefined &&
|
|
63
|
+
["--version", "-v", "-V"].includes(argv[0])) {
|
|
64
|
+
process.stdout.write(`${encode(VERSION)}\n`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
59
67
|
await runAxiCli({
|
|
60
68
|
description: DESCRIPTION,
|
|
61
69
|
version: VERSION,
|