Skip to content

feat(cli): aisix export — emit resources.yaml from a running etcd store#761

Open
moonming wants to merge 1 commit into
mainfrom
feat/aisix-export
Open

feat(cli): aisix export — emit resources.yaml from a running etcd store#761
moonming wants to merge 1 commit into
mainfrom
feat/aisix-export

Conversation

@moonming

@moonming moonming commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

What

Adds an aisix export subcommand — the inverse of the file source (resources_file). It reads the resources currently stored in etcd and emits a resources.yaml that the file loader can load back, so a standalone deployment can move from Admin-API-plus-etcd to the declarative file (or take a portable, declarative backup of its live config).

aisix export --etcd <endpoints> [--prefix /aisix] [--reveal-secrets] [-o resources.yaml]

Default output is stdout; the secret-placeholder list and any warnings go to stderr.

How it stays faithful to the loader

Export decodes every kind through the same typed models the gateway loads (the shared snapshot builder), so the exported set is exactly what the running gateway would serve. It then rewrites each entry into idiomatic file form — the exact sugar the file source desugars on the way in:

  • Resugar references (canonical → file):
    • model.provider_key_idprovider_key: <that provider key's display_name>
    • rate_limit_policy.scope_ref for api_key / model scopes → the referenced entry's name; team / member / team_member pass through verbatim.
    • A reference that resolves to nothing in the export set is kept as a raw id and warned — a dangling reference is a real data issue to surface, not hide.
  • Strip ids — the file derives ids from names (UUIDv5), so no id is emitted. Canonical api-key documents carry no name, so a deterministic apikey-<hash…> display_name is synthesized from the entry's already-hashed key_hash (emitted verbatim — it round-trips exactly and is not plaintext).
  • Duplicate identities within a kind (possible in raw etcd, impossible in the file) are surfaced as warnings.
  • Entries the loader would reject during decode, and guardrail_attachments (which the file format has no collection for), are reported on stderr rather than dropped silently.

Secret policy

No live credential is written in cleartext by default. Each secret-bearing field is replaced with a derived, stable, greppable ${VAR} placeholder, and a companion "set these before loading" list is printed to stderr — never into the file:

  • provider_key.api_key, mcp_server.secret, a2a_agent.secret
  • guardrail moderation credentials (api_key, access_key_secret, and the nested Bedrock secret_access_key)
  • OTLP exporter auth headers

api_keys emit key_hash directly (already a hash — round-trips exactly; no placeholder). --reveal-secrets opts into emitting the real stored values inline; it is documented as unsafe and intended only for air-gapped, same-host migration.

The placeholder names use the AISIXSECRET_… prefix rather than AISIX_…: the gateway's config loader claims the AISIX_ prefix and rejects unknown keys, so an AISIX_… secret variable set in the data plane's environment would be misread as a bad config override and fail boot (the same reason the existing SLS_CRED_… / OBJSTORE_CRED_… conventions keep secret variables off that prefix).

Emission reuses the file source's own YAML library, so the output is guaranteed to re-parse (including the mandatory quoted _format_version: "1").

Tests

  • Rust unit (20): per-kind resugar (provider-key ref by name, scope_ref resolution, dangling-ref warn), synthetic api-key name + key_hash passthrough, duplicate-name warn, id-stripping, secret placeholder derivation + no-cleartext-by-default + --reveal-secrets, recursive/nested guardrail + header redaction, and YAML round-trip (format-version stays a string, placeholders survive).
  • e2e round-trip (tests/e2e/src/cases/export-roundtrip-e2e.test.ts): seed etcd → run aisix export → load the exported file into a file-mode gateway (supplying the real secret through the emitted placeholder variable) → assert identical observable behavior (/v1/models, a chat completion, and an allowed_models 403) against the etcd-mode gateway, and assert the exported file contains no live credential at default settings. This closes the loop: etcd → export → file → same behavior.

Full workspace suite green (2299 passed) run twice; cargo fmt --check and cargo clippy --workspace --all-targets -D warnings clean; resource schemas untouched.

Summary by CodeRabbit

  • New Features

    • Added an aisix export command to export gateway resources from etcd to YAML.
    • Supports custom endpoints, key prefixes, output files, and optional secret revelation.
    • Automatically redacts secrets using environment-variable placeholders and reports required values.
    • Preserves resource references, ordering, and scalar types for reliable re-import.
    • Displays warnings for skipped entries, unresolved references, duplicates, and omitted attachments.
  • Tests

    • Added end-to-end coverage verifying export/import round trips and equivalent gateway behavior.

Add an `aisix export` subcommand that reads the resources currently stored
in etcd and emits a resources.yaml the file source (`resources_file`) can
load back — the inverse of the file loader and the migration/backup path
for a standalone deployment moving from Admin-API-plus-etcd to the
declarative file.

Export decodes each kind through the same typed models the gateway loads
(via the shared snapshot builder), then rewrites the set into idiomatic
file form:
- resugar references: model.provider_key_id -> `provider_key: <name>`;
  rate_limit_policy.scope_ref for api_key/model scopes -> the referenced
  entry's name (team-family scopes pass through). Dangling references are
  kept verbatim and warned, never silently dropped.
- ids are dropped (the file derives them from names); api_keys get a
  deterministic `apikey-<hash…>` display_name synthesized from their
  already-hashed key_hash, which is emitted verbatim.
- duplicate identities within a kind are surfaced as warnings.

Secrets are never emitted in cleartext by default: provider_key api_key,
mcp/a2a secret, guardrail moderation credentials, and OTLP exporter auth
headers are replaced with derived `${VAR}` placeholders, and a companion
"set these before loading" list is printed to stderr (never into the
file). `--reveal-secrets` opts into inline values for air-gapped
same-host migration.

Emission reuses the file source's own YAML library so the output is
guaranteed to re-parse. Round-trip proven end to end by a new e2e:
seed etcd -> `aisix export` -> load the file into a file-mode gateway ->
assert identical /v1/models, chat completion, and allowed_models 403
against the etcd-mode gateway, plus no live credential at default
settings.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds aisix export, which loads gateway resources from etcd, rewrites them into resources YAML, redacts secrets with environment placeholders, reports diagnostics, and validates etcd-to-file round-trip behavior.

Changes

Resource export workflow

Layer / File(s) Summary
Secret placeholder handling
crates/aisix-server/src/export/secrets.rs
Adds deterministic environment-variable naming and redaction for top-level fields, nested secret keys, and headers, with unit coverage.
Canonical snapshot transformation
crates/aisix-server/src/export/document.rs
Converts snapshots into ordered resource collections, rewrites identifiers and references, and records duplicate, dangling, and unrepresentable-entry warnings.
Resources YAML emission
crates/aisix-server/src/export/yaml_emit.rs, crates/aisix-server/Cargo.toml
Emits ordered YAML using yaml-rust2, preserving scalar types, quoted format metadata, and placeholders.
Export CLI orchestration
crates/aisix-server/src/main.rs, crates/aisix-server/src/export/mod.rs
Adds CLI arguments, etcd loading, document construction, output writing, and stderr reporting for rejections, warnings, and secrets.
Etcd-to-file round-trip validation
tests/e2e/src/cases/export-roundtrip-e2e.test.ts
Validates redaction, placeholder injection, exported diagnostics, and equivalent gateway behavior after loading the exported YAML.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as aisix export
  participant Etcd as etcd
  participant Loader as Snapshot loader
  participant Builder as build_export_document
  participant Emitter as emit_yaml
  participant FileGateway as file-mode gateway
  CLI->>Etcd: load resources under prefix
  Etcd-->>Loader: return stored documents
  Loader-->>Builder: provide AisixSnapshot
  Builder-->>Emitter: provide redacted ExportDocument
  Emitter-->>CLI: return resources YAML
  CLI->>FileGateway: start with YAML and environment values
  FileGateway-->>CLI: serve equivalent gateway behavior
Loading

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Security Check ❌ Error Export still writes --reveal-secrets files with std::fs::write, exports provider-key request.default_headers verbatim, and never checks duplicate secret placeholders. Open output with 0600, redact or placeholderize provider-key request.default_headers, and add a post-pass warning/error for duplicate env_var collisions.
E2e Test Quality Review ⚠️ Warning E2E flow is solid, but the PR still hard-fails on missing etcd in CI, writes --reveal-secrets files with default perms, and doesn’t detect secret placeholder collisions. Use ctx.skip() for unreachable CI etcd, create output files with 0600 on Unix, and warn on duplicate env_var placeholders before shipping.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding an aisix export CLI that emits a resources.yaml from a running etcd store.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/aisix-export

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
crates/aisix-server/src/export/secrets.rs (1)

76-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a shared redaction-context struct instead of repeating the 5-arg tail across redact_top_level, redact_by_key, and redact_headers.

All three functions carry the same (kind_token, kind, identity, reveal, out) tail. Bundling them into a small struct RedactionCtx<'a> { kind_token: &'a str, kind: &'static str, identity: &'a str, reveal: bool, out: &'a mut Vec<SecretPlaceholder> } would cut duplication and remove the risk of transposing two same-typed positional &str args (kind_token vs kind) at a call site.

♻️ Sketch
pub struct RedactionCtx<'a> {
    pub kind_token: &'a str,
    pub kind: &'static str,
    pub identity: &'a str,
    pub reveal: bool,
    pub out: &'a mut Vec<SecretPlaceholder>,
}

pub fn redact_top_level(doc: &mut Value, field: &str, ctx: &mut RedactionCtx) { ... }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aisix-server/src/export/secrets.rs` around lines 76 - 183, Introduce a
shared RedactionCtx<'a> containing kind_token, kind, identity, reveal, and out,
then update redact_top_level, redact_by_key, and redact_headers to accept the
context instead of their repeated five-argument tail. Adjust recursive
redact_by_key calls and all callers to pass the context while preserving
existing redaction behavior and placeholder output.
tests/e2e/src/cases/export-roundtrip-e2e.test.ts (1)

110-114: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

afterAll cleanup isn't isolated — an early failure skips the remaining teardown.

If fileApp?.exit() rejects, etcdApp?.exit() and upstream?.close() never run, potentially leaking the spawned processes for subsequent test files.

♻️ Proposed fix
 afterAll(async () => {
-  await fileApp?.exit();
-  await etcdApp?.exit();
-  await upstream?.close();
+  await Promise.allSettled([fileApp?.exit(), etcdApp?.exit(), upstream?.close()]);
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/src/cases/export-roundtrip-e2e.test.ts` around lines 110 - 114,
Update the afterAll teardown to isolate cleanup failures so a rejection from
fileApp?.exit() does not prevent etcdApp?.exit() or upstream?.close() from
running. Ensure each resource cleanup is attempted independently while
preserving the existing optional-resource behavior.
crates/aisix-server/src/export/yaml_emit.rs (1)

20-29: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Centralize the resources-file format version yaml_emit.rs and filesource/mod.rs both pin "1" separately. Keep a single shared constant so a future version bump updates the emitter and loader together.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aisix-server/src/export/yaml_emit.rs` around lines 20 - 29, Centralize
the resources-file format version currently duplicated in yaml_emit.rs and
filesource/mod.rs into one shared public constant. Update emit_yaml and the
loader’s version validation to reference that constant, removing both local "1"
definitions so future version changes require a single update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/aisix-server/src/export/mod.rs`:
- Around line 84-96: Update the output-file handling in the export command
around args.output and Export::reveal_secrets so files written with
--reveal-secrets are created with owner-only permissions (0600) before any
secret-containing data is written. Preserve the existing write error reporting
and normal output behavior for exports without revealed secrets.

In `@crates/aisix-server/src/export/secrets.rs`:
- Around line 46-66: Add a post-pass in document.rs::build_export_document over
ExportDocument::secret_placeholders to detect repeated env_var values. Emit a
warning, matching the existing duplicate file-identity check, whenever two
distinct placeholders resolve to the same environment variable, including enough
identity context to identify both secrets. Preserve the existing placeholder
generation and other validation behavior.

In `@crates/aisix-server/src/main.rs`:
- Around line 87-89: Update the prefix argument definition in the main CLI
configuration to reuse the canonical default from aisix_core::EtcdConfig instead
of hardcoding "/aisix". Preserve the existing long option and String type while
ensuring aisix export stays aligned with future EtcdConfig prefix changes.

In `@tests/e2e/src/cases/export-roundtrip-e2e.test.ts`:
- Around line 56-66: Update the beforeAll setup around EtcdClient.ping so
unavailable etcd always follows the existing skip flow, including when
process.env.CI is set. Remove the hard Error path and ensure the test’s
established ctx.skip() handling remains responsible for skipping without
failing.

---

Nitpick comments:
In `@crates/aisix-server/src/export/secrets.rs`:
- Around line 76-183: Introduce a shared RedactionCtx<'a> containing kind_token,
kind, identity, reveal, and out, then update redact_top_level, redact_by_key,
and redact_headers to accept the context instead of their repeated five-argument
tail. Adjust recursive redact_by_key calls and all callers to pass the context
while preserving existing redaction behavior and placeholder output.

In `@crates/aisix-server/src/export/yaml_emit.rs`:
- Around line 20-29: Centralize the resources-file format version currently
duplicated in yaml_emit.rs and filesource/mod.rs into one shared public
constant. Update emit_yaml and the loader’s version validation to reference that
constant, removing both local "1" definitions so future version changes require
a single update.

In `@tests/e2e/src/cases/export-roundtrip-e2e.test.ts`:
- Around line 110-114: Update the afterAll teardown to isolate cleanup failures
so a rejection from fileApp?.exit() does not prevent etcdApp?.exit() or
upstream?.close() from running. Ensure each resource cleanup is attempted
independently while preserving the existing optional-resource behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d4ad96d1-be9d-4d5c-9125-90254ff5a78a

📥 Commits

Reviewing files that changed from the base of the PR and between 5357034 and 83acbca.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • crates/aisix-server/Cargo.toml
  • crates/aisix-server/src/export/document.rs
  • crates/aisix-server/src/export/mod.rs
  • crates/aisix-server/src/export/secrets.rs
  • crates/aisix-server/src/export/yaml_emit.rs
  • crates/aisix-server/src/main.rs
  • tests/e2e/src/cases/export-roundtrip-e2e.test.ts

Comment on lines +84 to +96
match &args.output {
Some(path) => {
std::fs::write(path, &yaml)
.map_err(|e| anyhow::anyhow!("writing {}: {e}", path.display()))?;
eprintln!(
"Exported {resource_count} resource(s) to {}",
path.display()
);
}
None => {
print!("{yaml}");
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Output file is written with default (umask) permissions — tighten for the --reveal-secrets case.

std::fs::write leaves the file's permissions to the process umask, typically group/world-readable on many hosts. The Export::reveal_secrets doc comment (main.rs) explicitly anticipates this flag being used on a live host, at which point this file contains real upstream credentials in the clear. It should be created with owner-only permissions.

🔒 Proposed fix
-use std::path::PathBuf;
+use std::io::Write;
+use std::path::PathBuf;
 use std::time::Duration;
+#[cfg(unix)]
+use std::os::unix::fs::OpenOptionsExt;
...
     match &args.output {
         Some(path) => {
-            std::fs::write(path, &yaml)
-                .map_err(|e| anyhow::anyhow!("writing {}: {e}", path.display()))?;
+            let mut opts = std::fs::OpenOptions::new();
+            opts.write(true).create(true).truncate(true);
+            #[cfg(unix)]
+            opts.mode(0o600);
+            let mut file = opts
+                .open(path)
+                .map_err(|e| anyhow::anyhow!("writing {}: {e}", path.display()))?;
+            file.write_all(yaml.as_bytes())
+                .map_err(|e| anyhow::anyhow!("writing {}: {e}", path.display()))?;
             eprintln!(
                 "Exported {resource_count} resource(s) to {}",
                 path.display()
             );
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
match &args.output {
Some(path) => {
std::fs::write(path, &yaml)
.map_err(|e| anyhow::anyhow!("writing {}: {e}", path.display()))?;
eprintln!(
"Exported {resource_count} resource(s) to {}",
path.display()
);
}
None => {
print!("{yaml}");
}
}
match &args.output {
Some(path) => {
let mut opts = std::fs::OpenOptions::new();
opts.write(true).create(true).truncate(true);
#[cfg(unix)]
opts.mode(0o600);
let mut file = opts
.open(path)
.map_err(|e| anyhow::anyhow!("writing {}: {e}", path.display()))?;
file.write_all(yaml.as_bytes())
.map_err(|e| anyhow::anyhow!("writing {}: {e}", path.display()))?;
eprintln!(
"Exported {resource_count} resource(s) to {}",
path.display()
);
}
None => {
print!("{yaml}");
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aisix-server/src/export/mod.rs` around lines 84 - 96, Update the
output-file handling in the export command around args.output and
Export::reveal_secrets so files written with --reveal-secrets are created with
owner-only permissions (0600) before any secret-containing data is written.
Preserve the existing write error reporting and normal output behavior for
exports without revealed secrets.

Comment on lines +46 to +66
pub fn sanitize(label: &str) -> String {
label
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() {
c.to_ascii_uppercase()
} else {
'_'
}
})
.collect()
}

/// `AISIXSECRET_<KIND>_<IDENTITY>_<FIELD>` — the derived variable name.
pub fn secret_var(kind_token: &str, identity: &str, field: &str) -> String {
format!(
"{SECRET_ENV_PREFIX}_{kind_token}_{}_{}",
sanitize(identity),
sanitize(field),
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

sanitize/secret_var can collide across different identities or fields, aliasing two distinct secrets to one placeholder.

sanitize maps every non-alphanumeric byte to _, so secret_var("PROVIDER_KEY", "openai-prod", "api_key") and secret_var("PROVIDER_KEY", "openai.prod", "api_key") both resolve to AISIXSECRET_PROVIDER_KEY_OPENAI_PROD_API_KEY. Two provider keys named e.g. openai-prod and openai_prod would receive the same exported placeholder; setting that one env var then feeds the same credential to both, silently misassigning one of them. Nothing in document.rs::build_export_document checks the collected placeholders for duplicate env_var values the way it already does for duplicate file identities (document.rs lines 340-345).

Add a post-pass over ExportDocument::secret_placeholders in build_export_document that warns (like the existing duplicate-identity check) when two placeholders share an env_var.

Also applies to: 60-66

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aisix-server/src/export/secrets.rs` around lines 46 - 66, Add a
post-pass in document.rs::build_export_document over
ExportDocument::secret_placeholders to detect repeated env_var values. Emit a
warning, matching the existing duplicate file-identity check, whenever two
distinct placeholders resolve to the same environment variable, including enough
identity context to identify both secrets. Preserve the existing placeholder
generation and other validation behavior.

Comment on lines +87 to +89
/// Key prefix the resources are stored under.
#[arg(long, default_value = "/aisix")]
prefix: 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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n '"/aisix"' --type=rust -B2 -A2

Repository: api7/aisix

Length of output: 148


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate any canonical default etcd prefix definitions or usages.
rg -n --hidden --glob '!target' --glob '!dist' --glob '!build' \
  'aisix|default.*prefix|prefix.*default|"/aisix"' \
  crates api7 -g '*.rs' -g '*.toml' -g '*.yaml' -g '*.yml'

Repository: api7/aisix

Length of output: 50367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the CLI and export wiring around the prefix field.
ast-grep outline crates/aisix-server/src/main.rs --view expanded

Repository: api7/aisix

Length of output: 3118


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --hidden --glob '!target' --glob '!dist' --glob '!build' \
  'aisix|default.*prefix|prefix.*default|"/aisix"' \
  crates api7 -g '*.rs' -g '*.toml' -g '*.yaml' -g '*.yml'

Repository: api7/aisix

Length of output: 50367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline crates/aisix-server/src/main.rs --view expanded

Repository: api7/aisix

Length of output: 3118


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any canonical default etcd-prefix constant or shared default value.
rg -n --hidden --glob '!target' --glob '!dist' --glob '!build' \
  'DEFAULT.*PREFIX|default.*prefix|prefix.*default|"/aisix"|aisix_prefix|etcd.*prefix' \
  crates api7 -g '*.rs' -g '*.toml' -g '*.yaml' -g '*.yml'

Repository: api7/aisix

Length of output: 10040


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '85,330p' crates/aisix-core/src/config.rs

Repository: api7/aisix

Length of output: 10456


Reuse the canonical etcd prefix default crates/aisix-server/src/main.rs:88-89 Use the same default as aisix_core::EtcdConfig instead of hardcoding "/aisix" here; that keeps aisix export aligned if the prefix changes later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aisix-server/src/main.rs` around lines 87 - 89, Update the prefix
argument definition in the main CLI configuration to reuse the canonical default
from aisix_core::EtcdConfig instead of hardcoding "/aisix". Preserve the
existing long option and String type while ensuring aisix export stays aligned
with future EtcdConfig prefix changes.

Comment on lines +56 to +66
beforeAll(async () => {
const etcd = new EtcdClient();
etcdReachable = await etcd.ping();
if (!etcdReachable) {
// The whole point of this case is the etcd → file equivalence; a
// silent skip on CI would let the migration contract rot invisibly.
if (process.env.CI) {
throw new Error("export round-trip requires etcd on CI (AISIX_E2E_ETCD)");
}
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Don't hard-fail on etcd unavailability even under CI — follow the established skip convention.

This throws a hard Error when etcd is unreachable and process.env.CI is set, rather than deferring uniformly to the test's own ctx.skip(). Based on learnings, "use ctx.skip() (instead of throwing or failing the test) when etcd is not reachable/available" and "individual test files should not deviate from this convention by throwing hard errors on etcd unavailability."

✅ Proposed fix
     const etcd = new EtcdClient();
     etcdReachable = await etcd.ping();
     if (!etcdReachable) {
-      // The whole point of this case is the etcd → file equivalence; a
-      // silent skip on CI would let the migration contract rot invisibly.
-      if (process.env.CI) {
-        throw new Error("export round-trip requires etcd on CI (AISIX_E2E_ETCD)");
-      }
       return;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeAll(async () => {
const etcd = new EtcdClient();
etcdReachable = await etcd.ping();
if (!etcdReachable) {
// The whole point of this case is the etcd → file equivalence; a
// silent skip on CI would let the migration contract rot invisibly.
if (process.env.CI) {
throw new Error("export round-trip requires etcd on CI (AISIX_E2E_ETCD)");
}
return;
}
beforeAll(async () => {
const etcd = new EtcdClient();
etcdReachable = await etcd.ping();
if (!etcdReachable) {
return;
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/src/cases/export-roundtrip-e2e.test.ts` around lines 56 - 66,
Update the beforeAll setup around EtcdClient.ping so unavailable etcd always
follows the existing skip flow, including when process.env.CI is set. Remove the
hard Error path and ensure the test’s established ctx.skip() handling remains
responsible for skipping without failing.

Source: Learnings

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.

1 participant