├── .gitignore ├── ikki-config ├── README.md ├── fixtures │ ├── nodeps.kdl │ └── dependencies.kdl ├── src │ ├── lib.rs │ ├── parse.rs │ ├── deps.rs │ └── deserialize.rs ├── Cargo.toml └── schema.kdl ├── Cargo.toml ├── ikki ├── justfile ├── src │ ├── console.rs │ ├── args.rs │ ├── explain.rs │ ├── main.rs │ ├── cmd.rs │ ├── supervisor.rs │ ├── docker_config.rs │ ├── listeners.rs │ ├── docker.rs │ └── builder.rs └── Cargo.toml ├── toposort ├── Cargo.toml └── src │ └── lib.rs ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /ikki-config/README.md: -------------------------------------------------------------------------------- 1 | # Ikki configuration parser 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = ["ikki", "ikki-config", "toposort"] 4 | -------------------------------------------------------------------------------- /ikki-config/fixtures/nodeps.kdl: -------------------------------------------------------------------------------- 1 | images { 2 | image "frontend" 3 | image "backend" 4 | image "cli" 5 | } 6 | -------------------------------------------------------------------------------- /ikki-config/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod deps; 2 | mod deserialize; 3 | mod parse; 4 | 5 | pub use deserialize::*; 6 | pub use parse::parse; 7 | pub use parse::BuildOrder; 8 | pub use parse::IkkiConfig; 9 | pub use parse::IkkiConfigError; 10 | -------------------------------------------------------------------------------- /ikki/justfile: -------------------------------------------------------------------------------- 1 | set shell := ["nu", "-c"] 2 | 3 | RUST_LOG := env_var_or_default("RUST_LOG", "info") 4 | 5 | exec-dbg cmd: 6 | let-env RUST_LOG = "{{RUST_LOG}},ikki=debug"; cargo run -- {{cmd}} 7 | 8 | exec cmd: 9 | let-env RUST_LOG = "{{RUST_LOG}}"; cargo run -- {{cmd}} 10 | -------------------------------------------------------------------------------- /ikki-config/fixtures/dependencies.kdl: -------------------------------------------------------------------------------- 1 | dependencies { 2 | apple { 3 | watermelon { 4 | mango 5 | } 6 | melon 7 | } 8 | banana { 9 | papaiya 10 | orange { 11 | tangerine 12 | grape 13 | peach { 14 | mango 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /toposort/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "toposort" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["jlkiri / Kirill Vasiltsov"] 6 | description = "Topological sorting" 7 | repository = "https://github.com/jlkiri/ikki" 8 | license = "MIT" 9 | keywords = ["sorting", "toposort"] 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | multimap = "0.8.3" 15 | -------------------------------------------------------------------------------- /ikki-config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ikki-config" 3 | version = "0.2.0" 4 | edition = "2021" 5 | authors = ["jlkiri / Kirill Vasiltsov"] 6 | description = "Ikki configuration parser" 7 | repository = "https://github.com/jlkiri/ikki.git" 8 | license = "MIT" 9 | keywords = ["ikki"] 10 | categories = ["parsing", "config"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | camino = "1.0.9" 16 | kdl = "4.3.0" 17 | toposort = { path = "../toposort", version = "0.1.0" } 18 | knuffel = "2.0.0" 19 | miette = "5.1.0" 20 | thiserror = "1.0.31" 21 | -------------------------------------------------------------------------------- /ikki/src/console.rs: -------------------------------------------------------------------------------- 1 | use indicatif::{ProgressBar, ProgressStyle}; 2 | 3 | pub fn default_pull_progress_bar() -> ProgressBar { 4 | let style = 5 | ProgressStyle::with_template("[{bar:60.cyan/blue}] ({bytes}/{total_bytes}) {wide_msg}") 6 | .expect("failed to parse progress bar style template") 7 | .progress_chars("##-"); 8 | let pb = ProgressBar::new(0); 9 | pb.set_style(style); 10 | pb 11 | } 12 | 13 | pub fn default_build_progress_bar() -> ProgressBar { 14 | let spinner_style = ProgressStyle::with_template("{spinner} Building image: `{msg}`") 15 | .unwrap() 16 | .tick_chars("⣾⣽⣻⢿⡿⣟⣯⣷"); 17 | let pb = ProgressBar::new(1024); 18 | pb.set_style(spinner_style); 19 | pb 20 | } 21 | -------------------------------------------------------------------------------- /ikki/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ikki" 3 | version = "0.2.1" 4 | edition = "2021" 5 | authors = ["jlkiri / Kirill Vasiltsov"] 6 | description = "Ikki container orchestrator for Docker" 7 | repository = "https://github.com/jlkiri/ikki.git" 8 | license = "MIT" 9 | keywords = ["ikki"] 10 | categories = ["command-line-utilities", "development-tools", "virtualization"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | ikki-config = { path = "../ikki-config", version = "0.2.0" } 16 | miette = { version = "4", features = ["fancy"] } 17 | bollard = "0.13.0" 18 | tokio = { version = "1.19.2", features = ["full"] } 19 | futures-util = "0.3.21" 20 | crossterm = "0.24.0" 21 | structopt = "0.3.26" 22 | clap = { version = "3.2.8", features = ["derive"] } 23 | multimap = "0.8.3" 24 | tar = "0.4.38" 25 | hyper = "0.14.19" 26 | thiserror = "1.0.31" 27 | futures = "0.3.21" 28 | notify = "4.0.17" 29 | tracing = "0.1.35" 30 | tracing-subscriber = { version = "0.3.14", features = ["env-filter"] } 31 | indicatif = { version = "0.17.0", features = ["tokio"] } 32 | -------------------------------------------------------------------------------- /ikki/src/args.rs: -------------------------------------------------------------------------------- 1 | use clap::{Args, Parser, Subcommand}; 2 | use std::path::PathBuf; 3 | 4 | const DEFAULT_CONFIG_FILE: &str = "ikki.kdl"; 5 | 6 | /// Ikki orchestrates Docker image builds and container launches 7 | #[derive(Parser, Debug)] 8 | #[clap(author, version, about, long_about = None)] 9 | #[clap(propagate_version = true)] 10 | pub struct Ikki { 11 | /// Ikki subcommand 12 | #[clap(subcommand)] 13 | pub command: Command, 14 | /// Path to Ikki configuration file 15 | #[clap(long, short, value_parser, default_value = DEFAULT_CONFIG_FILE)] 16 | pub file: PathBuf, 17 | } 18 | 19 | /// Ikki subcommand 20 | #[derive(Debug, Subcommand)] 21 | pub enum Command { 22 | Build(BuildCmdArgs), 23 | /// Build (or pull) all images and start the services 24 | Up(UpOptions), 25 | Explain, 26 | } 27 | 28 | #[derive(Args, Debug)] 29 | pub struct UpOptions { 30 | #[clap(long)] 31 | /// Watch for FS changes and Docker events to trigger necessary rebuilds and restarts 32 | watch: bool, 33 | } 34 | 35 | #[derive(Debug, Args)] 36 | pub struct BuildCmdArgs { 37 | #[clap(value_parser)] 38 | name: Option, 39 | } 40 | -------------------------------------------------------------------------------- /ikki/src/explain.rs: -------------------------------------------------------------------------------- 1 | use crate::docker_config::{BuildOptions, RunOptions}; 2 | 3 | impl BuildOptions { 4 | pub fn explain(&self) -> String { 5 | let mut s = String::new(); 6 | 7 | if self.path.is_none() { 8 | s.push_str("docker pull "); 9 | s.push_str(self.pull.as_ref().unwrap()); 10 | return s; 11 | } 12 | 13 | s.push_str("docker build "); 14 | 15 | // build-args 16 | for (name, value) in &self.build_args { 17 | let arg = format!("--build-arg {}={} ", name, value); 18 | s.push_str(&arg); 19 | } 20 | 21 | // tag 22 | let tag = format!("--tag {} ", &self.tag); 23 | s.push_str(&tag); 24 | 25 | // path 26 | s.push_str(&self.path.as_ref().unwrap().display().to_string()); 27 | 28 | s 29 | } 30 | } 31 | 32 | impl RunOptions { 33 | pub fn explain(&self) -> String { 34 | let mut s = String::new(); 35 | 36 | s.push_str("docker run"); 37 | 38 | // name 39 | let name = format!(" --name {}", self.container_name); 40 | s.push_str(&name); 41 | 42 | // env 43 | for kv in &self.env { 44 | let env = format!(" --env {}", kv); 45 | s.push_str(&env); 46 | } 47 | 48 | // ports 49 | for port in &self.ports { 50 | let publish = format!(" --publish {}", port); 51 | s.push_str(&publish); 52 | } 53 | 54 | s.push(' '); 55 | s.push_str(&self.image_name); 56 | 57 | s 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ikki-config/src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::{deps::parse_deps, parse_image_config, Image, ImageConfig}; 2 | use kdl::{KdlDocument, KdlError}; 3 | use toposort::Toposort; 4 | 5 | use thiserror::Error; 6 | 7 | pub type BuildOrder = Vec>; 8 | 9 | #[derive(Error, Debug)] 10 | pub enum IkkiConfigError { 11 | #[error("Invalid Ikki configuration: {0}")] 12 | InvalidConfiguration(String), 13 | #[error("Configuration deserialization failed: {0}")] 14 | Knuffel(#[from] knuffel::Error), 15 | } 16 | 17 | #[derive(Debug)] 18 | pub struct IkkiConfig { 19 | image_config: ImageConfig, 20 | build_order: Vec>, 21 | } 22 | 23 | impl IkkiConfig { 24 | pub fn images(&self) -> &Vec { 25 | &self.image_config.images.images 26 | } 27 | 28 | pub fn find_image(&self, name: &str) -> Option<&Image> { 29 | self.image_config 30 | .images 31 | .images 32 | .iter() 33 | .find(|img| img.name == name) 34 | } 35 | 36 | pub fn build_order(&self) -> BuildOrder { 37 | self.build_order.clone() 38 | } 39 | } 40 | 41 | pub fn parse(filename: &str, input: &str) -> Result { 42 | let doc: KdlDocument = input 43 | .parse() 44 | .map_err(|e: KdlError| IkkiConfigError::InvalidConfiguration(e.to_string()))?; 45 | 46 | let images = doc 47 | .get("images") 48 | .ok_or(IkkiConfigError::InvalidConfiguration( 49 | "missing `images` configuration".to_string(), 50 | ))?; 51 | 52 | let dependencies = doc.get("dependencies"); 53 | let image_config = parse_image_config(filename, &images.to_string())?; 54 | let build_order = dependencies 55 | .map(|deps| { 56 | let dag = parse_deps(deps); 57 | dag.toposort().unwrap_or_default() 58 | }) 59 | .unwrap_or_else(|| { 60 | let image_names = image_config.image_names(); 61 | vec![image_names] 62 | }); 63 | 64 | Ok(IkkiConfig { 65 | image_config, 66 | build_order, 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /ikki/src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::args::*; 2 | 3 | use clap::Parser; 4 | use docker::DockerError; 5 | use miette::{self, Diagnostic}; 6 | use std::path::Path; 7 | 8 | use ikki_config::*; 9 | use thiserror::Error; 10 | use tokio::fs; 11 | use tracing::{debug, error}; 12 | use tracing_subscriber::EnvFilter; 13 | 14 | mod args; 15 | mod builder; 16 | mod cmd; 17 | mod console; 18 | mod docker; 19 | mod docker_config; 20 | mod explain; 21 | mod listeners; 22 | mod supervisor; 23 | 24 | type Result = miette::Result; 25 | 26 | #[derive(Debug, Error, Diagnostic)] 27 | pub enum IkkiError { 28 | #[error("Image does not exist: {0}")] 29 | NoSuchImage(String), 30 | #[error("FS change watcher failed")] 31 | FileWatcher, 32 | #[error("No Ikki configuration file found at: {0}")] 33 | NoConfig(String), 34 | #[error("Ikki configuration error")] 35 | Config(#[from] ikki_config::IkkiConfigError), 36 | #[error("Docker build failed")] 37 | Build(#[from] DockerError), 38 | #[error("Unexpected error: {0}")] 39 | Other(String), 40 | } 41 | 42 | fn setup() { 43 | let filter_layer = EnvFilter::try_from_default_env() 44 | .or_else(|_| EnvFilter::try_new("info")) 45 | .expect("failed to create EnvFilter"); 46 | 47 | tracing_subscriber::fmt() 48 | .with_env_filter(filter_layer) 49 | .init(); 50 | } 51 | 52 | async fn read_config

(file: P) -> std::result::Result 53 | where 54 | P: AsRef, 55 | { 56 | let path = file.as_ref().to_string_lossy(); 57 | let input = fs::read_to_string(&file) 58 | .await 59 | .or(Err(IkkiError::NoConfig(path.to_string())))?; 60 | let config: IkkiConfig = ikki_config::parse(&path, &input)?; 61 | Ok(config) 62 | } 63 | 64 | #[tokio::main] 65 | async fn main() -> Result<()> { 66 | setup(); 67 | 68 | debug!("initialized tracing_subscriber"); 69 | 70 | let args = Ikki::parse(); 71 | let config = read_config(args.file.clone()).await?; 72 | 73 | debug!("loaded configuration from {}", args.file.display()); 74 | 75 | match args.command { 76 | Command::Up(_opts) => cmd::up(config).await?, 77 | Command::Explain => cmd::explain(config).await?, 78 | _ => unimplemented!(), 79 | } 80 | 81 | Ok(()) 82 | } 83 | -------------------------------------------------------------------------------- /ikki-config/src/deps.rs: -------------------------------------------------------------------------------- 1 | use kdl::{KdlDocument, KdlNode}; 2 | use toposort::Dag; 3 | 4 | fn child_nodes(doc_node: &KdlNode) -> &[KdlNode] { 5 | doc_node 6 | .children() 7 | .map(KdlDocument::nodes) 8 | .unwrap_or_default() 9 | } 10 | 11 | fn traverse(node: &KdlNode, dag: &mut Dag) { 12 | for child in child_nodes(node).iter() { 13 | let name = child.name().to_string(); 14 | dag.before(name, node.name().to_string()); 15 | traverse(child, dag) 16 | } 17 | } 18 | 19 | pub fn parse_deps(dependencies_node: &KdlNode) -> Dag { 20 | let mut dag = Dag::new(); 21 | for child in child_nodes(dependencies_node) { 22 | traverse(child, &mut dag); 23 | } 24 | dag 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use crate::{parse::parse, IkkiConfigError}; 30 | 31 | use super::*; 32 | use toposort::Toposort; 33 | 34 | fn parse_deps_from_string(input: &str) -> Result, IkkiConfigError> { 35 | let doc: KdlDocument = input.parse().expect("failed to parse"); 36 | let dependencies = doc.get("dependencies").expect("no dependencies"); 37 | Ok(parse_deps(dependencies)) 38 | } 39 | 40 | #[test] 41 | fn correct_dependency_order() { 42 | let input = include_str!("../fixtures/dependencies.kdl"); 43 | let order = parse_deps_from_string(input).unwrap(); 44 | let order = order.toposort(); 45 | 46 | assert!(order.is_some()); 47 | 48 | let mut expected = vec![ 49 | vec!["grape", "mango", "melon", "tangerine", "papaiya"], 50 | vec!["watermelon", "peach"], 51 | vec!["apple", "orange"], 52 | vec!["banana"], 53 | ]; 54 | 55 | let mut order = order.unwrap(); 56 | for suborder in order.iter_mut() { 57 | suborder.sort(); 58 | } 59 | for suborder in expected.iter_mut() { 60 | suborder.sort(); 61 | } 62 | 63 | assert_eq!(order, expected); 64 | } 65 | 66 | #[test] 67 | fn no_depedencies() { 68 | let input = include_str!("../fixtures/nodeps.kdl"); 69 | let order = parse("nodeps.kdl", input).unwrap(); 70 | 71 | let expected = vec![vec![ 72 | "frontend".to_string(), 73 | "backend".to_string(), 74 | "cli".to_string(), 75 | ]]; 76 | 77 | assert_eq!(order.build_order(), expected); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /ikki-config/src/deserialize.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::path::PathBuf; 4 | 5 | #[derive(Debug, Clone, knuffel::Decode)] 6 | pub struct KeyValue( 7 | #[knuffel(argument)] pub String, 8 | #[knuffel(argument)] pub String, 9 | ); 10 | 11 | #[derive(Debug, Clone, knuffel::Decode)] 12 | pub struct Secret { 13 | #[knuffel(property)] 14 | pub id: String, 15 | #[knuffel(property)] 16 | pub src: PathBuf, 17 | } 18 | 19 | #[derive(Debug, Clone, knuffel::Decode)] 20 | pub struct Mount { 21 | #[knuffel(property(name = "type"))] 22 | pub mount_type: String, 23 | #[knuffel(property)] 24 | pub src: PathBuf, 25 | #[knuffel(property)] 26 | pub dest: PathBuf, 27 | } 28 | 29 | #[derive(Debug, Clone, knuffel::Decode)] 30 | pub struct Service { 31 | #[knuffel(child, unwrap(arguments))] 32 | pub ports: Option>, 33 | #[knuffel(children(name = "env"))] 34 | pub env: Vec, 35 | #[knuffel(child, unwrap(argument))] 36 | pub user: Option, 37 | #[knuffel(children(name = "mount"))] 38 | pub mounts: Vec, 39 | #[knuffel(child, unwrap(arguments))] 40 | pub networks: Option>, 41 | } 42 | 43 | #[derive(Debug, knuffel::Decode)] 44 | pub struct BuildArg { 45 | #[knuffel(arguments)] 46 | pub values: Vec, 47 | } 48 | 49 | #[derive(Debug, Clone, knuffel::Decode)] 50 | pub struct Image { 51 | #[knuffel(property)] 52 | pub path: Option, 53 | #[knuffel(property)] 54 | pub file: Option, 55 | #[knuffel(property)] 56 | pub output: Option, 57 | #[knuffel(property)] 58 | pub pull: Option, 59 | #[knuffel(children(name = "build-arg"))] 60 | pub build_args: Vec, 61 | #[knuffel(child)] 62 | pub service: Option, 63 | #[knuffel(child)] 64 | pub secret: Option, 65 | #[knuffel(argument)] 66 | pub name: String, 67 | } 68 | 69 | #[derive(Debug, knuffel::Decode)] 70 | pub struct Images { 71 | #[knuffel(children(name = "image"))] 72 | pub images: Vec, 73 | } 74 | 75 | #[derive(Debug, knuffel::Decode)] 76 | pub struct ImageConfig { 77 | #[knuffel(child)] 78 | pub images: Images, 79 | } 80 | 81 | impl ImageConfig { 82 | pub fn image_names(&self) -> Vec { 83 | self.images 84 | .images 85 | .iter() 86 | .map(|img| img.name.clone()) 87 | .collect() 88 | } 89 | } 90 | 91 | pub fn parse_image_config(filename: &str, input: &str) -> Result { 92 | knuffel::parse::(filename, input) 93 | } 94 | -------------------------------------------------------------------------------- /ikki/src/cmd.rs: -------------------------------------------------------------------------------- 1 | use bollard::Docker; 2 | use ikki_config::IkkiConfig; 3 | use miette::IntoDiagnostic; 4 | use tokio::signal; 5 | use tracing::debug; 6 | 7 | use crate::{ 8 | builder::BuilderHandle, 9 | docker::DockerError, 10 | docker_config::*, 11 | supervisor::{ImageSourceLocations, Mode, SupervisorHandle}, 12 | }; 13 | 14 | pub async fn explain(config: IkkiConfig) -> miette::Result<()> { 15 | let build_options = config 16 | .images() 17 | .iter() 18 | .map(build_options) 19 | .collect::, DockerError>>() 20 | .into_diagnostic()?; 21 | 22 | let cmds = build_options.into_iter().map(|opt| opt.explain()); 23 | 24 | for cmd in cmds { 25 | println!("{cmd}"); 26 | } 27 | 28 | let run_options = config 29 | .images() 30 | .iter() 31 | .cloned() 32 | .filter(|img| img.service.is_some()) 33 | .map(|img| { 34 | ( 35 | img.name.clone(), 36 | img.pull.unwrap_or(img.name), 37 | img.service.unwrap(), 38 | ) 39 | }) 40 | .map(create_run_options) 41 | .collect::>(); 42 | 43 | let cmds = run_options.into_iter().map(|opt| opt.explain()); 44 | 45 | for cmd in cmds { 46 | println!("{cmd}"); 47 | } 48 | 49 | Ok(()) 50 | } 51 | 52 | pub async fn up(config: IkkiConfig) -> miette::Result<()> { 53 | let docker = Docker::connect_with_local_defaults().into_diagnostic()?; 54 | 55 | debug!("connected to docker daemon"); 56 | 57 | println!("Calculated image build order:"); 58 | println!(); 59 | 60 | for (i, chunk) in config.build_order().iter().enumerate() { 61 | println!("[{}] {}", i + 1, chunk.join(", ")); 62 | } 63 | println!(); 64 | 65 | let image_source_locations: ImageSourceLocations = config 66 | .images() 67 | .iter() 68 | .filter_map(|img| { 69 | img.path 70 | .clone() 71 | .map(|path| (path.canonicalize().unwrap(), img.name.clone())) 72 | }) 73 | .collect(); 74 | 75 | let mut builder = BuilderHandle::new(docker.clone(), config); 76 | 77 | builder.build_all().await?; 78 | builder.run_all().await?; 79 | 80 | let supervisor = SupervisorHandle::new(image_source_locations, builder, Mode::Run); 81 | 82 | println!("Watching for source changes..."); 83 | 84 | match signal::ctrl_c().await { 85 | Ok(()) => { 86 | debug!("received SIGINT signal, shutting down..."); 87 | 88 | supervisor 89 | .shutdown() 90 | .await 91 | .expect("failed to gracefully shutdown the supervisor") 92 | } 93 | Err(err) => { 94 | eprintln!("unable to listen for shutdown signal: {}", err); 95 | } 96 | } 97 | 98 | debug!("all shutdown"); 99 | Ok(()) 100 | } 101 | -------------------------------------------------------------------------------- /ikki-config/schema.kdl: -------------------------------------------------------------------------------- 1 | document { 2 | info { 3 | title "Ikki configuration schema" lang="en" 4 | author "Kirill Vasiltsov" { 5 | link "https://github.com/jlkiri" 6 | } 7 | } 8 | 9 | node "images" description="Images (and optionally) services to be built" { 10 | max 1 11 | children { 12 | node { 13 | prop "path" { 14 | required false 15 | type "string" 16 | } 17 | 18 | prop "file" { 19 | required false 20 | type "string" 21 | } 22 | 23 | prop "pull" { 24 | required false 25 | type "string" 26 | } 27 | 28 | prop "output" { 29 | required false 30 | type "string" 31 | } 32 | 33 | children { 34 | node "build-args" { 35 | min 0 36 | max 1 37 | children { 38 | node { 39 | value { 40 | max 1 41 | type "string" 42 | } 43 | } 44 | } 45 | } 46 | 47 | node "secret" { 48 | min 0 49 | max 1 50 | prop "id" { 51 | required true 52 | type "string" 53 | } 54 | 55 | prop "src" { 56 | required true 57 | type "string" 58 | } 59 | } 60 | 61 | node "service" { 62 | min 0 63 | max 1 64 | children { 65 | node "ports" { 66 | min 0 67 | max 1 68 | value { 69 | type "string" 70 | } 71 | } 72 | 73 | node "env" { 74 | min 0 75 | max 1 76 | children { 77 | node { 78 | value { 79 | max 1 80 | type "string" 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | other-nodes-allowed true 93 | } 94 | -------------------------------------------------------------------------------- /ikki/src/supervisor.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::PathBuf; 3 | use tokio::sync::mpsc; 4 | use tokio::task; 5 | use tokio::task::JoinHandle; 6 | use tracing::debug; 7 | 8 | use crate::builder::BuilderHandle; 9 | use crate::listeners::FsEventListenerHandle; 10 | use crate::IkkiError; 11 | 12 | type ImageName = String; 13 | pub type ImageSourceLocations = HashMap; 14 | 15 | #[derive(Debug)] 16 | pub enum Event { 17 | SourceChanged(ImageName), 18 | Shutdown, 19 | } 20 | 21 | pub type EventReceiver = mpsc::Receiver; 22 | pub type EventSender = mpsc::Sender; 23 | 24 | pub struct Supervisor { 25 | builder_handle: BuilderHandle, 26 | receiver: EventReceiver, 27 | image_source_locations: ImageSourceLocations, 28 | } 29 | 30 | impl Supervisor { 31 | fn new( 32 | builder: BuilderHandle, 33 | image_source_locations: ImageSourceLocations, 34 | receiver: EventReceiver, 35 | ) -> Self { 36 | Self { 37 | builder_handle: builder, 38 | receiver, 39 | image_source_locations, 40 | } 41 | } 42 | } 43 | 44 | #[derive(Debug)] 45 | pub enum Mode { 46 | BuildOnly, 47 | Run, 48 | } 49 | 50 | pub struct SupervisorHandle { 51 | sender: EventSender, 52 | fs_event_handle: FsEventListenerHandle, 53 | handle: JoinHandle<()>, 54 | } 55 | 56 | impl SupervisorHandle { 57 | pub fn new( 58 | image_source_locations: ImageSourceLocations, 59 | builder: BuilderHandle, 60 | mode: Mode, 61 | ) -> Self { 62 | let (sender, rx) = mpsc::channel::(10); 63 | let supervisor = Supervisor::new(builder, image_source_locations.clone(), rx); 64 | let handle = task::spawn(run_supervisor(supervisor, mode)); 65 | let fs_event_handle = FsEventListenerHandle::new(image_source_locations, sender.clone()); 66 | 67 | Self { 68 | sender, 69 | handle, 70 | fs_event_handle, 71 | } 72 | } 73 | 74 | pub async fn shutdown(self) -> Result<(), IkkiError> { 75 | let _ = self.sender.send(Event::Shutdown).await; 76 | 77 | drop(self.sender); 78 | 79 | debug!("shutting down fs event listener..."); 80 | self.fs_event_handle.shutdown().await; 81 | 82 | debug!("shutting down supervisor loop..."); 83 | self.handle 84 | .await 85 | .map_err(|e| IkkiError::Other(e.to_string()))?; 86 | Ok(()) 87 | } 88 | } 89 | 90 | async fn run_supervisor(mut supervisor: Supervisor, mode: Mode) { 91 | while let Some(msg) = supervisor.receiver.recv().await { 92 | match msg { 93 | Event::Shutdown => { 94 | if let Err(e) = supervisor.builder_handle.stop_all().await { 95 | println!("Ikki error: {}", e) 96 | } 97 | } 98 | Event::SourceChanged(image_name) => { 99 | if let Err(e) = supervisor.builder_handle.build(image_name.clone()).await { 100 | println!("Ikki error: {}", e) 101 | } 102 | 103 | if let Mode::Run = mode { 104 | if let Err(e) = supervisor.builder_handle.run(image_name).await { 105 | println!("Ikki error: {}", e) 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ikki/src/docker_config.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::PathBuf}; 2 | 3 | use bollard::{ 4 | container::Config, 5 | models::{HostConfig, PortBinding}, 6 | }; 7 | use ikki_config::{Image, KeyValue, Service}; 8 | 9 | use crate::docker::DockerError; 10 | 11 | #[derive(Debug)] 12 | pub struct BuildOptions { 13 | pub path: Option, 14 | pub pull: Option, 15 | pub build_args: HashMap, 16 | pub tag: String, 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct RunOptions { 21 | pub container_name: String, 22 | pub image_name: String, 23 | pub env: Vec, 24 | pub ports: Vec, 25 | } 26 | 27 | pub fn build_options(image: &Image) -> Result { 28 | let build_args = image 29 | .build_args 30 | .iter() 31 | .map(|kv| (kv.0.to_string(), kv.1.to_string())) 32 | .collect(); 33 | 34 | if image.pull.is_none() && image.path.is_none() { 35 | return Err(DockerError::Settings( 36 | "missing either `path` or `pull` parameter".into(), 37 | )); 38 | } 39 | 40 | Ok(BuildOptions { 41 | build_args, 42 | pull: image.pull.clone(), 43 | path: image.path.clone(), 44 | tag: image.name.clone(), 45 | }) 46 | } 47 | 48 | type ContainerPortConfig = String; 49 | 50 | fn parse_port_binding(binding: String) -> (ContainerPortConfig, PortBinding) { 51 | let (host_port, container_address) = binding.split_once(':').unwrap_or(("", &binding)); 52 | let (container_port, protocol) = container_address 53 | .split_once('/') 54 | .unwrap_or((container_address, "tcp")); 55 | 56 | let (host_ip, host_port) = if host_port.is_empty() { 57 | (None, None) 58 | } else { 59 | (Some("127.0.0.1".to_string()), Some(host_port.to_string())) 60 | }; 61 | 62 | ( 63 | format!("{}/{}", container_port, protocol), 64 | PortBinding { host_ip, host_port }, 65 | ) 66 | } 67 | 68 | fn create_ports_config(ports: Vec) -> HashMap>> { 69 | let mut port_bindings = HashMap::new(); 70 | for binding in ports { 71 | let (container, host) = parse_port_binding(binding); 72 | port_bindings.insert(container, Some(vec![host])); 73 | } 74 | port_bindings 75 | } 76 | 77 | fn create_env_config(env: Vec) -> Vec { 78 | env.into_iter() 79 | .map(|KeyValue(k, v)| format!("{}={}", k, v)) 80 | .collect() 81 | } 82 | 83 | pub fn create_run_options( 84 | (container_name, image_name, service): (String, String, Service), 85 | ) -> RunOptions { 86 | RunOptions { 87 | container_name: container_name, 88 | env: create_env_config(service.env), 89 | ports: service.ports.unwrap_or_default(), 90 | image_name: image_name, 91 | } 92 | } 93 | 94 | pub fn create_container_config( 95 | container_name: &str, 96 | image_name: &str, 97 | service: Service, 98 | ) -> Config { 99 | let mut config = Config::default(); 100 | 101 | let options = create_run_options((container_name.to_string(), image_name.to_string(), service)); 102 | 103 | config.image = Some(options.image_name); 104 | config.host_config = Some(HostConfig { 105 | port_bindings: Some(create_ports_config(options.ports)), 106 | ..Default::default() 107 | }); 108 | config.env = Some(options.env); 109 | 110 | config 111 | } 112 | -------------------------------------------------------------------------------- /ikki/src/listeners.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use futures::prelude::*; 4 | use notify::DebouncedEvent; 5 | use notify::Watcher; 6 | use notify::{watcher, RecursiveMode}; 7 | 8 | use tokio::sync::mpsc; 9 | use tokio::sync::oneshot; 10 | use tokio::task; 11 | use tokio::task::JoinHandle; 12 | use tracing::debug; 13 | 14 | use crate::supervisor::Event; 15 | use crate::supervisor::EventSender; 16 | 17 | use crate::supervisor::ImageSourceLocations; 18 | use crate::IkkiError; 19 | 20 | struct FsEventListener { 21 | shutdown: oneshot::Receiver<()>, 22 | event_sender: EventSender, 23 | image_source_locations: ImageSourceLocations, 24 | } 25 | 26 | impl FsEventListener { 27 | fn new( 28 | event_sender: EventSender, 29 | shutdown: oneshot::Receiver<()>, 30 | image_source_locations: ImageSourceLocations, 31 | ) -> Self { 32 | Self { 33 | event_sender, 34 | shutdown, 35 | image_source_locations, 36 | } 37 | } 38 | } 39 | 40 | pub struct FsEventListenerHandle { 41 | sender: oneshot::Sender<()>, 42 | handle: JoinHandle>, 43 | } 44 | 45 | impl FsEventListenerHandle { 46 | pub fn new(image_source_locations: ImageSourceLocations, event_sender: EventSender) -> Self { 47 | debug!("setup FS event listener"); 48 | let (sender, rx) = oneshot::channel(); 49 | let listener = FsEventListener::new(event_sender, rx, image_source_locations); 50 | let handle = task::spawn(run_fs_event_listener(listener)); 51 | debug!("FS event listener setup successful"); 52 | Self { sender, handle } 53 | } 54 | 55 | pub async fn shutdown(self) { 56 | let _ = self.sender.send(()); 57 | let _ = self.handle.await; 58 | } 59 | } 60 | 61 | async fn run_fs_event_listener(mut listener: FsEventListener) -> Result<(), IkkiError> { 62 | let (fs_event_sender, mut fs_event_receiver) = mpsc::channel(10); 63 | let (watcher_sender, blocking_fs_receiver) = std::sync::mpsc::channel(); 64 | 65 | debug!("starting FS event watcher"); 66 | 67 | let mut watcher = 68 | watcher(watcher_sender, Duration::from_secs(2)).map_err(|_| IkkiError::FileWatcher)?; 69 | 70 | for path in listener.image_source_locations.keys() { 71 | watcher 72 | .watch(path, RecursiveMode::Recursive) 73 | .map_err(|_| IkkiError::FileWatcher)?; 74 | } 75 | 76 | let fs_watcher = 77 | task::spawn_blocking(move || watch_file_changes(blocking_fs_receiver, fs_event_sender)); 78 | 79 | loop { 80 | tokio::select! { 81 | _ = &mut listener.shutdown => { 82 | drop(watcher); 83 | let _ = fs_watcher.await; 84 | debug!("FS event listener thread successfully shutdown"); 85 | return Ok(()) 86 | }, 87 | Some(event) = fs_event_receiver.recv() => { 88 | // Ignore everything that is not a create/write/remove event 89 | match event { 90 | DebouncedEvent::Create(path) 91 | | DebouncedEvent::Write(path) 92 | | DebouncedEvent::Remove(path) => { 93 | let canonical_path = path.parent().and_then(|p| p.canonicalize().ok()).unwrap(); 94 | if let Some(image_name) = listener.image_source_locations.get(&canonical_path) { 95 | listener.event_sender.send(Event::SourceChanged(image_name.clone())).await 96 | .map_err(|_| IkkiError::FileWatcher)? 97 | } 98 | } 99 | _ => () 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | fn watch_file_changes( 107 | receiver: std::sync::mpsc::Receiver, 108 | sender: mpsc::Sender, 109 | ) -> Result<(), IkkiError> { 110 | while let Ok(event) = receiver.recv() { 111 | debug!(?event, "detected filesystem change"); 112 | sender 113 | .blocking_send(event) 114 | .map_err(|_| IkkiError::FileWatcher)?; 115 | } 116 | 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /toposort/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashSet, VecDeque}; 2 | 3 | use multimap::MultiMap; 4 | 5 | #[derive(Debug)] 6 | pub struct Dag { 7 | precedence: MultiMap, 8 | } 9 | 10 | pub trait Toposort { 11 | fn toposort(&self) -> Option>>; 12 | } 13 | 14 | impl Dag 15 | where 16 | Node: Eq + std::hash::Hash + Clone, 17 | { 18 | pub fn new() -> Self { 19 | Self { 20 | precedence: MultiMap::new(), 21 | } 22 | } 23 | 24 | pub fn before(&mut self, first: Node, second: Node) { 25 | self.precedence.insert(first, second) 26 | } 27 | 28 | fn has_parent(&self, node: &Node) -> bool { 29 | self.precedence 30 | .iter_all() 31 | .any(|(_, children)| children.contains(node)) 32 | } 33 | 34 | fn has_parents_except( 35 | &self, 36 | node: &Node, 37 | except: &Node, 38 | removed_edges: &HashSet<&Node>, 39 | ) -> bool { 40 | self.precedence.iter_all().any(|(parent, children)| { 41 | parent != except && !removed_edges.contains(&parent) && children.contains(node) 42 | }) 43 | } 44 | 45 | fn orphans(&self) -> VecDeque<&Node> { 46 | let mut no_incoming_edge = VecDeque::new(); 47 | for node in self.precedence.keys() { 48 | if !self.has_parent(node) { 49 | no_incoming_edge.push_back(node); 50 | } 51 | } 52 | no_incoming_edge 53 | } 54 | } 55 | 56 | impl Toposort for Dag 57 | where 58 | Node: Eq + std::hash::Hash + Clone, 59 | { 60 | fn toposort(&self) -> Option>> { 61 | let mut order = vec![]; 62 | let mut orphans = self.orphans(); 63 | let mut removed_edges = HashSet::new(); 64 | 65 | while !orphans.is_empty() { 66 | let mut sorted = Vec::with_capacity(orphans.len()); 67 | for _ in 0..orphans.len() { 68 | let node = orphans.pop_front().unwrap(); 69 | if let Some(children) = self.precedence.get_vec(node) { 70 | for child in children { 71 | if !self.has_parents_except(child, node, &removed_edges) { 72 | orphans.push_back(child); 73 | } 74 | } 75 | } 76 | removed_edges.insert(node); 77 | sorted.push(node.clone()); 78 | } 79 | order.push(sorted) 80 | } 81 | 82 | if removed_edges.len() < self.precedence.keys().len() { 83 | return None; 84 | } 85 | 86 | Some(order) 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::{Dag, Toposort}; 93 | 94 | #[test] 95 | fn normal_sort() { 96 | let mut dag = Dag::new(); 97 | dag.before("a", "b"); 98 | dag.before("b", "c"); 99 | let order = dag.toposort(); 100 | let expected = vec![vec!["a"], vec!["b"], vec!["c"]]; 101 | assert_eq!(order, Some(expected)); 102 | } 103 | 104 | #[test] 105 | fn dag_with_cycle() { 106 | let mut dag = Dag::new(); 107 | dag.before("a", "b"); 108 | dag.before("b", "a"); 109 | let order = dag.toposort(); 110 | assert!(order.is_none()); 111 | } 112 | 113 | #[test] 114 | fn parallel_when_ambiguous() { 115 | let mut dag = Dag::new(); 116 | dag.before("a", "b"); 117 | dag.before("c", "b"); 118 | dag.before("d", "b"); 119 | let order = dag.toposort(); 120 | assert!(matches!(order, Some(_))); 121 | let mut order = order.unwrap(); 122 | for suborder in order.iter_mut() { 123 | suborder.sort(); 124 | } 125 | let expected = vec![vec!["a", "c", "d"], vec!["b"]]; 126 | assert_eq!(order, expected); 127 | } 128 | 129 | #[test] 130 | fn parallel_when_ambiguous_2() { 131 | let mut dag = Dag::new(); 132 | dag.before("a", "b"); 133 | dag.before("a", "c"); 134 | let order = dag.toposort(); 135 | assert!(matches!(order, Some(_))); 136 | let mut order = order.unwrap(); 137 | for suborder in order.iter_mut() { 138 | suborder.sort(); 139 | } 140 | let expected = vec![vec!["a"], vec!["b", "c"]]; 141 | assert_eq!(order, expected); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ikki 2 | 3 | ![crates.io](https://img.shields.io/crates/v/ikki.svg) 4 | 5 | Ikki is a tool for defining and running multi-container Docker applications. It is similar to Docker Compose but comes with some differences. 6 | 7 | ikki-demo 8 | 9 | ## Goals 10 | 11 | * Possible to make one image build dependent on another 12 | * Possible to "unmigrate" easily 13 | * Watch filesystem and rebuild what is necessary 14 | 15 | The primary goal of Ikki is to make it possible to specify dependencies between multiple image builds. Consider the following two Dockerfiles: 16 | 17 | ```dockerfile 18 | // Dockerfile.assets 19 | FROM node:latest 20 | 21 | WORKDIR /assets 22 | // output assets to current workdir 23 | ``` 24 | 25 | ```dockerfile 26 | // Dockerfile.main 27 | FROM node:latest 28 | 29 | // Copy assets from previously built image 30 | COPY --from=assets /assets ./assets 31 | // start application 32 | ``` 33 | 34 | When building `Dockerfile.main`, Docker (by specification) will try to find image called `assets` locally. If it does not exist, it will try to pull it from the registry. It will *not* try to build it first, because there is no way to tell it to do so. A common solution is [*multi-stage*](https://docs.docker.com/develop/develop-images/multistage-build/) builds but if more than one `Dockerfile` depends on the same base stage/image then duplication is needed. Docker Compose configuration does not help because it only allows to specify dependencies between running containers and not builds. This means that you have to give up declarative configuration partially to run some image builds in order manually. Ikki aims to preserve Compose-like configuration but also add declarative build dependency configuration in the same file. Ikki uses [KDL](https://kdl.dev/). 35 | 36 | The secondary goal is to help the user avoid "vendor-locking" to Ikki and just migrate back to plain Docker CLI commands. This is done with `explain` command that just reads the Ikki config and translates the declarative configuration to imperative sequence of Docker commands that can be copy-pasted to any `bash` script as-is. 37 | 38 | ## Usage 39 | ``` 40 | Ikki container orchestrator for Docker 41 | 42 | USAGE: 43 | ikki [OPTIONS] 44 | 45 | OPTIONS: 46 | -f, --file Path to Ikki configuration file [default: ikki.kdl] 47 | -h, --help Print help information 48 | -V, --version Print version information 49 | 50 | SUBCOMMANDS: 51 | build 52 | explain 53 | help Print this message or the help of the given subcommand(s) 54 | up Build (or pull) all images and start the services 55 | ``` 56 | 57 | ## Configuration 58 | 59 | Ikki uses [KDL](https://kdl.dev/) for configuration. By default it looks for configuration in `ikki.kdl` file. The (unfinished) schema can be found in `ikki-config\schema.kdl`. Currently the schema is not enforced and `knuffel` library is used instead. 60 | 61 | The `images` node is basically what you would normally find in a Docker Compose file. Only those images that have a `service` configuration are run as containers with the `up` command. 62 | 63 | The `dependencies` node is a DAG that specifies the dependencies. Names should match images names under the `images` configuration. 64 | 65 | ### Example 66 | 67 | ```kdl 68 | images { 69 | image "protobuf" path="./protobuf" output="./output/protobuf" { 70 | build-arg "PROTOBUF_VERSION" "1.28.0" 71 | build-arg "PROTOC_VERSION" "21.4" 72 | } 73 | 74 | image "redis" pull="redis:latest" { 75 | service { 76 | ports "6379:6379" 77 | } 78 | } 79 | 80 | image "db" pull="postgres:latest" { 81 | service { 82 | env "POSTGRES_PASSWORD" "example" 83 | env "POSTGRES_USER" "test" 84 | 85 | ports "5432:5432" 86 | } 87 | } 88 | 89 | image "api" path="./api" { 90 | service { 91 | mount type="volume" src="cache" dest="/cache" 92 | mount type="bind" src="./api/config" dest="/config" 93 | 94 | ports "3000:3000" 95 | } 96 | } 97 | 98 | image "cli-rust" path="./cli" 99 | } 100 | 101 | dependencies { 102 | api { 103 | protobuf 104 | redis 105 | db 106 | } 107 | cli-rust { 108 | protobuf 109 | } 110 | } 111 | ``` 112 | 113 | ### Explain 114 | 115 | By using the `explain` command it is possible to turn the above config into a sequence of Docker commands: 116 | 117 | ``` 118 | ❯ ikki explain 119 | docker build --build-arg PROTOBUF_VERSION=1.28.0 --build-arg PROTOC_VERSION=21.4 --tag protobuf ./protobuf 120 | docker pull redis:latest 121 | docker pull postgres:latest 122 | docker build --tag api ./api 123 | docker build --tag cli-rust ./cli 124 | docker run --name redis --publish 6379:6379 redis:latest 125 | docker run --name db --env POSTGRES_PASSWORD=example --env POSTGRES_USER=test --publish 5432:5432 postgres:latest 126 | docker run --name api --publish 3000:3000 api 127 | ``` 128 | 129 | ## Status 130 | 131 | **Experimental** 132 | 133 | Use at your own risk. The following is the rough TODO list: 134 | 135 | - [ ] Reach parity with Docker Compose by recognizing more options and passing them to the Docker daemon 136 | - [ ] Distinguish build and run dependencies 137 | 138 | ## Install 139 | 140 | ### From source 141 | ``` 142 | cargo install ikki 143 | ``` 144 | -------------------------------------------------------------------------------- /ikki/src/docker.rs: -------------------------------------------------------------------------------- 1 | use crate::{console, docker_config::*}; 2 | use bollard::{ 3 | container::CreateContainerOptions, 4 | image::{BuildImageOptions, CreateImageOptions}, 5 | Docker, 6 | }; 7 | use futures::StreamExt; 8 | use futures_util::TryStreamExt; 9 | use ikki_config::*; 10 | use indicatif::{MultiProgress, ProgressBar}; 11 | use std::{ 12 | collections::{HashMap, HashSet}, 13 | io::{self, Write}, 14 | }; 15 | use tar::Builder; 16 | use thiserror::Error; 17 | use tokio::task; 18 | use tracing::debug; 19 | 20 | static STATUS_DOWNLOADING: &str = "Downloading"; 21 | 22 | #[derive(Error, Debug)] 23 | pub enum DockerError { 24 | #[error("Invalid settings: {0}")] 25 | Settings(String), 26 | #[error("Failed to archive a directory")] 27 | Archive(String), 28 | #[error("Docker daemon error: {0}")] 29 | DockerDaemonError(#[from] bollard::errors::Error), 30 | } 31 | 32 | pub async fn build_image( 33 | docker: Docker, 34 | image: Image, 35 | mp: MultiProgress, 36 | ) -> Result<(), DockerError> { 37 | debug!("building {}...", image.name); 38 | 39 | let pb = mp.add(console::default_build_progress_bar()); 40 | pb.set_message(image.name.clone()); 41 | 42 | let build_opts = build_options(&image)?; 43 | 44 | let build_path = build_opts.path.ok_or(DockerError::Settings(format!( 45 | "missing image build path for image `{}`", 46 | image.name 47 | )))?; 48 | 49 | let archive_task = task::spawn_blocking(|| { 50 | let mut buf = vec![]; 51 | let mut tar = Builder::new(&mut buf); 52 | tar.append_dir_all("", build_path)?; 53 | tar.into_inner().cloned() 54 | }); 55 | 56 | let tar = archive_task 57 | .await 58 | .map(|res| res.map_err(|e| DockerError::Archive(e.to_string()))) 59 | .map_err(|e| DockerError::Archive(e.to_string()))??; 60 | 61 | let build_options = BuildImageOptions { 62 | dockerfile: "Dockerfile".to_string(), 63 | t: image.name.clone(), 64 | buildargs: build_opts.build_args, 65 | rm: true, 66 | ..Default::default() 67 | }; 68 | 69 | let mut build_stream = docker.build_image(build_options, None, Some(tar.into())); 70 | 71 | let mut progresses = HashMap::new(); 72 | 73 | let mut dl_pb: Option = None; 74 | 75 | while let Some(info) = build_stream.next().await { 76 | let info = info?; 77 | 78 | if let Some(status) = info.status { 79 | if status == STATUS_DOWNLOADING { 80 | if dl_pb.is_none() { 81 | dl_pb = Some(mp.add(console::default_pull_progress_bar())); 82 | } 83 | 84 | let detail = info 85 | .progress_detail 86 | .and_then(|det| match (det.total, det.current) { 87 | (Some(total), Some(current)) => Some((total, current)), 88 | _ => None, 89 | }); 90 | 91 | let id = info.id.unwrap_or_default(); 92 | 93 | dl_pb 94 | .as_ref() 95 | .unwrap() 96 | .set_message(format!("Pulling missing layers for {}", image.name)); 97 | 98 | if let Some((total, current)) = detail { 99 | let e = progresses.entry(id).or_insert((total, current)); 100 | *e = (e.0, current); 101 | 102 | let all_total: i64 = progresses.values().map(|(t, _)| t).sum(); 103 | let all_current: i64 = progresses.values().map(|(_, c)| c).sum(); 104 | dl_pb.as_ref().unwrap().set_length(all_total as u64); 105 | dl_pb.as_ref().unwrap().set_position(all_current as u64); 106 | } 107 | } 108 | } 109 | 110 | pb.tick(); 111 | } 112 | 113 | if let Some(dl_pb) = dl_pb { 114 | dl_pb.finish_and_clear(); 115 | } 116 | pb.finish_and_clear(); 117 | 118 | Ok(()) 119 | } 120 | 121 | pub async fn pull_image( 122 | docker: Docker, 123 | image: Image, 124 | mp: MultiProgress, 125 | ) -> Result<(), DockerError> { 126 | let name = image.pull.unwrap_or("".into()); 127 | debug!("pulling {}...", name); 128 | 129 | let image_list = docker.list_images::(None).await?; 130 | if image_list 131 | .iter() 132 | .any(|img| img.repo_tags.iter().any(|tag| tag.contains(&name))) 133 | { 134 | debug!("image `{}` already exists, skipping", name); 135 | println!("Image `{}` already exists and/or is up-to-date", name); 136 | return Ok(()); 137 | } 138 | 139 | let pb = mp.add(console::default_pull_progress_bar()); 140 | 141 | let mut pull_stream = docker.create_image( 142 | Some(CreateImageOptions { 143 | from_image: name.clone(), 144 | ..Default::default() 145 | }), 146 | None, 147 | None, 148 | ); 149 | 150 | let mut progresses = HashMap::new(); 151 | 152 | while let Some(info) = pull_stream.next().await { 153 | let info = info?; 154 | 155 | if let Some(status) = info.status { 156 | if status == STATUS_DOWNLOADING { 157 | let detail = info 158 | .progress_detail 159 | .and_then(|det| match (det.total, det.current) { 160 | (Some(total), Some(current)) => Some((total, current)), 161 | _ => None, 162 | }); 163 | 164 | let id = info.id.unwrap_or_default(); 165 | 166 | pb.set_message(format!("Pulling {}", name)); 167 | 168 | if let Some((total, current)) = detail { 169 | let e = progresses.entry(id).or_insert((total, current)); 170 | *e = (e.0, current); 171 | 172 | let all_total: i64 = progresses.values().map(|(t, _)| t).sum(); 173 | let all_current: i64 = progresses.values().map(|(_, c)| c).sum(); 174 | pb.set_length(all_total as u64); 175 | pb.set_position(all_current as u64); 176 | } 177 | } 178 | } 179 | } 180 | 181 | pb.finish_and_clear(); 182 | 183 | Ok(()) 184 | } 185 | 186 | pub async fn run( 187 | docker: Docker, 188 | container_name: String, 189 | image_name: String, 190 | service: Service, 191 | ) -> Result { 192 | let options = CreateContainerOptions { 193 | name: container_name.to_string(), 194 | }; 195 | 196 | let config = create_container_config(&container_name, &image_name, service); 197 | 198 | let id = docker.create_container(Some(options), config).await?.id; 199 | docker.start_container::(&id, None).await?; 200 | 201 | println!("Started container {} ({})", container_name, id); 202 | debug!("started container {} ({})", container_name, id); 203 | 204 | Ok(id) 205 | } 206 | 207 | pub async fn remove_container(docker: Docker, id: &str) -> Result<(), DockerError> { 208 | docker.stop_container(id, None).await?; 209 | docker.remove_container(id, None).await?; 210 | 211 | Ok(()) 212 | } 213 | -------------------------------------------------------------------------------- /ikki/src/builder.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Mul; 2 | 3 | use bollard::Docker; 4 | use futures::prelude::*; 5 | use futures::stream::FuturesUnordered; 6 | use ikki_config::{BuildOrder, IkkiConfig, Image}; 7 | use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; 8 | use tokio::sync::oneshot::Sender; 9 | use tokio::sync::{mpsc, oneshot}; 10 | use tokio::task; 11 | use tracing::debug; 12 | 13 | use crate::docker::DockerError; 14 | use crate::{docker, IkkiError}; 15 | 16 | type ImageName = String; 17 | 18 | #[derive(Debug)] 19 | pub enum BuildResult { 20 | Success, 21 | Error(IkkiError), 22 | } 23 | 24 | #[derive(Debug)] 25 | pub enum RunResult { 26 | Success(ContainerIds), 27 | Error(IkkiError), 28 | } 29 | 30 | #[derive(Debug)] 31 | pub enum StopResult { 32 | Success, 33 | Error(IkkiError), 34 | } 35 | 36 | type BuildResultSender = oneshot::Sender; 37 | type RunResultSender = oneshot::Sender; 38 | type StopResultSender = oneshot::Sender; 39 | 40 | type ContainerIds = Vec; 41 | 42 | #[derive(Debug)] 43 | pub enum Command { 44 | Build((ImageName, BuildResultSender)), 45 | Run((ImageName, RunResultSender)), 46 | BuildAll(BuildResultSender), 47 | RunAll(RunResultSender), 48 | StopAll((ContainerIds, StopResultSender)), 49 | } 50 | 51 | pub type CommandReceiver = mpsc::Receiver; 52 | pub type CommandSender = mpsc::Sender; 53 | 54 | struct Builder { 55 | receiver: CommandReceiver, 56 | client: Docker, 57 | config: IkkiConfig, 58 | } 59 | 60 | async fn create_docker_job( 61 | docker: Docker, 62 | image: Image, 63 | mp: MultiProgress, 64 | ) -> Result<(), DockerError> { 65 | if let Some(_pull) = &image.pull { 66 | docker::pull_image(docker, image, mp).await?; 67 | } else if let Some(_path) = &image.path { 68 | docker::build_image(docker, image, mp).await?; 69 | } 70 | Ok(()) 71 | } 72 | 73 | impl Builder { 74 | fn new(receiver: CommandReceiver, client: Docker, config: IkkiConfig) -> Self { 75 | Self { 76 | receiver, 77 | client, 78 | config, 79 | } 80 | } 81 | 82 | fn report_build_result(&self, sender: Sender, result: Result<(), IkkiError>) { 83 | match result { 84 | Ok(()) => { 85 | let _ = sender.send(BuildResult::Success); 86 | } 87 | Err(e) => { 88 | let _ = sender.send(BuildResult::Error(e)); 89 | } 90 | } 91 | } 92 | 93 | fn report_run_result( 94 | &self, 95 | sender: Sender, 96 | result: Result, 97 | ) { 98 | match result { 99 | Ok(ids) => { 100 | let _ = sender.send(RunResult::Success(ids)); 101 | } 102 | Err(e) => { 103 | let _ = sender.send(RunResult::Error(e)); 104 | } 105 | } 106 | } 107 | 108 | fn report_stop_result(&self, sender: Sender, result: Result<(), IkkiError>) { 109 | match result { 110 | Ok(()) => { 111 | let _ = sender.send(StopResult::Success); 112 | } 113 | Err(e) => { 114 | let _ = sender.send(StopResult::Error(e)); 115 | } 116 | } 117 | } 118 | 119 | async fn handle_command(&self, cmd: Command) { 120 | match cmd { 121 | Command::BuildAll(sender) => { 122 | let result = self.full_build().await; 123 | self.report_build_result(sender, result) 124 | } 125 | Command::RunAll(sender) => { 126 | let result = self.full_run().await; 127 | self.report_run_result(sender, result) 128 | } 129 | Command::Build((image_name, sender)) => { 130 | let result = self.build_dependers(&image_name).await; 131 | self.report_build_result(sender, result) 132 | } 133 | Command::Run((image_name, sender)) => { 134 | let result = self.run_dependers(&image_name).await; 135 | self.report_run_result(sender, result) 136 | } 137 | Command::StopAll((ids, sender)) => { 138 | let result = self.stop_all(ids).await; 139 | self.report_stop_result(sender, result) 140 | } 141 | } 142 | } 143 | 144 | async fn ordered_build(&self, order: BuildOrder) -> Result<(), IkkiError> { 145 | debug!("executing build jobs in configured order"); 146 | let mp = MultiProgress::new(); 147 | 148 | for chunk in order { 149 | // Concurrently run builds/pulls in a single chunk because they do not depend on each other. 150 | let queue = FuturesUnordered::new(); 151 | 152 | for image_name in chunk { 153 | let image = self 154 | .config 155 | .find_image(&image_name) 156 | .cloned() 157 | .ok_or(IkkiError::NoSuchImage(image_name))?; 158 | let job = create_docker_job(self.client.clone(), image, mp.clone()); 159 | queue.push(job); 160 | } 161 | 162 | queue 163 | .collect::>() 164 | .await 165 | .into_iter() 166 | .collect::, DockerError>>()?; 167 | } 168 | 169 | mp.clear().expect("failed to clear multiple progress bars"); 170 | 171 | debug!("all build jobs finished successfully"); 172 | Ok(()) 173 | } 174 | 175 | async fn ordered_run(&self, order: BuildOrder) -> Result { 176 | debug!("executing run jobs in configured order"); 177 | let mut container_ids = vec![]; 178 | 179 | for chunk in order { 180 | // Concurrently run builds/pulls in a single chunk because they do not depend on each other. 181 | let queue = FuturesUnordered::new(); 182 | 183 | for image_name in chunk { 184 | let image = self 185 | .config 186 | .find_image(&image_name) 187 | .cloned() 188 | .ok_or(IkkiError::NoSuchImage(image_name))?; 189 | if let Some(service) = image.service { 190 | let image_name = if let Some(name) = image.pull { 191 | name 192 | } else { 193 | image.name.clone() 194 | }; 195 | let container_name = image.name; 196 | let job = docker::run(self.client.clone(), container_name, image_name, service); 197 | queue.push(job); 198 | } 199 | } 200 | 201 | let ids = queue 202 | .collect::>() 203 | .await 204 | .into_iter() 205 | .collect::, DockerError>>()?; 206 | 207 | container_ids.extend_from_slice(&ids); 208 | } 209 | debug!("all run jobs finished successfully"); 210 | Ok(container_ids) 211 | } 212 | 213 | fn dependent_images_of(&self, name: &str) -> Vec> { 214 | let mut dependent_images: Vec> = self 215 | .config 216 | .build_order() 217 | .into_iter() 218 | .skip_while(|chunk| !chunk.contains(&name.to_string())) 219 | .collect(); 220 | 221 | // We want to avoid building anything in the same chunk as the target image 222 | // but still need to build it 223 | let chunk = &mut dependent_images[0]; 224 | *chunk = vec![name.to_string()]; 225 | 226 | dependent_images 227 | } 228 | 229 | async fn build_dependers(&self, name: &str) -> Result<(), IkkiError> { 230 | let dependers = self.dependent_images_of(name); 231 | self.ordered_build(dependers).await 232 | } 233 | 234 | async fn full_build(&self) -> Result<(), IkkiError> { 235 | self.ordered_build(self.config.build_order()).await 236 | } 237 | 238 | async fn run_dependers(&self, name: &str) -> Result { 239 | let dependers = self.dependent_images_of(name); 240 | self.ordered_run(dependers).await 241 | } 242 | 243 | async fn full_run(&self) -> Result { 244 | self.ordered_run(self.config.build_order()).await 245 | } 246 | 247 | async fn stop_all(&self, ids: ContainerIds) -> Result<(), IkkiError> { 248 | for id in ids { 249 | docker::remove_container(self.client.clone(), &id).await?; 250 | } 251 | Ok(()) 252 | } 253 | } 254 | 255 | #[derive(Debug, Clone)] 256 | pub struct BuilderHandle { 257 | sender: CommandSender, 258 | ids: ContainerIds, 259 | } 260 | 261 | impl BuilderHandle { 262 | pub fn new(client: Docker, config: IkkiConfig) -> Self { 263 | debug!("setup builder actor"); 264 | let (sender, rx) = mpsc::channel::(50); 265 | let builder = Builder::new(rx, client, config); 266 | task::spawn(run_builder(builder)); 267 | debug!("builder actor setup successful"); 268 | BuilderHandle { 269 | sender, 270 | ids: vec![], 271 | } 272 | } 273 | 274 | pub async fn run(&self, name: String) -> Result<(), IkkiError> { 275 | debug!("builder received run request"); 276 | let (response_tx, response_rx) = oneshot::channel(); 277 | let _ = self.sender.send(Command::Run((name, response_tx))).await; 278 | let run_result = response_rx.await; 279 | debug!(?run_result, "run result"); 280 | match run_result { 281 | Err(e) => Err(IkkiError::Other(e.to_string())), 282 | Ok(RunResult::Error(e)) => Err(e), 283 | _ => Ok(()), 284 | } 285 | } 286 | 287 | pub async fn run_all(&mut self) -> Result<(), IkkiError> { 288 | debug!("builder received full run request"); 289 | let (response_tx, response_rx) = oneshot::channel(); 290 | let _ = self.sender.send(Command::RunAll(response_tx)).await; 291 | let run_result = response_rx.await; 292 | debug!(?run_result, "run all result"); 293 | match run_result { 294 | Err(e) => Err(IkkiError::Other(e.to_string())), 295 | Ok(RunResult::Error(e)) => Err(e), 296 | Ok(RunResult::Success(ids)) => { 297 | self.ids = ids; 298 | Ok(()) 299 | } 300 | } 301 | } 302 | 303 | pub async fn build(&self, name: String) -> Result<(), IkkiError> { 304 | debug!("builder received build request"); 305 | let (response_tx, response_rx) = oneshot::channel(); 306 | let _ = self.sender.send(Command::Build((name, response_tx))).await; 307 | let build_result = response_rx.await; 308 | debug!(?build_result, "build result"); 309 | match build_result { 310 | Err(e) => Err(IkkiError::Other(e.to_string())), 311 | Ok(BuildResult::Error(e)) => Err(e), 312 | _ => Ok(()), 313 | } 314 | } 315 | 316 | pub async fn build_all(&self) -> Result<(), IkkiError> { 317 | debug!("builder received full build request"); 318 | let (response_tx, response_rx) = oneshot::channel(); 319 | let _ = self.sender.send(Command::BuildAll(response_tx)).await; 320 | let result = response_rx.await; 321 | debug!(?result, "build all result"); 322 | match result { 323 | Err(e) => Err(IkkiError::Other(e.to_string())), 324 | Ok(BuildResult::Error(e)) => Err(e), 325 | _ => Ok(()), 326 | } 327 | } 328 | 329 | pub async fn stop_all(&self) -> Result<(), IkkiError> { 330 | debug!("builder received full stop request"); 331 | println!("Stopping and removing all running containers..."); 332 | let (response_tx, response_rx) = oneshot::channel(); 333 | let _ = self 334 | .sender 335 | .send(Command::StopAll((self.ids.clone(), response_tx))) 336 | .await; 337 | let result = response_rx.await; 338 | debug!(?result, "stop all result"); 339 | match result { 340 | Err(e) => Err(IkkiError::Other(e.to_string())), 341 | Ok(StopResult::Error(e)) => Err(e), 342 | _ => { 343 | println!("Successfully stopped and removed all running containers"); 344 | Ok(()) 345 | } 346 | } 347 | } 348 | } 349 | 350 | async fn run_builder(mut builder: Builder) { 351 | while let Some(msg) = builder.receiver.recv().await { 352 | builder.handle_command(msg).await; 353 | } 354 | debug!("shutting down builder loop") 355 | } 356 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.17.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "0.7.18" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "ansi_term" 31 | version = "0.12.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 34 | dependencies = [ 35 | "winapi 0.3.9", 36 | ] 37 | 38 | [[package]] 39 | name = "atty" 40 | version = "0.2.14" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 43 | dependencies = [ 44 | "hermit-abi", 45 | "libc", 46 | "winapi 0.3.9", 47 | ] 48 | 49 | [[package]] 50 | name = "autocfg" 51 | version = "1.1.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 54 | 55 | [[package]] 56 | name = "backtrace" 57 | version = "0.3.65" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" 60 | dependencies = [ 61 | "addr2line", 62 | "cc", 63 | "cfg-if 1.0.0", 64 | "libc", 65 | "miniz_oxide", 66 | "object", 67 | "rustc-demangle", 68 | ] 69 | 70 | [[package]] 71 | name = "base64" 72 | version = "0.13.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 75 | 76 | [[package]] 77 | name = "bitflags" 78 | version = "1.3.2" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 81 | 82 | [[package]] 83 | name = "bollard" 84 | version = "0.13.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "d82e7850583ead5f8bbef247e2a3c37a19bd576e8420cd262a6711921827e1e5" 87 | dependencies = [ 88 | "base64", 89 | "bollard-stubs", 90 | "bytes", 91 | "futures-core", 92 | "futures-util", 93 | "hex", 94 | "http", 95 | "hyper", 96 | "hyperlocal", 97 | "log", 98 | "pin-project-lite", 99 | "serde", 100 | "serde_derive", 101 | "serde_json", 102 | "serde_urlencoded", 103 | "thiserror", 104 | "tokio", 105 | "tokio-util", 106 | "url", 107 | "winapi 0.3.9", 108 | ] 109 | 110 | [[package]] 111 | name = "bollard-stubs" 112 | version = "1.42.0-rc.3" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" 115 | dependencies = [ 116 | "serde", 117 | "serde_with", 118 | ] 119 | 120 | [[package]] 121 | name = "bytes" 122 | version = "1.1.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 125 | 126 | [[package]] 127 | name = "camino" 128 | version = "1.0.9" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" 131 | 132 | [[package]] 133 | name = "cc" 134 | version = "1.0.73" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 137 | 138 | [[package]] 139 | name = "cfg-if" 140 | version = "0.1.10" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 143 | 144 | [[package]] 145 | name = "cfg-if" 146 | version = "1.0.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 149 | 150 | [[package]] 151 | name = "chumsky" 152 | version = "0.8.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4" 155 | 156 | [[package]] 157 | name = "clap" 158 | version = "2.34.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 161 | dependencies = [ 162 | "ansi_term", 163 | "atty", 164 | "bitflags", 165 | "strsim 0.8.0", 166 | "textwrap 0.11.0", 167 | "unicode-width", 168 | "vec_map", 169 | ] 170 | 171 | [[package]] 172 | name = "clap" 173 | version = "3.2.8" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" 176 | dependencies = [ 177 | "atty", 178 | "bitflags", 179 | "clap_derive", 180 | "clap_lex", 181 | "indexmap", 182 | "once_cell", 183 | "strsim 0.10.0", 184 | "termcolor", 185 | "textwrap 0.15.0", 186 | ] 187 | 188 | [[package]] 189 | name = "clap_derive" 190 | version = "3.2.7" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" 193 | dependencies = [ 194 | "heck 0.4.0", 195 | "proc-macro-error", 196 | "proc-macro2", 197 | "quote", 198 | "syn", 199 | ] 200 | 201 | [[package]] 202 | name = "clap_lex" 203 | version = "0.2.4" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 206 | dependencies = [ 207 | "os_str_bytes", 208 | ] 209 | 210 | [[package]] 211 | name = "console" 212 | version = "0.15.1" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" 215 | dependencies = [ 216 | "encode_unicode", 217 | "libc", 218 | "once_cell", 219 | "terminal_size", 220 | "unicode-width", 221 | "winapi 0.3.9", 222 | ] 223 | 224 | [[package]] 225 | name = "crossterm" 226 | version = "0.24.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "ab9f7409c70a38a56216480fba371ee460207dd8926ccf5b4160591759559170" 229 | dependencies = [ 230 | "bitflags", 231 | "crossterm_winapi", 232 | "libc", 233 | "mio 0.8.4", 234 | "parking_lot", 235 | "signal-hook", 236 | "signal-hook-mio", 237 | "winapi 0.3.9", 238 | ] 239 | 240 | [[package]] 241 | name = "crossterm_winapi" 242 | version = "0.9.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" 245 | dependencies = [ 246 | "winapi 0.3.9", 247 | ] 248 | 249 | [[package]] 250 | name = "darling" 251 | version = "0.13.4" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" 254 | dependencies = [ 255 | "darling_core", 256 | "darling_macro", 257 | ] 258 | 259 | [[package]] 260 | name = "darling_core" 261 | version = "0.13.4" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" 264 | dependencies = [ 265 | "fnv", 266 | "ident_case", 267 | "proc-macro2", 268 | "quote", 269 | "strsim 0.10.0", 270 | "syn", 271 | ] 272 | 273 | [[package]] 274 | name = "darling_macro" 275 | version = "0.13.4" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" 278 | dependencies = [ 279 | "darling_core", 280 | "quote", 281 | "syn", 282 | ] 283 | 284 | [[package]] 285 | name = "encode_unicode" 286 | version = "0.3.6" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 289 | 290 | [[package]] 291 | name = "filetime" 292 | version = "0.2.17" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" 295 | dependencies = [ 296 | "cfg-if 1.0.0", 297 | "libc", 298 | "redox_syscall", 299 | "windows-sys", 300 | ] 301 | 302 | [[package]] 303 | name = "fnv" 304 | version = "1.0.7" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 307 | 308 | [[package]] 309 | name = "form_urlencoded" 310 | version = "1.0.1" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 313 | dependencies = [ 314 | "matches", 315 | "percent-encoding", 316 | ] 317 | 318 | [[package]] 319 | name = "fsevent" 320 | version = "0.4.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" 323 | dependencies = [ 324 | "bitflags", 325 | "fsevent-sys", 326 | ] 327 | 328 | [[package]] 329 | name = "fsevent-sys" 330 | version = "2.0.1" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" 333 | dependencies = [ 334 | "libc", 335 | ] 336 | 337 | [[package]] 338 | name = "fuchsia-zircon" 339 | version = "0.3.3" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 342 | dependencies = [ 343 | "bitflags", 344 | "fuchsia-zircon-sys", 345 | ] 346 | 347 | [[package]] 348 | name = "fuchsia-zircon-sys" 349 | version = "0.3.3" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 352 | 353 | [[package]] 354 | name = "futures" 355 | version = "0.3.21" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" 358 | dependencies = [ 359 | "futures-channel", 360 | "futures-core", 361 | "futures-executor", 362 | "futures-io", 363 | "futures-sink", 364 | "futures-task", 365 | "futures-util", 366 | ] 367 | 368 | [[package]] 369 | name = "futures-channel" 370 | version = "0.3.21" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 373 | dependencies = [ 374 | "futures-core", 375 | "futures-sink", 376 | ] 377 | 378 | [[package]] 379 | name = "futures-core" 380 | version = "0.3.21" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 383 | 384 | [[package]] 385 | name = "futures-executor" 386 | version = "0.3.21" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" 389 | dependencies = [ 390 | "futures-core", 391 | "futures-task", 392 | "futures-util", 393 | ] 394 | 395 | [[package]] 396 | name = "futures-io" 397 | version = "0.3.21" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 400 | 401 | [[package]] 402 | name = "futures-macro" 403 | version = "0.3.21" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 406 | dependencies = [ 407 | "proc-macro2", 408 | "quote", 409 | "syn", 410 | ] 411 | 412 | [[package]] 413 | name = "futures-sink" 414 | version = "0.3.21" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 417 | 418 | [[package]] 419 | name = "futures-task" 420 | version = "0.3.21" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 423 | 424 | [[package]] 425 | name = "futures-util" 426 | version = "0.3.21" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 429 | dependencies = [ 430 | "futures-channel", 431 | "futures-core", 432 | "futures-io", 433 | "futures-macro", 434 | "futures-sink", 435 | "futures-task", 436 | "memchr", 437 | "pin-project-lite", 438 | "pin-utils", 439 | "slab", 440 | ] 441 | 442 | [[package]] 443 | name = "gimli" 444 | version = "0.26.1" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" 447 | 448 | [[package]] 449 | name = "h2" 450 | version = "0.3.13" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" 453 | dependencies = [ 454 | "bytes", 455 | "fnv", 456 | "futures-core", 457 | "futures-sink", 458 | "futures-util", 459 | "http", 460 | "indexmap", 461 | "slab", 462 | "tokio", 463 | "tokio-util", 464 | "tracing", 465 | ] 466 | 467 | [[package]] 468 | name = "hashbrown" 469 | version = "0.12.1" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" 472 | 473 | [[package]] 474 | name = "heck" 475 | version = "0.3.3" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 478 | dependencies = [ 479 | "unicode-segmentation", 480 | ] 481 | 482 | [[package]] 483 | name = "heck" 484 | version = "0.4.0" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 487 | 488 | [[package]] 489 | name = "hermit-abi" 490 | version = "0.1.19" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 493 | dependencies = [ 494 | "libc", 495 | ] 496 | 497 | [[package]] 498 | name = "hex" 499 | version = "0.4.3" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 502 | 503 | [[package]] 504 | name = "http" 505 | version = "0.2.8" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 508 | dependencies = [ 509 | "bytes", 510 | "fnv", 511 | "itoa", 512 | ] 513 | 514 | [[package]] 515 | name = "http-body" 516 | version = "0.4.5" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 519 | dependencies = [ 520 | "bytes", 521 | "http", 522 | "pin-project-lite", 523 | ] 524 | 525 | [[package]] 526 | name = "httparse" 527 | version = "1.7.1" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" 530 | 531 | [[package]] 532 | name = "httpdate" 533 | version = "1.0.2" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 536 | 537 | [[package]] 538 | name = "hyper" 539 | version = "0.14.19" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" 542 | dependencies = [ 543 | "bytes", 544 | "futures-channel", 545 | "futures-core", 546 | "futures-util", 547 | "h2", 548 | "http", 549 | "http-body", 550 | "httparse", 551 | "httpdate", 552 | "itoa", 553 | "pin-project-lite", 554 | "socket2", 555 | "tokio", 556 | "tower-service", 557 | "tracing", 558 | "want", 559 | ] 560 | 561 | [[package]] 562 | name = "hyperlocal" 563 | version = "0.8.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" 566 | dependencies = [ 567 | "futures-util", 568 | "hex", 569 | "hyper", 570 | "pin-project", 571 | "tokio", 572 | ] 573 | 574 | [[package]] 575 | name = "ident_case" 576 | version = "1.0.1" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 579 | 580 | [[package]] 581 | name = "idna" 582 | version = "0.2.3" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 585 | dependencies = [ 586 | "matches", 587 | "unicode-bidi", 588 | "unicode-normalization", 589 | ] 590 | 591 | [[package]] 592 | name = "ikki" 593 | version = "0.2.1" 594 | dependencies = [ 595 | "bollard", 596 | "clap 3.2.8", 597 | "crossterm", 598 | "futures", 599 | "futures-util", 600 | "hyper", 601 | "ikki-config", 602 | "indicatif", 603 | "miette 4.7.1", 604 | "multimap", 605 | "notify", 606 | "structopt", 607 | "tar", 608 | "thiserror", 609 | "tokio", 610 | "tracing", 611 | "tracing-subscriber", 612 | ] 613 | 614 | [[package]] 615 | name = "ikki-config" 616 | version = "0.2.0" 617 | dependencies = [ 618 | "camino", 619 | "kdl", 620 | "knuffel", 621 | "miette 5.1.0", 622 | "thiserror", 623 | "toposort", 624 | ] 625 | 626 | [[package]] 627 | name = "indexmap" 628 | version = "1.9.1" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 631 | dependencies = [ 632 | "autocfg", 633 | "hashbrown", 634 | ] 635 | 636 | [[package]] 637 | name = "indicatif" 638 | version = "0.17.0" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "fcc42b206e70d86ec03285b123e65a5458c92027d1fb2ae3555878b8113b3ddf" 641 | dependencies = [ 642 | "console", 643 | "number_prefix", 644 | "tokio", 645 | "unicode-width", 646 | ] 647 | 648 | [[package]] 649 | name = "inotify" 650 | version = "0.7.1" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" 653 | dependencies = [ 654 | "bitflags", 655 | "inotify-sys", 656 | "libc", 657 | ] 658 | 659 | [[package]] 660 | name = "inotify-sys" 661 | version = "0.1.5" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 664 | dependencies = [ 665 | "libc", 666 | ] 667 | 668 | [[package]] 669 | name = "iovec" 670 | version = "0.1.4" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 673 | dependencies = [ 674 | "libc", 675 | ] 676 | 677 | [[package]] 678 | name = "is_ci" 679 | version = "1.1.1" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" 682 | 683 | [[package]] 684 | name = "itoa" 685 | version = "1.0.2" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 688 | 689 | [[package]] 690 | name = "kdl" 691 | version = "4.3.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "d0170e808d03465c564754ac497fe718aae33146b135a0f882a7ca4066257e0b" 694 | dependencies = [ 695 | "miette 4.7.1", 696 | "nom", 697 | "thiserror", 698 | ] 699 | 700 | [[package]] 701 | name = "kernel32-sys" 702 | version = "0.2.2" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 705 | dependencies = [ 706 | "winapi 0.2.8", 707 | "winapi-build", 708 | ] 709 | 710 | [[package]] 711 | name = "knuffel" 712 | version = "2.0.0" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "8f9f7a07459e9dc5d07f5dabfc2c2a965bf39195451eb785b61460c65f386eef" 715 | dependencies = [ 716 | "base64", 717 | "chumsky", 718 | "knuffel-derive", 719 | "miette 4.7.1", 720 | "thiserror", 721 | "unicode-width", 722 | ] 723 | 724 | [[package]] 725 | name = "knuffel-derive" 726 | version = "2.0.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "fbcbdb0b6f26a4e5ecb0dd9074a430398a41b2c1624c205bcc202541ddc15488" 729 | dependencies = [ 730 | "heck 0.4.0", 731 | "proc-macro-error", 732 | "proc-macro2", 733 | "quote", 734 | "syn", 735 | ] 736 | 737 | [[package]] 738 | name = "lazy_static" 739 | version = "1.4.0" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 742 | 743 | [[package]] 744 | name = "lazycell" 745 | version = "1.3.0" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 748 | 749 | [[package]] 750 | name = "libc" 751 | version = "0.2.126" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 754 | 755 | [[package]] 756 | name = "lock_api" 757 | version = "0.4.7" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 760 | dependencies = [ 761 | "autocfg", 762 | "scopeguard", 763 | ] 764 | 765 | [[package]] 766 | name = "log" 767 | version = "0.4.17" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 770 | dependencies = [ 771 | "cfg-if 1.0.0", 772 | ] 773 | 774 | [[package]] 775 | name = "matchers" 776 | version = "0.1.0" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 779 | dependencies = [ 780 | "regex-automata", 781 | ] 782 | 783 | [[package]] 784 | name = "matches" 785 | version = "0.1.9" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 788 | 789 | [[package]] 790 | name = "memchr" 791 | version = "2.5.0" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 794 | 795 | [[package]] 796 | name = "miette" 797 | version = "4.7.1" 798 | source = "registry+https://github.com/rust-lang/crates.io-index" 799 | checksum = "1c90329e44f9208b55f45711f9558cec15d7ef8295cc65ecd6d4188ae8edc58c" 800 | dependencies = [ 801 | "atty", 802 | "backtrace", 803 | "miette-derive 4.7.1", 804 | "once_cell", 805 | "owo-colors", 806 | "supports-color", 807 | "supports-hyperlinks", 808 | "supports-unicode", 809 | "terminal_size", 810 | "textwrap 0.15.0", 811 | "thiserror", 812 | "unicode-width", 813 | ] 814 | 815 | [[package]] 816 | name = "miette" 817 | version = "5.1.0" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "6ec753a43fd71bb5f28751c9ec17fbe89d6d26ca8282d1e1f82f5ac3dbd5581e" 820 | dependencies = [ 821 | "miette-derive 5.1.0", 822 | "once_cell", 823 | "thiserror", 824 | "unicode-width", 825 | ] 826 | 827 | [[package]] 828 | name = "miette-derive" 829 | version = "4.7.1" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "6b5bc45b761bcf1b5e6e6c4128cd93b84c218721a8d9b894aa0aff4ed180174c" 832 | dependencies = [ 833 | "proc-macro2", 834 | "quote", 835 | "syn", 836 | ] 837 | 838 | [[package]] 839 | name = "miette-derive" 840 | version = "5.1.0" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "fdfc33ea15c5446600f91d319299dd40301614afff7143cdfa9bf4c09da3ca64" 843 | dependencies = [ 844 | "proc-macro2", 845 | "quote", 846 | "syn", 847 | ] 848 | 849 | [[package]] 850 | name = "minimal-lexical" 851 | version = "0.2.1" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 854 | 855 | [[package]] 856 | name = "miniz_oxide" 857 | version = "0.5.3" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" 860 | dependencies = [ 861 | "adler", 862 | ] 863 | 864 | [[package]] 865 | name = "mio" 866 | version = "0.6.23" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" 869 | dependencies = [ 870 | "cfg-if 0.1.10", 871 | "fuchsia-zircon", 872 | "fuchsia-zircon-sys", 873 | "iovec", 874 | "kernel32-sys", 875 | "libc", 876 | "log", 877 | "miow", 878 | "net2", 879 | "slab", 880 | "winapi 0.2.8", 881 | ] 882 | 883 | [[package]] 884 | name = "mio" 885 | version = "0.8.4" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" 888 | dependencies = [ 889 | "libc", 890 | "log", 891 | "wasi", 892 | "windows-sys", 893 | ] 894 | 895 | [[package]] 896 | name = "mio-extras" 897 | version = "2.0.6" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" 900 | dependencies = [ 901 | "lazycell", 902 | "log", 903 | "mio 0.6.23", 904 | "slab", 905 | ] 906 | 907 | [[package]] 908 | name = "miow" 909 | version = "0.2.2" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" 912 | dependencies = [ 913 | "kernel32-sys", 914 | "net2", 915 | "winapi 0.2.8", 916 | "ws2_32-sys", 917 | ] 918 | 919 | [[package]] 920 | name = "multimap" 921 | version = "0.8.3" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" 924 | dependencies = [ 925 | "serde", 926 | ] 927 | 928 | [[package]] 929 | name = "net2" 930 | version = "0.2.37" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" 933 | dependencies = [ 934 | "cfg-if 0.1.10", 935 | "libc", 936 | "winapi 0.3.9", 937 | ] 938 | 939 | [[package]] 940 | name = "nom" 941 | version = "7.1.1" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 944 | dependencies = [ 945 | "memchr", 946 | "minimal-lexical", 947 | ] 948 | 949 | [[package]] 950 | name = "notify" 951 | version = "4.0.17" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" 954 | dependencies = [ 955 | "bitflags", 956 | "filetime", 957 | "fsevent", 958 | "fsevent-sys", 959 | "inotify", 960 | "libc", 961 | "mio 0.6.23", 962 | "mio-extras", 963 | "walkdir", 964 | "winapi 0.3.9", 965 | ] 966 | 967 | [[package]] 968 | name = "num_cpus" 969 | version = "1.13.1" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 972 | dependencies = [ 973 | "hermit-abi", 974 | "libc", 975 | ] 976 | 977 | [[package]] 978 | name = "number_prefix" 979 | version = "0.4.0" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 982 | 983 | [[package]] 984 | name = "object" 985 | version = "0.28.4" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" 988 | dependencies = [ 989 | "memchr", 990 | ] 991 | 992 | [[package]] 993 | name = "once_cell" 994 | version = "1.12.0" 995 | source = "registry+https://github.com/rust-lang/crates.io-index" 996 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 997 | 998 | [[package]] 999 | name = "os_str_bytes" 1000 | version = "6.1.0" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" 1003 | 1004 | [[package]] 1005 | name = "owo-colors" 1006 | version = "3.4.0" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" 1009 | 1010 | [[package]] 1011 | name = "parking_lot" 1012 | version = "0.12.1" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1015 | dependencies = [ 1016 | "lock_api", 1017 | "parking_lot_core", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "parking_lot_core" 1022 | version = "0.9.3" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 1025 | dependencies = [ 1026 | "cfg-if 1.0.0", 1027 | "libc", 1028 | "redox_syscall", 1029 | "smallvec", 1030 | "windows-sys", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "percent-encoding" 1035 | version = "2.1.0" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1038 | 1039 | [[package]] 1040 | name = "pin-project" 1041 | version = "1.0.11" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" 1044 | dependencies = [ 1045 | "pin-project-internal", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "pin-project-internal" 1050 | version = "1.0.11" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" 1053 | dependencies = [ 1054 | "proc-macro2", 1055 | "quote", 1056 | "syn", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "pin-project-lite" 1061 | version = "0.2.9" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 1064 | 1065 | [[package]] 1066 | name = "pin-utils" 1067 | version = "0.1.0" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1070 | 1071 | [[package]] 1072 | name = "proc-macro-error" 1073 | version = "1.0.4" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1076 | dependencies = [ 1077 | "proc-macro-error-attr", 1078 | "proc-macro2", 1079 | "quote", 1080 | "syn", 1081 | "version_check", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "proc-macro-error-attr" 1086 | version = "1.0.4" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1089 | dependencies = [ 1090 | "proc-macro2", 1091 | "quote", 1092 | "version_check", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "proc-macro2" 1097 | version = "1.0.40" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 1100 | dependencies = [ 1101 | "unicode-ident", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "quote" 1106 | version = "1.0.20" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 1109 | dependencies = [ 1110 | "proc-macro2", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "redox_syscall" 1115 | version = "0.2.13" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 1118 | dependencies = [ 1119 | "bitflags", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "regex" 1124 | version = "1.5.6" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" 1127 | dependencies = [ 1128 | "aho-corasick", 1129 | "memchr", 1130 | "regex-syntax", 1131 | ] 1132 | 1133 | [[package]] 1134 | name = "regex-automata" 1135 | version = "0.1.10" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1138 | dependencies = [ 1139 | "regex-syntax", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "regex-syntax" 1144 | version = "0.6.26" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" 1147 | 1148 | [[package]] 1149 | name = "rustc-demangle" 1150 | version = "0.1.21" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" 1153 | 1154 | [[package]] 1155 | name = "ryu" 1156 | version = "1.0.10" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" 1159 | 1160 | [[package]] 1161 | name = "same-file" 1162 | version = "1.0.6" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1165 | dependencies = [ 1166 | "winapi-util", 1167 | ] 1168 | 1169 | [[package]] 1170 | name = "scopeguard" 1171 | version = "1.1.0" 1172 | source = "registry+https://github.com/rust-lang/crates.io-index" 1173 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1174 | 1175 | [[package]] 1176 | name = "serde" 1177 | version = "1.0.138" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" 1180 | dependencies = [ 1181 | "serde_derive", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "serde_derive" 1186 | version = "1.0.138" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" 1189 | dependencies = [ 1190 | "proc-macro2", 1191 | "quote", 1192 | "syn", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "serde_json" 1197 | version = "1.0.82" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" 1200 | dependencies = [ 1201 | "itoa", 1202 | "ryu", 1203 | "serde", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "serde_urlencoded" 1208 | version = "0.7.1" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1211 | dependencies = [ 1212 | "form_urlencoded", 1213 | "itoa", 1214 | "ryu", 1215 | "serde", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "serde_with" 1220 | version = "1.14.0" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" 1223 | dependencies = [ 1224 | "serde", 1225 | "serde_with_macros", 1226 | ] 1227 | 1228 | [[package]] 1229 | name = "serde_with_macros" 1230 | version = "1.5.2" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" 1233 | dependencies = [ 1234 | "darling", 1235 | "proc-macro2", 1236 | "quote", 1237 | "syn", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "sharded-slab" 1242 | version = "0.1.4" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1245 | dependencies = [ 1246 | "lazy_static", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "signal-hook" 1251 | version = "0.3.14" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" 1254 | dependencies = [ 1255 | "libc", 1256 | "signal-hook-registry", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "signal-hook-mio" 1261 | version = "0.2.3" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" 1264 | dependencies = [ 1265 | "libc", 1266 | "mio 0.8.4", 1267 | "signal-hook", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "signal-hook-registry" 1272 | version = "1.4.0" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1275 | dependencies = [ 1276 | "libc", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "slab" 1281 | version = "0.4.6" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" 1284 | 1285 | [[package]] 1286 | name = "smallvec" 1287 | version = "1.9.0" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 1290 | 1291 | [[package]] 1292 | name = "smawk" 1293 | version = "0.3.1" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" 1296 | 1297 | [[package]] 1298 | name = "socket2" 1299 | version = "0.4.4" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 1302 | dependencies = [ 1303 | "libc", 1304 | "winapi 0.3.9", 1305 | ] 1306 | 1307 | [[package]] 1308 | name = "strsim" 1309 | version = "0.8.0" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1312 | 1313 | [[package]] 1314 | name = "strsim" 1315 | version = "0.10.0" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1318 | 1319 | [[package]] 1320 | name = "structopt" 1321 | version = "0.3.26" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 1324 | dependencies = [ 1325 | "clap 2.34.0", 1326 | "lazy_static", 1327 | "structopt-derive", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "structopt-derive" 1332 | version = "0.4.18" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 1335 | dependencies = [ 1336 | "heck 0.3.3", 1337 | "proc-macro-error", 1338 | "proc-macro2", 1339 | "quote", 1340 | "syn", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "supports-color" 1345 | version = "1.3.0" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9" 1348 | dependencies = [ 1349 | "atty", 1350 | "is_ci", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "supports-hyperlinks" 1355 | version = "1.2.0" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "590b34f7c5f01ecc9d78dba4b3f445f31df750a67621cf31626f3b7441ce6406" 1358 | dependencies = [ 1359 | "atty", 1360 | ] 1361 | 1362 | [[package]] 1363 | name = "supports-unicode" 1364 | version = "1.0.2" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "a8b945e45b417b125a8ec51f1b7df2f8df7920367700d1f98aedd21e5735f8b2" 1367 | dependencies = [ 1368 | "atty", 1369 | ] 1370 | 1371 | [[package]] 1372 | name = "syn" 1373 | version = "1.0.98" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 1376 | dependencies = [ 1377 | "proc-macro2", 1378 | "quote", 1379 | "unicode-ident", 1380 | ] 1381 | 1382 | [[package]] 1383 | name = "tar" 1384 | version = "0.4.38" 1385 | source = "registry+https://github.com/rust-lang/crates.io-index" 1386 | checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" 1387 | dependencies = [ 1388 | "filetime", 1389 | "libc", 1390 | "xattr", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "termcolor" 1395 | version = "1.1.3" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 1398 | dependencies = [ 1399 | "winapi-util", 1400 | ] 1401 | 1402 | [[package]] 1403 | name = "terminal_size" 1404 | version = "0.1.17" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 1407 | dependencies = [ 1408 | "libc", 1409 | "winapi 0.3.9", 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "textwrap" 1414 | version = "0.11.0" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1417 | dependencies = [ 1418 | "unicode-width", 1419 | ] 1420 | 1421 | [[package]] 1422 | name = "textwrap" 1423 | version = "0.15.0" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 1426 | dependencies = [ 1427 | "smawk", 1428 | "unicode-linebreak", 1429 | "unicode-width", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "thiserror" 1434 | version = "1.0.31" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" 1437 | dependencies = [ 1438 | "thiserror-impl", 1439 | ] 1440 | 1441 | [[package]] 1442 | name = "thiserror-impl" 1443 | version = "1.0.31" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" 1446 | dependencies = [ 1447 | "proc-macro2", 1448 | "quote", 1449 | "syn", 1450 | ] 1451 | 1452 | [[package]] 1453 | name = "thread_local" 1454 | version = "1.1.4" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 1457 | dependencies = [ 1458 | "once_cell", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "tinyvec" 1463 | version = "1.6.0" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1466 | dependencies = [ 1467 | "tinyvec_macros", 1468 | ] 1469 | 1470 | [[package]] 1471 | name = "tinyvec_macros" 1472 | version = "0.1.0" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1475 | 1476 | [[package]] 1477 | name = "tokio" 1478 | version = "1.19.2" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" 1481 | dependencies = [ 1482 | "bytes", 1483 | "libc", 1484 | "memchr", 1485 | "mio 0.8.4", 1486 | "num_cpus", 1487 | "once_cell", 1488 | "parking_lot", 1489 | "pin-project-lite", 1490 | "signal-hook-registry", 1491 | "socket2", 1492 | "tokio-macros", 1493 | "winapi 0.3.9", 1494 | ] 1495 | 1496 | [[package]] 1497 | name = "tokio-macros" 1498 | version = "1.8.0" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 1501 | dependencies = [ 1502 | "proc-macro2", 1503 | "quote", 1504 | "syn", 1505 | ] 1506 | 1507 | [[package]] 1508 | name = "tokio-util" 1509 | version = "0.7.3" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" 1512 | dependencies = [ 1513 | "bytes", 1514 | "futures-core", 1515 | "futures-sink", 1516 | "pin-project-lite", 1517 | "tokio", 1518 | "tracing", 1519 | ] 1520 | 1521 | [[package]] 1522 | name = "toposort" 1523 | version = "0.1.0" 1524 | dependencies = [ 1525 | "multimap", 1526 | ] 1527 | 1528 | [[package]] 1529 | name = "tower-service" 1530 | version = "0.3.2" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1533 | 1534 | [[package]] 1535 | name = "tracing" 1536 | version = "0.1.35" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" 1539 | dependencies = [ 1540 | "cfg-if 1.0.0", 1541 | "pin-project-lite", 1542 | "tracing-attributes", 1543 | "tracing-core", 1544 | ] 1545 | 1546 | [[package]] 1547 | name = "tracing-attributes" 1548 | version = "0.1.22" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" 1551 | dependencies = [ 1552 | "proc-macro2", 1553 | "quote", 1554 | "syn", 1555 | ] 1556 | 1557 | [[package]] 1558 | name = "tracing-core" 1559 | version = "0.1.28" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" 1562 | dependencies = [ 1563 | "once_cell", 1564 | "valuable", 1565 | ] 1566 | 1567 | [[package]] 1568 | name = "tracing-log" 1569 | version = "0.1.3" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 1572 | dependencies = [ 1573 | "lazy_static", 1574 | "log", 1575 | "tracing-core", 1576 | ] 1577 | 1578 | [[package]] 1579 | name = "tracing-subscriber" 1580 | version = "0.3.14" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" 1583 | dependencies = [ 1584 | "ansi_term", 1585 | "matchers", 1586 | "once_cell", 1587 | "regex", 1588 | "sharded-slab", 1589 | "smallvec", 1590 | "thread_local", 1591 | "tracing", 1592 | "tracing-core", 1593 | "tracing-log", 1594 | ] 1595 | 1596 | [[package]] 1597 | name = "try-lock" 1598 | version = "0.2.3" 1599 | source = "registry+https://github.com/rust-lang/crates.io-index" 1600 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1601 | 1602 | [[package]] 1603 | name = "unicode-bidi" 1604 | version = "0.3.8" 1605 | source = "registry+https://github.com/rust-lang/crates.io-index" 1606 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 1607 | 1608 | [[package]] 1609 | name = "unicode-ident" 1610 | version = "1.0.1" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" 1613 | 1614 | [[package]] 1615 | name = "unicode-linebreak" 1616 | version = "0.1.2" 1617 | source = "registry+https://github.com/rust-lang/crates.io-index" 1618 | checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" 1619 | dependencies = [ 1620 | "regex", 1621 | ] 1622 | 1623 | [[package]] 1624 | name = "unicode-normalization" 1625 | version = "0.1.21" 1626 | source = "registry+https://github.com/rust-lang/crates.io-index" 1627 | checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" 1628 | dependencies = [ 1629 | "tinyvec", 1630 | ] 1631 | 1632 | [[package]] 1633 | name = "unicode-segmentation" 1634 | version = "1.9.0" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" 1637 | 1638 | [[package]] 1639 | name = "unicode-width" 1640 | version = "0.1.9" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1643 | 1644 | [[package]] 1645 | name = "url" 1646 | version = "2.2.2" 1647 | source = "registry+https://github.com/rust-lang/crates.io-index" 1648 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1649 | dependencies = [ 1650 | "form_urlencoded", 1651 | "idna", 1652 | "matches", 1653 | "percent-encoding", 1654 | ] 1655 | 1656 | [[package]] 1657 | name = "valuable" 1658 | version = "0.1.0" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1661 | 1662 | [[package]] 1663 | name = "vec_map" 1664 | version = "0.8.2" 1665 | source = "registry+https://github.com/rust-lang/crates.io-index" 1666 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1667 | 1668 | [[package]] 1669 | name = "version_check" 1670 | version = "0.9.4" 1671 | source = "registry+https://github.com/rust-lang/crates.io-index" 1672 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1673 | 1674 | [[package]] 1675 | name = "walkdir" 1676 | version = "2.3.2" 1677 | source = "registry+https://github.com/rust-lang/crates.io-index" 1678 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 1679 | dependencies = [ 1680 | "same-file", 1681 | "winapi 0.3.9", 1682 | "winapi-util", 1683 | ] 1684 | 1685 | [[package]] 1686 | name = "want" 1687 | version = "0.3.0" 1688 | source = "registry+https://github.com/rust-lang/crates.io-index" 1689 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1690 | dependencies = [ 1691 | "log", 1692 | "try-lock", 1693 | ] 1694 | 1695 | [[package]] 1696 | name = "wasi" 1697 | version = "0.11.0+wasi-snapshot-preview1" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1700 | 1701 | [[package]] 1702 | name = "winapi" 1703 | version = "0.2.8" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1706 | 1707 | [[package]] 1708 | name = "winapi" 1709 | version = "0.3.9" 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" 1711 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1712 | dependencies = [ 1713 | "winapi-i686-pc-windows-gnu", 1714 | "winapi-x86_64-pc-windows-gnu", 1715 | ] 1716 | 1717 | [[package]] 1718 | name = "winapi-build" 1719 | version = "0.1.1" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1722 | 1723 | [[package]] 1724 | name = "winapi-i686-pc-windows-gnu" 1725 | version = "0.4.0" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1728 | 1729 | [[package]] 1730 | name = "winapi-util" 1731 | version = "0.1.5" 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" 1733 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1734 | dependencies = [ 1735 | "winapi 0.3.9", 1736 | ] 1737 | 1738 | [[package]] 1739 | name = "winapi-x86_64-pc-windows-gnu" 1740 | version = "0.4.0" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1743 | 1744 | [[package]] 1745 | name = "windows-sys" 1746 | version = "0.36.1" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1749 | dependencies = [ 1750 | "windows_aarch64_msvc", 1751 | "windows_i686_gnu", 1752 | "windows_i686_msvc", 1753 | "windows_x86_64_gnu", 1754 | "windows_x86_64_msvc", 1755 | ] 1756 | 1757 | [[package]] 1758 | name = "windows_aarch64_msvc" 1759 | version = "0.36.1" 1760 | source = "registry+https://github.com/rust-lang/crates.io-index" 1761 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1762 | 1763 | [[package]] 1764 | name = "windows_i686_gnu" 1765 | version = "0.36.1" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1768 | 1769 | [[package]] 1770 | name = "windows_i686_msvc" 1771 | version = "0.36.1" 1772 | source = "registry+https://github.com/rust-lang/crates.io-index" 1773 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1774 | 1775 | [[package]] 1776 | name = "windows_x86_64_gnu" 1777 | version = "0.36.1" 1778 | source = "registry+https://github.com/rust-lang/crates.io-index" 1779 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1780 | 1781 | [[package]] 1782 | name = "windows_x86_64_msvc" 1783 | version = "0.36.1" 1784 | source = "registry+https://github.com/rust-lang/crates.io-index" 1785 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1786 | 1787 | [[package]] 1788 | name = "ws2_32-sys" 1789 | version = "0.2.1" 1790 | source = "registry+https://github.com/rust-lang/crates.io-index" 1791 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1792 | dependencies = [ 1793 | "winapi 0.2.8", 1794 | "winapi-build", 1795 | ] 1796 | 1797 | [[package]] 1798 | name = "xattr" 1799 | version = "0.2.3" 1800 | source = "registry+https://github.com/rust-lang/crates.io-index" 1801 | checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" 1802 | dependencies = [ 1803 | "libc", 1804 | ] 1805 | --------------------------------------------------------------------------------