1use std::{
2 path::{Path, PathBuf},
3 str::FromStr,
4};
5
6use clap::ValueEnum;
7use miette::bail;
8use oci_client::Reference;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq, Hash)]
12pub enum Platform {
13 #[value(name = "linux/amd64")]
14 LinuxAmd64,
15
16 #[value(name = "linux/amd64/v2")]
17 LinuxAmd64V2,
18
19 #[value(name = "linux/arm64")]
20 LinuxArm64,
21
22 #[value(name = "linux/arm")]
23 LinuxArm,
24
25 #[value(name = "linux/arm/v6")]
26 LinuxArmV6,
27
28 #[value(name = "linux/arm/v7")]
29 LinuxArmV7,
30
31 #[value(name = "linux/386")]
32 Linux386,
33
34 #[value(name = "linux/loong64")]
35 LinuxLoong64,
36
37 #[value(name = "linux/mips")]
38 LinuxMips,
39
40 #[value(name = "linux/mipsle")]
41 LinuxMipsle,
42
43 #[value(name = "linux/mips64")]
44 LinuxMips64,
45
46 #[value(name = "linux/mips64le")]
47 LinuxMips64le,
48
49 #[value(name = "linux/ppc64")]
50 LinuxPpc64,
51
52 #[value(name = "linux/ppc64le")]
53 LinuxPpc64le,
54
55 #[value(name = "linux/riscv64")]
56 LinuxRiscv64,
57
58 #[value(name = "linux/s390x")]
59 LinuxS390x,
60}
61
62impl Platform {
63 #[must_use]
65 pub const fn arch(&self) -> &str {
66 match *self {
67 Self::LinuxAmd64 | Self::LinuxAmd64V2 => "amd64",
68 Self::LinuxArm64 => "arm64",
69 Self::LinuxArm | Self::LinuxArmV6 | Self::LinuxArmV7 => "arm",
70 Self::Linux386 => "386",
71 Self::LinuxLoong64 => "loong64",
72 Self::LinuxMips => "mips",
73 Self::LinuxMipsle => "mipsle",
74 Self::LinuxMips64 => "mips64",
75 Self::LinuxMips64le => "mips64le",
76 Self::LinuxPpc64 => "ppc64",
77 Self::LinuxPpc64le => "ppc64le",
78 Self::LinuxRiscv64 => "riscv64",
79 Self::LinuxS390x => "s390x",
80 }
81 }
82
83 #[must_use]
85 pub const fn variant(&self) -> Option<&str> {
86 match *self {
87 Self::LinuxAmd64V2 => Some("v2"),
88 Self::LinuxArmV6 => Some("v6"),
89 Self::LinuxArmV7 => Some("v7"),
90 _ => None,
91 }
92 }
93
94 #[must_use]
96 pub fn tagged_image(&self, image: &Reference) -> Reference {
97 Reference::with_tag(
98 image.registry().to_string(),
99 image.repository().to_string(),
100 format!("{}_{self}", image.tag().unwrap_or("latest")).replace('/', "_"),
101 )
102 }
103
104 #[must_use]
106 pub fn tagged_path(&self, path: &Path) -> Option<PathBuf> {
107 if let Some(file_stem) = path.file_stem()
108 && let Some(extension) = path.extension()
109 {
110 Some(
111 path.with_file_name(format!(
112 "{}_{}",
113 file_stem.display(),
114 self.to_string().replace('/', "_")
115 ))
116 .with_extension(extension),
117 )
118 } else {
119 None
120 }
121 }
122}
123
124impl Default for Platform {
125 fn default() -> Self {
126 match std::env::consts::ARCH {
127 "x86_64" => Self::LinuxAmd64,
128 "aarch64" => Self::LinuxArm64,
129 "x86" => Self::Linux386,
130 "arm" => Self::LinuxArm,
131 "mips" => Self::LinuxMips,
132 "mips32r6" => Self::LinuxMipsle,
133 "mips64" => Self::LinuxMips64,
134 "mips64r6" => Self::LinuxMips64le,
135 "powerpc64" => Self::LinuxPpc64,
136 "riscv64" => Self::LinuxRiscv64,
137 "s390x" => Self::LinuxS390x,
138 "loongarch64" => Self::LinuxLoong64,
139 arch => unimplemented!("Arch {arch} is unsupported"),
140 }
141 }
142}
143
144impl std::fmt::Display for Platform {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 write!(
147 f,
148 "{}",
149 match *self {
150 Self::LinuxAmd64 => "linux/amd64",
151 Self::LinuxAmd64V2 => "linux/amd64/v2",
152 Self::LinuxArm64 => "linux/arm64",
153 Self::LinuxArm => "linux/arm",
154 Self::LinuxArmV6 => "linux/arm/v6",
155 Self::LinuxArmV7 => "linux/arm/v7",
156 Self::Linux386 => "linux/386",
157 Self::LinuxLoong64 => "linux/loong64",
158 Self::LinuxMips => "linux/mips",
159 Self::LinuxMipsle => "linux/mipsle",
160 Self::LinuxMips64 => "linux/mips64",
161 Self::LinuxMips64le => "linux/mips64le",
162 Self::LinuxPpc64 => "linux/ppc64",
163 Self::LinuxPpc64le => "linux/ppc64le",
164 Self::LinuxRiscv64 => "linux/riscv64",
165 Self::LinuxS390x => "linux/s390x",
166 }
167 )
168 }
169}
170
171impl FromStr for Platform {
172 type Err = miette::Error;
173
174 fn from_str(s: &str) -> Result<Self, Self::Err> {
175 Ok(match s {
176 "linux/amd64" => Self::LinuxAmd64,
177 "linux/amd64/v2" => Self::LinuxAmd64V2,
178 "linux/arm64" => Self::LinuxArm64,
179 "linux/arm" => Self::LinuxArm,
180 "linux/arm/v6" => Self::LinuxArmV6,
181 "linux/arm/v7" => Self::LinuxArmV7,
182 "linux/386" => Self::Linux386,
183 "linux/loong64" => Self::LinuxLoong64,
184 "linux/mips" => Self::LinuxMips,
185 "linux/mipsle" => Self::LinuxMipsle,
186 "linux/mips64" => Self::LinuxMips64,
187 "linux/mips64le" => Self::LinuxMips64le,
188 "linux/ppc64" => Self::LinuxPpc64,
189 "linux/ppc64le" => Self::LinuxPpc64le,
190 "linux/riscv64" => Self::LinuxRiscv64,
191 "linux/s390x" => Self::LinuxS390x,
192 platform => bail!("Platform {platform} unsupported"),
193 })
194 }
195}
196
197impl<'de> Deserialize<'de> for Platform {
198 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
199 where
200 D: serde::Deserializer<'de>,
201 {
202 let value = String::deserialize(deserializer)?;
203 value.parse().map_err(serde::de::Error::custom)
204 }
205}
206
207impl Serialize for Platform {
208 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
209 where
210 S: serde::Serializer,
211 {
212 serializer.serialize_str(&self.to_string())
213 }
214}