Permalink
Browse files

Improve Docker Volume Creation

- Fix multiline volume mounts
- Make volumes of companions optional
- Use traefik PathPrefixStrip
  • Loading branch information...
schrieveslaach committed Jan 3, 2019
1 parent 863f8e0 commit 9a75f9457f12eb7958b80833bf859e83100bda2a

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -18,7 +18,7 @@ serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
serde_yaml = "0.7"
shiplift = { git = "https://github.com/softprops/shiplift", rev = "3c06c3a11ba1f7d362bde8b14d94b07b529850e8" }
shiplift = "0.4"
tokio = "0.1"
toml = "0.4"
regex = "1.0"
@@ -90,8 +90,8 @@ impl AppsService {
&app_companion_config,
app_name,
&configs,
);
configs.push(applied_template_config?);
)?;
configs.push(applied_template_config);
}

let services = self.infrastructure.start_services(
@@ -53,7 +53,7 @@ pub struct Companion {
#[serde(rename = "type")]
companion_type: CompanionType,
image: String,
env: Vec<String>,
env: Option<Vec<String>>,
volumes: Option<BTreeMap<String, String>>,
}

@@ -202,7 +202,7 @@ impl TryFrom<&Companion> for ServiceConfig {
config.set_registry(&registry);
config.set_image_user(&user);
config.set_image_tag(&tag);
config.set_env(&Some(companion.env.clone())); // TODO: use move semantics
config.set_env(&companion.env.clone()); // TODO: use move semantics

if let Some(volumes) = &companion.volumes {
config.set_volumes(&Some(volumes.clone())); // TODO: use move semantics
@@ -271,7 +271,7 @@ mod tests {

assert_eq!(companion_configs.len(), 1);
companion_configs.iter().for_each(|config| {
assert_eq!(config.get_volumes().len(), 2);
assert_eq!(config.get_volumes().unwrap().len(), 2);
});
}

@@ -172,13 +172,18 @@ impl DockerInfrastructure {
}

if let Some(_volumes) = service_config.get_volumes() {
self.create_volume(&mut runtime, app_name, service_config)?;
let volumes: Vec<String> = self
.create_volumes(&mut runtime, app_name, service_config)?
.iter()
.map(|(path, volume_name)| format!("{}:{}", volume_name, path))
.collect();
options.volumes(volumes.iter().map(|v| v.as_ref()).collect());
}

let traefik_frontend = format!(
"ReplacePathRegex: ^/{p1}/{p2}(.*) /$1;PathPrefix:/{p1}/{p2};",
p1 = app_name,
p2 = service_config.get_service_name()
"PathPrefixStrip: /{app_name}/{service_name};",
app_name = app_name,
service_name = service_config.get_service_name()
);
let mut labels: HashMap<&str, &str> = HashMap::new();
labels.insert(APP_NAME_LABEL, app_name);
@@ -230,20 +235,70 @@ impl DockerInfrastructure {
Ok(service)
}

fn create_volume(
fn create_volumes(
&self,
runtime: &mut Runtime,
app_name: &String,
config: &ServiceConfig,
) -> Result<String, ShipLiftError> {
) -> Result<HashMap<String, String>, ShipLiftError> {
let mut volumes: HashMap<String, String> = HashMap::new();

info!(
"Creating volume for {:?} of app {:?}",
"Creating volumes for {:?} of app {:?}",
config.get_service_name(),
app_name
);

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

let mut create_futures = Vec::new();
for (mount_point, file_content) in config.get_volumes().unwrap() {
let target = Path::new(mount_point)
.parent()
.map_or_else(|| "", |p| p.to_str().unwrap())
.to_string();

if let None = volumes.get(&target) {
volumes.insert(
target.clone(),
self.create_volume(runtime, app_name, config)?,
);
}
let volume_name = volumes.get(&target).unwrap();

let cmd = "echo -e \"".to_owned()
+ &file_content.replace("\n", "\\n")
+ "\" > "
+ mount_point;

create_futures.push(
containers.create(
&ContainerOptions::builder("bash")
.volumes(vec![format!("{}:{}", volume_name, target).as_ref()])
.cmd(vec!["sh", "-c", &cmd])
.auto_remove(true)
.build(),
),
);
}

let mut echo_futures = Vec::new();
for info in runtime.block_on(join_all(create_futures))? {
echo_futures.push(docker.containers().get(&info.id).start());
}
runtime.block_on(join_all(echo_futures))?;

Ok(volumes)
}

fn create_volume(
&self,
runtime: &mut Runtime,
app_name: &String,
config: &ServiceConfig,
) -> Result<String, ShipLiftError> {
let docker = Docker::new();
let volumes = docker.volumes();

let mut labels: HashMap<&str, &str> = HashMap::new();
@@ -266,39 +321,6 @@ impl DockerInfrastructure {
}),
)?;

let mut futures = Vec::new();
for (mount_point, file_content) in config.get_volumes().unwrap() {
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])
.auto_remove(true)
.build(),
)
.map(move |info| {
let docker = Docker::new();
tokio::run(
docker
.containers()
.get(&info.id)
.start()
.map_err(|e| warn!("Volume init error: {}", e)),
);
}),
);
}

runtime.block_on(join_all(futures))?;

Ok(name)
}

@@ -109,11 +109,11 @@ mod tests {
#[test]
fn should_apply_app_companion_templating_with_service_name() {
let env = vec![];
let config = ServiceConfig::new(
let mut config = ServiceConfig::new(
&String::from("postgres-{{application.name}}"),
&String::from("postgres"),
Some(env),
);
config.set_env(&Some(env));

let service_configs = vec![];
let templated_config = apply_templating_for_application_companion(
@@ -135,15 +135,13 @@ mod tests {
{{~/each~}}"#,
)];

let config = ServiceConfig::new(
&String::from("postgres-db"),
&String::from("postgres"),
Some(env),
);
let mut config =
ServiceConfig::new(&String::from("postgres-db"), &String::from("postgres"));
config.set_env(&Some(env));

let service_configs = vec![
ServiceConfig::new(&String::from("service-a"), &String::from("service"), None),
ServiceConfig::new(&String::from("service-b"), &String::from("service"), None),
ServiceConfig::new(&String::from("service-a"), &String::from("service")),
ServiceConfig::new(&String::from("service-b"), &String::from("service")),
];
let templated_config = apply_templating_for_application_companion(
&config,
@@ -167,11 +165,9 @@ mod tests {
{{~/each~}}"#,
)];

let config = ServiceConfig::new(
&String::from("postgres-db"),
&String::from("postgres"),
Some(env),
);
let mut config =
ServiceConfig::new(&String::from("postgres-db"), &String::from("postgres"));
config.set_env(&Some(env));

let templated_config =
apply_templating_for_application_companion(&config, &String::from("master"), &vec![]);
@@ -181,8 +177,7 @@ mod tests {

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

let mount_path = String::from("/etc/ningx/conf.d/default.conf");
let mut volumes = BTreeMap::new();
@@ -196,11 +191,11 @@ location /{{name}} {
{{/each}}"#,
),
);
config.set_volumes(&volumes);
config.set_volumes(&Some(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),
ServiceConfig::new(&String::from("service-a"), &String::from("service")),
ServiceConfig::new(&String::from("service-b"), &String::from("service")),
];
let templated_config = apply_templating_for_application_companion(
&config,
@@ -210,7 +205,11 @@ location /{{name}} {
.unwrap();

assert_eq!(
templated_config.get_volumes().get(&mount_path).unwrap(),
templated_config
.get_volumes()
.unwrap()
.get(&mount_path)
.unwrap(),
r#"
location /service-a {
proxy_pass http://service-a;
@@ -6,7 +6,7 @@ services:
network_mode: "bridge"
userns_mode: "host"
labels:
traefik.frontend.rule: 'ReplacePathRegex: ^/api(.*) /$$1;PathPrefix:/api;'
traefik.frontend.rule: 'PathPrefixStrip: /api;'
volumes:
- /var/run/docker.sock:/var/run/docker.sock

@@ -123,7 +123,7 @@
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.27.2</version>
<version>0.28.0</version>

<configuration>
<dockerFileDir>${project.basedir}</dockerFileDir>

0 comments on commit 9a75f94

Please sign in to comment.