manta-cli 2.0.0-beta.64

Another CLI for ALPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
//! Clap definitions for `manta apply *` subcommands.
//!
//! Builds the `manta apply` subtree — the "roll out" verb covering SAT
//! files, boot parameters (per-node and per-group), kernel parameters,
//! Redfish endpoint updates, hardware rescaling, ephemeral
//! environments, and BOS session-template-driven boots. Execution is
//! dispatched in `crate::dispatch::apply`.
//!
//! Notes:
//! - Several subcommands share argument sets, factored into private
//!   `add_*_args(cmd)` helpers so the same flags appear on both the
//!   `nodes` and `group` variants.
//! - `apply boot nodes` / `apply boot group` use an
//!   `ArgGroup("boot-image_or_boot-config")` to make `--boot-image`
//!   and `--boot-image-configuration` mutually exclusive.
//! - `apply sat-file` uses `output_flag_long_only` because `-o` is
//!   taken by `--overwrite-configuration`.

use clap::{ArgAction, ArgGroup, Command, ValueHint, arg, value_parser};
use std::path::PathBuf;

use super::{HOSTLIST_HELP, dry_run_flag, output_flag, output_flag_long_only};

/// Attach the hardware-rescale argument set to a clap `Command`.
///
/// `--target-cluster`/`--parent-cluster` are kept as visible aliases
/// for `--target-group`/`--parent-group`. Same for the
/// create/delete lifecycle flags.
fn add_apply_hw_group_args(cmd: Command) -> Command {
  cmd
    .arg_required_else_help(true)
    .arg(
      arg!(-P -- pattern <PATTERN> "Hardware pattern: <component>:<qty>[:<component>:<qty>...].\neg: 'a100:12:epyc:5'")
        .required(true),
    )
    .arg(
      arg!(-t --"target-group" <TARGET_GROUP_NAME> "Group to rescale")
        .required(true)
        .visible_alias("target-cluster"),
    )
    .arg(
      arg!(-p --"parent-group" <PARENT_GROUP_NAME> "Group that donates or receives the redistributed nodes")
        .required(true)
        .visible_alias("parent-cluster"),
    )
    .arg(dry_run_flag())
    .arg(
      arg!(-c --"create-target-group" "Create the target group if it does not exist")
        .visible_alias("create-target-hsm-group"),
    )
    .arg(
      arg!(-D --"delete-empty-parent-group" "Delete the parent group if empty after this operation")
        .visible_alias("delete-empty-parent-hsm-group"),
    )
    .arg(arg!(-u --"unpin-nodes" "Allow any available nodes to be selected"))
    .arg(output_flag())
}

/// `manta apply hardware group` — pattern-driven group rescale.
/// Handler: `crate::dispatch::apply::hw_configuration`.
pub fn subcommand_apply_hw_configuration() -> Command {
  Command::new("hardware")
    .about("[experimental] Rescale a group's hardware allocation")
    .arg_required_else_help(true)
    .subcommand(
      add_apply_hw_group_args(Command::new("group"))
        .about("[experimental] Rescale a group's hardware allocation")
        .long_about(
          "[experimental] Upscale or downscale a group by specifying a hardware component pattern.\n\n\
          If the group does not exist it will be created; otherwise its node assignment is updated.\n\n\
          Pattern format: <component>:<quantity>[:<component>:<quantity>...]\n\
          eg: 'a100:12:epyc:5'  — assign nodes with 12 A100 GPUs and 5 EPYC CPUs total",
        ),
    )
}

/// `manta apply template` — create a BOS session from an existing
/// session template and run a boot/reboot/shutdown operation.
/// Handler: `crate::dispatch::apply::template`.
pub fn subcommand_apply_template() -> Command {
  Command::new("template")
    .arg_required_else_help(true)
    .about("Boot nodes using an existing session template")
    .arg(arg!(-n --name <VALUE> "Name of the boot session to create"))
    .arg(
      arg!(-o --operation <VALUE> "Boot operation to perform")
        .value_parser(["reboot", "boot", "shutdown"])
        .default_value("reboot"),
    )
    .arg(arg!(-t --template <VALUE> "Session template name").required(true))
    .arg(
      arg!(-l --limit <VALUE>
        "Limit to specific nodes, groups, or roles (OR by default; prefix with '&' for AND or '!' for NOT)")
        .required(true),
    )
    .arg(
      arg!(-i --"include-disabled" "Include nodes marked as disabled in the hardware state manager")
        .action(ArgAction::SetTrue),
    )
    .arg(dry_run_flag())
}

/// `manta apply ephemeral-environment` — spin up an SSH-reachable
/// throwaway environment from an image. Handler:
/// `crate::dispatch::apply::ephemeral_environment`.
pub fn subcommand_apply_ephemeral_environment() -> Command {
  Command::new("ephemeral-environment")
    .arg_required_else_help(true)
    .about("Launch an ephemeral SSH environment from an image")
    .long_about(
      "Launch an ephemeral SSH environment from an image.\n\n\
      Returns an SSH hostname once the environment is ready (usually within a few seconds).",
    )
    .arg(arg!(-i --"image-id" <IMAGE_ID> "Image ID to use").required(true))
    .arg(dry_run_flag())
    .arg(output_flag())
}

/// `manta apply sat-file` — process a Site Admin Toolkit (SAT) file
/// declaring configurations, images, and session templates. Supports
/// Jinja2 templating via `--values-file` / `--values`. Handler:
/// `crate::dispatch::apply::sat_file` (Jinja2 rendering lives in
/// `crate::dispatch::apply::sat_file::render`).
pub fn subcommand_apply_sat_file() -> Command {
  Command::new("sat-file")
    .arg_required_else_help(true)
    .about("Process a SAT file to create configurations, images, and session templates")
    .long_about(
      "Process a SAT file containing up to three sections:\n\
      \n\
      - `configurations`:   configurations to create\n\
      - `images`:           images to build from those configurations\n\
      - `session_templates`: session templates to create\n\
      \n\
      Use --image-only to process only configurations and images.\n\
      Use --sessiontemplate-only to process only configurations and session templates.",
    )
    .arg(
      arg!(-t --"sat-template-file" <FILE> "SAT file path (may be a jinja2 template)")
        .value_parser(value_parser!(PathBuf))
        .required(true)
        .value_hint(ValueHint::FilePath),
    )
    .arg(
      arg!(-f --"values-file" <FILE> "Values file to expand jinja2 variables in the SAT file")
        .value_parser(value_parser!(PathBuf))
        .value_hint(ValueHint::FilePath),
    )
    .arg(arg!(-V --"values" <VALUE> ... "Inline values to expand jinja2 variables (overrides --values-file)"))
    .arg(arg!(--"create-bos-session" "After each BOS session template is created, create a BOS session from it so its target nodes boot via the new template (this typically causes a reboot)").action(ArgAction::SetTrue))
    .arg(
      arg!(-v --"ansible-verbosity" <VALUE> "Ansible verbosity level (1 = -v, 2 = -vv, …, max 4)")
        .value_parser(["1", "2", "3", "4"])
        .num_args(1)
        .default_value("2")
        .default_missing_value("2"),
    )
    .arg(
      arg!(-P --"ansible-passthrough" <VALUE>
        "Additional Ansible flags (limited to --extra-vars, --forks, --skip-tags, --start-at-task, --tags)")
        .allow_hyphen_values(true),
    )
    .arg(
      arg!(-o --"overwrite-configuration" "Overwrite an existing configuration with the same name")
        .action(ArgAction::SetTrue),
    )
    .arg(arg!(-w --"watch-logs" "Stream session logs to stdout").action(ArgAction::SetTrue))
    .arg(arg!(-T --timestamps "Show log timestamps").action(ArgAction::SetTrue))
    .arg(
      arg!(-i --"image-only" "Process only the `configurations` and `images` sections")
        .action(ArgAction::SetTrue),
    )
    .arg(
      arg!(-s --"sessiontemplate-only" "Process only the `configurations` and `session_templates` sections")
        .action(ArgAction::SetTrue),
    )
    .arg(arg!(-p --"pre-hook" <SCRIPT> "Command to run before processing.\neg: --pre-hook \"echo hello\""))
    .arg(arg!(-a --"post-hook" <SCRIPT> "Command to run after successful processing.\neg: --post-hook \"echo hello\""))
    .arg(arg!(-y --"assume-yes" "Skip confirmation prompts").action(ArgAction::SetTrue))
    .arg(dry_run_flag())
    .arg(output_flag_long_only())
}

/// `manta apply boot nodes` — set the boot image and kernel
/// parameters for an arbitrary node set. Runtime CFS configuration
/// lives on the dedicated `apply runtime-configuration` command.
/// Handler: `crate::dispatch::apply::boot::nodes`.
pub fn subcommand_apply_boot_nodes() -> Command {
  Command::new("nodes")
    .arg_required_else_help(true)
    .about("Update boot parameters for a set of nodes")
    .long_about(
      "Update the boot parameters (image and kernel parameters) for a set of nodes.\n\n\
      The boot image can be specified by image ID or by the configuration name used to build it \
      (the most recent matching image is used). To set the CFS runtime configuration on the same \
      nodes, use `manta apply runtime-configuration nodes`.\n\n\
      eg:\n  \
      manta apply boot nodes \\\n    \
        --boot-image-configuration <config-name> <nodes>",
    )
    .arg(arg!(-i --"boot-image" <IMAGE_ID> "Image ID to boot the nodes"))
    .arg(
      arg!(-b --"boot-image-configuration" <NAME>
        "Configuration name used to build the boot image (uses the most recent matching image)"),
    )
    .arg(arg!(-k --"kernel-parameters" <VALUE> "Kernel parameters to assign to the nodes"))
    .arg(dry_run_flag())
    .group(
      ArgGroup::new("boot-image_or_boot-config")
        .args(["boot-image", "boot-image-configuration"]),
    )
    // ID preserved as "VALUE" for handler compatibility
    .arg(arg!(<VALUE>).value_name("NODES").help(HOSTLIST_HELP))
    .arg(output_flag())
}

/// Attach the per-group boot-parameter argument set.
fn add_apply_boot_group_args(cmd: Command) -> Command {
  cmd
    .arg_required_else_help(true)
    .arg(arg!(-i --"boot-image" <IMAGE_ID> "Image ID to boot the nodes"))
    .arg(
      arg!(-b --"boot-image-configuration" <NAME>
        "Configuration name used to build the boot image (uses the most recent matching image)"),
    )
    .arg(arg!(-k --"kernel-parameters" <VALUE> "Kernel parameters to assign to all group members"))
    .arg(dry_run_flag())
    .group(
      ArgGroup::new("boot-image_or_boot-config")
        .args(["boot-image", "boot-image-configuration"]),
    )
    .arg(arg!(<GROUP_NAME> "Group name").required(true))
    .arg(output_flag())
}

/// `manta apply boot group` — same as `apply boot nodes` but scoped
/// to every member of a group. Runtime CFS configuration lives on the
/// dedicated `apply runtime-configuration` command. Handler:
/// `crate::dispatch::apply::boot::group`.
pub fn subcommand_apply_boot_group() -> Command {
  add_apply_boot_group_args(Command::new("group"))
    .about("Update boot parameters for all nodes in a group")
    .long_about(
      "Update the boot parameters (image and kernel parameters) for all nodes in a group.\n\n\
      The boot image can be specified by image ID or by the configuration name used to build it \
      (the most recent matching image is used). To set the CFS runtime configuration on the same \
      group, use `manta apply runtime-configuration group`.\n\n\
      eg:\n  \
      manta apply boot group \\\n    \
        --boot-image-configuration <config-name> <group-name>",
    )
}

/// `manta apply runtime-configuration nodes` — set the CFS
/// `desired_configuration` and `enabled` flag on the given nodes.
/// Handler: `crate::dispatch::apply::runtime_configuration_node`.
pub fn subcommand_apply_runtime_configuration_nodes() -> Command {
  Command::new("nodes")
    .arg_required_else_help(true)
    .about("Set the runtime CFS configuration on a set of nodes")
    .long_about(
      "Assigns a CFS configuration as the runtime desired_configuration on each targeted node's CFS component. \
      Idempotent: repeating the same call leaves the components in the same state.\n\n\
      eg:\n  \
      manta apply runtime-configuration nodes \\\n    \
        --configuration-name <config-name> <nodes>",
    )
    .arg(
      arg!(-n --"configuration-name" <NAME> "CFS configuration to assign as the runtime desired configuration")
        .required(true),
    )
    .arg(
      arg!(-D --disable "Stage the configuration without enabling CFS to apply it (sets the CFS component `enabled` flag to false)")
        .action(ArgAction::SetTrue),
    )
    .arg(dry_run_flag())
    .arg(arg!(<VALUE>).value_name("NODES").help(HOSTLIST_HELP))
}

/// `manta apply runtime-configuration group` — same as
/// `apply runtime-configuration nodes` but scoped to every member of a
/// group. Handler:
/// `crate::dispatch::apply::runtime_configuration_group`.
pub fn subcommand_apply_runtime_configuration_group() -> Command {
  Command::new("group")
    .arg_required_else_help(true)
    .about("Set the runtime CFS configuration on all nodes in a group")
    .long_about(
      "Resolves the group's members and assigns the given CFS configuration as the runtime desired_configuration \
      on each of them.\n\n\
      eg:\n  \
      manta apply runtime-configuration group \\\n    \
        --configuration-name <config-name> <group-name>",
    )
    .arg(
      arg!(-n --"configuration-name" <NAME> "CFS configuration to assign as the runtime desired configuration")
        .required(true),
    )
    .arg(
      arg!(-D --disable "Stage the configuration without enabling CFS to apply it (sets the CFS component `enabled` flag to false)")
        .action(ArgAction::SetTrue),
    )
    .arg(dry_run_flag())
    .arg(arg!(<GROUP_NAME> "Group name").required(true))
}

/// Attach the boot-parameter argument set to a clap `Command`.
fn add_boot_parameters_args(cmd: Command) -> Command {
  cmd
    .arg_required_else_help(true)
    .arg(
      arg!(-H --"hosts" <XNAMES> "Xnames of the nodes to update")
        .required(true),
    )
    .arg(arg!(-p --"params" <VALUE> "Kernel parameters"))
    .arg(arg!(-k --"kernel" <VALUE> "S3 path to the kernel file"))
    .arg(arg!(-i --"initrd" <VALUE> "S3 path to the initrd file"))
    .arg(dry_run_flag())
    .arg(output_flag())
}

/// `manta apply boot-parameters` — update raw BSS boot-parameters for
/// the listed hosts. Handler:
/// `crate::dispatch::apply::boot_parameters`.
pub fn subcommand_apply_boot_parameters() -> Command {
  add_boot_parameters_args(Command::new("boot-parameters"))
    .about("Update boot parameters for nodes")
}

/// Attach the Redfish-endpoint argument set to a clap `Command`.
fn add_redfish_endpoint_args(cmd: Command) -> Command {
  cmd
    .visible_alias("redfish-endpoint")
    .arg_required_else_help(true)
    .arg(arg!(-i --id <XNAME> "Xname of the endpoint to update").required(true))
    .arg(arg!(-n --name <VALUE> "Arbitrary user-provided name for the endpoint"))
    .arg(arg!(-H --hostname <VALUE> "Hostname (FQDN host portion)"))
    .arg(arg!(-D --domain <VALUE> "Domain (FQDN domain portion)"))
    .arg(
      arg!(-f --fqdn <VALUE> "Fully-qualified domain name on the management network"),
    )
    .arg(arg!(-e --enabled "Enable the endpoint").action(ArgAction::SetTrue))
    .arg(arg!(-u --user <VALUE> "Username for endpoint authentication"))
    .arg(arg!(-p --password <VALUE> "Password for endpoint authentication"))
    .arg(arg!(-U --"use-ssdp" "Use SSDP for discovery if the endpoint supports it").action(ArgAction::SetTrue))
    .arg(arg!(-m --"mac-required" "Require a MAC address for geolocation").action(ArgAction::SetTrue))
    .arg(arg!(-M --macaddr <VALUE> "MAC address of the Redfish endpoint on the management network"))
    .arg(
      arg!(-I --ipaddress <VALUE> "IP address of the Redfish endpoint on the management network (IPv4 or IPv6)"),
    )
    .arg(
      arg!(-r --"rediscover-on-update" "Trigger rediscovery when endpoint information is updated")
        .action(ArgAction::SetTrue),
    )
    .arg(arg!(-t --"template-id" <VALUE> "Discovery template ID"))
    .arg(output_flag())
}

/// `manta apply redfish-endpoints` — update an existing Redfish
/// endpoint. Handler: `crate::dispatch::apply::redfish_endpoint`.
pub fn subcommand_apply_redfish_endpoint() -> Command {
  add_redfish_endpoint_args(Command::new("redfish-endpoints"))
    .about("Update an existing Redfish endpoint")
    .arg(dry_run_flag())
}

/// `manta apply kernel-parameters` — replace the full kernel-cmdline
/// string for a node set or group (any parameter not listed is
/// dropped). Uses `ArgGroup("cluster_or_nodes")` to require exactly
/// one of `--nodes` / `--group`. Handler:
/// `crate::dispatch::apply::kernel_parameters`.
pub fn subcommand_apply_kernel_parameters() -> Command {
  Command::new("kernel-parameters")
    .arg_required_else_help(true)
    .about("Replace the full kernel-parameters string on nodes (drops any existing parameters not listed)")
    .arg(arg!(-n --nodes <NODES>).help(HOSTLIST_HELP))
    .arg(
      arg!(-H --group <GROUP_NAME> "Replace kernel parameters on every node in this group")
        .visible_alias("hsm-group"),
    )
    .arg(dry_run_flag())
    // ID preserved as "VALUE" for handler compatibility
    .arg(
      arg!(<VALUE> "Space-separated kernel parameters to apply.\neg: bos_update_frequency=4h console=ttyS0,115200 crashkernel=512M")
        .value_name("PARAMS"),
    )
    .group(
      ArgGroup::new("cluster_or_nodes")
        .args(["group", "nodes"])
        .required(true),
    )
    .arg(output_flag())
}

/// Top-level `manta apply` verb — wires every `apply <noun>`
/// subcommand together. Invoked from `build_cli` in `super::mod`.
pub fn subcommand_apply() -> Command {
  Command::new("apply")
    .arg_required_else_help(true)
    .about("Roll out configurations, images, session templates, boot/kernel parameters, and hardware rescaling")
    .subcommand(subcommand_apply_hw_configuration())
    .subcommand(subcommand_apply_sat_file())
    .subcommand(
      Command::new("boot")
        .arg_required_else_help(true)
        .about("Update boot parameters")
        .subcommand(subcommand_apply_boot_nodes())
        .subcommand(subcommand_apply_boot_group()),
    )
    .subcommand(
      Command::new("runtime-configuration")
        .arg_required_else_help(true)
        .about("Set the CFS runtime configuration on a set of nodes or a group")
        .subcommand(subcommand_apply_runtime_configuration_nodes())
        .subcommand(subcommand_apply_runtime_configuration_group()),
    )
    .subcommand(subcommand_apply_boot_parameters())
    .subcommand(subcommand_apply_redfish_endpoint())
    .subcommand(subcommand_apply_kernel_parameters())
    .subcommand(subcommand_apply_ephemeral_environment())
    .subcommand(subcommand_apply_template())
}