gatehouse_runtime_protocol/lifecycle_v2/
readonly.rs1use super::{
5 native_image::{decode_image_for, image_route_for, ImageFacts},
6 native_info::decode_info_read_only,
7 native_status::{decode_inspect, inspect_route, InspectOutcome, State},
8 observation::{info_route, read_only_api, API},
9 ownership::ExpectedContainer,
10 protected_context::ProtectedContext,
11};
12
13pub struct CapturedImage {
18 facts: ImageFacts,
19 image_bytes: Vec<u8>,
20 config_bytes: Vec<u8>,
21 epoch: String,
22}
23impl std::fmt::Debug for CapturedImage {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("CapturedImage").finish_non_exhaustive()
26 }
27}
28impl CapturedImage {
29 pub fn facts(&self) -> &ImageFacts {
30 &self.facts
31 }
32 pub fn image_bytes(&self) -> &[u8] {
33 &self.image_bytes
34 }
35 pub fn config_bytes(&self) -> &[u8] {
36 &self.config_bytes
37 }
38 pub fn operator_epoch(&self) -> &str {
39 &self.epoch
40 }
41 pub fn match_context(&self, context: &ProtectedContext) -> Result<(), &'static str> {
43 let (expected, bytes) = context.snapshot()?;
44 if bytes != self.config_bytes || expected.operator_epoch != self.epoch {
45 return Err("captured image context mismatch");
46 }
47 Ok(())
48 }
49}
50
51impl ProtectedContext {
52 pub fn capture_image_for_plan<F>(&self, id: &str, get: F) -> Result<CapturedImage, &'static str>
55 where
56 F: FnMut(&str) -> Result<(u16, Vec<u8>), &'static str>,
57 {
58 let (expected, bytes) = self.snapshot()?;
59 let config_bytes = bytes.to_vec();
60 let epoch = expected.operator_epoch.clone();
61 let api = self.selected_read_only_api()?;
62 let route = image_route_for(&api, id)?;
63 let (facts, image_bytes) =
64 self.inspect_checked_with_api(&api, &route, get, |route, status, bytes| {
65 Ok((
66 decode_image_for(&api, id, "GET", route, status, bytes)?,
67 bytes.to_vec(),
68 ))
69 })?;
70 let captured = CapturedImage {
71 facts,
72 image_bytes,
73 config_bytes,
74 epoch,
75 };
76 captured.match_context(self)?;
77 Ok(captured)
78 }
79
80 pub fn inspect_with<F>(&self, id: &str, get: F) -> Result<InspectOutcome, &'static str>
83 where
84 F: FnMut(&str) -> Result<(u16, Vec<u8>), &'static str>,
85 {
86 let api = self.selected_read_only_api()?;
87 if api != API {
88 return Err("5.7 container inspect is not supported");
89 }
90 self.inspect_checked_with_api(&api, &inspect_route(id)?, get, |route, status, bytes| {
91 decode_inspect(id, "GET", route, status, bytes)
92 })
93 }
94 pub fn inspect_owned_with<F>(
97 &self,
98 owner: &ExpectedContainer,
99 get: F,
100 ) -> Result<State, &'static str>
101 where
102 F: FnMut(&str) -> Result<(u16, Vec<u8>), &'static str>,
103 {
104 owner.validate()?;
105 let api = self.selected_read_only_api()?;
106 if api != API {
107 return Err("5.7 ownership inspect is not supported");
108 }
109 self.inspect_checked_with_api(
110 &api,
111 &inspect_route(&owner.id)?,
112 get,
113 |route, status, bytes| owner.match_inspect("GET", route, status, bytes),
114 )
115 }
116 pub fn inspect_image_with<F>(&self, id: &str, get: F) -> Result<ImageFacts, &'static str>
118 where
119 F: FnMut(&str) -> Result<(u16, Vec<u8>), &'static str>,
120 {
121 let api = self.selected_read_only_api()?;
122 let route = image_route_for(&api, id)?;
123 self.inspect_checked_with_api(&api, &route, get, |route, status, bytes| {
124 decode_image_for(&api, id, "GET", route, status, bytes)
125 })
126 }
127 fn selected_read_only_api(&self) -> Result<String, &'static str> {
128 let (expected, _) = self.snapshot()?;
129 let api = expected.observation.selected_api.clone();
130 if !read_only_api(&api) || expected.observation.server_version != api {
131 return Err("unsupported read-only Podman API");
132 }
133 Ok(api)
134 }
135
136 fn inspect_checked_with_api<F, T, D>(
137 &self,
138 api: &str,
139 route: &str,
140 mut get: F,
141 decode: D,
142 ) -> Result<T, &'static str>
143 where
144 F: FnMut(&str) -> Result<(u16, Vec<u8>), &'static str>,
145 D: FnOnce(&str, u16, &[u8]) -> Result<T, &'static str>,
146 {
147 let (expected, _) = self.snapshot()?;
148 if expected.observation.selected_api != api {
149 return Err("native route API differs from protected context");
150 }
151 expected.validate_local_socket_policy_read_only()?;
152 let info = info_route(api)?;
153 let exchange = |get: &mut F, path: &str| {
154 self.revalidate()?;
155 let response = get(path)?;
156 self.revalidate()?;
157 Ok::<_, &'static str>(response)
158 };
159 let (status, bytes) = exchange(&mut get, &info)?;
160 if status != 200 {
161 return Err("initial native info unavailable");
162 }
163 let before = decode_info_read_only(&bytes, expected.observation.socket.clone(), api)?;
164 expected.match_current_operation_read_only(expected, &before, &before)?;
165 let (status, bytes) = exchange(&mut get, route)?;
166 let result = decode(route, status, &bytes)?;
167 let (status, bytes) = exchange(&mut get, &info)?;
168 if status != 200 {
169 return Err("final native info unavailable");
170 }
171 let after = decode_info_read_only(&bytes, expected.observation.socket.clone(), api)?;
172 expected.match_current_operation_read_only(expected, &before, &after)?;
173 self.revalidate()?;
174 Ok(result)
175 }
176}