Skip to content

Commit

Permalink
Build docker bootstrap image (#6)
Browse files Browse the repository at this point in the history
* wip. bug in docker build

* build docker bootstrap image

* cargo-fmt/clippy. change to docker-build default
  • Loading branch information
gregcusack authored Apr 10, 2024
1 parent 3df6f4b commit 044c9d2
Show file tree
Hide file tree
Showing 9 changed files with 614 additions and 32 deletions.
2 changes: 1 addition & 1 deletion PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
- [x] Generate faucet and bootstrap accounts
- [x] Build genesis
- [ ] Docker Build
- [ ] Build Bootstrap Image
- [x] Build Bootstrap Image
- [ ] Push Image to registry
- [ ] Create & Deploy Secrets
- [ ] Bootstrap
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ cargo run --bin cluster --
--release-channel <agave-version: e.g. v1.17.28> # note: MUST include the "v"
```

#### Build from Local Repo and Configure Genesis
#### Build from Local Repo and Configure Genesis and Bootstrap Validator Image
Example:
```
cargo run --bin cluster --
Expand All @@ -51,4 +51,9 @@ cargo run --bin cluster --
--max-genesis-archive-unpacked-size <size in bytes>
--target-lamports-per-signature <lamports-per-signature>
--slots-per-epoch <slots-per-epoch>
# docker config
--registry <docker-registry> # e.g. gregcusack
--tag <docker-image-tag> # e.g. v1
--base-image <base-image> # e.g. ubuntu:20.04
--image-name <docker-image-name> # e.g. cluster-image
```
190 changes: 190 additions & 0 deletions src/docker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use {
crate::{new_spinner_progress_bar, release::DeployMethod, ValidatorType, BUILD},
log::*,
std::{
env,
error::Error,
fs,
path::{Path, PathBuf},
process::{Command, Output, Stdio},
},
};

pub struct DockerConfig {
pub base_image: String,
pub image_name: String,
pub tag: String,
pub registry: String,
deploy_method: DeployMethod,
}

impl DockerConfig {
pub fn new(
base_image: String,
image_name: String,
tag: String,
registry: String,
deploy_method: DeployMethod,
) -> Self {
DockerConfig {
base_image,
image_name,
tag,
registry,
deploy_method,
}
}

pub fn build_image(
&self,
solana_root_path: &Path,
validator_type: &ValidatorType,
) -> Result<(), Box<dyn Error>> {
match validator_type {
ValidatorType::Bootstrap => (),
ValidatorType::Standard | ValidatorType::RPC | ValidatorType::Client => {
return Err(format!(
"Build docker image for validator type: {validator_type} not supported yet"
)
.into());
}
}
let image_name = format!("{validator_type}-{}", self.image_name);
let docker_path = solana_root_path.join(format!("docker-build/{validator_type}"));
match self.create_base_image(solana_root_path, image_name, &docker_path, validator_type) {
Ok(res) => {
if res.status.success() {
info!("Successfully created base Image");
Ok(())
} else {
error!("Failed to build base image");
Err(String::from_utf8_lossy(&res.stderr).into())
}
}
Err(err) => Err(err),
}
}

fn create_base_image(
&self,
solana_root_path: &Path,
image_name: String,
docker_path: &PathBuf,
validator_type: &ValidatorType,
) -> Result<Output, Box<dyn Error>> {
self.create_dockerfile(validator_type, docker_path, None)?;

trace!("Tmp: {}", docker_path.as_path().display());
trace!("Exists: {}", docker_path.as_path().exists());

// We use std::process::Command here because Docker-rs is very slow building dockerfiles
// when they are in large repos. Docker-rs doesn't seem to support the `--file` flag natively.
// so we result to using std::process::Command
let dockerfile = docker_path.join("Dockerfile");
let context_path = solana_root_path.display().to_string();

let progress_bar = new_spinner_progress_bar();
progress_bar.set_message(format!("{BUILD}Building {validator_type} docker image...",));

let command = format!(
"docker build -t {}/{image_name}:{} -f {dockerfile:?} {context_path}",
self.registry, self.tag,
);

let output = match Command::new("sh")
.arg("-c")
.arg(&command)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("Failed to execute command")
.wait_with_output()
{
Ok(res) => Ok(res),
Err(err) => Err(Box::new(err) as Box<dyn Error>),
};
progress_bar.finish_and_clear();
info!("{validator_type} image build complete");

output
}

fn copy_file_to_docker(
source_dir: &Path,
docker_dir: &Path,
file_name: &str,
) -> std::io::Result<()> {
let source_path = source_dir.join("src/scripts").join(file_name);
let destination_path = docker_dir.join(file_name);
fs::copy(source_path, destination_path)?;
Ok(())
}

fn create_dockerfile(
&self,
validator_type: &ValidatorType,
docker_path: &PathBuf,
content: Option<&str>,
) -> Result<(), Box<dyn Error>> {
if docker_path.exists() {
fs::remove_dir_all(docker_path)?;
}
fs::create_dir_all(docker_path)?;

if let DeployMethod::Local(_) = self.deploy_method {
if validator_type == &ValidatorType::Bootstrap {
let manifest_path =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR"));
let files_to_copy = ["bootstrap-startup-script.sh", "common.sh"];
for file_name in files_to_copy.iter() {
Self::copy_file_to_docker(&manifest_path, docker_path, file_name)?;
}
}
}

let (solana_build_directory, startup_script_directory) =
if let DeployMethod::ReleaseChannel(_) = self.deploy_method {
("solana-release", "./src/scripts".to_string())
} else {
("farf", format!("./docker-build/{validator_type}"))
};

let dockerfile = format!(
r#"
FROM {}
RUN apt-get update
RUN apt-get install -y iputils-ping curl vim bzip2
RUN useradd -ms /bin/bash solana
RUN adduser solana sudo
USER solana
RUN mkdir -p /home/solana/k8s-cluster-scripts
# TODO: this needs to be changed for non bootstrap, this should be ./src/scripts/<validator-type>-startup-scripts.sh
COPY {startup_script_directory}/bootstrap-startup-script.sh /home/solana/k8s-cluster-scripts
RUN mkdir -p /home/solana/ledger
COPY --chown=solana:solana ./config-k8s/bootstrap-validator /home/solana/ledger
RUN mkdir -p /home/solana/.cargo/bin
COPY ./{solana_build_directory}/bin/ /home/solana/.cargo/bin/
COPY ./{solana_build_directory}/version.yml /home/solana/
RUN mkdir -p /home/solana/config
ENV PATH="/home/solana/.cargo/bin:${{PATH}}"
WORKDIR /home/solana
"#,
self.base_image
);

debug!("dockerfile: {dockerfile:?}");
std::fs::write(
docker_path.join("Dockerfile"),
content.unwrap_or(dockerfile.as_str()),
)?;
Ok(())
}
}
13 changes: 5 additions & 8 deletions src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ impl Genesis {
.for_each(|account_type| {
args.push(
self.config_dir
.join(format!("bootstrap-validator/{}.json", account_type))
.join(format!("bootstrap-validator/{account_type}.json"))
.to_string_lossy()
.to_string(),
);
Expand All @@ -269,22 +269,19 @@ impl Genesis {
args
}

pub fn setup_spl_args(
&self,
solana_root_path: &PathBuf,
) -> Result<Vec<String>, Box<dyn Error>> {
pub fn setup_spl_args(&self, solana_root_path: &Path) -> Result<Vec<String>, Box<dyn Error>> {
let fetch_spl_file = solana_root_path.join("fetch-spl.sh");
fetch_spl(&fetch_spl_file)?;

// add in spl stuff
// add in spl
let spl_file = solana_root_path.join("spl-genesis-args.sh");
parse_spl_genesis_file(&spl_file)
}

pub fn generate(
&mut self,
solana_root_path: &PathBuf,
build_path: &PathBuf,
solana_root_path: &Path,
build_path: &Path,
) -> Result<(), Box<dyn Error>> {
let mut args = self.setup_genesis_flags();
let mut spl_args = self.setup_spl_args(solana_root_path)?;
Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,14 @@ pub enum ValidatorType {
Client,
}

pub mod docker;
pub mod genesis;
pub mod kubernetes;
pub mod release;

static SUN: Emoji = Emoji("🌞 ", "");
static BUILD: Emoji = Emoji("👷 ", "");
static PACKAGE: Emoji = Emoji("📦 ", "");
static SUN: Emoji = Emoji("🌞 ", "");
static TRUCK: Emoji = Emoji("🚚 ", "");

/// Creates a new process bar for processing that will take an unknown amount of time
Expand All @@ -78,7 +80,7 @@ pub fn cat_file(path: &PathBuf) -> std::io::Result<()> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
info!("{:?}:\n{}", path.file_name(), contents);
info!("{:?}:\n{contents}", path.file_name());

Ok(())
}
Expand Down
Loading

0 comments on commit 044c9d2

Please sign in to comment.