提交 10e4c1e4 authored 作者: Serhij S's avatar Serhij S

docker support

上级 8834d453
...@@ -695,13 +695,14 @@ dependencies = [ ...@@ -695,13 +695,14 @@ dependencies = [
[[package]] [[package]]
name = "roboplc-cli" name = "roboplc-cli"
version = "0.4.1" version = "0.4.2"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"clap", "clap",
"colored", "colored",
"dirs", "dirs",
"futures-util", "futures-util",
"once_cell",
"serde", "serde",
"serde_json", "serde_json",
"shlex", "shlex",
......
[package] [package]
name = "roboplc-cli" name = "roboplc-cli"
version = "0.4.1" version = "0.4.2"
edition = "2021" edition = "2021"
authors = ["Serhij S. <div@altertech.com>"] authors = ["Serhij S. <div@altertech.com>"]
license = "Apache-2.0" license = "Apache-2.0"
...@@ -28,6 +28,7 @@ which = "3" ...@@ -28,6 +28,7 @@ which = "3"
term_size = "0.3.2" term_size = "0.3.2"
tokio = { version = "=1.36", features = ["rt", "fs"] } tokio = { version = "=1.36", features = ["rt", "fs"] }
tokio-tungstenite = { version = "0.23.1", features = ["rustls"] } tokio-tungstenite = { version = "0.23.1", features = ["rustls"] }
once_cell = "1.19.0"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
ansi_term = "0.12.1" ansi_term = "0.12.1"
......
...@@ -63,6 +63,34 @@ pub struct NewCommand { ...@@ -63,6 +63,34 @@ pub struct NewCommand {
pub extras: Vec<String>, pub extras: Vec<String>,
#[clap(short = 'L', long, help = "Locking policy)", default_value = "rt-safe")] #[clap(short = 'L', long, help = "Locking policy)", default_value = "rt-safe")]
pub locking: LockingPolicy, pub locking: LockingPolicy,
#[clap(long, help = "Docker project (specify an architecture)")]
pub docker: Option<Docker>,
}
#[derive(ValueEnum, Copy, Clone)]
pub enum Docker {
#[clap(name = "x86_64", help = "x86_64 architecture")]
X86_64,
#[clap(name = "aarch64", help = "ARM-64 bit architecture")]
Aarch64,
}
impl Docker {
pub fn target(self) -> &'static str {
match self {
Docker::X86_64 => "x86_64-unknown-linux-gnu",
Docker::Aarch64 => "aarch64-unknown-linux-gnu",
}
}
pub fn binary_path_for(self, name: &str) -> PathBuf {
PathBuf::from_iter(vec!["target", self.target(), "release", name])
}
pub fn docker_image_name(self) -> &'static str {
match self {
Docker::X86_64 => "bmauto/roboplc-x86_64:latest",
Docker::Aarch64 => "bmauto/roboplc-aarch64:latest",
}
}
} }
#[derive(ValueEnum, Copy, Clone)] #[derive(ValueEnum, Copy, Clone)]
...@@ -98,10 +126,14 @@ pub struct FlashCommand { ...@@ -98,10 +126,14 @@ pub struct FlashCommand {
#[clap( #[clap(
short = 'f', short = 'f',
long, long,
help = "Force flash (automatically put remote in CONFIG mode)" help = "Force flash (automatically put remote in CONFIG mode), for Docker: run privileged"
)] )]
pub force: bool, pub force: bool,
#[clap(short = 'r', long, help = "Put remote in RUN mode after flashing")] #[clap(
short = 'r',
long,
help = "Put remote in RUN mode after flashing, for Docker: run the container"
)]
pub run: bool, pub run: bool,
} }
......
...@@ -68,15 +68,37 @@ impl fmt::Display for Mode { ...@@ -68,15 +68,37 @@ impl fmt::Display for Mode {
} }
} }
#[derive(Deserialize, Debug)]
struct CargoToml {
package: CargoPackage,
}
#[derive(Deserialize, Debug)]
struct CargoPackage {
name: String,
version: String,
}
pub fn find_robo_toml() -> Option<PathBuf> { pub fn find_robo_toml() -> Option<PathBuf> {
let mut current_dir = env::current_dir().ok()?; let mut current_dir = env::current_dir().ok()?;
loop { loop {
let mut cargo_toml_path = current_dir.clone(); let mut cargo_toml_path = current_dir.clone();
cargo_toml_path.push("Cargo.toml"); cargo_toml_path.push("Cargo.toml");
if cargo_toml_path.exists() { if cargo_toml_path.exists() {
let contents =
std::fs::read_to_string(cargo_toml_path).expect("Failed to read Cargo.toml");
let cargo_toml: CargoToml =
toml::from_str(&contents).expect("Failed to parse Cargo.toml");
crate::TARGET_PACKAGE_NAME
.set(cargo_toml.package.name)
.expect("Failed to set target package name");
crate::TARGET_PACKAGE_VERSION
.set(cargo_toml.package.version)
.expect("Failed to set target package version");
let mut roboplc_toml_path = current_dir.clone(); let mut roboplc_toml_path = current_dir.clone();
roboplc_toml_path.push(CONFIG_FILE_NAME); roboplc_toml_path.push(CONFIG_FILE_NAME);
if roboplc_toml_path.exists() { if roboplc_toml_path.exists() {
std::env::set_current_dir(current_dir).expect("Failed to set current dir");
return Some(roboplc_toml_path); return Some(roboplc_toml_path);
} }
} }
......
...@@ -34,25 +34,70 @@ fn flash_file( ...@@ -34,25 +34,70 @@ fn flash_file(
if exec_only { if exec_only {
return crate::exec::exec(url, key, file, force, program_args); return crate::exec::exec(url, key, file, force, program_args);
} }
let (content_type, data) = MultipartBuilder::new() if let Some(docker_img) = url.strip_prefix("docker://") {
.add_file("file", file)? let tag = std::env::var("ROBO_DOCKER_TAG").unwrap_or_else(|_| {
.add_text( crate::TARGET_PACKAGE_VERSION
"params", .get()
&serde_json::to_string(&json! { .cloned()
{ .unwrap_or_else(|| "latest".to_owned())
"force": force, });
"run": run, let img_name = format!("{}:{}", docker_img, tag);
} println!("Building docker image: {}", img_name.yellow());
let result = std::process::Command::new("docker")
.args(["build", "-t", &img_name, "."])
.status()?;
if !result.success() {
return Err("Compilation failed".into());
}
println!();
println!("Docker image ready: {}", img_name.green());
if run {
println!("Running docker image...");
let mut args = vec!["run", "--rm", "-it"];
let port = std::env::var("ROBO_DOCKER_PORT").unwrap_or_else(|_| "7700".to_owned());
let port_mapping = if port.is_empty() {
None
} else {
Some(format!("{}:7700", port))
};
if let Some(ref port_mapping) = port_mapping {
args.push("-p");
args.push(port_mapping);
println!(
"RoboPLC manager is available at {}",
format!("http://localhost:{}", port).yellow()
);
}
if force {
args.push("--privileged");
}
args.push(&img_name);
let result = std::process::Command::new("docker").args(args).status()?;
if !result.success() {
return Err("Execution failed".into());
}
}
} else {
let (content_type, data) = MultipartBuilder::new()
.add_file("file", file)?
.add_text(
"params",
&serde_json::to_string(&json! {
{
"force": force,
"run": run,
}
})?, })?,
)? )?
.finish()?; .finish()?;
agent agent
.post(&format!("{}{}/flash", url, API_PREFIX)) .post(&format!("{}{}/flash", url, API_PREFIX))
.set("x-auth-key", key) .set("x-auth-key", key)
.set("content-type", &content_type) .set("content-type", &content_type)
.send_bytes(&data) .send_bytes(&data)
.process_error()?; .process_error()?;
}
Ok(()) Ok(())
} }
......
...@@ -3,6 +3,7 @@ use std::{fs, time::Duration}; ...@@ -3,6 +3,7 @@ use std::{fs, time::Duration};
use arguments::{Args, SubCommand}; use arguments::{Args, SubCommand};
use clap::Parser; use clap::Parser;
use common::{find_robo_toml, Mode}; use common::{find_robo_toml, Mode};
use once_cell::sync::OnceCell;
use ureq::Agent; use ureq::Agent;
use crate::config::Config; use crate::config::Config;
...@@ -11,6 +12,10 @@ const API_PREFIX: &str = "/roboplc/api"; ...@@ -11,6 +12,10 @@ const API_PREFIX: &str = "/roboplc/api";
const DEFAULT_TIMEOUT: u64 = 60; const DEFAULT_TIMEOUT: u64 = 60;
const TPL_DEFAULT_RS: &str = include_str!("../tpl/default.rs"); const TPL_DEFAULT_RS: &str = include_str!("../tpl/default.rs");
// filled by find_robo_toml if Cargo.toml is found
static TARGET_PACKAGE_NAME: OnceCell<String> = OnceCell::new();
static TARGET_PACKAGE_VERSION: OnceCell<String> = OnceCell::new();
mod arguments; mod arguments;
mod common; mod common;
mod config; mod config;
...@@ -20,6 +25,7 @@ mod project; ...@@ -20,6 +25,7 @@ mod project;
mod remote; mod remote;
mod ureq_err; mod ureq_err;
#[allow(clippy::too_many_lines)]
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
let _ansi_enabled = ansi_term::enable_ansi_support(); let _ansi_enabled = ansi_term::enable_ansi_support();
...@@ -61,7 +67,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { ...@@ -61,7 +67,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
maybe_url = maybe_url.map(|v| { maybe_url = maybe_url.map(|v| {
let mut u = v.trim_end_matches('/').to_owned(); let mut u = v.trim_end_matches('/').to_owned();
if !u.starts_with("http://") && !u.starts_with("https://") { if !u.starts_with("http://") && !u.starts_with("https://") && !u.starts_with("docker://") {
u = format!("http://{}", u); u = format!("http://{}", u);
} }
u u
...@@ -71,7 +77,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { ...@@ -71,7 +77,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
return Ok(()); return Ok(());
} }
let url = maybe_url.ok_or("URL not specified")?; let url = maybe_url.ok_or("URL not specified")?;
let key = maybe_key.ok_or("Key not specified")?; let key = if let Some(k) = maybe_key {
k
} else if url.starts_with("docker://") {
String::new()
} else {
return Err("Key not specified".into());
};
let timeout = maybe_timeout.unwrap_or(DEFAULT_TIMEOUT); let timeout = maybe_timeout.unwrap_or(DEFAULT_TIMEOUT);
let agent: Agent = ureq::AgentBuilder::new() let agent: Agent = ureq::AgentBuilder::new()
.timeout_read(Duration::from_secs(timeout)) .timeout_read(Duration::from_secs(timeout))
......
use std::env; use std::{env, fs::File, io::Write};
use colored::Colorize as _; use colored::Colorize as _;
...@@ -42,7 +42,7 @@ pub fn create( ...@@ -42,7 +42,7 @@ pub fn create(
true, true,
)?; )?;
add_dependency("tracing@0.1", &["log"], None, false)?; add_dependency("tracing@0.1", &["log"], None, false)?;
let robo_toml = Config { let mut robo_toml = Config {
remote: config::Remote { remote: config::Remote {
key: maybe_key, key: maybe_key,
url: maybe_url, url: maybe_url,
...@@ -51,6 +51,19 @@ pub fn create( ...@@ -51,6 +51,19 @@ pub fn create(
build: <_>::default(), build: <_>::default(),
build_custom: <_>::default(), build_custom: <_>::default(),
}; };
if let Some(docker_arch) = opts.docker {
robo_toml.build.target = Some(docker_arch.target().to_owned());
if robo_toml.remote.url.is_none() {
robo_toml.remote.url = Some(format!("docker://{}", opts.name));
}
let mut f = File::create("Dockerfile")?;
writeln!(f, "FROM {}", docker_arch.docker_image_name())?;
writeln!(
f,
"COPY ./{} /var/roboplc/program/current",
docker_arch.binary_path_for(&opts.name).display()
)?;
}
std::fs::write(CONFIG_FILE_NAME, toml::to_string_pretty(&robo_toml)?)?; std::fs::write(CONFIG_FILE_NAME, toml::to_string_pretty(&robo_toml)?)?;
std::fs::write("src/main.rs", prepare_main(TPL_DEFAULT_RS, &robo_features))?; std::fs::write("src/main.rs", prepare_main(TPL_DEFAULT_RS, &robo_features))?;
println!("Project created: {}", opts.name.green().bold()); println!("Project created: {}", opts.name.green().bold());
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论