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

docker support

上级 8834d453
......@@ -695,13 +695,14 @@ dependencies = [
[[package]]
name = "roboplc-cli"
version = "0.4.1"
version = "0.4.2"
dependencies = [
"ansi_term",
"clap",
"colored",
"dirs",
"futures-util",
"once_cell",
"serde",
"serde_json",
"shlex",
......
[package]
name = "roboplc-cli"
version = "0.4.1"
version = "0.4.2"
edition = "2021"
authors = ["Serhij S. <div@altertech.com>"]
license = "Apache-2.0"
......@@ -28,6 +28,7 @@ which = "3"
term_size = "0.3.2"
tokio = { version = "=1.36", features = ["rt", "fs"] }
tokio-tungstenite = { version = "0.23.1", features = ["rustls"] }
once_cell = "1.19.0"
[target.'cfg(windows)'.dependencies]
ansi_term = "0.12.1"
......
......@@ -63,6 +63,34 @@ pub struct NewCommand {
pub extras: Vec<String>,
#[clap(short = 'L', long, help = "Locking policy)", default_value = "rt-safe")]
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)]
......@@ -98,10 +126,14 @@ pub struct FlashCommand {
#[clap(
short = 'f',
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,
#[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,
}
......
......@@ -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> {
let mut current_dir = env::current_dir().ok()?;
loop {
let mut cargo_toml_path = current_dir.clone();
cargo_toml_path.push("Cargo.toml");
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();
roboplc_toml_path.push(CONFIG_FILE_NAME);
if roboplc_toml_path.exists() {
std::env::set_current_dir(current_dir).expect("Failed to set current dir");
return Some(roboplc_toml_path);
}
}
......
......@@ -34,25 +34,70 @@ fn flash_file(
if exec_only {
return crate::exec::exec(url, key, file, force, program_args);
}
let (content_type, data) = MultipartBuilder::new()
.add_file("file", file)?
.add_text(
"params",
&serde_json::to_string(&json! {
{
"force": force,
"run": run,
}
if let Some(docker_img) = url.strip_prefix("docker://") {
let tag = std::env::var("ROBO_DOCKER_TAG").unwrap_or_else(|_| {
crate::TARGET_PACKAGE_VERSION
.get()
.cloned()
.unwrap_or_else(|| "latest".to_owned())
});
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()?;
agent
.post(&format!("{}{}/flash", url, API_PREFIX))
.set("x-auth-key", key)
.set("content-type", &content_type)
.send_bytes(&data)
.process_error()?;
})?,
)?
.finish()?;
agent
.post(&format!("{}{}/flash", url, API_PREFIX))
.set("x-auth-key", key)
.set("content-type", &content_type)
.send_bytes(&data)
.process_error()?;
}
Ok(())
}
......
......@@ -3,6 +3,7 @@ use std::{fs, time::Duration};
use arguments::{Args, SubCommand};
use clap::Parser;
use common::{find_robo_toml, Mode};
use once_cell::sync::OnceCell;
use ureq::Agent;
use crate::config::Config;
......@@ -11,6 +12,10 @@ const API_PREFIX: &str = "/roboplc/api";
const DEFAULT_TIMEOUT: u64 = 60;
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 common;
mod config;
......@@ -20,6 +25,7 @@ mod project;
mod remote;
mod ureq_err;
#[allow(clippy::too_many_lines)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(target_os = "windows")]
let _ansi_enabled = ansi_term::enable_ansi_support();
......@@ -61,7 +67,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
maybe_url = maybe_url.map(|v| {
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
......@@ -71,7 +77,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
return Ok(());
}
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 agent: Agent = ureq::AgentBuilder::new()
.timeout_read(Duration::from_secs(timeout))
......
use std::env;
use std::{env, fs::File, io::Write};
use colored::Colorize as _;
......@@ -42,7 +42,7 @@ pub fn create(
true,
)?;
add_dependency("tracing@0.1", &["log"], None, false)?;
let robo_toml = Config {
let mut robo_toml = Config {
remote: config::Remote {
key: maybe_key,
url: maybe_url,
......@@ -51,6 +51,19 @@ pub fn create(
build: <_>::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("src/main.rs", prepare_main(TPL_DEFAULT_RS, &robo_features))?;
println!("Project created: {}", opts.name.green().bold());
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论