Commit 1446d731 authored by PERE Alexandre's avatar PERE Alexandre

Merge branch 'develop' into 'master'

V0.3.1

See merge request !24
parents 8e39f06c 6766d814
[package]
name = "liborchestra"
version = "0.3.0"
version = "0.3.1"
authors = ["Alexandre Péré <alexandre.pere@protonmail.com>"]
edition = "2018"
......
......@@ -10,7 +10,6 @@
use crate::{ssh, repository, misc, commons};
use std::{io, error, fmt};
use regex;
//use git2;
use yaml_rust;
......
......@@ -338,7 +338,7 @@ struct Host {
impl Host {
// Builds a host from a configuration.
#[instrument(name="Host::from_conf")]
fn from_conf(conf: HostConf) -> Result<Host, Error> {
fn from_conf(conf: HostConf, context: FrontendContext) -> Result<Host, Error> {
trace!("Loading from conf");
// We retrieve the ssh profile from the configuration
let profile = ssh::config::get_profile(
......@@ -351,7 +351,7 @@ impl Host {
trace!(?conn, "Connection to frontend acquired");
// We generate the host
let mut context = FrontendContext(TerminalContext::default());
let mut context = context;
context.0.envs.insert(EnvironmentKey("RUNAWAY_PATH".into()),
EnvironmentValue(conf.directory.to_str().unwrap().into()));
Ok(Host {
......@@ -382,7 +382,7 @@ impl Host {
// We retrieve node ids from the terminal context
let node_ids = extract_nodes(&frontend_context.0)?;
trace!(?node_ids, "Retrieved nodes");
trace!("Retrieved nodes: {:?}", node_ids);
// We spawn the nodes
trace!("Spawning nodes");
......@@ -542,8 +542,8 @@ pub struct HostHandle {
impl HostHandle {
/// This function spawns the thread that will handle all the repository operations using the
/// CampaignResource, and returns a handle to it.
pub fn spawn(host_conf: HostConf) -> Result<HostHandle, Error> {
let host = Host::from_conf(host_conf.clone())?;
pub fn spawn(host_conf: HostConf, context: TerminalContext<PathBuf>) -> Result<HostHandle, Error> {
let host = Host::from_conf(host_conf.clone(), FrontendContext(context))?;
let conn = host.conn.clone();
let (sender, receiver) = mpsc::unbounded();
let handle = thread::Builder::new().name("host".into())
......@@ -771,7 +771,7 @@ fn extract_nodes(context: &TerminalContext<PathBuf>) -> Result<Vec<NodeId>, Erro
// We search the nodes string in environment variables
.envs
.get(&EnvironmentKey("RUNAWAY_NODES".into()))
.map(|EnvironmentValue(s)| s)
.map(|EnvironmentValue(s)| s.trim_start_matches(' ').trim_end_matches(' ').to_owned())
.ok_or(Error::AllocationFailed("RUNAWAY_NODES was not set.".to_string()))?
// We split and map to node ids
.split(' ')
......@@ -860,7 +860,7 @@ fn extract_handles(context: &TerminalContext<PathBuf>) -> Result<Vec<HandleId>,
// We search the nodes string in environment variables
.envs
.get(&EnvironmentKey("RUNAWAY_HANDLES".into()))
.map(|EnvironmentValue(s)| s)
.map(|EnvironmentValue(s)| s.trim_start_matches(' ').trim_end_matches(' ').to_owned())
.ok_or(Error::AllocationFailed(format!("RUNAWAY_HANDLES was not set.")))?
// We split and map to node ids
.split(' ')
......@@ -990,8 +990,10 @@ mod test {
execution: vec!["$RUNAWAY_COMMAND".to_owned()],
directory: path::PathBuf::from("/projets/flowers/alex/executions"),
};
let res_handle = HostHandle::spawn(conf).unwrap();
let context = TerminalContext::default();
let res_handle = HostHandle::spawn(conf, context).unwrap();
// We test environment of first connection
let conn1 = {
......@@ -1080,7 +1082,10 @@ mod test {
directory: path::PathBuf::from("/projets/flowers/alex/executions"),
};
let res_handle = HostHandle::spawn(conf).unwrap();
let context = TerminalContext::default();
let res_handle = HostHandle::spawn(conf, context).unwrap();
async fn test(res: HostHandle) {
let conn = res.async_acquire().await.unwrap();
......
This diff is collapsed.
......@@ -37,7 +37,7 @@ rw_run() {
local stdin;
# We format the line
if read line ; then
echo "RUNAWAY_STDOUT: $line" ;
printf "RUNAWAY_STDOUT: %s\n" "$line" ;
# We try to acquire a shared lock on the way_in_lock. This can only happen when the exclusive
# lock hold by the command will be released, after the command was executed.
elif flock -ns 201; then
......@@ -53,7 +53,7 @@ rw_run() {
flock -s 202;
while true; do
if read line ; then
echo "RUNAWAY_STDERR: $line" ;
printf "RUNAWAY_STDERR: %s\n" "$line";
elif flock -ns 201; then
flock -u 202;
break;
......@@ -91,7 +91,7 @@ rw_run() {
rw_close(){
# We output the current working directory
printf 'RUNAWAY_CWD: %s' $(pwd)
printf 'RUNAWAY_CWD: %s\n' $(pwd)
# We echo the environment variables
env | sed 's/^/RUNAWAY_ENV: /g'
......
......@@ -215,7 +215,7 @@ impl ProxyCommandForwarder {
.spawn()
.map_err(|e| Error::ProxyCommandStartup(format!("failed to start command: {}", e)))
}?;
debug!("ProxyCommand started with pid {}", command.id());
debug!("ProxyCommand {} started with pid {}", command_string, command.id());
trace!("Spawning proxy command forwarding thread");
......@@ -742,8 +742,6 @@ impl RemoteHandle {
fn new_session(stream: &TcpStream) -> Result<Session, Error>{
trace!("Creates a new session");
let mut session = Session::new().unwrap();
session.method_pref(MethodType::HostKey, "ssh-rsa")
.map_err(|_| Error::ConnectionFailed("Failed to set preferences".to_string()))?;
session.handshake(stream)
.map_err(|e| Error::ConnectionFailed(format!("Failed to perform handshake: \n{}", e)))?;
Ok(session)
......@@ -1006,7 +1004,7 @@ async fn perform_pty(channel: &mut ssh2::Channel<'_>,
await_wouldblock_io!(stream.read_line(&mut buffer))
.map_err(|e| Error::ExecutionFailed(format!("Failed to read outputs: {}", e)))?;
buffer = buffer.replace("\r\n", "\n");
trace!("Reading command output: {:?}", buffer);
debug!("Reading command output: {:?}", buffer);
// We receive an exit code
if buffer.starts_with("RUNAWAY_ECODE: "){
trace!("Ecode message detected");
......@@ -1128,7 +1126,7 @@ async fn setup_scp_send<'a>(remote: &'a Arc<Mutex<Remote>>,
error!("Failed to obtain channel");
return Err(Error::ChannelNotAvailable)
},
Err(e) => return Err(Error::ScpSendFailed(format!("Failed to open scp send channel: {}", e))),
Err(e) => return Err(Error::ScpSendFailed(format!("Failed to open scp send channel. Does the path exist ? : {}", e))),
};
Ok((local_file, bytes as i64, channel))
}
......@@ -1384,12 +1382,12 @@ mod test {
};
let remote = RemoteHandle::spawn(profile).unwrap();
// Check order of outputs
let commands = vec![RawCommand("a=KIKOU".into()),
let commands = vec![RawCommand("a=\"KIKOU KIKOU\"".into()),
RawCommand("echo $a".into())];
let context = TerminalContext::default();
let (_, outputs) = remote.async_pty(context, commands, None, None).await.unwrap();
let output = misc::compact_outputs(outputs);
assert_eq!(String::from_utf8(output.stdout).unwrap(), "KIKOU\n");
assert_eq!(String::from_utf8(output.stdout).unwrap(), "KIKOU KIKOU\n");
assert_eq!(output.status.code().unwrap(), 0);
}
block_on(test());
......@@ -1467,11 +1465,11 @@ mod test {
EnvironmentValue("VAL1".into()));
let commands = vec![RawCommand("pwd".into()),
RawCommand("echo $RUNAWAY_TEST".into()),
RawCommand("export RUNAWAY_TEST=VAL2".into())];
RawCommand("export RUNAWAY_TEST=\"VAL2 VAL3\"".into())];
let (context, outputs) = remote.async_pty(context, commands, None, None).await.unwrap();
let output = misc::compact_outputs(outputs);
assert_eq!(String::from_utf8(output.stdout).unwrap(), "/tmp\nVAL1\n");
assert_eq!(context.envs.get(&EnvironmentKey("RUNAWAY_TEST".into())).unwrap(), &EnvironmentValue("VAL2".into()));
assert_eq!(context.envs.get(&EnvironmentKey("RUNAWAY_TEST".into())).unwrap(), &EnvironmentValue("VAL2 VAL3".into()));
assert_eq!(output.status.code().unwrap(), 0);
}
block_on(test());
......
[package]
name = "runaway-cli"
version = "0.3.0"
version = "0.3.1"
authors = ["Alexandre Péré <alexandre.pere@protonmail.com>"]
edition = "2018"
......@@ -23,4 +23,5 @@ tracing = "0.1.10"
tracing-attributes = "0.1.5"
tracing-futures = {version = "0.1.0", features=["futures-preview"]}
openssl-probe = "0.1.2"
path_abs = "0.5.0"
liborchestra = { path = "../liborchestra"}
......@@ -57,6 +57,7 @@ pub enum Exit {
SchedulerCrashed,
SchedulerShutdown,
InstallCompletion,
PostScriptPath,
}
impl std::fmt::Display for Exit {
......@@ -113,7 +114,8 @@ impl std::fmt::Display for Exit {
Exit::RecordFeatures => write!(f, "failed to record features"),
Exit::SchedulerCrashed => write!(f, "scheduler crashed"),
Exit::SchedulerShutdown => write!(f, "scheduler was shutdown"),
Exit::InstallCompletion => write!(f, "failed to install completion")
Exit::InstallCompletion => write!(f, "failed to install completion"),
Exit::PostScriptPath => write!(f, "failed to load post script"),
}
}
}
......@@ -170,6 +172,7 @@ impl From<Exit> for i32 {
Exit::SchedulerCrashed => 9946,
Exit::SchedulerShutdown => 9947,
Exit::InstallCompletion => 9948,
Exit::PostScriptPath => 9949,
}
}
}
......@@ -28,7 +28,9 @@ impl Visit for EchoVisitor{
// This logger only echoes messages to stderr, without any more formatting.
pub enum EchoSubscriber{
Normal,
Verbose
Verbose,
Ssh,
Trace(String)
}
impl EchoSubscriber{
......@@ -40,13 +42,28 @@ impl EchoSubscriber{
tracing::subscriber::set_global_default(EchoSubscriber::Verbose)
.expect("Setting logger failed.");
}
pub fn setup_ssh(){
tracing::subscriber::set_global_default(EchoSubscriber::Ssh)
.expect("Setting logger failed.");
}
pub fn setup_trace(module: String){
tracing::subscriber::set_global_default(EchoSubscriber::Trace(module))
.expect("Setting logger failed.");
}
}
impl Subscriber for EchoSubscriber{
fn enabled(&self, metadata: &Metadata) -> bool {
match (self, metadata.level()){
(_, &Level::INFO) | (_, &Level::ERROR) => true,
(EchoSubscriber::Verbose, &Level::DEBUG) | (EchoSubscriber::Verbose, &Level::WARN) => true,
match (self, metadata.level(), metadata.target()){
(_, &Level::INFO, _) => true,
(_, &Level::ERROR, _) => true,
(EchoSubscriber::Verbose, &Level::DEBUG, t) if t != "liborchestra::ssh" => true,
(EchoSubscriber::Verbose, &Level::WARN, _) => true,
(EchoSubscriber::Ssh, &Level::DEBUG, "liborchestra::ssh") => true,
(EchoSubscriber::Ssh, &Level::WARN, "liborchestra::ssh") => true,
(EchoSubscriber::Trace(m), &Level::TRACE, t) if t == format!("liborchestra::{}", m) => true,
(EchoSubscriber::Trace(_), &Level::DEBUG, _) => true,
_ => false
}
}
......
......@@ -69,6 +69,13 @@ fn main(){
.short("V")
.long("verbose")
.help("Print more messages"))
.arg(clap::Arg::with_name("debug-ssh")
.long("debug-ssh")
.help("Print ssh messages"))
.arg(clap::Arg::with_name("trace")
.takes_value(true)
.long("trace")
.help("Print trace message of the mentionned module"))
.arg(clap::Arg::with_name("silent")
.short("S")
.long("silent")
......@@ -130,6 +137,13 @@ fn main(){
.short("V")
.long("verbose")
.help("Print more messages"))
.arg(clap::Arg::with_name("debug-ssh")
.long("debug-ssh")
.help("Print ssh messages"))
.arg(clap::Arg::with_name("trace")
.takes_value(true)
.long("trace")
.help("Print trace message of the mentionned module"))
.arg(clap::Arg::with_name("silent")
.short("S")
.long("silent")
......@@ -225,11 +239,19 @@ fn main(){
.help("File name of the script to be executed")
.required(true))
.arg(clap::Arg::with_name("SCHEDULER")
.help("Search command to use to schedule experiment parameters."))
.help("Search command to use to schedule experiment parameters.")
.required(true))
.arg(clap::Arg::with_name("verbose")
.short("V")
.long("verbose")
.help("Print more messages"))
.arg(clap::Arg::with_name("debug-ssh")
.long("debug-ssh")
.help("Print ssh messages"))
.arg(clap::Arg::with_name("trace")
.takes_value(true)
.long("trace")
.help("Print trace message of the mentionned module"))
.arg(clap::Arg::with_name("silent")
.short("S")
.long("silent")
......
......@@ -18,7 +18,7 @@ use clap;
use crate::exit::Exit;
use crate::logger::EchoSubscriber;
use liborchestra::primitives::{read_globs_from_file, list_local_folder, Glob};
use liborchestra::commons::{EnvironmentStore, EnvironmentKey, EnvironmentValue};
use liborchestra::commons::{EnvironmentStore, EnvironmentKey, EnvironmentValue, TerminalContext};
use liborchestra::scheduler::SchedulerHandle;
use itertools::Itertools;
use tracing::{self, info, error};
......@@ -96,10 +96,12 @@ macro_rules! try_return_err {
/// Allows to load host from a host path configuration
pub fn get_host(host_name: &str) -> Result<HostHandle, Exit>{
pub fn get_host(host_name: &str, envs: EnvironmentStore) -> Result<HostHandle, Exit>{
let host_path = get_host_path(host_name);
let mut context = TerminalContext::default();
context.envs = envs;
let config = to_exit!(HostConf::from_file(&host_path), Exit::LoadHostConfiguration)?;
to_exit!(HostHandle::spawn(config), Exit::SpawnHost)
to_exit!(HostHandle::spawn(config, context), Exit::SpawnHost)
}
/// Allows to generate globs from send and fetch ignore file.
......@@ -285,6 +287,10 @@ pub fn init_logger(matches: &clap::ArgMatches) {
if matches.is_present("silent"){
} else if matches.is_present("verbose"){
EchoSubscriber::setup_verbose();
} else if matches.is_present("debug-ssh"){
EchoSubscriber::setup_ssh();
} else if matches.is_present("trace"){
EchoSubscriber::setup_trace(matches.value_of("trace").unwrap().to_owned());
} else {
EchoSubscriber::setup_normal();
}
......
......@@ -32,6 +32,7 @@ use rand::{self, Rng};
use std::io::Write;
use tracing::{self, error, debug, info, warn};
use liborchestra::commons::format_env;
use path_abs::PathAbs;
//--------------------------------------------------------------------------------------- SUBCOMMAND
......@@ -57,7 +58,7 @@ pub fn batch(matches: clap::ArgMatches<'static>) -> Result<Exit, Exit>{
// We load the host
info!("Loading host");
let host = misc::get_host(matches.value_of("REMOTE").unwrap())?;
let host = misc::get_host(matches.value_of("REMOTE").unwrap(), store.clone())?;
push_env(&mut store, "RUNAWAY_REMOTE", host.get_name());
debug!("Host {} loaded", host);
......@@ -430,7 +431,7 @@ async fn perform_on_node(store: EnvironmentStore,
let mut store = store;
push_env(&mut store, "RUNAWAY_ARGUMENTS", arguments);
// We generate an uuid
let id = uuid::Uuid::new_v4().hyphenated().to_string();
push_env(&mut store, "RUNAWAY_UUID", id.clone());
......@@ -518,7 +519,9 @@ async fn perform_on_node(store: EnvironmentStore,
local_output_folder = remote_folder.clone();
} else {
let local_output_string = substitute_environment(&execution_context.envs, output_folder_pattern.as_str());
local_output_folder = to_exit!(PathBuf::from(local_output_string).canonicalize(), Exit::OutputFolder)?;
let abs_local_output_folder = to_exit!(PathAbs::new(local_output_string), Exit::OutputFolder)?;
let abs_local_output_folder: &PathBuf = abs_local_output_folder.as_ref();
local_output_folder = abs_local_output_folder.to_owned();
}
debug!("Local output folder set to: {}", local_output_folder.to_str().unwrap());
if !local_output_folder.exists(){
......@@ -606,13 +609,12 @@ fn unpacks_fetch_post_proc(matches: &clap::ArgMatches<'_>,
// We execute the post processing
debug!("Executing post script");
let command_string = if matches.is_present("post-script"){
let path_str = PathBuf::from(matches.value_of("post-script").unwrap())
.canonicalize()
.unwrap()
.to_str()
let path = PathBuf::from(matches.value_of("post-script").unwrap());
let path = to_exit!(path.canonicalize(), Exit::PostScriptPath)?;
let path = path.to_str()
.unwrap()
.to_owned();
format!("bash {}", path_str)
format!("bash {}", path)
} else {
matches.value_of("post-command").unwrap().to_owned()
};
......
......@@ -38,9 +38,18 @@ pub fn exec(matches: clap::ArgMatches) -> Result<Exit, Exit>{
// We create the store that will keep env vars
let mut store = EnvironmentStore::new();
// We read the envs to the store.
if !matches.is_present("no-env-read"){
let envs = misc::read_local_runaway_envs();
debug!("Local environment variables captured: {}", envs.iter()
.fold(String::new(), |mut acc, (k, v)| {acc.push_str(&format!("\n{:?}={:?}", k, v)); acc}));
envs.into_iter()
.for_each(|(k, v)| {push_env(&mut store, k.0, v.0);});
}
// We load the host
info!("Loading host");
let host = misc::get_host(matches.value_of("REMOTE").unwrap())?;
let host = misc::get_host(matches.value_of("REMOTE").unwrap(), store.clone())?;
push_env(&mut store, "RUNAWAY_REMOTE", host.get_name());
debug!("Host {:?} loaded", host);
......
......@@ -32,6 +32,7 @@ use std::convert::TryInto;
use rand::{self, Rng};
use std::io::Write;
use tracing::{self, info, error, debug};
use path_abs::PathAbs;
//--------------------------------------------------------------------------------------- SUBCOMMAND
......@@ -57,7 +58,7 @@ pub fn sched(matches: clap::ArgMatches<'static>) -> Result<Exit, Exit>{
// We load the host
info!("Loading host");
let host = misc::get_host(matches.value_of("REMOTE").unwrap())?;
let host = misc::get_host(matches.value_of("REMOTE").unwrap(), store.clone())?;
push_env(&mut store, "RUNAWAY_REMOTE", host.get_name());
debug!("Host {} loaded", host);
......@@ -179,10 +180,16 @@ pub fn sched(matches: clap::ArgMatches<'static>) -> Result<Exit, Exit>{
// Again, we make local copies.
let sched = sched.clone();
let host = host.clone();
let mut store = store;
// We generate an uuid
let id = uuid::Uuid::new_v4().hyphenated().to_string();
push_env(&mut store, "RUNAWAY_UUID", id.clone());
debug!("Execution id set to {}", format!("{}", id));
// We get the arguments
info!("Querying the scheduler");
let arguments: String = match sched.async_request_parameters().await{
let arguments: String = match sched.async_request_parameters(id.clone()).await{
Ok(arg) => Ok(arg),
Err(liborchestra::scheduler::Error::Crashed) => Err(Exit::SchedulerCrashed),
Err(liborchestra::scheduler::Error::Shutdown) => Err(Exit::SchedulerShutdown),
......@@ -192,18 +199,18 @@ pub fn sched(matches: clap::ArgMatches<'static>) -> Result<Exit, Exit>{
// We acquire the node
let node = to_exit!(host.async_acquire().await, Exit::NodeAcquisition)?;
let mut store = store;
store.extend(node.context.envs.clone().into_iter());
debug!("Acquired node with context: \nCwd: {}\nEnvs:\n {}",
node.context.cwd.0.to_str().unwrap(),
format_env(&node.context.envs).replace("\n", "\n ")
);
Ok((arguments, node, store))
Ok((arguments, node, store, id))
};
// We execute this future and breaks if an error was encountered
let (arguments, node, store) = match executor.run(arg_and_node_and_store_fut){
let (arguments, node, store, id) = match executor.run(arg_and_node_and_store_fut){
Ok(a) => a,
Err(e) => break e
};
......@@ -212,7 +219,7 @@ pub fn sched(matches: clap::ArgMatches<'static>) -> Result<Exit, Exit>{
let perform_fut = async move {
info!("Starting execution with arguments\"{}\"", arguments);
// We perform the exec
let (local_fetch_archive, store, remote_fetch_hash, execution_code) = perform_on_node(
let (local_fetch_archive, store, remote_fetch_hash, output) = perform_on_node(
store,
node,
&host,
......@@ -225,13 +232,21 @@ pub fn sched(matches: clap::ArgMatches<'static>) -> Result<Exit, Exit>{
&fetch_include_globs,
matches.is_present("on-local"),
).await?;
let ret = unpacks_fetch_post_proc(&matches, local_fetch_archive, store.clone(), remote_fetch_hash, execution_code);
if let Some(EnvironmentValue(features)) = store.get(&EnvironmentKey("RUNAWAY_FEATURES".into())) {
to_exit!(sched.async_record_output(arguments, features.to_string()).await, Exit::RecordFeatures)?;
} else {
error!("RUNAWAY_FEATURES was not set.");
return Err(Exit::FeaturesNotSet);
}
let ret = unpacks_fetch_post_proc(&matches, local_fetch_archive.clone(), store.clone(), remote_fetch_hash, output.ecode);
let features = match store.get(&EnvironmentKey("RUNAWAY_FEATURES".into())){
Some(EnvironmentValue(features)) => features.to_string(),
None => "".to_owned()
};
let path = local_fetch_archive
.parent()
.unwrap()
.canonicalize()
.unwrap()
.to_str()
.unwrap()
.to_owned();
to_exit!(sched.async_record_output(id, arguments, output.stdout, output.stderr, output.ecode, features, path).await,
Exit::RecordFeatures)?;
ret
};
......@@ -374,19 +389,13 @@ async fn perform_on_node(store: EnvironmentStore,
fetch_ignore_globs: &Vec<Glob<String>>,
fetch_include_globs: &Vec<Glob<String>>,
on_local: bool
) -> Result<(PathBuf, EnvironmentStore, Sha1Hash, i32), Exit>{
) -> Result<(PathBuf, EnvironmentStore, Sha1Hash, OutputBuf), Exit>{
let mut store = store;
push_env(&mut store, "RUNAWAY_ARGUMENTS", arguments);
// We generate an uuid
let id = uuid::Uuid::new_v4().hyphenated().to_string();
push_env(&mut store, "RUNAWAY_UUID", id.clone());
debug!("Execution id set to {}", format!("{}", id));
// We generate the remote folder and unpack data into it
let remote_folder;
if on_local{
......@@ -402,6 +411,8 @@ async fn perform_on_node(store: EnvironmentStore,
&node
).await?;
// We retrieve the id
let EnvironmentValue(id) = store.get(&EnvironmentKey("RUNAWAY_UUID".into())).unwrap().to_owned();
// We perform the job
debug!("Executing script");
......@@ -459,7 +470,9 @@ async fn perform_on_node(store: EnvironmentStore,
local_output_folder = remote_folder.clone();
} else {
let local_output_string = substitute_environment(&execution_context.envs, output_folder_pattern);
local_output_folder = to_exit!(PathBuf::from(local_output_string).canonicalize(), Exit::OutputFolder)?;
let abs_local_output_folder = to_exit!(PathAbs::new(local_output_string), Exit::OutputFolder)?;
let abs_local_output_folder: &PathBuf = abs_local_output_folder.as_ref();
local_output_folder = abs_local_output_folder.to_owned();
}
debug!("Local output folder set to: {}", local_output_folder.to_str().unwrap());
......@@ -511,7 +524,7 @@ async fn perform_on_node(store: EnvironmentStore,
// We return needed informations
Ok((local_fetch_archive, execution_context.envs, remote_fetch_hash, out.ecode))
Ok((local_fetch_archive, execution_context.envs, remote_fetch_hash, out))
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment