1use super::{command_mechanism, ActionSpec};
12use sysknife_types::RiskLevel;
13
14const ACTION_STEPS: &str = "/usr/lib/sysknife/action-steps";
17
18pub fn specs() -> Vec<ActionSpec> {
19 vec![
20 get_system_state(),
21 collect_diagnostics(),
22 get_deployment_history(),
23 list_deployments(),
24 update_system(),
25 pin_deployment(0),
26 unpin_deployment(0),
27 rebase_system("fedora/41/x86_64/silverblue"),
28 cleanup_deployments(),
29 reboot_system(),
30 rollback_deployment(),
31 get_kernel_arguments(),
32 set_kernel_arguments(&[], &[]),
33 ]
34}
35
36pub fn get_system_state() -> ActionSpec {
37 ActionSpec {
38 action_name: "GetSystemState",
39 mechanism: command_mechanism("rpm-ostree", ["status", "--json"]),
40 risk_level: RiskLevel::Low,
41 reboot_required: false,
42 rollback_available: false,
43 }
44}
45
46pub fn collect_diagnostics() -> ActionSpec {
47 ActionSpec {
48 action_name: "CollectDiagnostics",
49 mechanism: command_mechanism("journalctl", ["-b", "-n", "500", "--no-pager"]),
50 risk_level: RiskLevel::Low,
51 reboot_required: false,
52 rollback_available: false,
53 }
54}
55
56pub fn get_deployment_history() -> ActionSpec {
57 ActionSpec {
58 action_name: "GetDeploymentHistory",
59 mechanism: command_mechanism("rpm-ostree", ["status", "--json"]),
60 risk_level: RiskLevel::Low,
61 reboot_required: false,
62 rollback_available: false,
63 }
64}
65
66pub fn list_deployments() -> ActionSpec {
67 ActionSpec {
68 action_name: "ListDeployments",
69 mechanism: command_mechanism("rpm-ostree", ["status", "--json"]),
70 risk_level: RiskLevel::Low,
71 reboot_required: false,
72 rollback_available: false,
73 }
74}
75
76pub fn update_system() -> ActionSpec {
77 ActionSpec {
78 action_name: "UpdateSystem",
79 mechanism: command_mechanism("sudo", ["rpm-ostree", "upgrade"]),
80 risk_level: RiskLevel::High,
81 reboot_required: true,
82 rollback_available: true,
83 }
84}
85
86pub fn pin_deployment(index: u32) -> ActionSpec {
87 ActionSpec {
88 action_name: "PinDeployment",
89 mechanism: super::ActionMechanism::Command {
90 program: "sudo",
91 args: vec![
92 "ostree".to_string(),
93 "admin".to_string(),
94 "pin".to_string(),
95 index.to_string(),
96 ],
97 },
98 risk_level: RiskLevel::High,
99 reboot_required: false,
100 rollback_available: false,
101 }
102}
103
104pub fn unpin_deployment(index: u32) -> ActionSpec {
105 ActionSpec {
106 action_name: "UnpinDeployment",
107 mechanism: super::ActionMechanism::Command {
108 program: "sudo",
109 args: vec![
110 "ostree".to_string(),
111 "admin".to_string(),
112 "pin".to_string(),
113 "--unpin".to_string(),
114 index.to_string(),
115 ],
116 },
117 risk_level: RiskLevel::High,
118 reboot_required: false,
119 rollback_available: false,
120 }
121}
122
123pub fn rebase_system(target_ref: &str) -> ActionSpec {
124 ActionSpec {
125 action_name: "RebaseSystem",
126 mechanism: command_mechanism("sudo", [ACTION_STEPS, "rpm-ostree", "rebase", target_ref]),
128 risk_level: RiskLevel::High,
129 reboot_required: true,
130 rollback_available: true,
131 }
132}
133
134pub fn cleanup_deployments() -> ActionSpec {
135 ActionSpec {
136 action_name: "CleanupDeployments",
137 mechanism: command_mechanism("sudo", ["rpm-ostree", "cleanup", "--rollback", "--pending"]),
138 risk_level: RiskLevel::High,
139 reboot_required: false,
140 rollback_available: false,
141 }
142}
143
144pub fn reboot_system() -> ActionSpec {
145 ActionSpec {
146 action_name: "RebootSystem",
147 mechanism: command_mechanism("sudo", ["systemctl", "reboot"]),
148 risk_level: RiskLevel::High,
149 reboot_required: true,
150 rollback_available: false,
151 }
152}
153
154pub fn rollback_deployment() -> ActionSpec {
155 ActionSpec {
156 action_name: "RollbackDeployment",
157 mechanism: command_mechanism("sudo", ["rpm-ostree", "rollback"]),
158 risk_level: RiskLevel::High,
159 reboot_required: true,
160 rollback_available: false,
161 }
162}
163
164pub fn get_kernel_arguments() -> ActionSpec {
165 ActionSpec {
166 action_name: "GetKernelArguments",
167 mechanism: command_mechanism("rpm-ostree", ["kargs"]),
168 risk_level: RiskLevel::Low,
169 reboot_required: false,
170 rollback_available: false,
171 }
172}
173
174pub fn set_kernel_arguments(args_to_add: &[&str], args_to_remove: &[&str]) -> ActionSpec {
179 let pairs = args_to_add
180 .iter()
181 .flat_map(|a| ["append", *a])
182 .chain(args_to_remove.iter().flat_map(|a| ["delete", *a]));
183
184 ActionSpec {
185 action_name: "SetKernelArguments",
186 mechanism: command_mechanism(
187 "sudo",
188 [ACTION_STEPS, "rpm-ostree", "kargs"]
189 .into_iter()
190 .chain(pairs),
191 ),
192 risk_level: RiskLevel::High,
193 reboot_required: true,
194 rollback_available: true,
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::actions::{exclusive_resource, ActionMechanism, ExclusiveResource};
202
203 fn argv(spec: &ActionSpec) -> (&'static str, Vec<&str>) {
204 match &spec.mechanism {
205 ActionMechanism::Command { program, args } => {
206 (*program, args.iter().map(String::as_str).collect())
207 }
208 other => panic!("{} is not a command: {other:?}", spec.action_name),
209 }
210 }
211
212 #[test]
216 fn rebase_and_kargs_go_through_the_helper_verb() {
217 let rebase = rebase_system("fedora:fedora/41/x86_64/silverblue");
218 let (program, args) = argv(&rebase);
219 assert_eq!(program, "sudo");
220 assert_eq!(
221 args,
222 [
223 ACTION_STEPS,
224 "rpm-ostree",
225 "rebase",
226 "fedora:fedora/41/x86_64/silverblue"
227 ]
228 );
229 let kargs = set_kernel_arguments(&["nomodeset", "quiet"], &["rhgb"]);
230 let (program, args) = argv(&kargs);
231 assert_eq!(program, "sudo");
232 assert_eq!(
233 args,
234 [
235 ACTION_STEPS,
236 "rpm-ostree",
237 "kargs",
238 "append",
239 "nomodeset",
240 "append",
241 "quiet",
242 "delete",
243 "rhgb"
244 ]
245 );
246 assert!(
247 args.iter().all(|a| !a.starts_with('-')),
248 "kargs sends an option; the helper writes --append=/--delete= itself: {args:?}"
249 );
250 }
251
252 #[test]
254 fn fixed_rpm_ostree_actions_keep_their_argv() {
255 for (spec, expected) in [
256 (update_system(), vec!["rpm-ostree", "upgrade"]),
257 (rollback_deployment(), vec!["rpm-ostree", "rollback"]),
258 (
259 cleanup_deployments(),
260 vec!["rpm-ostree", "cleanup", "--rollback", "--pending"],
261 ),
262 ] {
263 let (program, args) = argv(&spec);
264 assert_eq!(program, "sudo", "{}", spec.action_name);
265 assert_eq!(args, expected, "{}", spec.action_name);
266 }
267 }
268
269 #[test]
270 fn deployment_actions_still_contend_for_the_rpm_ostree_lock() {
271 for spec in [
272 update_system(),
273 rebase_system("fedora"),
274 cleanup_deployments(),
275 rollback_deployment(),
276 set_kernel_arguments(&["quiet"], &[]),
277 set_kernel_arguments(&[], &[]),
278 ] {
279 assert_eq!(
280 exclusive_resource(&spec),
281 Some(ExclusiveResource::RpmOstree),
282 "{} lost the rpm-ostree lock",
283 spec.action_name
284 );
285 }
286 }
287}