1use std::collections::BTreeMap;
2use std::path::PathBuf;
3use std::process::Command;
4
5#[derive(Clone, Debug)]
7pub(crate) struct PackBuildCommand {
8 build_cache_volume_name: String,
9 builder: String,
10 buildpacks: Vec<BuildpackReference>,
11 env: BTreeMap<String, String>,
12 image_name: String,
13 launch_cache_volume_name: String,
14 path: PathBuf,
15 pull_policy: PullPolicy,
16 trust_builder: bool,
17 trust_extra_buildpacks: bool,
18}
19
20#[derive(Clone, Debug)]
21pub(crate) enum BuildpackReference {
22 Id(String),
23 Path(PathBuf),
24}
25
26impl From<PathBuf> for BuildpackReference {
27 fn from(path: PathBuf) -> Self {
28 Self::Path(path)
29 }
30}
31
32impl From<String> for BuildpackReference {
33 fn from(id: String) -> Self {
34 Self::Id(id)
35 }
36}
37
38#[derive(Clone, Debug)]
39#[allow(dead_code)]
41pub(crate) enum PullPolicy {
42 Always,
44 IfNotPresent,
46 Never,
48}
49
50impl PackBuildCommand {
51 pub(crate) fn new(
52 builder: impl Into<String>,
53 path: impl Into<PathBuf>,
54 image_name: impl Into<String>,
55 build_cache_volume_name: impl Into<String>,
56 launch_cache_volume_name: impl Into<String>,
57 ) -> Self {
58 Self {
59 build_cache_volume_name: build_cache_volume_name.into(),
60 builder: builder.into(),
61 buildpacks: Vec::new(),
62 env: BTreeMap::new(),
63 image_name: image_name.into(),
64 launch_cache_volume_name: launch_cache_volume_name.into(),
65 path: path.into(),
66 pull_policy: PullPolicy::IfNotPresent,
68 trust_builder: true,
69 trust_extra_buildpacks: true,
70 }
71 }
72
73 pub(crate) fn buildpack(&mut self, b: impl Into<BuildpackReference>) -> &mut Self {
74 self.buildpacks.push(b.into());
75 self
76 }
77
78 pub(crate) fn env(&mut self, k: impl Into<String>, v: impl Into<String>) -> &mut Self {
79 self.env.insert(k.into(), v.into());
80 self
81 }
82}
83
84impl From<PackBuildCommand> for Command {
85 fn from(pack_build_command: PackBuildCommand) -> Self {
86 let mut command = Self::new("pack");
87
88 command.args([
89 "build",
90 &pack_build_command.image_name,
91 "--builder",
92 &pack_build_command.builder,
93 "--cache",
94 &format!(
95 "type=build;format=volume;name={}",
96 pack_build_command.build_cache_volume_name
97 ),
98 "--cache",
99 &format!(
100 "type=launch;format=volume;name={}",
101 pack_build_command.launch_cache_volume_name
102 ),
103 "--path",
104 &pack_build_command.path.to_string_lossy(),
105 "--pull-policy",
106 match pack_build_command.pull_policy {
107 PullPolicy::Always => "always",
108 PullPolicy::IfNotPresent => "if-not-present",
109 PullPolicy::Never => "never",
110 },
111 ]);
112
113 for buildpack in pack_build_command.buildpacks {
114 command.args([
115 "--buildpack",
116 &match buildpack {
117 BuildpackReference::Id(id) => id,
118 BuildpackReference::Path(path_buf) => path_buf.to_string_lossy().to_string(),
119 },
120 ]);
121 }
122
123 for (env_key, env_value) in &pack_build_command.env {
124 command.args(["--env", &format!("{env_key}={env_value}")]);
125 }
126
127 if pack_build_command.trust_builder {
128 command.arg("--trust-builder");
129 }
130
131 if pack_build_command.trust_extra_buildpacks {
132 command.arg("--trust-extra-buildpacks");
133 }
134
135 command
136 }
137}
138
139#[derive(Clone, Debug)]
140pub(crate) struct PackSbomDownloadCommand {
141 image_name: String,
142 output_dir: Option<PathBuf>,
143}
144
145impl PackSbomDownloadCommand {
147 pub(crate) fn new(image_name: impl Into<String>) -> Self {
148 Self {
149 image_name: image_name.into(),
150 output_dir: None,
151 }
152 }
153
154 pub(crate) fn output_dir(&mut self, output_dir: impl Into<PathBuf>) -> &mut Self {
155 self.output_dir = Some(output_dir.into());
156 self
157 }
158}
159
160impl From<PackSbomDownloadCommand> for Command {
161 fn from(pack_command: PackSbomDownloadCommand) -> Self {
162 let mut command = Self::new("pack");
163
164 command.args(["sbom", "download", &pack_command.image_name]);
165
166 if let Some(output_dir) = pack_command.output_dir {
167 command.args(["--output-dir", &output_dir.to_string_lossy()]);
168 }
169
170 command
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use std::ffi::OsStr;
178
179 #[test]
180 fn from_pack_build_command_to_command() {
181 let mut input = PackBuildCommand {
182 build_cache_volume_name: String::from("build-cache-volume"),
183 builder: String::from("builder:20"),
184 buildpacks: vec![
185 BuildpackReference::Id(String::from("libcnb/buildpack1")),
186 BuildpackReference::Path(PathBuf::from("/tmp/buildpack2")),
187 ],
188 env: BTreeMap::from([
189 (String::from("ENV_FOO"), String::from("FOO_VALUE")),
190 (String::from("ENV_BAR"), String::from("WHITESPACE VALUE")),
191 ]),
192 image_name: String::from("my-image"),
193 launch_cache_volume_name: String::from("launch-cache-volume"),
194 path: PathBuf::from("/tmp/foo/bar"),
195 pull_policy: PullPolicy::IfNotPresent,
196 trust_builder: true,
197 trust_extra_buildpacks: true,
198 };
199
200 let command: Command = input.clone().into();
201
202 assert_eq!(command.get_program(), "pack");
203
204 assert_eq!(
205 command.get_args().collect::<Vec<&OsStr>>(),
206 [
207 "build",
208 "my-image",
209 "--builder",
210 "builder:20",
211 "--cache",
212 "type=build;format=volume;name=build-cache-volume",
213 "--cache",
214 "type=launch;format=volume;name=launch-cache-volume",
215 "--path",
216 "/tmp/foo/bar",
217 "--pull-policy",
218 "if-not-present",
219 "--buildpack",
220 "libcnb/buildpack1",
221 "--buildpack",
222 "/tmp/buildpack2",
223 "--env",
224 "ENV_BAR=WHITESPACE VALUE",
225 "--env",
226 "ENV_FOO=FOO_VALUE",
227 "--trust-builder",
228 "--trust-extra-buildpacks",
229 ]
230 );
231
232 assert_eq!(command.get_envs().collect::<Vec<_>>(), Vec::new());
233
234 input.trust_builder = false;
236 let command: Command = input.clone().into();
237 assert!(
238 !command
239 .get_args()
240 .any(|arg| arg == OsStr::new("--trust-builder"))
241 );
242 }
243
244 #[test]
245 fn from_pack_sbom_download_command_to_command() {
246 let mut input = PackSbomDownloadCommand {
247 image_name: String::from("my-image"),
248 output_dir: None,
249 };
250
251 let command: Command = input.clone().into();
252
253 assert_eq!(command.get_program(), "pack");
254
255 assert_eq!(
256 command.get_args().collect::<Vec<&OsStr>>(),
257 ["sbom", "download", "my-image"]
258 );
259
260 assert_eq!(command.get_envs().collect::<Vec<_>>(), Vec::new());
261
262 input.output_dir = Some(PathBuf::from("/tmp/sboms"));
264 let command: Command = input.into();
265
266 assert_eq!(command.get_program(), "pack");
267
268 assert_eq!(
269 command.get_args().collect::<Vec<&OsStr>>(),
270 ["sbom", "download", "my-image", "--output-dir", "/tmp/sboms"]
271 );
272
273 assert_eq!(command.get_envs().collect::<Vec<_>>(), Vec::new());
274 }
275}