feat: Squash builds (#155)

### Buildah/Podman support

Buildah and podman can make heavy use of the squash feature. Something
that I've noticed when trying to build from inside of a container,
requiring intermediate layers with mounts causes build times to
skyrocket. Build times are much faster when using the `--squash`
functionality (seen as `--layers=false`).

Here are the following results from my personal build using both squash
and non-squash functionality.

#### Squash upgrade:

```
$> rpm-ostree upgrade
Pulling manifest: ostree-image-signed:docker://registry.gitlab.com/wunker-bunker/wunker-os/jp-laptop
Importing: ostree-image-signed:docker://registry.gitlab.com/wunker-bunker/wunker-os/jp-laptop (digest: sha256:60f743ba322041918d302e7e7f10438c59502e19343c294064bacb676c8eb7b7)
ostree chunk layers already present: 65
custom layers already present: 3
custom layers needed: 1 (814.0 MB)
```

All changes appear to show as a single custom layer. Any small change
even at the end of the build appears to require completely downloading
the new layer (squash only squashes additional layers on top of the base
layer). This makes sense as layers cannot currently be downloaded by
diff.

#### Non-squash upgrade:

```
$> rpm-ostree upgrade
Pulling manifest: ostree-image-signed:docker://registry.gitlab.com/wunker-bunker/wunker-os/jp-desktop-gaming:latest
Importing: ostree-image-signed:docker://registry.gitlab.com/wunker-bunker/wunker-os/jp-desktop-gaming:latest (digest: sha256:0658b51febfcbaa1722961b7a6d2b197d3823a6228e330f45dd1e1aaefd145c5)
ostree chunk layers already present: 65
custom layers already present: 4
custom layers needed: 15 (942.4 MB)
```

As expected, there are more layers when not squashing and the size is
slightly bigger. Most likely due to there being extra information stored
in the layers that is subsequently removed.

### Docker support

Docker is apparently [no longer
supporting](https://github.com/docker/buildx/issues/1287) the use of the
`--squash` arg. The use of squash will not be available for the docker
driver in this case.
This commit is contained in:
Gerald Pinder 2024-04-11 15:15:30 -04:00 committed by GitHub
parent e9c96e204d
commit 6e3a193e92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 269 additions and 101 deletions

View file

@ -8,7 +8,11 @@ use serde::Deserialize;
use crate::image_metadata::ImageMetadata;
use super::{credentials, opts::CompressionType, BuildDriver, DriverVersion, InspectDriver};
use super::{
credentials,
opts::{BuildOpts, GetMetadataOpts, PushOpts, TagOpts},
BuildDriver, DriverVersion, InspectDriver,
};
#[derive(Debug, Deserialize)]
struct PodmanVersionJsonClient {
@ -31,6 +35,9 @@ impl DriverVersion for PodmanDriver {
const VERSION_REQ: &'static str = ">=4";
fn version() -> Result<Version> {
trace!("PodmanDriver::version()");
trace!("podman version -f json");
let output = Command::new("podman")
.arg("version")
.arg("-f")
@ -38,64 +45,80 @@ impl DriverVersion for PodmanDriver {
.output()?;
let version_json: PodmanVersionJson = serde_json::from_slice(&output.stdout)?;
trace!("{version_json:#?}");
Ok(version_json.client.version)
}
}
impl BuildDriver for PodmanDriver {
fn build(&self, image: &str) -> Result<()> {
trace!("podman build . -t {image}");
fn build(&self, opts: &BuildOpts) -> Result<()> {
trace!("PodmanDriver::build({opts:#?})");
trace!(
"podman build --pull=true --layers={} . -t {}",
!opts.squash,
opts.image,
);
let status = Command::new("podman")
.arg("build")
.arg("--pull=true")
.arg(format!("--layers={}", !opts.squash))
.arg(".")
.arg("-t")
.arg(image)
.arg(opts.image.as_ref())
.status()?;
if status.success() {
info!("Successfully built {image}");
info!("Successfully built {}", opts.image);
} else {
bail!("Failed to build {image}");
bail!("Failed to build {}", opts.image);
}
Ok(())
}
fn tag(&self, src_image: &str, image_name: &str, tag: &str) -> Result<()> {
let dest_image = format!("{image_name}:{tag}");
fn tag(&self, opts: &TagOpts) -> Result<()> {
trace!("PodmanDriver::tag({opts:#?})");
trace!("podman tag {src_image} {dest_image}");
trace!("podman tag {} {}", opts.src_image, opts.dest_image);
let status = Command::new("podman")
.arg("tag")
.arg(src_image)
.arg(&dest_image)
.arg(opts.src_image.as_ref())
.arg(opts.dest_image.as_ref())
.status()?;
if status.success() {
info!("Successfully tagged {dest_image}!");
info!("Successfully tagged {}!", opts.dest_image);
} else {
bail!("Failed to tag image {dest_image}");
bail!("Failed to tag image {}", opts.dest_image);
}
Ok(())
}
fn push(&self, image: &str, compression: CompressionType) -> Result<()> {
trace!("podman push {image}");
fn push(&self, opts: &PushOpts) -> Result<()> {
trace!("PodmanDriver::push({opts:#?})");
trace!("podman push {}", opts.image);
let status = Command::new("podman")
.arg("push")
.arg(format!("--compression-format={compression}"))
.arg(image)
.arg(format!(
"--compression-format={}",
opts.compression_type.unwrap_or_default()
))
.arg(opts.image.as_ref())
.status()?;
if status.success() {
info!("Successfully pushed {image}!");
info!("Successfully pushed {}!", opts.image);
} else {
bail!("Failed to push image {image}")
bail!("Failed to push image {}", opts.image)
}
Ok(())
}
fn login(&self) -> Result<()> {
trace!("PodmanDriver::login()");
let (registry, username, password) =
credentials::get().map(|c| (&c.registry, &c.username, &c.password))?;
@ -118,12 +141,18 @@ impl BuildDriver for PodmanDriver {
}
impl InspectDriver for PodmanDriver {
fn get_metadata(&self, image_name: &str, tag: &str) -> Result<ImageMetadata> {
let url = format!("docker://{image_name}:{tag}");
fn get_metadata(&self, opts: &GetMetadataOpts) -> Result<ImageMetadata> {
trace!("PodmanDriver::get_metadata({opts:#?})");
let url = opts.tag.as_ref().map_or_else(
|| format!("docker://{}", opts.image),
|tag| format!("docker://{}:{tag}", opts.image),
);
trace!("podman run {SKOPEO_IMAGE} inspect {url}");
let output = Command::new("podman")
.arg("run")
.arg("--rm")
.arg(SKOPEO_IMAGE)
.arg("inspect")
.arg(&url)
@ -133,7 +162,7 @@ impl InspectDriver for PodmanDriver {
if output.status.success() {
debug!("Successfully inspected image {url}!");
} else {
bail!("Failed to inspect image {url}")
bail!("Failed to inspect image {url}");
}
Ok(serde_json::from_slice(&output.stdout)?)
}