ostree_ext/container/
deploy.rs1use std::collections::HashSet;
4
5use anyhow::Result;
6use fn_error_context::context;
7use ostree::glib;
8
9use super::store::{gc_image_layers, LayeredImageState};
10use super::{ImageReference, OstreeImageReference};
11use crate::container::store::PrepareResult;
12use crate::keyfileext::KeyFileExt;
13use crate::sysroot::SysrootLock;
14
15pub const ORIGIN_CONTAINER: &str = "container-image-reference";
17
18pub const STATEROOT_DEFAULT: &str = "default";
21
22#[derive(Debug, Default)]
24#[non_exhaustive]
25pub struct DeployOpts<'a> {
26 pub kargs: Option<&'a [&'a str]>,
28 pub target_imgref: Option<&'a OstreeImageReference>,
37
38 pub proxy_cfg: Option<super::store::ImageProxyConfig>,
40
41 pub no_imgref: bool,
46
47 pub no_clean: bool,
49}
50
51#[context("Performing deployment")]
55pub async fn deploy(
56 sysroot: &ostree::Sysroot,
57 stateroot: &str,
58 imgref: &OstreeImageReference,
59 options: Option<DeployOpts<'_>>,
60) -> Result<Box<LayeredImageState>> {
61 let cancellable = ostree::gio::Cancellable::NONE;
62 let options = options.unwrap_or_default();
63 let repo = &sysroot.repo();
64 let merge_deployment = sysroot.merge_deployment(Some(stateroot));
65 let mut imp =
66 super::store::ImageImporter::new(repo, imgref, options.proxy_cfg.unwrap_or_default())
67 .await?;
68 imp.require_bootable();
69 if let Some(target) = options.target_imgref {
70 imp.set_target(target);
71 }
72 if options.no_imgref {
73 imp.set_no_imgref();
74 }
75 let state = match imp.prepare().await? {
76 PrepareResult::AlreadyPresent(r) => r,
77 PrepareResult::Ready(prep) => {
78 if let Some(warning) = prep.deprecated_warning() {
79 crate::cli::print_deprecated_warning(warning).await;
80 }
81
82 imp.import(prep).await?
83 }
84 };
85 let commit = state.merge_commit.as_str();
86 let origin = glib::KeyFile::new();
87 let target_imgref = options.target_imgref.unwrap_or(imgref);
88 origin.set_string("origin", ORIGIN_CONTAINER, &target_imgref.to_string());
89
90 let opts = ostree::SysrootDeployTreeOpts {
91 override_kernel_argv: options.kargs,
92 ..Default::default()
93 };
94
95 if sysroot.booted_deployment().is_some() {
96 sysroot.stage_tree_with_options(
97 Some(stateroot),
98 commit,
99 Some(&origin),
100 merge_deployment.as_ref(),
101 &opts,
102 cancellable,
103 )?;
104 } else {
105 let deployment = &sysroot.deploy_tree_with_options(
106 Some(stateroot),
107 commit,
108 Some(&origin),
109 merge_deployment.as_ref(),
110 Some(&opts),
111 cancellable,
112 )?;
113 let flags = if options.no_clean {
114 ostree::SysrootSimpleWriteDeploymentFlags::NO_CLEAN
115 } else {
116 ostree::SysrootSimpleWriteDeploymentFlags::NONE
117 };
118 sysroot.simple_write_deployment(
119 Some(stateroot),
120 deployment,
121 merge_deployment.as_ref(),
122 flags,
123 cancellable,
124 )?;
125 if !options.no_clean {
126 sysroot.cleanup(cancellable)?;
127 }
128 }
129
130 Ok(state)
131}
132
133fn deployment_origin_container(
135 deploy: &ostree::Deployment,
136) -> Result<Option<OstreeImageReference>> {
137 let origin = deploy
138 .origin()
139 .map(|o| o.optional_string("origin", ORIGIN_CONTAINER))
140 .transpose()?
141 .flatten();
142 let r = origin
143 .map(|v| OstreeImageReference::try_from(v.as_str()))
144 .transpose()?;
145 Ok(r)
146}
147
148pub fn remove_undeployed_images(sysroot: &SysrootLock) -> Result<Vec<ImageReference>> {
154 let repo = &sysroot.repo();
155 let deployment_origins: Result<HashSet<_>> = sysroot
156 .deployments()
157 .into_iter()
158 .filter_map(|deploy| {
159 deployment_origin_container(&deploy)
160 .map(|v| v.map(|v| v.imgref))
161 .transpose()
162 })
163 .collect();
164 let deployment_origins = deployment_origins?;
165 let all_images = super::store::list_images(&sysroot.repo())?
167 .into_iter()
168 .filter_map(|img| ImageReference::try_from(img.as_str()).ok());
169 let mut removed = Vec::new();
170 for image in all_images {
171 if !deployment_origins.contains(&image) {
172 super::store::remove_image(repo, &image)?;
173 removed.push(image);
174 }
175 }
176 Ok(removed)
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct Pruned {
182 pub n_images: u32,
184 pub n_layers: u32,
186 pub n_objects_pruned: u32,
188 pub objsize: u64,
190}
191
192impl Pruned {
193 pub fn is_empty(&self) -> bool {
195 self.n_images == 0 && self.n_layers == 0 && self.n_objects_pruned == 0
196 }
197}
198
199pub fn prune(sysroot: &SysrootLock) -> Result<Pruned> {
201 let repo = &sysroot.repo();
202 let n_images = remove_undeployed_images(sysroot)?.len().try_into().unwrap();
205 let n_layers = gc_image_layers(repo)?;
207 let (_, n_objects_pruned, objsize) = repo.prune(
209 ostree::RepoPruneFlags::REFS_ONLY,
210 0,
211 ostree::gio::Cancellable::NONE,
212 )?;
213 let n_objects_pruned = u32::try_from(n_objects_pruned).unwrap();
215 Ok(Pruned {
216 n_images,
217 n_layers,
218 n_objects_pruned,
219 objsize,
220 })
221}