PageRenderTime 30ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/components/builder-worker/src/runner/docker.rs

https://bitbucket.org/hdhoang/habitat
Rust | 241 lines | 193 code | 21 blank | 27 comment | 8 complexity | da5997ca8bab245bd70148fc0d3db06b MD5 | raw file
Possible License(s): Apache-2.0
  1. // Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. use std::fs::{self, File};
  15. use std::io::{self, Write};
  16. use std::path::PathBuf;
  17. use std::process::{Child, Command, ExitStatus, Stdio};
  18. use hab_core::env;
  19. use hab_core::fs as hfs;
  20. use hab_core::os::process::{self, Pid, Signal};
  21. use error::{Error, Result};
  22. use runner::log_pipe::LogPipe;
  23. use runner::{NONINTERACTIVE_ENVVAR, RUNNER_DEBUG_ENVVAR};
  24. use runner::workspace::Workspace;
  25. lazy_static! {
  26. /// Absolute path to the Docker exporter program
  27. static ref DOCKER_EXPORTER_PROGRAM: PathBuf = hfs::resolve_cmd_in_pkg(
  28. "hab-pkg-export-docker",
  29. include_str!(concat!(env!("OUT_DIR"), "/DOCKER_EXPORTER_PKG_IDENT")),
  30. );
  31. /// Absolute path to the Dockerd program
  32. static ref DOCKERD_PROGRAM: PathBuf = hfs::resolve_cmd_in_pkg(
  33. "dockerd",
  34. include_str!(concat!(env!("OUT_DIR"), "/DOCKER_PKG_IDENT")),
  35. );
  36. }
  37. const DOCKER_HOST_ENVVAR: &'static str = "DOCKER_HOST";
  38. pub struct DockerExporterSpec {
  39. pub username: String,
  40. pub password: String,
  41. pub registry_type: String,
  42. pub registry_url: Option<String>,
  43. pub docker_hub_repo_name: String,
  44. pub latest_tag: bool,
  45. pub version_tag: bool,
  46. pub version_release_tag: bool,
  47. pub custom_tag: Option<String>,
  48. }
  49. pub struct DockerExporter<'a> {
  50. spec: DockerExporterSpec,
  51. workspace: &'a Workspace,
  52. bldr_url: &'a str,
  53. }
  54. impl<'a> DockerExporter<'a> {
  55. /// Creates a new Docker exporter for a given `Workspace` and Builder URL.
  56. pub fn new(spec: DockerExporterSpec, workspace: &'a Workspace, bldr_url: &'a str) -> Self {
  57. DockerExporter {
  58. spec,
  59. workspace,
  60. bldr_url,
  61. }
  62. }
  63. /// Spawns a Docker export command, pipes output streams to the given `LogPipe` and returns the
  64. /// process' `ExitStatus`.
  65. ///
  66. /// # Errors
  67. ///
  68. /// * If the child process can't be spawned
  69. /// * If the calling thread can't wait on the child process
  70. /// * If the `LogPipe` fails to pipe output
  71. pub fn export(&self, log_pipe: &mut LogPipe) -> Result<ExitStatus> {
  72. let dockerd = self.spawn_dockerd().map_err(Error::Exporter)?;
  73. let exit_status = self.run_export(log_pipe);
  74. self.teardown_dockerd(dockerd).err().map(|e| {
  75. error!("failed to teardown dockerd instance, err={:?}", e)
  76. });
  77. exit_status
  78. }
  79. fn run_export(&self, log_pipe: &mut LogPipe) -> Result<ExitStatus> {
  80. let sock = self.dockerd_sock();
  81. let mut cmd = Command::new(&*DOCKER_EXPORTER_PROGRAM);
  82. cmd.current_dir(self.workspace.root());
  83. cmd.arg("--image-name");
  84. cmd.arg(&self.spec.docker_hub_repo_name);
  85. cmd.arg("--base-pkgs-url");
  86. cmd.arg(&self.bldr_url);
  87. cmd.arg("--url");
  88. cmd.arg(&self.bldr_url);
  89. if self.spec.latest_tag {
  90. cmd.arg("--tag-latest");
  91. }
  92. if self.spec.version_tag {
  93. cmd.arg("--tag-version");
  94. }
  95. if self.spec.version_release_tag {
  96. cmd.arg("--tag-version-release");
  97. }
  98. if let Some(ref custom_tag) = self.spec.custom_tag {
  99. cmd.arg("--tag-custom");
  100. cmd.arg(custom_tag);
  101. }
  102. cmd.arg("--push-image");
  103. cmd.arg("--username");
  104. cmd.arg(&self.spec.username);
  105. cmd.arg("--password");
  106. cmd.arg(&self.spec.password);
  107. cmd.arg("--rm-image");
  108. if let Some(ref registry_url) = self.spec.registry_url {
  109. cmd.arg("--registry-url");
  110. cmd.arg(registry_url);
  111. }
  112. cmd.arg("--registry-type");
  113. cmd.arg(&self.spec.registry_type);
  114. cmd.arg(self.workspace.last_built()?.path); // Locally built artifact
  115. debug!(
  116. "building docker export command, cmd={}",
  117. format!("building docker export command, cmd={:?}", &cmd)
  118. .replace(&self.spec.username, "<username-redacted>")
  119. .replace(&self.spec.password, "<password-redacted>")
  120. );
  121. cmd.env_clear();
  122. if let Some(_) = env::var_os(RUNNER_DEBUG_ENVVAR) {
  123. cmd.env("RUST_LOG", "debug");
  124. }
  125. cmd.env(NONINTERACTIVE_ENVVAR, "true"); // Disables progress bars
  126. cmd.env("TERM", "xterm-256color"); // Emits ANSI color codes
  127. debug!(
  128. "setting docker export command env, {}={}",
  129. DOCKER_HOST_ENVVAR,
  130. sock
  131. );
  132. cmd.env(DOCKER_HOST_ENVVAR, sock); // Use the job-specific `dockerd`
  133. cmd.stdout(Stdio::piped());
  134. cmd.stderr(Stdio::piped());
  135. debug!("spawning docker export command");
  136. let mut child = cmd.spawn().map_err(Error::Exporter)?;
  137. log_pipe.pipe(&mut child)?;
  138. let exit_status = child.wait().map_err(Error::Exporter)?;
  139. debug!("completed docker export command, status={:?}", exit_status);
  140. Ok(exit_status)
  141. }
  142. fn spawn_dockerd(&self) -> io::Result<Child> {
  143. let root = self.dockerd_path();
  144. let env_path = &*DOCKERD_PROGRAM.parent().expect(
  145. "Dockerd parent directory exists",
  146. );
  147. let daemon_json = root.join("etc/daemon.json");
  148. fs::create_dir_all(daemon_json.parent().expect(
  149. "Daemon JSON parent directory exists",
  150. ))?;
  151. {
  152. let mut f = File::create(&daemon_json)?;
  153. f.write_all(b"{}")?;
  154. }
  155. let mut cmd = Command::new(&*DOCKERD_PROGRAM);
  156. if let Some(_) = env::var_os(RUNNER_DEBUG_ENVVAR) {
  157. cmd.arg("-D");
  158. }
  159. cmd.arg("-H");
  160. cmd.arg(self.dockerd_sock());
  161. cmd.arg("--pidfile");
  162. cmd.arg(root.join("var/run/docker.pid"));
  163. cmd.arg("--data-root");
  164. cmd.arg(root.join("var/lib/docker"));
  165. cmd.arg("--exec-root");
  166. cmd.arg(root.join("var/run/docker"));
  167. cmd.arg("--config-file");
  168. cmd.arg(daemon_json);
  169. // TODO fn: Hard-coding this feels wrong. I'd like the {{pkg.svc_run_group}} for this
  170. // service ideally. Probably plumb more config through for this.
  171. cmd.arg("--group");
  172. cmd.arg("hab");
  173. cmd.arg("--iptables=false");
  174. cmd.arg("--ip-masq=false");
  175. cmd.arg("--ipv6=false");
  176. cmd.arg("--raw-logs");
  177. debug!("building dockerd command, cmd={:?}", &cmd);
  178. cmd.env_clear();
  179. debug!(
  180. "setting docker export command env, PATH={}",
  181. env_path.display()
  182. );
  183. cmd.env("PATH", env_path); // Sadly, `dockerd` needs its collaborator programs on `PATH`
  184. cmd.stdout(Stdio::from(File::create(
  185. self.workspace.root().join("dockerd.stdout.log"),
  186. )?));
  187. cmd.stderr(Stdio::from(File::create(
  188. self.workspace.root().join("dockerd.stderr.log"),
  189. )?));
  190. debug!("spawning dockerd export command");
  191. cmd.spawn()
  192. }
  193. fn teardown_dockerd(&self, mut dockerd: Child) -> io::Result<()> {
  194. debug!(
  195. "signaling dockerd to shutdown pid={}, sig={:?}",
  196. dockerd.id(),
  197. Signal::TERM
  198. );
  199. if let Err(err) = process::signal(dockerd.id() as Pid, Signal::TERM) {
  200. warn!(
  201. "Error sending TERM signal to dockerd, {}, {}",
  202. dockerd.id(),
  203. err
  204. );
  205. }
  206. dockerd.wait()?;
  207. debug!("terminated dockerd");
  208. // TODO fn: clean up `self.dockerd_root()` directory
  209. Ok(())
  210. }
  211. fn dockerd_path(&self) -> PathBuf {
  212. self.workspace.root().join("dockerd")
  213. }
  214. fn dockerd_sock(&self) -> String {
  215. format!(
  216. "unix://{}",
  217. self.dockerd_path().join("var/run/docker.sock").display()
  218. )
  219. }
  220. }