Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ab7b22f
learned a touch of rust
wagoodman Dec 16, 2017
83713d3
Merge branch 'master' into adding-rust
wagoodman Dec 17, 2017
6f747cc
no decorator
wagoodman Dec 17, 2017
6c74f17
base rust entrypoint
willmurphyscode Dec 17, 2017
ef4bddf
with bare docker builder and executor (#3)
wagoodman Dec 17, 2017
a581508
Real traits jim (#4)
willmurphyscode Dec 28, 2017
03c05ef
initial builder working
wagoodman Dec 28, 2017
992dfdd
Merge branch 'master' into adding-rust
wagoodman Dec 29, 2017
73e1d66
Merge branch 'master' into adding-rust
wagoodman Jan 7, 2018
8e6ae33
added docker run support; cleaned up command execs
wagoodman Jan 7, 2018
c60bce0
Merge branch 'master' into adding-rust
wagoodman Jan 15, 2018
e424107
refactor to str refs; added readme
wagoodman Jan 15, 2018
7ea033a
We need strings here, I think
willmurphyscode Jan 15, 2018
e5bbc4b
Merge branch 'master' into adding-rust
wagoodman Jan 21, 2018
d2c5f73
Merge branch 'master' into adding-rust
wagoodman Jan 27, 2018
5d199e6
added web example
wagoodman Jan 27, 2018
493b3b6
use executable's path as Docker context
willmurphyscode Jan 28, 2018
6880062
Merge pull request #5 from wagoodman/pwd-fix
willmurphyscode Jan 28, 2018
886fb01
wrapped docker cmds in sh
wagoodman Jan 28, 2018
92c045c
Merge branch 'adding-rust' of github.com:wagoodman/package into addin…
wagoodman Jan 28, 2018
aff0386
write dockerfile to docker context explicitly
wagoodman Jan 28, 2018
dd3c9ef
fix copy paste error in README
willmurphyscode Feb 23, 2018
37aa66d
Merge branch 'adding-rust' of github.com:wagoodman/package into addin…
willmurphyscode Feb 23, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions rust/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
Cargo.lock
Dockerfile
13 changes: 13 additions & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "metaparticle"
version = "0.0.1"

[[example]]
name = "hello"
path = "examples/hello.rs"

[[example]]
name = "web"
path = "examples/web.rs"

[dependencies]
52 changes: 52 additions & 0 deletions rust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Metaparticle for Rust
Metaparticle/Package is a collection of libraries intended to
make building and deploying containers a seamless and idiomatic
experience for developers.

This is the implementation for Rust.

## Introduction
Metaparticle/Package simplifies and centralizes the task of
building and deploying a container image.

Here is a quick example.

Consider this simple Rust application:
```rust
fn main() {
println!("Hello World!");
}
```

To containerize this application, you need to use the `metaparticle` crate and
the `containerize` wrapper function like this:

```rust
fn run() {
println!("Hello World!");
}

fn main() {
let runtime = metaparticle::Runtime{
..Default::default()
};
let package = metaparticle::Package{
name: "hello".to_string(),
..Default::default()
};
metaparticle::containerize(run, runtime, package)
}
```

When you run this application, instead of printing "Hello world", it first packages itself as a container, and
then (optionally) deploys itself inside that container.

## Tutorial

```bash
git clone https://github.com/metaparticle-io/package/
cd package/rust

cargo build --example hello
./target/debug/examples/hello
```
17 changes: 17 additions & 0 deletions rust/examples/hello.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
extern crate metaparticle;

fn run() {
println!("Hello World!");
}

fn main() {
let runtime = metaparticle::Runtime{
..Default::default()
};
let package = metaparticle::Package{
name: "hello".to_string(),
repository: "brendanburns".to_string(),
..Default::default()
};
metaparticle::containerize(run, runtime, package)
}
61 changes: 61 additions & 0 deletions rust/examples/web.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
extern crate metaparticle;

use std::net::{TcpStream, TcpListener};
use std::io::{Read, Write};
use std::thread;


fn handle_read(mut stream: &TcpStream) {
let mut buf = [0u8 ;4096];
match stream.read(&mut buf) {
Ok(_) => {
let req_str = String::from_utf8_lossy(&buf);
println!("{}", req_str);
},
Err(e) => println!("Unable to read stream: {}", e),
}
}

fn handle_write(mut stream: TcpStream) {
let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<html><body>Hello world</body></html>\r\n";
match stream.write(response) {
Ok(_) => println!("Response sent"),
Err(e) => println!("Failed sending response: {}", e),
}
}

fn handle_client(stream: TcpStream) {
handle_read(&stream);
handle_write(stream);
}

fn run() {
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
println!("Listening for connections on port {}", 8080);

for stream in listener.incoming() {
match stream {
Ok(stream) => {
thread::spawn(|| {
handle_client(stream)
});
}
Err(e) => {
println!("Unable to connect: {}", e);
}
}
}
}

fn main() {
let runtime = metaparticle::Runtime{
ports: Some(8080),
..Default::default()
};
let package = metaparticle::Package{
name: "web".to_string(),
repository: "brendanburns".to_string(),
..Default::default()
};
metaparticle::containerize(run, runtime, package)
}
13 changes: 13 additions & 0 deletions rust/src/builder/docker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use super::run_docker_process;
use Builder;

pub struct DockerBuilder{}

impl Builder for DockerBuilder {
fn build(&self, dir: &str, image: &str) {
run_docker_process(vec!["docker", "build", &*format!("-t{}", image), dir]);
}
fn push(&self, image: &str) {
run_docker_process(vec!["docker", "push", image]);
}
}
8 changes: 8 additions & 0 deletions rust/src/builder/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub mod docker;
use super::run_docker_process;


pub trait Builder {
fn build(&self, dir: &str, image: &str);
fn push(&self, image: &str);
}
31 changes: 31 additions & 0 deletions rust/src/executor/docker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use super::run_docker_process;
use super::Runtime;
use Executor;

pub struct DockerExecutor{}

impl Executor for DockerExecutor {
fn cancel(&self, name: &str) {
run_docker_process(vec!["docker", "stop", name]);
run_docker_process(vec!["docker", "rm", "-f", name]);
}

fn logs(&self, name: &str) {
run_docker_process(vec!["docker", "logs", "-f", name]);
}

fn run(&self, image: &str, name: &str, config: Runtime) {
let mut ports = String::new();
let mut args = vec!["docker", "run", "-d", "--rm", "--name", name];

if let Some(port) = config.ports {
ports.push_str(&format!("-p {port}", port=port));
args.push(&ports);
}

args.push(image);

run_docker_process(args);
}
}

9 changes: 9 additions & 0 deletions rust/src/executor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub mod docker;
use super::Runtime;
use super::run_docker_process;

pub trait Executor {
fn cancel(&self, name: &str);
fn logs(&self, name: &str);
fn run(&self, image: &str, name: &str, config: Runtime);
}
166 changes: 166 additions & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@

mod builder;
mod executor;

use builder::Builder;
use executor::Executor;

use std::env;
use std::error::Error;
use std::fs::File;
use std::ffi::OsStr;
use std::io::prelude::*;
use std::path::Path;
use std::process;

#[derive(Debug)]
pub struct Runtime {
pub replicas: Option<u64>,
pub shards: Option<u64>,
pub url_shard_pattern: Option<String>,
pub executor: String,
pub ports: Option<u64>,
pub public_address: Option<bool>,
}


impl Default for Runtime {
fn default() -> Runtime {
Runtime {
replicas: None,
shards: None,
url_shard_pattern: Some("".to_string()),
executor: "docker".to_string(),
ports: None,
public_address: Some(false)
}
}
}

#[derive(Debug)]
pub struct Package {
pub name: String,
pub repository: String,
pub verbose: Option<bool>,
pub quiet: Option<bool>,
pub builder: String,
pub publish: Option<bool>,
}


impl Default for Package {
fn default() -> Package {
Package {
name: "".to_string(),
repository: "".to_string(),
verbose: Some(false),
quiet: Some(false),
builder: "docker".to_string(),
publish: Some(false)
}
}
}

pub fn run_docker_process(args: Vec<&str>) {
let name = args[1].clone();
let cmd = args.join(" ");

let mut child = process::Command::new("sh")
.arg("-c")
.arg(cmd)
.spawn()
.expect(&format!("failed to execute 'docker {name}'", name=name));

let status = child.wait()
.ok().expect(&format!("couldn't wait for 'docker {name}'", name=name));

if !status.success() {
match status.code() {
Some(code) => panic!("'docker {}' failed with code {:?}", name, code),
None => panic!("'docker {}' failed", name)
}
}
}

fn in_docker_container() -> bool {
let env_var_key = OsStr::new("METAPARTICLE_IN_CONTAINER");
let env_var = env::var(env_var_key);
if let Ok(value) = env_var {
return value == "true" || value == "1";
}

let mut buffer = String::with_capacity(256); // kind of a guess on initial capacity

if let Ok(mut file) = File::open("/proc/1/cgroup") {
if let Ok(_) = file.read_to_string(&mut buffer) {
return buffer.contains("docker");
}
}
false
}

fn executor_from_runtime(executor_name: String) -> Box<Executor> {
let executor : Box<Executor> = match executor_name.as_ref() {
"docker" => Box::new(executor::docker::DockerExecutor{}),
_ => panic!("Unsupported executor type {}", executor_name),
};
return executor;
}

fn build_from_runtime(builder_name: String) -> Box<Builder> {
let builder : Box<Builder> = match builder_name.as_ref() {
"docker" => Box::new(builder::docker::DockerBuilder{}),
_ => panic!("Unsupported builder type {}", builder_name),
};
builder

}

fn write_dockerfile(name: &str, dest: &Path) {
let dockerfile = &format!("FROM ubuntu:16.04
COPY ./{name} /tmp/{name}
CMD /tmp/{name}
", name=name);
let file = Path::new("Dockerfile");
let path = dest.join(&file);
let display = path.display();

let mut file = match File::create(&path) {
Err(why) => panic!("couldn't create {}: {}",
display,
why.description()),
Ok(file) => file,
};

if let Err(why) = file.write_all(dockerfile.as_bytes()) {
panic!("Could not write dockerfile at {} because {}", display, why.description());
}
}

pub fn containerize<F>(f: F, runtime: Runtime, package: Package) where F: Fn() {
if in_docker_container() {
f();
} else {

if package.repository.len() == 0 {
panic!("A package must be given a 'repository' value");
}
if package.name.len() == 0 {
panic!("A package must be given a 'name' value");
}
let image = &format!("{repo}/{name}:latest", repo=package.repository, name=package.name);

let arg_0 = env::args().nth(0).unwrap();
let path = Path::new(&arg_0);
let docker_context = Path::new(path.parent().unwrap());

write_dockerfile(&package.name, docker_context);
let builder = build_from_runtime(package.builder.clone());

builder.build(docker_context.to_str().unwrap(), image);

let executor = executor_from_runtime(runtime.executor.clone());
executor.run(image, &package.name, runtime);
executor.logs(&package.name);
}
}