1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! The core definitions and structs for the Process Foundry
//!
//! After getting lost in the weeds, this is all the things that I seem to see come up as useful in every
//! application and container
//!
//! # THINK: Additional traits
//!   - Actionable: Possibly part of app trait, since all should be able to utilize and emit Actions/Events
//!   - ActionResult: To abstract the result so we can pass it to something that has the code to actually use
//!   - Routable (possibly part of action)
//!   - consider https://abronan.com/rust-trait-objects-box-and-rc/ - Arc<Mutex<impl trait>>

use anyhow::{Context, Result};
use serde_derive::{Deserialize, Serialize};
use std::rc::Rc;

use super::Bash;
use super::FoundryError;

// Re-exports
pub mod application;

#[doc(inline)]
pub use application::{AppInstance, AppTrait};

/// Ways to manage applications (eg Docker, Bash) contained within itself
pub trait ContainerTrait: std::fmt::Debug {
  /// This will find a list of apps with configurations that the container knows about
  fn find(&self, query: AppQuery) -> Result<Vec<AppInstance>>;

  /// Find a unique app that matches the query
  fn find_one(&self, query: AppQuery) -> Result<AppInstance> {
    let all = self.find(query.clone())?;
    match all.len() {
      0 => Err(FoundryError::NotFound).context(format!(
          "No instances matching your query of {} have been registered",
          query.name
      )),
      1 => Ok(all.get(0).unwrap().clone()),
      x => Err(FoundryError::MultipleMatches).context(format!(
          "{} instances matching your definition for {} have been registered. Please narrow your search criteria",
          x, query.name
      )),
    }
  }

  /// Send a stringified action to the AppInstance
  fn forward(&self, to: AppInstance, message: Message) -> Result<String>;

  /// List the known items in the app cache
  fn cached_apps(&self) -> Result<Vec<AppInstance>>;

  /// Get the name/version of the container, usually for use in logging/errors.
  fn get_name(&self) -> String;
}

/// An app running on the local system (eg: bash shell)
pub trait LocalTrait {
  /// Get a local instance of the given app
  fn get_local() -> Result<AppInstance>;
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CliQueryOptions {
  pub search_paths: Option<Vec<String>>,
}

/// A wrapper for looking for specific instances of apps or where they are running
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum QueryOptions {
  Container,
  Network,
  Cli,
}

/// This defines the a needed version of container/application needed for the module.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AppQuery {
  // Main options
  pub name: String,
  pub works_with: Option<semver::VersionReq>,

  pub aliases: Option<Vec<String>>,
  pub search_paths: Option<Vec<String>>,

  // Searches high and low for apps that match the criteria. If false it returns an error with multiple matches.
  pub find_all: bool,
}

impl AppQuery {
  pub fn new(name: String) -> AppQuery {
    AppQuery {
      name,
      works_with: None,

      aliases: None,
      search_paths: None,
      find_all: false,
    }
  }

  // TODO: this seems to make sense
  // pub fn new2(def: AppDescription) -> AppQuery {}

  // Set find all option to true
  pub fn find_all(&self) -> AppQuery {
    AppQuery {
      find_all: true,
      ..self.clone()
    }
  }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ShellType {
  Dash,
  Bash,
  Zsh,
}

/// A special case for bootstrapping. I'm trying to find the enumerations that actually deserve to be
/// traits themselves
#[derive(Debug, Clone)]
pub struct Shell {
  pub shell_type: ShellType,
  pub instance: AppInstance,
  pub running: Rc<dyn ContainerTrait>,
}

impl Shell {
  pub fn get_local_shell() -> Result<Shell> {
    // Bash seems to be on most systems, so we'll prefer that
    let default = "bash";
    let shell_name = option_env!("SHELL").map_or(default, |cmd| {
      let regex = regex::Regex::new(r"/([\w-]+)$").unwrap();
      regex.captures(&cmd).map_or(default, |cap| {
        cap.get(1).map_or(default, |val| val.as_str())
      })
    });

    let shell_type = match &shell_name.to_lowercase()[..] {
      "bash" => ShellType::Bash,
      x => {
        log::warn!("Found preferred shell is '{}', but using bash anyways", x);
        ShellType::Bash
      }
    };

    let (instance, running) = match shell_type {
      ShellType::Bash => {
        let instance = Bash::get_local().context("Could not get a local Bash shell")?;
        (instance.clone(), Rc::new(Bash::build(instance, None)?))
      }
      _ => unreachable!("Should currently not be able to use any local shell other than bash"),
    };

    Ok(Shell {
      instance,
      shell_type,
      running,
    })
  }
}

// THINK: This is very specific to forwarding to shell and is more like a script. Does this belong with
//        the future workflows?
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cmd {
  pub run_as: Option<String>,
  pub command: String,
  pub args: Vec<String>,
}

///  A generic message designed to be sent to a container
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Message {
  /// For use in a remote shell, like one contained within a docker container
  Command(Cmd),

  /// Build an Rpc call
  Rpc,

  /// Call a Restful API
  Rest,
}

/// Handlers for serialized action requests
///
/// THINK: Should there be a send/receive?
pub trait ActionTrait {
  type RESPONSE;

  // Have the application directly run the function run the command and return the result
  fn run(&self, target: AppInstance) -> Result<Self::RESPONSE>;

  // Convert this action into a std::process::Command style vector to be run in a place where the
  // foundry cannot directly access (like inside docker container)
  // THINK: Should run just naturally use this when the target is remote?
  fn to_message(&self, target: Option<AppInstance>) -> Result<Vec<Message>>;

  // This is a long term goal, be able to generate stand-alone scripts based on the container actions
  // fn to_file(&self, application: Box<dyn AppTrait>) -> Result<String, Error>;
}