Skip to content

Commit

Permalink
Allow specifying only a tag or subtarget for images in config (#1491)
Browse files Browse the repository at this point in the history
Closes #1169.

Also supports specifying only a hash (`@sha256:...`) or subtarget
(`-centos`).
  • Loading branch information
Emilgardis authored May 14, 2024
2 parents 53a45db + 350e314 commit 78357ea
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 76 deletions.
5 changes: 5 additions & 0 deletions .changes/1491.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"description": "Allow specifying only a tag or subtarget for images in config",
"issues": [1169],
"type": "changed"
}
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ search = "<!-- next-url -->"
replace = "<!-- next-url -->\n\n[Unreleased]: https://github.com/cross-rs/{{crate_name}}/compare/v{{version}}...HEAD"
exactly = 1

[[package.metadata.release.pre-release-replacements]]
file = "docs/config_file.md"
search = "(# Translates to `.*?:).*?(-centos`)"
replace = "${1}{{version}}$2"
exactly = 1

[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }.tar.gz"
bin-dir = "{ bin }{ binary-ext }"
Expand Down
21 changes: 21 additions & 0 deletions docs/config_file.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,27 @@ the default one. Normal Docker behavior applies, so:
- If only `image:latest` is specified, then Docker won't look in Docker Hub.
- If the tag is omitted, then Docker will use the `latest` tag.

If you specify a tag but no image name, `cross` will use the default image with
the tag you provided:

```toml
[target.aarch64-unknown-linux-gnu]
# Translates to `ghcr.io/cross-rs/aarch64-unknown-linux-gnu:edge`
image = ":edge"

[target.x86_64-unknown-linux-musl]
# Translates to `ghcr.io/cross-rs/x86_64-unknown-linux-musl@sha256:77db671d8356a64ae72a3e1415e63f547f26d374fbe3c4762c1cd36c7eac7b99`
image = "@sha256:77db671d8356a64ae72a3e1415e63f547f26d374fbe3c4762c1cd36c7eac7b99"
```

You can also specify a subtarget with no tag nor image name:

```toml
[target.x86_64-unknown-linux-gnu]
# Translates to `ghcr.io/cross-rs/x86_64-unknown-linux-gnu:0.3.0-centos`
image = "-centos"
```

The `image` key can also take the toolchains/platforms supported by the image:

```toml
Expand Down
2 changes: 1 addition & 1 deletion src/bin/commands/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl Run {
}
};

let image = image.to_definite_with(&engine, msg_info);
let image = image.to_definite_with(&engine, msg_info)?;

let paths = docker::DockerPaths::create(&engine, metadata, cwd, toolchain, msg_info)?;
let options = docker::DockerOptions::new(
Expand Down
6 changes: 3 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ impl<T: PartialEq> PartialEq<(Option<T>, Option<T>)> for ConfVal<T> {
}

#[derive(Debug)]
struct Environment(&'static str, Option<HashMap<&'static str, &'static str>>);
pub(crate) struct Environment(&'static str, Option<HashMap<&'static str, &'static str>>);

impl Environment {
fn new(map: Option<HashMap<&'static str, &'static str>>) -> Self {
pub(crate) fn new(map: Option<HashMap<&'static str, &'static str>>) -> Self {
Environment("CROSS", map)
}

Expand Down Expand Up @@ -352,7 +352,7 @@ impl Config {
}

#[cfg(test)]
fn new_with(toml: Option<CrossToml>, env: Environment) -> Self {
pub(crate) fn new_with(toml: Option<CrossToml>, env: Environment) -> Self {
Config { toml, env }
}

Expand Down
12 changes: 6 additions & 6 deletions src/cross_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ where

#[cfg(test)]
mod tests {
use crate::docker::ImagePlatform;
use crate::docker::{ImagePlatform, ImageReference};

use super::*;
use crate::shell;
Expand Down Expand Up @@ -741,7 +741,7 @@ mod tests {
build_std: None,
zig: None,
image: Some(PossibleImage {
name: "test-image".to_owned(),
reference: ImageReference::Name("test-image".to_owned()),
toolchain: vec![ImagePlatform::from_target(
"aarch64-unknown-linux-musl".into(),
)?],
Expand Down Expand Up @@ -773,7 +773,7 @@ mod tests {
enable: None,
version: None,
image: Some(PossibleImage {
name: "zig:local".to_owned(),
reference: ImageReference::Name("zig:local".to_owned()),
toolchain: vec![ImagePlatform::from_target(
"aarch64-unknown-linux-gnu".into(),
)?],
Expand Down Expand Up @@ -939,7 +939,7 @@ mod tests {
[target.target3]
xargo = false
build-std = true
image = "test-image3"
image = "@sha256:test-image3"
[target.target3.env]
volumes = ["VOL3_ARG"]
Expand Down Expand Up @@ -978,7 +978,7 @@ mod tests {
[target.target3]
xargo = false
build-std = true
image = "test-image3"
image = "@sha256:test-image3"
[target.target3.env]
volumes = ["VOL3_ARG"]
Expand Down Expand Up @@ -1042,7 +1042,7 @@ mod tests {
let target3 = &targets[&Target::new_custom("target3")];
assert_eq!(target3.build_std, Some(BuildStd::Bool(true)));
assert_eq!(target3.xargo, Some(false));
assert_eq!(target3.image, Some(p!("test-image3")));
assert_eq!(target3.image, Some(p!("@sha256:test-image3")));
assert_eq!(target3.pre_build, None);
assert_eq!(target3.dockerfile, None);
assert_eq!(target3.env.passthrough, Some(vec![p!("VAR3")]));
Expand Down
80 changes: 69 additions & 11 deletions src/docker/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ use std::str::FromStr;

use serde::{Deserialize, Serialize};

use crate::{errors::*, shell::MessageInfo, TargetTriple};
use crate::{
docker::{CROSS_IMAGE, DEFAULT_IMAGE_VERSION},
errors::*,
shell::MessageInfo,
TargetTriple,
};

use super::Engine;

Expand All @@ -21,18 +26,23 @@ impl std::fmt::Display for Image {

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct PossibleImage {
pub name: String,
#[serde(rename = "name")]
pub reference: ImageReference,
// The toolchain triple the image is built for
pub toolchain: Vec<ImagePlatform>,
}

impl PossibleImage {
pub fn to_definite_with(&self, engine: &Engine, msg_info: &mut MessageInfo) -> Image {
pub fn to_definite_with(&self, engine: &Engine, msg_info: &mut MessageInfo) -> Result<Image> {
let ImageReference::Name(name) = self.reference.clone() else {
eyre::bail!("cannot make definite Image from unqualified PossibleImage");
};

if self.toolchain.is_empty() {
Image {
name: self.name.clone(),
Ok(Image {
name,
platform: ImagePlatform::DEFAULT,
}
})
} else {
let platform = if self.toolchain.len() == 1 {
self.toolchain.first().expect("should contain at least one")
Expand Down Expand Up @@ -71,18 +81,18 @@ impl PossibleImage {
platform
}
};
Image {
Ok(Image {
platform: platform.clone(),
name: self.name.clone(),
}
name,
})
}
}
}

impl<T: AsRef<str>> From<T> for PossibleImage {
fn from(s: T) -> Self {
PossibleImage {
name: s.as_ref().to_owned(),
reference: s.as_ref().to_owned().into(),
toolchain: vec![],
}
}
Expand All @@ -98,9 +108,57 @@ impl FromStr for PossibleImage {

impl std::fmt::Display for PossibleImage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.name)
f.write_str(self.reference.get())
}
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(from = "String", untagged)]
pub enum ImageReference {
/// Partially qualified reference, with or without tag/digest
Name(String),
/// Unqualified reference, only a tag or digest
Identifier(String),
/// Unqualified reference, only a subtarget
Subtarget(String),
}

impl ImageReference {
pub fn get(&self) -> &str {
match self {
Self::Name(s) => s,
Self::Identifier(s) => s,
Self::Subtarget(s) => s,
}
}

pub fn ensure_qualified(&mut self, target_name: &str) {
let image_name = match self {
Self::Name(_) => return,
Self::Identifier(id) => {
format!("{CROSS_IMAGE}/{target_name}{id}")
}
Self::Subtarget(sub) => {
format!("{CROSS_IMAGE}/{target_name}:{DEFAULT_IMAGE_VERSION}{sub}")
}
};

*self = Self::Name(image_name);
}
}

impl From<String> for ImageReference {
fn from(s: String) -> Self {
if s.starts_with('-') {
Self::Subtarget(s)
} else if s.starts_with(':') || s.starts_with('@') {
Self::Identifier(s)
} else {
Self::Name(s)
}
}
}

/// The architecture/platform to use in the image
///
/// <https://github.com/containerd/containerd/blob/release/1.6/platforms/platforms.go#L63>
Expand Down
8 changes: 7 additions & 1 deletion src/docker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ pub use self::engine::*;
pub use self::provided_images::PROVIDED_IMAGES;
pub use self::shared::*;

pub use image::{Architecture, Image, ImagePlatform, Os as ContainerOs, PossibleImage};
pub use image::{
Architecture, Image, ImagePlatform, ImageReference, Os as ContainerOs, PossibleImage,
};

use std::process::ExitStatus;

Expand All @@ -31,6 +33,10 @@ impl ProvidedImage {
pub fn image_name(&self, repository: &str, tag: &str) -> String {
image_name(self.name, self.sub, repository, tag)
}

pub fn default_image_name(&self) -> String {
self.image_name(CROSS_IMAGE, DEFAULT_IMAGE_VERSION)
}
}

pub fn image_name(target: &str, sub: Option<&str>, repository: &str, tag: &str) -> String {
Expand Down
Loading

0 comments on commit 78357ea

Please sign in to comment.