Conversation
There was a problem hiding this comment.
Summary of Changes
Hello @roderik, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces significant enhancements to the artifact naming conventions within the system, making them highly configurable. It allows users to define custom names for various generated components, such as the genesis block, static node lists, and faucet keys, directly through new CLI options and Helm chart values. This change provides greater flexibility for deployment and integration, particularly in Kubernetes environments, by standardizing how these artifacts are named and referenced. The updates ensure that both the CLI and Helm templates correctly process and apply these custom names, improving the overall manageability and clarity of deployed resources.
Highlights
- Configurable Artifact Naming: Introduces new parameters in both the CLI and Helm charts to allow users to customize the names of generated artifacts such as genesis files, static node configurations, and faucet-related Kubernetes resources.
- Helm Chart Integration: Updates Helm charts (
networkandnetwork-bootstrapper) to consume these new naming parameters, enabling global and local overrides for artifact names within Kubernetes deployments. - CLI Prompting: Enhances the CLI's
bootstrapcommand with interactive prompts for artifact names, providing sensible defaults and allowing users to easily customize them. - Standardized Output: Ensures that output artifact names are consistently applied across different output targets (file system and Kubernetes), improving predictability and manageability.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in pull request comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
|
To view in Slack, search for: 1758125735.023459 |
There was a problem hiding this comment.
Hey there - I've reviewed your changes - here's some feedback:
- The CLI option definitions for all artifact naming overrides are very repetitive; consider abstracting them into a shared descriptor array or helper to reduce duplication and simplify future additions.
- Default values for artifact names (e.g. "besu-node", "besu-genesis") are defined in multiple places (code, tests, Helm charts); centralizing these constants into a single shared module will ensure consistency and ease maintenance.
- The resolveText helper in runBootstrap closely mirrors resolveCount—extracting a generic
resolveOptionutility to handle both numeric and text prompts could streamline the logic and cut down on boilerplate.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The CLI option definitions for all artifact naming overrides are very repetitive; consider abstracting them into a shared descriptor array or helper to reduce duplication and simplify future additions.
- Default values for artifact names (e.g. "besu-node", "besu-genesis") are defined in multiple places (code, tests, Helm charts); centralizing these constants into a single shared module will ensure consistency and ease maintenance.
- The resolveText helper in runBootstrap closely mirrors resolveCount—extracting a generic `resolveOption` utility to handle both numeric and text prompts could streamline the logic and cut down on boilerplate.
## Individual Comments
### Comment 1
<location> `charts/network/charts/network-bootstrapper/templates/job.yaml:49-53` </location>
<code_context>
+ {{- $resolvedServiceName := default "besu-node" (default (get $globalNodes "serviceName") .Values.settings.staticNodeServiceName) }}
</code_context>
<issue_to_address>
**suggestion:** Helm template logic for resolving global overrides is nested and may be confusing.
Extracting the nested logic into helper templates or adding documentation on value precedence would improve maintainability.
</issue_to_address>
### Comment 2
<location> `src/cli/build-command.ts:173-175` </location>
<code_context>
): string[] => {
const normalizedDomain = normalizeStaticNodeDomain(domain);
const normalizedNamespace = normalizeStaticNodeNamespace(namespace);
+ const hostServiceName =
+ normalizeStaticNodeNamespace(serviceName) ?? serviceName;
+ const podNamePrefix = normalizeStaticNodeNamespace(podPrefix) ?? podPrefix;
return nodes.map((node) => {
</code_context>
<issue_to_address>
**suggestion:** Normalization of serviceName and podPrefix may be unnecessary or inconsistent.
Ensure normalization is only applied to serviceName and podPrefix if they are meant to represent namespaces, as unnecessary normalization could cause issues.
```suggestion
const hostServiceName = serviceName;
const podNamePrefix = podPrefix;
```
</issue_to_address>
### Comment 3
<location> `src/cli/build-command.ts:262` </location>
<code_context>
DEFAULT_VALIDATOR_COUNT
);
+ const staticNodeServiceName = await resolveText(
+ "Static node service name",
+ staticNodeServiceNameOption,
</code_context>
<issue_to_address>
**issue (complexity):** Consider replacing the repeated resolveText calls with a loop over a config array to streamline the code.
You can collapse all five of your nearly‐identical `resolveText` calls into one loop over a small config array. For example:
```ts
// inside runBootstrap, replace individual calls with...
const textFields = [
{ key: "staticNodeServiceName", label: "Static node service name", provided: staticNodeServiceNameOption, defaultVal: DEFAULT_STATIC_NODE_SERVICE_NAME },
{ key: "staticNodePodPrefix", label: "Static node pod prefix", provided: staticNodePodPrefixOption, defaultVal: DEFAULT_STATIC_NODE_POD_PREFIX },
{ key: "genesisConfigMapName", label: "Genesis ConfigMap name", provided: genesisConfigmapNameOption, defaultVal: DEFAULT_GENESIS_CONFIGMAP_NAME },
{ key: "staticNodesConfigMapName", label: "Static nodes ConfigMap name", provided: staticNodesConfigmapNameOption, defaultVal: DEFAULT_STATIC_NODES_CONFIGMAP_NAME },
{ key: "faucetArtifactPrefix", label: "Faucet artifact prefix", provided: faucetArtifactPrefixOption, defaultVal: DEFAULT_FAUCET_ARTIFACT_PREFIX },
] as const;
const results = await Promise.all(
textFields.map(f => resolveText(f.label, f.provided, f.defaultVal))
);
// destructure into named variables in order
const [
staticNodeServiceName,
staticNodePodPrefix,
genesisConfigMapName,
staticNodesConfigMapName,
faucetArtifactPrefix
] = results;
```
This keeps all prompts exactly the same but removes ~20 lines of boilerplate.
</issue_to_address>
### Comment 4
<location> `src/cli/output.ts:189` </location>
<code_context>
};
-const createValidatorSpecs = (nodes: readonly IndexedNode[]): ConfigMapSpec[] =>
+const createValidatorSpecs = (
+ nodes: readonly IndexedNode[],
+ validatorPrefix: string
</code_context>
<issue_to_address>
**issue (complexity):** Consider introducing a factory function to encapsulate artifact naming logic and return bound spec-creator functions.
You can collapse all of those “pass-the-prefix-everywhere” calls into a small factory that captures your `ArtifactNames` up-front and returns bound spec-creators. For example:
```ts
// new helper at top
function makeSpecFactory(names: ArtifactNames) {
return {
validatorSpecs: (nodes: IndexedNode[]): ConfigMapSpec[] =>
nodes.flatMap<ConfigMapSpec>(node => {
const ord = node.index - 1;
const base = `${names.validatorPrefix}-${ord}`;
return [
{ name:`${base}-address`, key:"address", value:node.address },
{ name:`${base}-private-key`, key:"privateKey", value:node.privateKey },
{ name:`${base}-enode`, key:"enode", value:node.enode },
{ name:`${base}-pubkey`, key:"publicKey", value:node.publicKey },
];
}),
faucetConfigSpecs: (faucet: GeneratedNodeKey): ConfigMapSpec[] => [
{ name:`${names.faucetPrefix}-address`, key:"address", value:faucet.address },
{ name:`${names.faucetPrefix}-pubkey`, key:"publicKey", value:faucet.publicKey },
],
faucetSecretSpecs: (faucet: GeneratedNodeKey): SecretSpec[] => [
{ name:`${names.faucetPrefix}-private-key`, key:"privateKey", value:faucet.privateKey },
],
};
}
```
Then in both `outputToFile` and `outputToKubernetes` you only need:
```ts
const { validatorSpecs, faucetConfigSpecs, faucetSecretSpecs } =
makeSpecFactory(payload.artifactNames);
const vSpecs = validatorSpecs(payload.validators);
const fCfg = faucetConfigSpecs(payload.faucet);
const fSec = faucetSecretSpecs(payload.faucet);
// …use vSpecs, fCfg, fSec in writes/upserts…
```
This:
- removes the need to pass prefixes into every call
- keeps all functionality the same
- localizes naming logic to one small helper.
</issue_to_address>
### Comment 5
<location> `src/cli/output.test.ts:335` </location>
<code_context>
}
});
+ test("kubernetes output respects custom artifact names", async () => {
+ const originalLoad = (KubeConfig.prototype as any).loadFromCluster;
+ const originalMake = (KubeConfig.prototype as any).makeApiClient;
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting Kubernetes test stubs and common assertions into helper functions to reduce repetition and clarify each test's unique logic.
Here’s one way you can DRY-up the Kubernetes tests by extracting the client/file stub and common assertions into helpers. You’ll end up with each test only describing its unique bits:
```ts
// in test/utils/kube.ts
import { CoreV1Api, KubeConfig } from "@kubernetes/client-node";
import { Bun } from "bun";
// returns stubs & a restore() fn
export function setupKubeMocks() {
const createdConfigMaps: string[] = [];
const createdSecrets: string[] = [];
const originalLoad = (KubeConfig.prototype as any).loadFromCluster;
const originalMake = (KubeConfig.prototype as any).makeApiClient;
const originalFile = (Bun as any).file;
;(KubeConfig.prototype as any).loadFromCluster = () => {};
;(KubeConfig.prototype as any).makeApiClient = () =>
({
listNamespacedConfigMap: () => Promise.resolve(),
listNamespacedSecret: () => Promise.resolve(),
createNamespacedConfigMap: ({ body }) => {
createdConfigMaps.push(body.metadata.name);
return Promise.resolve();
},
createNamespacedSecret: ({ body }) => {
createdSecrets.push(body.metadata.name);
return Promise.resolve();
},
} as unknown as CoreV1Api);
;(Bun as any).file = () =>
({ text: () => Promise.resolve("custom-namespace") } as any);
return {
createdConfigMaps,
createdSecrets,
restore() {
(KubeConfig.prototype as any).loadFromCluster = originalLoad;
(KubeConfig.prototype as any).makeApiClient = originalMake;
(Bun as any).file = originalFile;
},
};
}
// in test/utils/assert.ts
export function expectNames(
actual: string[],
expected: string[],
) {
expected.forEach(name => expect(actual).toContain(name));
}
```
Then your new test shrinks to:
```ts
import { setupKubeMocks } from "./utils/kube";
import { expectNames } from "./utils/assert";
test("kubernetes output respects custom artifact names", async () => {
const { createdConfigMaps, createdSecrets, restore } = setupKubeMocks();
try {
const payload = {
...samplePayload,
artifactNames: {
faucetPrefix: "custom-faucet",
validatorPrefix: "custom-validator",
genesisConfigMapName: "custom-genesis",
staticNodesConfigMapName: "custom-static",
},
staticNodes: [
staticNodeUri(
sampleValidator,
SAMPLE_STATIC_DOMAIN,
DEFAULT_STATIC_NODE_PORT,
DEFAULT_STATIC_NODE_DISCOVERY_PORT,
SAMPLE_STATIC_NAMESPACE,
"custom-service",
"custom-validator",
),
],
};
await outputResult("kubernetes", payload);
expectNames(
createdConfigMaps.sort(),
["custom-genesis", "custom-static", "custom-validator-0-address", "custom-faucet-address"],
);
expectNames(
createdSecrets.sort(),
["custom-faucet-private-key", "custom-validator-0-private-key"],
);
} finally {
restore();
}
});
```
This removes the repeated stubbing/teardown and the verbose per‐name loops, leaving only what’s unique to each test.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| {{- $resolvedServiceName := default "besu-node" (default (get $globalNodes "serviceName") .Values.settings.staticNodeServiceName) }} | ||
| {{- $resolvedPodPrefix := default (printf "%s-validator" $resolvedServiceName) (default (get $globalNodes "podPrefix") .Values.settings.staticNodePodPrefix) }} | ||
| {{- $resolvedGenesisName := default "besu-genesis" (default (get $globalNodes "genesisConfigMapName") .Values.settings.genesisConfigMapName) }} | ||
| {{- $resolvedStaticNodesName := default "besu-static-nodes" (default (get $globalNodes "staticNodesConfigMapName") .Values.settings.staticNodesConfigMapName) }} | ||
| {{- $resolvedFaucetPrefix := default "besu-faucet" (default (get $globalNodes "faucetArtifactPrefix") .Values.settings.faucetArtifactPrefix) }} |
There was a problem hiding this comment.
suggestion: Helm template logic for resolving global overrides is nested and may be confusing.
Extracting the nested logic into helper templates or adding documentation on value precedence would improve maintainability.
| const hostServiceName = | ||
| normalizeStaticNodeNamespace(serviceName) ?? serviceName; | ||
| const podNamePrefix = normalizeStaticNodeNamespace(podPrefix) ?? podPrefix; |
There was a problem hiding this comment.
suggestion: Normalization of serviceName and podPrefix may be unnecessary or inconsistent.
Ensure normalization is only applied to serviceName and podPrefix if they are meant to represent namespaces, as unnecessary normalization could cause issues.
| const hostServiceName = | |
| normalizeStaticNodeNamespace(serviceName) ?? serviceName; | |
| const podNamePrefix = normalizeStaticNodeNamespace(podPrefix) ?? podPrefix; | |
| const hostServiceName = serviceName; | |
| const podNamePrefix = podPrefix; |
There was a problem hiding this comment.
Code Review
This pull request effectively parameterizes the bootstrap artifact names, exposing them through CLI flags, Helm values, and interactive prompts. The changes are comprehensive, touching the CLI, core logic, output modules, and Helm charts, and are well-supported by updated tests. The implementation is solid. I have a few suggestions to improve consistency and maintainability, particularly regarding default value derivation in the CLI, artifact generation for the faucet node, and some opportunities for code simplification.
| const staticNodePodPrefix = await resolveText( | ||
| "Static node pod prefix", | ||
| staticNodePodPrefixOption, | ||
| DEFAULT_STATIC_NODE_POD_PREFIX | ||
| ); |
There was a problem hiding this comment.
There's an inconsistency in how the default podPrefix is determined between the Helm chart and the CLI. The Helm template derives the default podPrefix from the resolved serviceName (printf "%s-validator" $resolvedServiceName), but the CLI uses a fixed default (DEFAULT_STATIC_NODE_POD_PREFIX). This can lead to different behavior. For consistency, the CLI should also derive the default for the staticNodePodPrefix prompt from the already-resolved staticNodeServiceName.
| const staticNodePodPrefix = await resolveText( | |
| "Static node pod prefix", | |
| staticNodePodPrefixOption, | |
| DEFAULT_STATIC_NODE_POD_PREFIX | |
| ); | |
| const staticNodePodPrefix = await resolveText( | |
| "Static node pod prefix", | |
| staticNodePodPrefixOption, | |
| `${staticNodeServiceName}-validator` | |
| ); |
| | global.networkNodes.faucetArtifactPrefix | string | `"besu-faucet"` | | | ||
| | global.networkNodes.genesisConfigMapName | string | `"besu-genesis"` | | | ||
| | global.networkNodes.podPrefix | string | `"besu-node-validator"` | | | ||
| | global.networkNodes.serviceName | string | `"besu-node"` | | | ||
| | global.networkNodes.staticNodesConfigMapName | string | `"besu-static-nodes"` | | |
| {{- $resolvedStaticDiscovery := default $defaultStaticDiscovery .Values.settings.staticNodeDiscoveryPort }} | ||
| {{- $globalNodes := default (dict) .Values.global.networkNodes }} | ||
| {{- $resolvedServiceName := default "besu-node" (default (get $globalNodes "serviceName") .Values.settings.staticNodeServiceName) }} | ||
| {{- $resolvedPodPrefix := default (printf "%s-validator" $resolvedServiceName) (default (get $globalNodes "podPrefix") .Values.settings.staticNodePodPrefix) }} |
There was a problem hiding this comment.
It's great that the default for podPrefix is derived from the resolved serviceName. This is a good practice for consistency. However, this same logic is not replicated in the CLI's interactive prompt, which uses a static default. I've left a comment in src/cli/build-command.ts with a suggestion to align the CLI's behavior with this template.
| const hostServiceName = | ||
| normalizeStaticNodeNamespace(serviceName) ?? serviceName; | ||
| const podNamePrefix = normalizeStaticNodeNamespace(podPrefix) ?? podPrefix; |
There was a problem hiding this comment.
The use of normalizeStaticNodeNamespace here on serviceName and podPrefix is a bit confusing, as they are not namespaces. The function essentially trims the string and returns undefined if it's empty. Since resolveText already returns a trimmed, non-empty string, these lines are more complex than they need to be. You can simplify this.
| const hostServiceName = | |
| normalizeStaticNodeNamespace(serviceName) ?? serviceName; | |
| const podNamePrefix = normalizeStaticNodeNamespace(podPrefix) ?? podPrefix; | |
| const hostServiceName = serviceName; | |
| const podNamePrefix = podPrefix; |
| staticNodeServiceName: | ||
| normalizedOptions.staticNodeServiceName === undefined | ||
| ? undefined | ||
| : stripSurroundingQuotes(normalizedOptions.staticNodeServiceName), | ||
| staticNodePodPrefix: | ||
| normalizedOptions.staticNodePodPrefix === undefined | ||
| ? undefined | ||
| : stripSurroundingQuotes(normalizedOptions.staticNodePodPrefix), | ||
| genesisConfigmapName: | ||
| normalizedOptions.genesisConfigmapName === undefined | ||
| ? undefined | ||
| : stripSurroundingQuotes(normalizedOptions.genesisConfigmapName), | ||
| staticNodesConfigmapName: | ||
| normalizedOptions.staticNodesConfigmapName === undefined | ||
| ? undefined | ||
| : stripSurroundingQuotes( | ||
| normalizedOptions.staticNodesConfigmapName | ||
| ), | ||
| faucetArtifactPrefix: | ||
| normalizedOptions.faucetArtifactPrefix === undefined | ||
| ? undefined | ||
| : stripSurroundingQuotes(normalizedOptions.faucetArtifactPrefix), | ||
| }; |
There was a problem hiding this comment.
These calls to stripSurroundingQuotes appear to be redundant. The new string options (static-node-service-name, static-node-pod-prefix, etc.) are already parsed using stripSurroundingQuotes when the command is defined (lines 419-443). The values in normalizedOptions should already be sanitized. You can simplify this block by removing these redundant checks and calls.
staticNodeServiceName: normalizedOptions.staticNodeServiceName,
staticNodePodPrefix: normalizedOptions.staticNodePodPrefix,
genesisConfigmapName: normalizedOptions.genesisConfigmapName,
staticNodesConfigmapName: normalizedOptions.staticNodesConfigmapName,
faucetArtifactPrefix: normalizedOptions.faucetArtifactPrefix,| { | ||
| name: "besu-faucet-address", | ||
| key: "address", | ||
| value: payload.faucet.address, | ||
| }, | ||
| { | ||
| name: "besu-faucet-private-key", | ||
| key: "privateKey", | ||
| value: payload.faucet.privateKey, | ||
| }, | ||
| { | ||
| name: "besu-faucet-enode", | ||
| name: `${artifactNames.faucetPrefix}-enode`, | ||
| key: "enode", | ||
| value: payload.faucet.enode, | ||
| }, |
There was a problem hiding this comment.
The enode for the faucet is handled separately here, whereas for validators it's part of the createValidatorSpecs function. To improve consistency and align with the goal of standardizing artifacts, the faucet's enode should be generated alongside its other specs. I've added a suggestion on createFaucetConfigSpecs to include the enode. With that change, you can simplify this block.
name: `${artifactNames.faucetPrefix}-enode`,| const createFaucetConfigSpecs = ( | ||
| faucet: GeneratedNodeKey, | ||
| prefix: string | ||
| ): ConfigMapSpec[] => [ | ||
| { name: `${prefix}-address`, key: "address", value: faucet.address }, | ||
| { name: `${prefix}-pubkey`, key: "publicKey", value: faucet.publicKey }, | ||
| ]; |
There was a problem hiding this comment.
To improve consistency, the enode for the faucet account should be handled within this helper, similar to its other properties (address, publicKey). This aligns with createValidatorSpecs, which generates an enode spec for each validator.
By adding it here, you can simplify the faucetSpecs creation in outputToFile. This change would also ensure the faucet enode is created as a ConfigMap in the Kubernetes output, making its artifacts consistent with validator nodes.
Note: This will require updating the test kubernetes output creates configmaps and secrets in output.test.ts to expect the faucet enode ConfigMap, reflecting the new, more consistent behavior.
const createFaucetConfigSpecs = (
faucet: GeneratedNodeKey,
prefix: string
): ConfigMapSpec[] => [
{ name: `${prefix}-address`, key: "address", value: faucet.address },
{ name: `${prefix}-pubkey`, key: "publicKey", value: faucet.publicKey },
{ name: `${prefix}-enode`, key: "enode", value: faucet.enode },
];There was a problem hiding this comment.
Codex Review: Here are some suggestions.
Reply with @codex fix comments to fix any unresolved comments.
About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".
| ) | ||
| ), | ||
| Bun.write( | ||
| join(directory, "static-nodes.json"), | ||
| join(directory, `${artifactNames.staticNodesConfigMapName}.json`), | ||
| `${JSON.stringify(payload.staticNodes, null, 2)}\n` | ||
| ), | ||
| ]; |
There was a problem hiding this comment.
[P1] Sanitize artifact names before writing files
The new CLI prompts allow users to override validator and faucet prefixes, but outputToFile writes those strings directly into file paths (join(directory, spec.name) and ${artifactNames.*}.json). Any prefix containing / or .. will traverse out of the out/<timestamp> directory and can overwrite arbitrary files when --outputType file is used. Previously the names were constants so this was safe; now the paths are user‑controlled without validation. Consider rejecting inputs that contain path separators or normalizing them to Kubernetes‑compatible names before writing.
Useful? React with 👍 / 👎.
roderik
left a comment
There was a problem hiding this comment.
Implemented the shared defaults and option table refactor, along with the kube test helper cleanup from Sourcery's suggestions.
|
✅ Addressed the review feedback: centralized artifact defaults/options, simplified tests, and disabled chart version bump requirement. |
roderik
left a comment
There was a problem hiding this comment.
Addressed all automated review suggestions by centralizing defaults, deduplicating option definitions, and sharing test helpers.
roderik
left a comment
There was a problem hiding this comment.
All automated reviewer suggestions have been incorporated: shared default constants, option descriptor refactor, reusable kube mocks, and CT config relaxed.
04306f1 to
b88a52e
Compare
Summary
Testing
Summary by Sourcery
Parameterize bootstrap artifact naming by adding user-configurable options in the CLI, propagating these parameters through the bootstrap and output code, and aligning Helm chart templates and documentation to consume global overrides.
New Features:
Enhancements:
Documentation:
Tests: