Skip to content

Commit

Permalink
Prepare container startup with volume creation
Browse files Browse the repository at this point in the history
  • Loading branch information
schrieveslaach committed Dec 21, 2018
1 parent 7d431f1 commit d70d6dc
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 35 deletions.
6 changes: 3 additions & 3 deletions api/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/Cargo.toml
Expand Up @@ -18,7 +18,7 @@ serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
serde_yaml = "0.7"
shiplift = { git = "https://github.com/schrieveslaach/shiplift", rev = "3ba3b05577cb62de65a1ec8edd824476f86fed3b" }
shiplift = { git = "https://github.com/schrieveslaach/shiplift", rev = "b8445b717ee92558a2f02bbff16f34ac325cd5f5" }
tokio = "0.1"
toml = "0.4"
regex = "1.0"
Expand Down
23 changes: 5 additions & 18 deletions api/src/services/config_service.rs
Expand Up @@ -23,7 +23,7 @@
* THE SOFTWARE.
* =========================LICENSE_END==================================
*/
use regex::Regex;
use models::service::{parse_image_string, ContainerType, ServiceConfig};
use serde::{de, Deserialize, Deserializer};
use std::collections::BTreeMap;
use std::convert::{From, TryFrom};
Expand All @@ -33,8 +33,6 @@ use std::io::Error as IOError;
use toml::de::Error as TomlError;
use toml::from_str;

use models::service::{ContainerType, ServiceConfig};

#[derive(Clone, Deserialize)]
pub struct ContainerConfig {
#[serde(deserialize_with = "ContainerConfig::parse_from_memory_string")]
Expand Down Expand Up @@ -195,22 +193,11 @@ impl TryFrom<&Companion> for ServiceConfig {
type Error = ConfigError;

fn try_from(companion: &Companion) -> Result<ServiceConfig, ConfigError> {
let regex =
Regex::new(r"^(((?P<registry>.+)/)?(?P<user>\w+)/)?(?P<repo>\w+)(:(?P<tag>\w+))?$")
.unwrap();
let captures = match regex.captures(&companion.image) {
Some(captures) => captures,
None => return Err(ConfigError::UnableToParseImage),
let (repo, user, registry, tag) = match parse_image_string(&companion.image) {
Ok((repo, user, registry, tag)) => (repo, user, registry, tag),
Err(_) => return Err(ConfigError::UnableToParseImage),
};

let repo = captures
.name("repo")
.map(|m| String::from(m.as_str()))
.unwrap();
let registry = captures.name("registry").map(|m| String::from(m.as_str()));
let user = captures.name("user").map(|m| String::from(m.as_str()));
let tag = captures.name("tag").map(|m| String::from(m.as_str()));

let mut config =
ServiceConfig::new(&companion.service_name, &repo, Some(companion.env.clone()));
config.set_registry(&registry);
Expand Down Expand Up @@ -271,7 +258,7 @@ mod tests {
[companions.openid]
serviceName = 'openid'
type = 'application'
image = 'private.example.com/library/opendid:latest'
image = 'private.example.com/library/opendid:11-alpine'
env = [ 'KEY=VALUE' ]
[companions.openid.volumes]
Expand Down
98 changes: 85 additions & 13 deletions api/src/services/docker/docker_infrastructure.rs
Expand Up @@ -23,30 +23,30 @@
* THE SOFTWARE.
* =========================LICENSE_END==================================
*/
use models::service::{ContainerType, Service, ServiceConfig, ServiceError};
use multimap::MultiMap;
use services::infrastructure::Infrastructure;

use super::super::config_service::ContainerConfig;
use failure::Error;
use futures::future::join_all;
use futures::{Future, Stream};
use models;
use models::service::{ContainerType, Service, ServiceConfig, ServiceError};
use multimap::MultiMap;
use services::infrastructure::Infrastructure;
use shiplift::builder::ContainerOptions;
use shiplift::errors::Error as ShipLiftError;
use shiplift::rep::Container;
use shiplift::{
ContainerConnectionOptions, ContainerFilter, ContainerListOptions, Docker,
NetworkCreateOptions, PullOptions,
NetworkCreateOptions, PullOptions, VolumeCreateOptions,
};
use std::collections::HashMap;
use std::convert::{From, TryFrom};
use std::path::Path;
use std::sync::mpsc;
use tokio::runtime::Runtime;

static APP_NAME_LABEL: &str = "preview.servant.app-name";
static SERVICE_NAME_LABEL: &str = "preview.servant.service-name";
static CONTAINER_TYPE_LABEL: &str = "preview.servant.container-type";
static APP_NAME_LABEL: &str = "com.aixigo.preview.servant.app-name";
static SERVICE_NAME_LABEL: &str = "com.aixigo.preview.servant.service-name";
static CONTAINER_TYPE_LABEL: &str = "com.aixigo.preview.servant.container-type";

pub struct DockerInfrastructure {}

Expand Down Expand Up @@ -171,9 +171,9 @@ impl DockerInfrastructure {
options.env(env.iter().map(|e| e.as_str()).collect());
}

// TODO: if let Some(ref volumes) = service_config.get_volumes() {
// options.volumes(volumes.iter().map(|v| v.as_str()).collect());
// }
if service_config.get_volumes().len() > 0 {
self.create_volume(&mut runtime, app_name, service_config)?;
}

let traefik_frontend = format!(
"ReplacePathRegex: ^/{p1}/{p2}(.*) /$1;PathPrefix:/{p1}/{p2};",
Expand Down Expand Up @@ -230,6 +230,78 @@ impl DockerInfrastructure {
Ok(service)
}

fn create_volume(
&self,
runtime: &mut Runtime,
app_name: &String,
config: &ServiceConfig,
) -> Result<String, ShipLiftError> {
info!(
"Creating volume for {:?} of app {:?}",
config.get_service_name(),
app_name
);

let docker = Docker::new();
let containers = docker.containers();
let volumes = docker.volumes();

let mut labels: HashMap<&str, &str> = HashMap::new();
labels.insert(APP_NAME_LABEL, app_name);
labels.insert(SERVICE_NAME_LABEL, &config.get_service_name());

let future_create_volume = volumes
.create(&VolumeCreateOptions::builder().labels(&labels).build())
.map(|create_info| create_info.name);

let name = runtime.block_on(future_create_volume)?;

runtime.block_on(
docker
.images()
.pull(&PullOptions::builder().image("bash").build())
.for_each(|output| {
debug!("{:?}", output);
Ok(())
}),
)?;

let mut futures = Vec::new();
for (mount_point, file_content) in config.get_volumes() {
let target = Path::new(mount_point)
.parent()
.map_or_else(|| "", |p| p.to_str().unwrap());
let volume = format!("{}:{}", name, target);

let cmd = "echo \"".to_owned() + &file_content + "\" > " + mount_point;

futures.push(
containers
.create(
&ContainerOptions::builder("bash")
.volumes(vec![&volume])
.cmd(vec!["sh", "-c", &cmd])
// TODO: use auto remove: https://github.com/softprops/shiplift/pull/137
.build(),
)
.map(move |info| {
let docker = Docker::new();
tokio::spawn(
docker
.containers()
.get(&info.id)
.start()
.map_err(|e| warn!("Volume init error: {}", e)),
);
}),
);
}

runtime.block_on(join_all(futures))?;

Ok(name)
}

fn pull_image(
&self,
runtime: &mut Runtime,
Expand Down Expand Up @@ -528,8 +600,8 @@ impl From<ServiceError> for DockerInfrastructureError {
unknown_label: label,
}
}
_err => DockerInfrastructureError::UnexpectedError {
internal_message: String::from(""),
err => DockerInfrastructureError::UnexpectedError {
internal_message: err.to_string(),
},
}
}
Expand Down
58 changes: 58 additions & 0 deletions api/src/services/service_templating.rs
Expand Up @@ -25,6 +25,7 @@
*/
use handlebars::{Handlebars, TemplateRenderError};
use models::service::{ContainerType, ServiceConfig};
use std::collections::BTreeMap;

pub fn apply_templating_for_application_companion(
service_config: &ServiceConfig,
Expand Down Expand Up @@ -64,6 +65,19 @@ pub fn apply_templating_for_application_companion(
templated_config.set_env(&Some(templated_env));
}

if service_config.get_volumes().len() > 0 {
let mut templated_volumes = BTreeMap::new();

for (mount_point, file_content) in service_config.get_volumes() {
templated_volumes.insert(
mount_point.clone(),
reg.render_template(file_content, &parameters)?,
);
}

templated_config.set_volumes(&templated_volumes);
}

Ok(templated_config)
}

Expand Down Expand Up @@ -164,4 +178,48 @@ mod tests {

assert_eq!(templated_config.is_err(), true);
}

#[test]
fn should_apply_app_companion_templating_with_volumes() {
let mut config =
ServiceConfig::new(&String::from("nginx-proxy"), &String::from("nginx"), None);

let mount_path = String::from("/etc/ningx/conf.d/default.conf");
let mut volumes = BTreeMap::new();
volumes.insert(
mount_path.clone(),
String::from(
r#"{{#each services}}
location /{{name}} {
proxy_pass http://{{~name~}};
}
{{/each}}"#,
),
);
config.set_volumes(&volumes);

let service_configs = vec![
ServiceConfig::new(&String::from("service-a"), &String::from("service"), None),
ServiceConfig::new(&String::from("service-b"), &String::from("service"), None),
];
let templated_config = apply_templating_for_application_companion(
&config,
&String::from("master"),
&service_configs,
)
.unwrap();

assert_eq!(
templated_config.get_volumes().get(&mount_path).unwrap(),
r#"
location /service-a {
proxy_pass http://service-a;
}
location /service-b {
proxy_pass http://service-b;
}
"#
);
}
}

0 comments on commit d70d6dc

Please sign in to comment.