mj_controller/targets/
bootstrap.rs1use super::*;
2
3pub fn ssh_login_script(ssh: &SshTarget, script: &str) -> CommandSpec {
6 let login_command = format!("exec /bin/sh -c {}", posix_quote(script));
7 ssh_command(
8 ssh,
9 [
10 "sh",
11 "-c",
12 "exec \"$SHELL\" -lc \"$1\"",
13 "mj-login",
14 &login_command,
15 ],
16 )
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ExecutionBoundary<'a> {
21 Direct,
22 Container {
23 engine: &'a str,
24 container_id: &'a str,
25 },
26 Ssh(&'a SshTarget),
27 SshContainer {
28 engine: &'a str,
29 ssh: &'a SshTarget,
30 container_id: &'a str,
31 },
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct HarnessProbe<'a> {
36 pub executable: &'a str,
37 pub version_args: &'a [&'a str],
38 pub bridge_executable: Option<&'a str>,
39}
40
41pub fn bootstrap_probe_plan(
45 boundary: ExecutionBoundary<'_>,
46 harness: HarnessProbe<'_>,
47) -> Result<CommandPlan> {
48 validate_executable(harness.executable)?;
49 let mut commands = vec![
50 at_boundary(
51 boundary,
52 std::iter::once(harness.executable)
53 .chain(harness.version_args.iter().copied())
54 .map(str::to_owned)
55 .collect(),
56 )
57 .purpose("probe harness version"),
58 ];
59 if let Some(bridge) = harness.bridge_executable {
60 validate_executable(bridge)?;
61 commands.push(
62 at_boundary(boundary, vec![bridge.to_owned(), "--version".to_owned()])
63 .purpose("probe ACP bridge version"),
64 );
65 }
66 commands.push(
67 at_boundary(boundary, vec!["git".to_owned(), "--version".to_owned()]).purpose("probe Git"),
68 );
69 Ok(CommandPlan {
70 description: "probe reusable target tools".to_owned(),
71 commands,
72 })
73}
74
75pub fn install_git_plan(boundary: ExecutionBoundary<'_>) -> CommandPlan {
78 let managed_container = matches!(
79 boundary,
80 ExecutionBoundary::Container { .. } | ExecutionBoundary::SshContainer { .. }
81 );
82 let script = if managed_container {
83 "set -eu; if ! command -v git >/dev/null 2>&1 || ! command -v gh >/dev/null 2>&1; then SUDO=''; if [ \"$(id -u)\" != 0 ]; then command -v sudo >/dev/null 2>&1 && sudo -n true || { echo 'Git and GitHub CLI installation requires root or passwordless sudo' >&2; exit 1; }; SUDO='sudo -n'; fi; if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update; $SUDO apt-get install -y git gh ca-certificates curl; elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y git gh ca-certificates curl; elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y git gh ca-certificates curl; elif command -v apk >/dev/null 2>&1; then $SUDO apk add --no-cache git github-cli ca-certificates curl; else echo 'Unsupported package manager; install Git and GitHub CLI in the image' >&2; exit 1; fi; fi; git config --global credential.https://github.com.helper '!gh auth git-credential'; git config --global credential.https://gist.github.com.helper '!gh auth git-credential'"
84 } else {
85 "set -eu; if command -v git >/dev/null 2>&1; then exit 0; fi; SUDO=''; if [ \"$(id -u)\" != 0 ]; then command -v sudo >/dev/null 2>&1 && sudo -n true || { echo 'Git installation requires root or passwordless sudo' >&2; exit 1; }; SUDO='sudo -n'; fi; if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update; $SUDO apt-get install -y git ca-certificates curl; elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y git ca-certificates curl; elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y git ca-certificates curl; elif command -v apk >/dev/null 2>&1; then $SUDO apk add --no-cache git ca-certificates curl; else echo 'Unsupported package manager; install Git manually' >&2; exit 1; fi"
86 };
87 CommandPlan {
88 description: "install missing Git".to_owned(),
89 commands: vec![
90 at_boundary(
91 boundary,
92 vec!["sh".to_owned(), "-c".to_owned(), script.to_owned()],
93 )
94 .purpose("install Git")
95 .stage(ProvisionStage::Cloning),
96 ],
97 }
98}
99
100pub(super) const BUNDLE_REPOSITORIES_PARALLEL_GROUP: u32 = 1;
104
105pub(super) fn clone_commands(
106 bundle: &ProjectBundleSpec,
107 workspace: &str,
108 wrap: impl Fn(Vec<String>) -> CommandSpec,
109) -> Vec<CommandSpec> {
110 let mut commands = vec![
111 wrap(vec![
112 "mkdir".to_owned(),
113 "-p".to_owned(),
114 workspace.to_owned(),
115 ])
116 .purpose("create bundle workspace")
117 .stage(ProvisionStage::Cloning),
118 ];
119 for repository in &bundle.repositories {
120 let destination = format!("{workspace}/{}", repository.destination);
121 let url = repository
122 .url
123 .as_ref()
124 .expect("validated network repository");
125 let mut args = vec!["git".to_owned(), "clone".to_owned()];
126 for push_url in &repository.push_urls {
127 args.extend([
128 "--config".into(),
129 format!("remote.origin.pushurl={push_url}"),
130 ]);
131 }
132 if let Some(reference) = &repository.reference {
133 args.extend(["--reference-if-able".to_owned(), reference.clone()]);
134 }
135 args.push("--".to_owned());
136 args.push(url.clone());
137 args.push(destination);
138 commands.push(
139 wrap(args)
140 .purpose(format!("clone {}", repository.destination))
141 .stage(ProvisionStage::Cloning)
142 .parallel_group(BUNDLE_REPOSITORIES_PARALLEL_GROUP),
143 );
144 }
145 commands
146}
147
148pub fn install_rsync_plan(boundary: ExecutionBoundary<'_>) -> CommandPlan {
150 let script = "set -eu; if rsync --protect-args --version >/dev/null 2>&1; then exit 0; fi; SUDO=''; if [ \"$(id -u)\" != 0 ]; then command -v sudo >/dev/null 2>&1 && sudo -n true || { echo 'rsync installation requires root or passwordless sudo' >&2; exit 1; }; SUDO='sudo -n'; fi; if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update; $SUDO apt-get install -y rsync; elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y rsync; elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y rsync; elif command -v apk >/dev/null 2>&1; then $SUDO apk add --no-cache rsync; else echo 'Unsupported package manager; install rsync 3.0 or newer in the image' >&2; exit 1; fi; rsync --protect-args --version >/dev/null";
151 CommandPlan {
152 description: "install EC2 Move transport".into(),
153 commands: vec![
154 at_boundary(boundary, vec!["sh".into(), "-c".into(), script.into()])
155 .purpose("install supported rsync")
156 .stage(ProvisionStage::Provisioning),
157 ],
158 }
159}