mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 22:01:42 +03:00
Update Docker output parsing (#5996)
Docker output is not really stable across releases. This PR adds usage of the `--quiet` flag, so it ends up in something that is effectively stable. Unfortunately, we cannot use the `--format,` as it does not work on Windows.
This commit is contained in:
parent
201358e5cc
commit
130fd803f0
@ -6,9 +6,25 @@ use crate::extensions::child::ChildExt;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Get the docker image identifier from the `docker build` command output.
|
||||||
|
///
|
||||||
|
/// This assumes format compatible with `--quiet` flag.
|
||||||
|
#[context("Failed to get the image identifier from the output: {output:?}.")]
|
||||||
|
fn get_image_id_from_build_output(output: &std::process::Output) -> Result<ImageId> {
|
||||||
|
trace!("Output: {:?}", output);
|
||||||
|
let built_image_id = std::str::from_utf8(&output.stdout)?
|
||||||
|
.lines()
|
||||||
|
.inspect(|line| debug!("{}", line))
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.last()
|
||||||
|
.with_context(|| "Docker provided no output.")?
|
||||||
|
.split(' ')
|
||||||
|
.last()
|
||||||
|
.with_context(|| "The last line has no space!")?;
|
||||||
|
debug!("Image {} successfully built!", built_image_id);
|
||||||
|
ImageId::from_str(built_image_id)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Ord, PartialOrd, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Ord, PartialOrd, Eq, Hash)]
|
||||||
pub enum NetworkDriver {
|
pub enum NetworkDriver {
|
||||||
@ -99,18 +115,8 @@ impl Docker {
|
|||||||
command.arg("build").args(options.args());
|
command.arg("build").args(options.args());
|
||||||
debug!("{:?}", command);
|
debug!("{:?}", command);
|
||||||
let output = command.output_ok().await?;
|
let output = command.output_ok().await?;
|
||||||
trace!("Output: {:?}", output);
|
let built_image_id = get_image_id_from_build_output(&output)?;
|
||||||
let built_image_id = std::str::from_utf8(&output.stdout)?
|
Ok(built_image_id)
|
||||||
.lines()
|
|
||||||
.inspect(|line| debug!("{}", line))
|
|
||||||
.filter(|line| !line.is_empty())
|
|
||||||
.last()
|
|
||||||
.ok_or_else(|| anyhow!("Docker provided no output"))?
|
|
||||||
.split(' ')
|
|
||||||
.last()
|
|
||||||
.ok_or_else(|| anyhow!("The last line has no space!"))?;
|
|
||||||
debug!("Image {} successfully built!", built_image_id);
|
|
||||||
Ok(ImageId(built_image_id.into()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_cmd(&self, options: &RunOptions) -> Result<Command> {
|
pub fn run_cmd(&self, options: &RunOptions) -> Result<Command> {
|
||||||
@ -281,6 +287,35 @@ impl BuildOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the environment variable and pass its value to docker build as a build argument.
|
||||||
|
///
|
||||||
|
/// Build argument name will be the same as the environment variable name.
|
||||||
|
/// If the environment variable is not set, an error is raised.
|
||||||
|
/// ```
|
||||||
|
/// use ide_ci::define_env_var;
|
||||||
|
/// use ide_ci::prelude::*;
|
||||||
|
/// use ide_ci::programs::docker::BuildOptions;
|
||||||
|
/// define_env_var! {DOCKER_USERNAME, String;};
|
||||||
|
/// let mut options = BuildOptions::new(".");
|
||||||
|
///
|
||||||
|
/// // Variable is not set, so this will fail.
|
||||||
|
/// assert!(options.add_build_arg_from_env(DOCKER_USERNAME).is_err());
|
||||||
|
/// assert!(options.build_args.is_empty());
|
||||||
|
///
|
||||||
|
/// // Set the variable and try again.
|
||||||
|
/// DOCKER_USERNAME.set("my_username".into()).unwrap();
|
||||||
|
/// options.add_build_arg_from_env(DOCKER_USERNAME).unwrap();
|
||||||
|
/// assert_eq!(options.build_args.get("DOCKER_USERNAME"), Some(&Some("my_username".into())));
|
||||||
|
/// ```
|
||||||
|
pub fn add_build_arg_from_env(
|
||||||
|
&mut self,
|
||||||
|
variable: impl crate::env::accessor::RawVariable,
|
||||||
|
) -> Result {
|
||||||
|
let value = variable.get_raw()?;
|
||||||
|
self.build_args.insert(variable.name().into(), Some(value));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_build_arg_from_env_or<R>(
|
pub fn add_build_arg_from_env_or<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl AsRef<str>,
|
name: impl AsRef<str>,
|
||||||
@ -300,6 +335,7 @@ impl BuildOptions {
|
|||||||
pub fn args(&self) -> Vec<OsString> {
|
pub fn args(&self) -> Vec<OsString> {
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
ret.push(self.context.clone().into());
|
ret.push(self.context.clone().into());
|
||||||
|
ret.push("--quiet".into());
|
||||||
if let Some(target) = self.target.as_ref() {
|
if let Some(target) = self.target.as_ref() {
|
||||||
ret.push("--target".into());
|
ret.push("--target".into());
|
||||||
ret.push(target.clone());
|
ret.push(target.clone());
|
||||||
@ -516,13 +552,21 @@ impl RunOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Display, Debug)]
|
#[derive(Clone, Display, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct ImageId(pub String);
|
pub struct ImageId(pub String);
|
||||||
|
|
||||||
|
impl std::str::FromStr for ImageId {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
Ok(ImageId(s.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Display, Deref, AsRef)]
|
#[derive(Clone, Debug, Display, Deref, AsRef)]
|
||||||
pub struct ContainerId(pub String);
|
pub struct ContainerId(pub String);
|
||||||
|
|
||||||
impl FromStr for ContainerId {
|
impl std::str::FromStr for ContainerId {
|
||||||
type Err = anyhow::Error;
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
@ -534,6 +578,44 @@ impl FromStr for ContainerId {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
/// Get the OS kernel version.
|
||||||
|
///
|
||||||
|
/// For Windows, the returned value is the build number, e.g. 20348 for Windows Server 2022
|
||||||
|
/// 21H2.
|
||||||
|
// This function might be unused, depending on what platform-specific tests are compiled.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn get_kernel_version() -> Result<u32> {
|
||||||
|
let mut sysinfo = sysinfo::System::new();
|
||||||
|
sysinfo.refresh_all();
|
||||||
|
let ret = sysinfo
|
||||||
|
.kernel_version()
|
||||||
|
.with_context(|| "Failed to get OS kernel version.")?
|
||||||
|
.parse2()?;
|
||||||
|
debug!("OS kernel version: {ret}.");
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See the tag listing on https://hub.docker.com/_/microsoft-windows-servercore
|
||||||
|
// This function might be unused, depending on what platform-specific tests are compiled.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn get_windows_image_tag(kernel_version: u32) -> Result<&'static str> {
|
||||||
|
Ok(match kernel_version {
|
||||||
|
20348..=u32::MAX => "ltsc2022",
|
||||||
|
17763..=20347 => "ltsc2019",
|
||||||
|
14393..=17762 => "ltsc2016",
|
||||||
|
_ => anyhow::bail!("Unsupported OS kernel version: {kernel_version}."),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Provide a Windows image tag for the OS that we are running on.
|
||||||
|
///
|
||||||
|
/// See also: [`get_windows_image_tag`].
|
||||||
|
fn get_windows_image_tag_for_local() -> Result<&'static str> {
|
||||||
|
let kernel_version = get_kernel_version()?;
|
||||||
|
get_windows_image_tag(kernel_version)
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
async fn network() -> Result {
|
async fn network() -> Result {
|
||||||
@ -550,4 +632,41 @@ mod tests {
|
|||||||
dbg!(Docker.build(opts).await?);
|
dbg!(Docker.build(opts).await?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn build_test_linux() -> Result {
|
||||||
|
setup_logging()?;
|
||||||
|
let temp = tempfile::tempdir()?;
|
||||||
|
if Docker.lookup().is_err() {
|
||||||
|
info!("Docker not found, skipping test.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let dockerfile_text = match TARGET_OS {
|
||||||
|
OS::Linux => r#"
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
RUN echo "Hello, world!"
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
OS::Windows => {
|
||||||
|
let tag = get_windows_image_tag_for_local()?;
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
FROM mcr.microsoft.com/windows/nanoserver:{tag}
|
||||||
|
RUN cmd /c "echo Hello World"
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
info!("Unsupported OS, skipping test.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let dockerfile_path = temp.path().join("Dockerfile");
|
||||||
|
crate::fs::tokio::write(&dockerfile_path, dockerfile_text).await?;
|
||||||
|
let opts = BuildOptions::new(temp.path());
|
||||||
|
// Make sure that the build succeeds and ID is returned.
|
||||||
|
let _id = Docker.build(opts).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user