2020-05-17 21:52:39 +02:00
|
|
|
// This Source Code Form is subject to the terms of
|
|
|
|
|
// the Mozilla Public License, v. 2.0. If a copy of the
|
|
|
|
|
// MPL was not distributed with this file, You can
|
|
|
|
|
// obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
|
|
|
2020-08-06 00:06:18 +02:00
|
|
|
// Source https://docs.oracle.com/cd/E23824_01/html/E21796/pkg-5.html
|
|
|
|
|
|
2020-06-04 19:03:51 +02:00
|
|
|
use crate::digest::Digest;
|
2025-07-22 14:10:37 +02:00
|
|
|
use crate::fmri::Fmri;
|
2023-03-25 12:47:54 +01:00
|
|
|
use crate::payload::{Payload, PayloadError};
|
2025-07-26 12:54:01 +02:00
|
|
|
use diff::Diff;
|
2025-07-26 15:33:39 +02:00
|
|
|
use miette::Diagnostic;
|
2021-04-19 23:38:56 -03:00
|
|
|
use pest::Parser;
|
2022-09-02 23:48:26 +02:00
|
|
|
use pest_derive::Parser;
|
2025-07-26 12:54:01 +02:00
|
|
|
use serde::{Deserialize, Serialize};
|
2023-03-25 12:47:54 +01:00
|
|
|
use std::clone::Clone;
|
2025-07-27 11:30:24 +02:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2023-03-25 12:47:54 +01:00
|
|
|
use std::fs::read_to_string;
|
|
|
|
|
use std::path::Path;
|
2022-09-02 23:48:26 +02:00
|
|
|
use std::result::Result as StdResult;
|
2023-03-25 12:47:54 +01:00
|
|
|
use std::str::FromStr;
|
|
|
|
|
use thiserror::Error;
|
2025-07-29 11:38:36 +02:00
|
|
|
use tracing::debug;
|
2022-09-02 23:48:26 +02:00
|
|
|
|
2025-08-19 11:06:48 +02:00
|
|
|
pub mod executors;
|
|
|
|
|
|
2022-09-02 23:48:26 +02:00
|
|
|
type Result<T> = StdResult<T, ActionError>;
|
|
|
|
|
|
2025-07-26 15:33:39 +02:00
|
|
|
#[derive(Debug, Error, Diagnostic)]
|
2022-09-02 23:48:26 +02:00
|
|
|
pub enum ActionError {
|
2023-03-25 12:47:54 +01:00
|
|
|
#[error(transparent)]
|
2025-07-26 15:33:39 +02:00
|
|
|
#[diagnostic(transparent)]
|
2022-09-02 23:48:26 +02:00
|
|
|
PayloadError(#[from] PayloadError),
|
|
|
|
|
|
2023-03-25 12:47:54 +01:00
|
|
|
#[error(transparent)]
|
2025-07-26 15:33:39 +02:00
|
|
|
#[diagnostic(transparent)]
|
2022-09-02 23:48:26 +02:00
|
|
|
FileError(#[from] FileError),
|
|
|
|
|
|
|
|
|
|
#[error("value {0} is not a boolean")]
|
2025-07-26 15:33:39 +02:00
|
|
|
#[diagnostic(
|
|
|
|
|
code(ips::action_error::invalid_boolean),
|
|
|
|
|
help("Boolean values must be 'true', 'false', 't', or 'f'.")
|
|
|
|
|
)]
|
2022-09-02 23:48:26 +02:00
|
|
|
NotBooleanValue(String),
|
|
|
|
|
|
2023-03-25 12:47:54 +01:00
|
|
|
#[error(transparent)]
|
2025-07-26 15:33:39 +02:00
|
|
|
#[diagnostic(code(ips::action_error::io))]
|
2022-09-02 23:48:26 +02:00
|
|
|
IOError(#[from] std::io::Error),
|
|
|
|
|
|
2023-03-25 12:47:54 +01:00
|
|
|
#[error(transparent)]
|
2025-07-26 15:33:39 +02:00
|
|
|
#[diagnostic(code(ips::action_error::parser))]
|
2023-03-25 12:47:54 +01:00
|
|
|
ParserError(#[from] pest::error::Error<Rule>),
|
2022-09-02 23:48:26 +02:00
|
|
|
}
|
2020-05-25 16:13:22 +02:00
|
|
|
|
2021-04-13 22:20:43 -03:00
|
|
|
pub trait FacetedAction {
|
2025-07-27 11:30:24 +02:00
|
|
|
// Add a facet to the action if the facet is already present, the function returns false.
|
2020-05-25 16:13:22 +02:00
|
|
|
fn add_facet(&mut self, facet: Facet) -> bool;
|
|
|
|
|
|
|
|
|
|
// Remove a facet from the action.
|
|
|
|
|
fn remove_facet(&mut self, facet: Facet) -> bool;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-04 16:09:09 +02:00
|
|
|
#[derive(Debug, Default)]
|
2020-05-25 16:13:22 +02:00
|
|
|
pub struct Action {
|
|
|
|
|
kind: ActionKind,
|
2020-06-04 16:09:09 +02:00
|
|
|
payload: Payload,
|
2021-04-19 23:38:56 -03:00
|
|
|
payload_string: String,
|
2020-05-25 16:13:22 +02:00
|
|
|
properties: Vec<Property>,
|
2021-04-19 09:35:05 -03:00
|
|
|
facets: HashMap<String, Facet>,
|
2020-05-25 16:13:22 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-04 16:09:09 +02:00
|
|
|
impl Action {
|
2023-03-25 12:47:54 +01:00
|
|
|
pub fn new(kind: ActionKind) -> Action {
|
|
|
|
|
Action {
|
2020-06-04 16:09:09 +02:00
|
|
|
kind,
|
|
|
|
|
payload: Payload::default(),
|
2021-04-19 23:38:56 -03:00
|
|
|
payload_string: String::new(),
|
2020-06-04 16:09:09 +02:00
|
|
|
properties: Vec::new(),
|
2021-04-19 09:35:05 -03:00
|
|
|
facets: HashMap::new(),
|
2020-06-04 16:09:09 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-25 16:13:22 +02:00
|
|
|
impl FacetedAction for Action {
|
|
|
|
|
fn add_facet(&mut self, facet: Facet) -> bool {
|
2023-03-25 12:47:54 +01:00
|
|
|
self.facets.insert(facet.name.clone(), facet).is_none()
|
2020-05-25 16:13:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn remove_facet(&mut self, facet: Facet) -> bool {
|
2023-03-25 12:47:54 +01:00
|
|
|
self.facets.remove(&facet.name) == Some(facet)
|
2020-05-25 16:13:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-05-17 21:52:39 +02:00
|
|
|
|
2024-08-15 21:27:00 +02:00
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
2020-05-17 21:52:39 +02:00
|
|
|
pub struct Dir {
|
|
|
|
|
pub path: String,
|
|
|
|
|
pub group: String,
|
|
|
|
|
pub owner: String,
|
|
|
|
|
pub mode: String, //TODO implement as bitmask
|
2020-05-19 22:14:28 +02:00
|
|
|
pub revert_tag: String,
|
|
|
|
|
pub salvage_from: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2021-04-19 09:35:05 -03:00
|
|
|
pub facets: HashMap<String, Facet>,
|
2020-05-19 22:14:28 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-19 23:38:56 -03:00
|
|
|
impl From<Action> for Dir {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut dir = Dir::default();
|
2021-04-20 19:57:47 -03:00
|
|
|
let mut props = act.properties;
|
|
|
|
|
if !act.payload_string.is_empty() {
|
|
|
|
|
let p_str = split_property(act.payload_string);
|
2023-03-25 12:47:54 +01:00
|
|
|
props.push(Property {
|
2021-04-20 19:57:47 -03:00
|
|
|
key: p_str.0,
|
2021-04-24 18:56:12 -03:00
|
|
|
value: p_str.1,
|
2021-04-20 19:57:47 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for prop in props {
|
2021-04-19 23:38:56 -03:00
|
|
|
match prop.key.as_str() {
|
2021-04-24 18:56:12 -03:00
|
|
|
"path" => dir.path = prop.value,
|
|
|
|
|
"owner" => dir.owner = prop.value,
|
|
|
|
|
"group" => dir.group = prop.value,
|
|
|
|
|
"mode" => dir.mode = prop.value,
|
|
|
|
|
"revert-tag" => dir.revert_tag = prop.value,
|
|
|
|
|
"salvage-from" => dir.salvage_from = prop.value,
|
2021-04-19 23:38:56 -03:00
|
|
|
_ => {
|
|
|
|
|
if is_facet(prop.key.clone()) {
|
2021-04-24 18:56:12 -03:00
|
|
|
dir.add_facet(Facet::from_key_value(prop.key, prop.value));
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dir
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-25 16:13:22 +02:00
|
|
|
impl FacetedAction for Dir {
|
|
|
|
|
fn add_facet(&mut self, facet: Facet) -> bool {
|
2023-03-25 12:47:54 +01:00
|
|
|
self.facets.insert(facet.name.clone(), facet).is_none()
|
2020-05-25 16:13:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn remove_facet(&mut self, facet: Facet) -> bool {
|
2023-03-25 12:47:54 +01:00
|
|
|
self.facets.remove(&facet.name) == Some(facet)
|
2020-05-25 16:13:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 21:27:00 +02:00
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
2020-05-21 17:49:51 +02:00
|
|
|
pub struct File {
|
2021-04-19 09:35:05 -03:00
|
|
|
pub payload: Option<Payload>,
|
2020-05-21 17:49:51 +02:00
|
|
|
pub path: String,
|
|
|
|
|
pub group: String,
|
|
|
|
|
pub owner: String,
|
|
|
|
|
pub mode: String, //TODO implement as bitmask
|
|
|
|
|
pub preserve: bool,
|
|
|
|
|
pub overlay: bool,
|
|
|
|
|
pub original_name: String,
|
|
|
|
|
pub revert_tag: String,
|
|
|
|
|
pub sys_attr: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2020-05-21 17:49:51 +02:00
|
|
|
pub properties: Vec<Property>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2021-04-19 09:35:05 -03:00
|
|
|
pub facets: HashMap<String, Facet>,
|
2020-05-21 17:49:51 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 22:20:43 -03:00
|
|
|
impl File {
|
2021-04-19 09:35:05 -03:00
|
|
|
pub fn read_from_path(p: &Path) -> Result<File> {
|
2021-04-13 22:20:43 -03:00
|
|
|
let mut f = File::default();
|
|
|
|
|
match p.to_str() {
|
2021-04-19 09:35:05 -03:00
|
|
|
Some(str) => {
|
|
|
|
|
f.path = str.to_string();
|
|
|
|
|
f.payload = Some(Payload::compute_payload(p)?);
|
2023-03-25 12:47:54 +01:00
|
|
|
}
|
2021-04-13 22:20:43 -03:00
|
|
|
None => return Err(FileError::FilePathIsNoStringError)?,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//TODO group owner mode
|
|
|
|
|
|
|
|
|
|
Ok(f)
|
|
|
|
|
}
|
2021-04-24 23:19:25 -03:00
|
|
|
|
2023-03-25 12:47:54 +01:00
|
|
|
pub fn get_original_path(&self) -> Option<String> {
|
2021-04-24 23:19:25 -03:00
|
|
|
for p in &self.properties {
|
|
|
|
|
if p.key.as_str() == "original-path" {
|
|
|
|
|
return Some(p.value.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
2021-04-13 22:20:43 -03:00
|
|
|
}
|
|
|
|
|
|
2021-04-19 23:38:56 -03:00
|
|
|
impl From<Action> for File {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut file = File::default();
|
|
|
|
|
let mut p = act.payload.clone();
|
2021-04-24 18:56:12 -03:00
|
|
|
let mut props = act.properties;
|
2021-04-19 23:38:56 -03:00
|
|
|
if !act.payload_string.is_empty() {
|
2023-03-25 12:47:54 +01:00
|
|
|
if act.payload_string.contains('/') {
|
|
|
|
|
if act.payload_string.contains('=') {
|
2021-04-24 18:56:12 -03:00
|
|
|
let p_str = split_property(act.payload_string);
|
2023-03-25 12:47:54 +01:00
|
|
|
props.push(Property {
|
2021-04-24 18:56:12 -03:00
|
|
|
key: p_str.0,
|
|
|
|
|
value: p_str.1,
|
|
|
|
|
})
|
|
|
|
|
} else {
|
2023-03-25 12:47:54 +01:00
|
|
|
file.properties.push(Property {
|
2021-04-24 18:56:12 -03:00
|
|
|
key: "original-path".to_string(),
|
2023-03-25 12:47:54 +01:00
|
|
|
value: act.payload_string.replace(['\"', '\\'], ""),
|
2021-04-24 18:56:12 -03:00
|
|
|
});
|
|
|
|
|
}
|
2021-04-19 23:38:56 -03:00
|
|
|
} else {
|
|
|
|
|
p.primary_identifier = Digest::from_str(&act.payload_string).unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-24 18:56:12 -03:00
|
|
|
for prop in props {
|
2021-04-19 23:38:56 -03:00
|
|
|
match prop.key.as_str() {
|
2021-04-24 18:56:12 -03:00
|
|
|
"path" => file.path = prop.value,
|
|
|
|
|
"owner" => file.owner = prop.value,
|
|
|
|
|
"group" => file.group = prop.value,
|
|
|
|
|
"mode" => file.mode = prop.value,
|
|
|
|
|
"revert-tag" => file.revert_tag = prop.value,
|
|
|
|
|
"original_name" => file.original_name = prop.value,
|
|
|
|
|
"sysattr" => file.sys_attr = prop.value,
|
2023-03-25 12:47:54 +01:00
|
|
|
"overlay" => {
|
|
|
|
|
file.overlay = match string_to_bool(&prop.value) {
|
|
|
|
|
Ok(b) => b,
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"preserve" => {
|
|
|
|
|
file.preserve = match string_to_bool(&prop.value) {
|
|
|
|
|
Ok(b) => b,
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
"chash" | "pkg.content-hash" => p
|
|
|
|
|
.additional_identifiers
|
|
|
|
|
.push(Digest::from_str(&prop.value).unwrap()),
|
2021-04-19 23:38:56 -03:00
|
|
|
_ => {
|
|
|
|
|
if is_facet(prop.key.clone()) {
|
2021-04-24 18:56:12 -03:00
|
|
|
file.add_facet(Facet::from_key_value(prop.key, prop.value));
|
2021-04-19 23:38:56 -03:00
|
|
|
} else {
|
2023-03-25 12:47:54 +01:00
|
|
|
file.properties.push(Property {
|
2021-04-20 19:57:47 -03:00
|
|
|
key: prop.key,
|
2021-04-24 18:56:12 -03:00
|
|
|
value: prop.value,
|
2021-04-20 19:57:47 -03:00
|
|
|
});
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-24 18:56:12 -03:00
|
|
|
if p.primary_identifier.hash.is_empty() {
|
|
|
|
|
file.payload = None;
|
|
|
|
|
} else {
|
|
|
|
|
file.payload = Some(p);
|
|
|
|
|
}
|
2021-04-19 23:38:56 -03:00
|
|
|
file
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-25 16:13:22 +02:00
|
|
|
impl FacetedAction for File {
|
|
|
|
|
fn add_facet(&mut self, facet: Facet) -> bool {
|
2023-03-25 12:47:54 +01:00
|
|
|
self.facets.insert(facet.name.clone(), facet).is_none()
|
2020-05-25 16:13:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn remove_facet(&mut self, facet: Facet) -> bool {
|
2023-03-25 12:47:54 +01:00
|
|
|
self.facets.remove(&facet.name) == Some(facet)
|
2020-05-25 16:13:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-26 15:33:39 +02:00
|
|
|
#[derive(Debug, Error, Diagnostic)]
|
2021-04-13 22:20:43 -03:00
|
|
|
pub enum FileError {
|
2022-03-24 19:48:41 -03:00
|
|
|
#[error("file path is not a string")]
|
2025-07-26 15:33:39 +02:00
|
|
|
#[diagnostic(
|
|
|
|
|
code(ips::action_error::file::invalid_path),
|
|
|
|
|
help("The file path must be convertible to a valid string.")
|
|
|
|
|
)]
|
2021-04-13 22:20:43 -03:00
|
|
|
FilePathIsNoStringError,
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-06 00:06:18 +02:00
|
|
|
//TODO implement multiple FMRI for require-any
|
2024-08-15 21:27:00 +02:00
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
2020-08-06 00:06:18 +02:00
|
|
|
pub struct Dependency {
|
2025-07-22 14:10:37 +02:00
|
|
|
pub fmri: Option<Fmri>, // FMRI of the dependency
|
2020-08-06 00:06:18 +02:00
|
|
|
pub dependency_type: String, //TODO make enum
|
2025-07-22 14:10:37 +02:00
|
|
|
pub predicate: Option<Fmri>, // FMRI for conditional dependencies
|
2023-03-25 12:47:54 +01:00
|
|
|
pub root_image: String, //TODO make boolean
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2020-08-06 00:06:18 +02:00
|
|
|
pub optional: Vec<Property>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2021-04-19 09:35:05 -03:00
|
|
|
pub facets: HashMap<String, Facet>,
|
2020-08-06 00:06:18 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-19 23:38:56 -03:00
|
|
|
impl From<Action> for Dependency {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut dep = Dependency::default();
|
2021-04-20 19:57:47 -03:00
|
|
|
let mut props = act.properties;
|
|
|
|
|
if !act.payload_string.is_empty() {
|
|
|
|
|
let p_str = split_property(act.payload_string);
|
2023-03-25 12:47:54 +01:00
|
|
|
props.push(Property {
|
2021-04-20 19:57:47 -03:00
|
|
|
key: p_str.0,
|
2021-04-24 18:56:12 -03:00
|
|
|
value: p_str.1,
|
2021-04-20 19:57:47 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for prop in props {
|
2021-04-19 23:38:56 -03:00
|
|
|
match prop.key.as_str() {
|
2025-07-26 12:54:01 +02:00
|
|
|
"fmri" => match Fmri::parse(&prop.value) {
|
|
|
|
|
Ok(fmri) => dep.fmri = Some(fmri),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
eprintln!("Error parsing FMRI '{}': {}", prop.value, err);
|
|
|
|
|
dep.fmri = None;
|
2025-07-22 14:10:37 +02:00
|
|
|
}
|
|
|
|
|
},
|
2021-04-24 18:56:12 -03:00
|
|
|
"type" => dep.dependency_type = prop.value,
|
2025-07-26 12:54:01 +02:00
|
|
|
"predicate" => match Fmri::parse(&prop.value) {
|
|
|
|
|
Ok(fmri) => dep.predicate = Some(fmri),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
eprintln!("Error parsing predicate FMRI '{}': {}", prop.value, err);
|
|
|
|
|
dep.predicate = None;
|
2025-07-22 14:10:37 +02:00
|
|
|
}
|
|
|
|
|
},
|
2021-04-24 18:56:12 -03:00
|
|
|
"root-image" => dep.root_image = prop.value,
|
2021-04-19 23:38:56 -03:00
|
|
|
_ => {
|
|
|
|
|
if is_facet(prop.key.clone()) {
|
2021-04-24 18:56:12 -03:00
|
|
|
dep.add_facet(Facet::from_key_value(prop.key, prop.value));
|
2021-04-19 23:38:56 -03:00
|
|
|
} else {
|
|
|
|
|
dep.optional.push(prop.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dep
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-06 00:06:18 +02:00
|
|
|
impl FacetedAction for Dependency {
|
|
|
|
|
fn add_facet(&mut self, facet: Facet) -> bool {
|
2023-03-25 12:47:54 +01:00
|
|
|
self.facets.insert(facet.name.clone(), facet).is_none()
|
2020-08-06 00:06:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn remove_facet(&mut self, facet: Facet) -> bool {
|
2023-03-25 12:47:54 +01:00
|
|
|
self.facets.remove(&facet.name) == Some(facet)
|
2020-08-06 00:06:18 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 21:27:00 +02:00
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
2020-05-19 22:14:28 +02:00
|
|
|
pub struct Facet {
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub value: String,
|
2020-05-17 21:52:39 +02:00
|
|
|
}
|
2020-05-17 01:17:19 +02:00
|
|
|
|
2021-04-19 23:38:56 -03:00
|
|
|
impl Facet {
|
|
|
|
|
fn from_key_value(key: String, value: String) -> Facet {
|
2023-03-25 12:47:54 +01:00
|
|
|
Facet {
|
2021-04-19 23:38:56 -03:00
|
|
|
name: get_facet_key(key),
|
|
|
|
|
value,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 21:27:00 +02:00
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
2020-05-17 01:17:19 +02:00
|
|
|
pub struct Attr {
|
2020-05-17 21:52:39 +02:00
|
|
|
pub key: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2020-05-17 21:52:39 +02:00
|
|
|
pub values: Vec<String>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2021-04-19 09:35:05 -03:00
|
|
|
pub properties: HashMap<String, Property>,
|
2020-05-17 21:52:39 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-19 23:38:56 -03:00
|
|
|
impl From<Action> for Attr {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut attr = Attr::default();
|
2021-04-20 19:57:47 -03:00
|
|
|
let mut props = act.properties;
|
|
|
|
|
if !act.payload_string.is_empty() {
|
|
|
|
|
let p_str = split_property(act.payload_string);
|
2023-03-25 12:47:54 +01:00
|
|
|
props.push(Property {
|
2021-04-20 19:57:47 -03:00
|
|
|
key: p_str.0,
|
2021-04-24 18:56:12 -03:00
|
|
|
value: p_str.1,
|
2021-04-20 19:57:47 -03:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for prop in props {
|
2021-04-19 23:38:56 -03:00
|
|
|
match prop.key.as_str() {
|
2021-04-24 18:56:12 -03:00
|
|
|
"name" => attr.key = prop.value,
|
|
|
|
|
"value" => attr.values.push(prop.value),
|
2021-04-19 23:38:56 -03:00
|
|
|
_ => {
|
2023-03-25 12:47:54 +01:00
|
|
|
attr.properties.insert(
|
|
|
|
|
prop.key.clone(),
|
|
|
|
|
Property {
|
|
|
|
|
key: prop.key,
|
|
|
|
|
value: prop.value,
|
|
|
|
|
},
|
|
|
|
|
);
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
attr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 21:27:00 +02:00
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
2021-04-24 18:56:12 -03:00
|
|
|
pub struct License {
|
|
|
|
|
pub payload: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2021-04-24 18:56:12 -03:00
|
|
|
pub properties: HashMap<String, Property>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Action> for License {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut license = License::default();
|
|
|
|
|
if !act.payload_string.is_empty() {
|
|
|
|
|
license.payload = act.payload_string;
|
|
|
|
|
}
|
|
|
|
|
for prop in act.properties {
|
2023-03-25 12:47:54 +01:00
|
|
|
let key = prop.key.as_str();
|
|
|
|
|
{
|
|
|
|
|
license.properties.insert(
|
|
|
|
|
key.to_owned(),
|
|
|
|
|
Property {
|
2021-04-24 18:56:12 -03:00
|
|
|
key: prop.key,
|
|
|
|
|
value: prop.value,
|
2023-03-25 12:47:54 +01:00
|
|
|
},
|
|
|
|
|
);
|
2021-04-24 18:56:12 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
license
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 21:27:00 +02:00
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
2021-04-24 18:56:12 -03:00
|
|
|
pub struct Link {
|
|
|
|
|
pub path: String,
|
|
|
|
|
pub target: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2021-04-24 18:56:12 -03:00
|
|
|
pub properties: HashMap<String, Property>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Action> for Link {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut link = Link::default();
|
|
|
|
|
let mut props = act.properties;
|
|
|
|
|
if !act.payload_string.is_empty() {
|
|
|
|
|
let p_str = split_property(act.payload_string);
|
2023-03-25 12:47:54 +01:00
|
|
|
props.push(Property {
|
2021-04-24 18:56:12 -03:00
|
|
|
key: p_str.0,
|
|
|
|
|
value: p_str.1,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for prop in props {
|
|
|
|
|
match prop.key.as_str() {
|
|
|
|
|
"path" => link.path = prop.value,
|
|
|
|
|
"target" => link.target = prop.value,
|
|
|
|
|
_ => {
|
2023-03-25 12:47:54 +01:00
|
|
|
link.properties.insert(
|
|
|
|
|
prop.key.clone(),
|
|
|
|
|
Property {
|
|
|
|
|
key: prop.key,
|
|
|
|
|
value: prop.value,
|
|
|
|
|
},
|
|
|
|
|
);
|
2021-04-24 18:56:12 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
link
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-27 11:30:24 +02:00
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
|
|
|
|
pub struct User {
|
|
|
|
|
pub username: String,
|
|
|
|
|
pub uid: String,
|
|
|
|
|
pub group: String,
|
|
|
|
|
pub home_dir: String,
|
|
|
|
|
pub login_shell: String,
|
|
|
|
|
pub password: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashSet::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub services: HashSet<String>,
|
|
|
|
|
pub gcos_field: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub properties: HashMap<String, Property>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub facets: HashMap<String, Facet>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Action> for User {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut user = User::default();
|
|
|
|
|
let mut props = act.properties;
|
|
|
|
|
if !act.payload_string.is_empty() {
|
|
|
|
|
let p_str = split_property(act.payload_string);
|
|
|
|
|
props.push(Property {
|
|
|
|
|
key: p_str.0,
|
|
|
|
|
value: p_str.1,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for prop in props {
|
|
|
|
|
match prop.key.as_str() {
|
|
|
|
|
"username" => user.username = prop.value,
|
|
|
|
|
"uid" => user.uid = prop.value,
|
|
|
|
|
"group" => user.group = prop.value,
|
|
|
|
|
"home-dir" => user.home_dir = prop.value,
|
|
|
|
|
"login-shell" => user.login_shell = prop.value,
|
|
|
|
|
"password" => user.password = prop.value,
|
|
|
|
|
"gcos-field" => user.gcos_field = prop.value,
|
|
|
|
|
"ftpuser" => {
|
|
|
|
|
// Parse ftpuser property into services
|
|
|
|
|
match string_to_bool(&prop.value) {
|
|
|
|
|
// If it's a boolean value (backward compatibility)
|
2025-07-27 15:22:49 +02:00
|
|
|
Ok(true) => {
|
|
|
|
|
user.services.insert("ftp".to_string());
|
|
|
|
|
}
|
|
|
|
|
Ok(false) => {} // No services if false
|
2025-07-27 11:30:24 +02:00
|
|
|
// If the value not a boolean, treat as a comma-separated list of services
|
|
|
|
|
_ => {
|
|
|
|
|
for service in prop.value.split(',') {
|
|
|
|
|
let service = service.trim();
|
|
|
|
|
if !service.is_empty() {
|
|
|
|
|
user.services.insert(service.to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
if is_facet(prop.key.clone()) {
|
|
|
|
|
user.add_facet(Facet::from_key_value(prop.key, prop.value));
|
|
|
|
|
} else {
|
|
|
|
|
user.properties.insert(
|
|
|
|
|
prop.key.clone(),
|
|
|
|
|
Property {
|
|
|
|
|
key: prop.key,
|
|
|
|
|
value: prop.value,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
user
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FacetedAction for User {
|
|
|
|
|
fn add_facet(&mut self, facet: Facet) -> bool {
|
|
|
|
|
self.facets.insert(facet.name.clone(), facet).is_none()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn remove_facet(&mut self, facet: Facet) -> bool {
|
|
|
|
|
self.facets.remove(&facet.name) == Some(facet)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
|
|
|
|
pub struct Group {
|
|
|
|
|
pub groupname: String,
|
|
|
|
|
pub gid: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub properties: HashMap<String, Property>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub facets: HashMap<String, Facet>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Action> for Group {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut group = Group::default();
|
|
|
|
|
let mut props = act.properties;
|
|
|
|
|
if !act.payload_string.is_empty() {
|
|
|
|
|
let p_str = split_property(act.payload_string);
|
|
|
|
|
props.push(Property {
|
|
|
|
|
key: p_str.0,
|
|
|
|
|
value: p_str.1,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for prop in props {
|
|
|
|
|
match prop.key.as_str() {
|
|
|
|
|
"groupname" => group.groupname = prop.value,
|
|
|
|
|
"gid" => group.gid = prop.value,
|
|
|
|
|
_ => {
|
|
|
|
|
if is_facet(prop.key.clone()) {
|
|
|
|
|
group.add_facet(Facet::from_key_value(prop.key, prop.value));
|
|
|
|
|
} else {
|
|
|
|
|
group.properties.insert(
|
|
|
|
|
prop.key.clone(),
|
|
|
|
|
Property {
|
|
|
|
|
key: prop.key,
|
|
|
|
|
value: prop.value,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
group
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FacetedAction for Group {
|
|
|
|
|
fn add_facet(&mut self, facet: Facet) -> bool {
|
|
|
|
|
self.facets.insert(facet.name.clone(), facet).is_none()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn remove_facet(&mut self, facet: Facet) -> bool {
|
|
|
|
|
self.facets.remove(&facet.name) == Some(facet)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
|
|
|
|
pub struct Driver {
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub class: String,
|
|
|
|
|
pub perms: String,
|
|
|
|
|
pub clone_perms: String,
|
|
|
|
|
pub alias: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub properties: HashMap<String, Property>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub facets: HashMap<String, Facet>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Action> for Driver {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut driver = Driver::default();
|
|
|
|
|
let mut props = act.properties;
|
|
|
|
|
if !act.payload_string.is_empty() {
|
|
|
|
|
let p_str = split_property(act.payload_string);
|
|
|
|
|
props.push(Property {
|
|
|
|
|
key: p_str.0,
|
|
|
|
|
value: p_str.1,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for prop in props {
|
|
|
|
|
match prop.key.as_str() {
|
|
|
|
|
"name" => driver.name = prop.value,
|
|
|
|
|
"class" => driver.class = prop.value,
|
|
|
|
|
"perms" => driver.perms = prop.value,
|
|
|
|
|
"clone_perms" => driver.clone_perms = prop.value,
|
|
|
|
|
"alias" => driver.alias = prop.value,
|
|
|
|
|
_ => {
|
|
|
|
|
if is_facet(prop.key.clone()) {
|
|
|
|
|
driver.add_facet(Facet::from_key_value(prop.key, prop.value));
|
|
|
|
|
} else {
|
|
|
|
|
driver.properties.insert(
|
|
|
|
|
prop.key.clone(),
|
|
|
|
|
Property {
|
|
|
|
|
key: prop.key,
|
|
|
|
|
value: prop.value,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
driver
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FacetedAction for Driver {
|
|
|
|
|
fn add_facet(&mut self, facet: Facet) -> bool {
|
|
|
|
|
self.facets.insert(facet.name.clone(), facet).is_none()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn remove_facet(&mut self, facet: Facet) -> bool {
|
|
|
|
|
self.facets.remove(&facet.name) == Some(facet)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
|
|
|
|
pub struct Legacy {
|
|
|
|
|
pub arch: String,
|
|
|
|
|
pub category: String,
|
|
|
|
|
pub desc: String,
|
|
|
|
|
pub hotline: String,
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub pkg: String,
|
|
|
|
|
pub vendor: String,
|
|
|
|
|
pub version: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub properties: HashMap<String, Property>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Action> for Legacy {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut legacy = Legacy::default();
|
|
|
|
|
let mut props = act.properties;
|
|
|
|
|
if !act.payload_string.is_empty() {
|
|
|
|
|
let p_str = split_property(act.payload_string);
|
|
|
|
|
props.push(Property {
|
|
|
|
|
key: p_str.0,
|
|
|
|
|
value: p_str.1,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for prop in props {
|
|
|
|
|
match prop.key.as_str() {
|
|
|
|
|
"arch" => legacy.arch = prop.value,
|
|
|
|
|
"category" => legacy.category = prop.value,
|
|
|
|
|
"desc" => legacy.desc = prop.value,
|
|
|
|
|
"hotline" => legacy.hotline = prop.value,
|
|
|
|
|
"name" => legacy.name = prop.value,
|
|
|
|
|
"pkg" => legacy.pkg = prop.value,
|
|
|
|
|
"vendor" => legacy.vendor = prop.value,
|
|
|
|
|
"version" => legacy.version = prop.value,
|
|
|
|
|
_ => {
|
|
|
|
|
legacy.properties.insert(
|
|
|
|
|
prop.key.clone(),
|
|
|
|
|
Property {
|
|
|
|
|
key: prop.key,
|
|
|
|
|
value: prop.value,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
legacy
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
|
|
|
|
pub struct Transform {
|
|
|
|
|
pub transform_type: String,
|
|
|
|
|
pub pattern: String,
|
|
|
|
|
pub match_type: String,
|
|
|
|
|
pub operation: String,
|
|
|
|
|
pub value: String,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "HashMap::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub properties: HashMap<String, Property>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Action> for Transform {
|
|
|
|
|
fn from(act: Action) -> Self {
|
|
|
|
|
let mut transform = Transform::default();
|
|
|
|
|
let mut props = act.properties;
|
|
|
|
|
if !act.payload_string.is_empty() {
|
|
|
|
|
let p_str = split_property(act.payload_string);
|
|
|
|
|
props.push(Property {
|
|
|
|
|
key: p_str.0,
|
|
|
|
|
value: p_str.1,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for prop in props {
|
|
|
|
|
match prop.key.as_str() {
|
|
|
|
|
"type" => transform.transform_type = prop.value,
|
|
|
|
|
"pattern" => transform.pattern = prop.value,
|
|
|
|
|
"match_type" => transform.match_type = prop.value,
|
|
|
|
|
"operation" => transform.operation = prop.value,
|
|
|
|
|
"value" => transform.value = prop.value,
|
|
|
|
|
_ => {
|
|
|
|
|
transform.properties.insert(
|
|
|
|
|
prop.key.clone(),
|
|
|
|
|
Property {
|
|
|
|
|
key: prop.key,
|
|
|
|
|
value: prop.value,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
transform
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 21:27:00 +02:00
|
|
|
#[derive(Hash, Eq, PartialEq, Debug, Default, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
2020-05-17 21:52:39 +02:00
|
|
|
pub struct Property {
|
|
|
|
|
pub key: String,
|
|
|
|
|
pub value: String,
|
2020-05-17 01:17:19 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-15 21:27:00 +02:00
|
|
|
#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, Diff)]
|
|
|
|
|
#[diff(attr(
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
))]
|
2020-05-17 01:17:19 +02:00
|
|
|
pub struct Manifest {
|
2025-07-29 11:38:36 +02:00
|
|
|
// Don't skip serializing attributes, as they contain critical information like pkg.fmri
|
|
|
|
|
#[serde(default)]
|
2020-05-17 21:52:39 +02:00
|
|
|
pub attributes: Vec<Attr>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2020-05-19 22:14:28 +02:00
|
|
|
pub directories: Vec<Dir>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2020-05-21 17:49:51 +02:00
|
|
|
pub files: Vec<File>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2020-08-06 00:06:18 +02:00
|
|
|
pub dependencies: Vec<Dependency>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2021-04-24 18:56:12 -03:00
|
|
|
pub licenses: Vec<License>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2023-03-25 12:47:54 +01:00
|
|
|
pub links: Vec<Link>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub users: Vec<User>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub groups: Vec<Group>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub drivers: Vec<Driver>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub legacies: Vec<Legacy>,
|
2025-07-29 11:38:36 +02:00
|
|
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
2025-07-27 11:30:24 +02:00
|
|
|
pub transforms: Vec<Transform>,
|
2020-05-17 01:17:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Manifest {
|
|
|
|
|
pub fn new() -> Manifest {
|
2023-03-25 12:47:54 +01:00
|
|
|
Manifest {
|
2020-05-17 21:52:39 +02:00
|
|
|
attributes: Vec::new(),
|
2020-05-19 22:14:28 +02:00
|
|
|
directories: Vec::new(),
|
2020-05-21 17:49:51 +02:00
|
|
|
files: Vec::new(),
|
2020-08-06 00:06:18 +02:00
|
|
|
dependencies: Vec::new(),
|
2021-04-24 18:56:12 -03:00
|
|
|
licenses: Vec::new(),
|
|
|
|
|
links: Vec::new(),
|
2025-07-27 11:30:24 +02:00
|
|
|
users: Vec::new(),
|
|
|
|
|
groups: Vec::new(),
|
|
|
|
|
drivers: Vec::new(),
|
|
|
|
|
legacies: Vec::new(),
|
|
|
|
|
transforms: Vec::new(),
|
2023-03-25 12:47:54 +01:00
|
|
|
}
|
2020-05-17 01:17:19 +02:00
|
|
|
}
|
2021-04-13 22:20:43 -03:00
|
|
|
|
|
|
|
|
pub fn add_file(&mut self, f: File) {
|
|
|
|
|
self.files.push(f);
|
|
|
|
|
}
|
2021-04-19 09:35:05 -03:00
|
|
|
|
2021-04-19 23:38:56 -03:00
|
|
|
fn add_action(&mut self, act: Action) {
|
|
|
|
|
match act.kind {
|
|
|
|
|
ActionKind::Attr => {
|
|
|
|
|
self.attributes.push(act.into());
|
|
|
|
|
}
|
|
|
|
|
ActionKind::Dir => {
|
|
|
|
|
self.directories.push(act.into());
|
|
|
|
|
}
|
|
|
|
|
ActionKind::File => {
|
|
|
|
|
self.files.push(act.into());
|
|
|
|
|
}
|
|
|
|
|
ActionKind::Dependency => {
|
|
|
|
|
self.dependencies.push(act.into());
|
|
|
|
|
}
|
|
|
|
|
ActionKind::User => {
|
2025-07-27 11:30:24 +02:00
|
|
|
self.users.push(act.into());
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
ActionKind::Group => {
|
2025-07-27 11:30:24 +02:00
|
|
|
self.groups.push(act.into());
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
ActionKind::Driver => {
|
2025-07-27 11:30:24 +02:00
|
|
|
self.drivers.push(act.into());
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
ActionKind::License => {
|
2021-04-24 18:56:12 -03:00
|
|
|
self.licenses.push(act.into());
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
ActionKind::Link => {
|
2021-04-24 18:56:12 -03:00
|
|
|
self.links.push(act.into());
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
ActionKind::Legacy => {
|
2025-07-27 11:30:24 +02:00
|
|
|
self.legacies.push(act.into());
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
ActionKind::Transform => {
|
2025-07-27 11:30:24 +02:00
|
|
|
self.transforms.push(act.into());
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
2023-03-25 12:47:54 +01:00
|
|
|
ActionKind::Unknown { action } => {
|
2021-04-24 18:56:12 -03:00
|
|
|
panic!("action {:?} not known", action)
|
2023-03-25 12:47:54 +01:00
|
|
|
}
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-24 19:48:41 -03:00
|
|
|
pub fn parse_file<P: AsRef<Path>>(f: P) -> Result<Manifest> {
|
|
|
|
|
let content = read_to_string(f)?;
|
2025-07-27 15:22:49 +02:00
|
|
|
|
2025-07-26 15:33:39 +02:00
|
|
|
// Try to parse as JSON first
|
|
|
|
|
match serde_json::from_str::<Manifest>(&content) {
|
|
|
|
|
Ok(manifest) => Ok(manifest),
|
2025-07-29 11:38:36 +02:00
|
|
|
Err(err) => {
|
2025-12-22 20:10:17 +01:00
|
|
|
debug!(
|
|
|
|
|
"Manifest::parse_file: Error in JSON deserialization: {}. Continuing with mtree like format parsing",
|
|
|
|
|
err
|
|
|
|
|
);
|
2025-07-26 15:33:39 +02:00
|
|
|
// If JSON parsing fails, fall back to string format
|
|
|
|
|
Manifest::parse_string(content)
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn parse_string(content: String) -> Result<Manifest> {
|
|
|
|
|
let mut m = Manifest::new();
|
|
|
|
|
|
|
|
|
|
let pairs = ManifestParser::parse(Rule::manifest, &content)?;
|
|
|
|
|
|
|
|
|
|
for p in pairs {
|
|
|
|
|
match p.as_rule() {
|
|
|
|
|
Rule::manifest => {
|
|
|
|
|
for manifest in p.clone().into_inner() {
|
|
|
|
|
match manifest.as_rule() {
|
|
|
|
|
Rule::action => {
|
|
|
|
|
let mut act = Action::default();
|
|
|
|
|
for action in manifest.clone().into_inner() {
|
|
|
|
|
match action.as_rule() {
|
|
|
|
|
Rule::action_name => {
|
|
|
|
|
act.kind = get_action_kind(action.as_str());
|
|
|
|
|
}
|
|
|
|
|
Rule::payload => {
|
2023-03-25 12:47:54 +01:00
|
|
|
act.payload_string = action.as_str().to_owned();
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
Rule::property => {
|
|
|
|
|
let mut property = Property::default();
|
|
|
|
|
for prop in action.clone().into_inner() {
|
|
|
|
|
match prop.as_rule() {
|
|
|
|
|
Rule::property_name => {
|
2023-03-25 12:47:54 +01:00
|
|
|
property.key = prop.as_str().to_owned();
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
Rule::property_value => {
|
2025-12-22 20:10:17 +01:00
|
|
|
let str_val: String =
|
|
|
|
|
prop.as_str().to_owned();
|
|
|
|
|
property.value =
|
|
|
|
|
str_val.replace(['\"', '\\'], "");
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
2025-12-22 20:10:17 +01:00
|
|
|
_ => panic!(
|
|
|
|
|
"unexpected rule {:?} inside action expected property_name or property_value",
|
|
|
|
|
prop.as_rule()
|
|
|
|
|
),
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
act.properties.push(property);
|
|
|
|
|
}
|
|
|
|
|
Rule::EOI => (),
|
2025-12-22 20:10:17 +01:00
|
|
|
_ => panic!(
|
|
|
|
|
"unexpected rule {:?} inside action expected payload, property, action_name",
|
|
|
|
|
action.as_rule()
|
|
|
|
|
),
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m.add_action(act);
|
|
|
|
|
}
|
|
|
|
|
Rule::EOI => (),
|
2021-04-24 18:56:12 -03:00
|
|
|
Rule::transform => (),
|
2023-03-25 12:47:54 +01:00
|
|
|
_ => panic!(
|
|
|
|
|
"unexpected rule {:?} inside manifest expected action",
|
|
|
|
|
manifest.as_rule()
|
|
|
|
|
),
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-04 23:58:43 -03:00
|
|
|
Rule::WHITESPACE => (),
|
2023-03-25 12:47:54 +01:00
|
|
|
_ => panic!(
|
|
|
|
|
"unexpected rule {:?} inside pair expected manifest",
|
|
|
|
|
p.as_rule()
|
|
|
|
|
),
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
}
|
2021-04-19 09:35:05 -03:00
|
|
|
|
2021-04-19 23:38:56 -03:00
|
|
|
Ok(m)
|
2021-04-19 09:35:05 -03:00
|
|
|
}
|
2020-05-17 01:17:19 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-25 16:13:22 +02:00
|
|
|
#[derive(Debug)]
|
2021-04-13 22:20:43 -03:00
|
|
|
pub enum ActionKind {
|
2020-05-17 21:52:39 +02:00
|
|
|
Attr,
|
|
|
|
|
Dir,
|
|
|
|
|
File,
|
|
|
|
|
Dependency,
|
|
|
|
|
User,
|
|
|
|
|
Group,
|
|
|
|
|
Driver,
|
|
|
|
|
License,
|
|
|
|
|
Link,
|
2020-05-18 16:57:21 +02:00
|
|
|
Legacy,
|
2023-03-25 12:47:54 +01:00
|
|
|
Unknown { action: String },
|
2021-04-19 09:35:05 -03:00
|
|
|
Transform,
|
2020-05-17 21:52:39 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-04 19:03:51 +02:00
|
|
|
impl Default for ActionKind {
|
2023-03-25 12:47:54 +01:00
|
|
|
fn default() -> Self {
|
|
|
|
|
ActionKind::Unknown {
|
|
|
|
|
action: String::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-06-04 19:03:51 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-18 16:57:21 +02:00
|
|
|
//TODO Multierror and no failure for these cases
|
2025-07-26 15:33:39 +02:00
|
|
|
#[derive(Debug, Error, Diagnostic)]
|
2020-05-17 01:17:19 +02:00
|
|
|
pub enum ManifestError {
|
2022-03-24 19:48:41 -03:00
|
|
|
#[error("unknown action {action:?} at line {line:?}")]
|
2025-07-26 15:33:39 +02:00
|
|
|
#[diagnostic(
|
|
|
|
|
code(ips::action_error::manifest::unknown_action),
|
|
|
|
|
help("Check the action name and make sure it's one of the supported action types.")
|
|
|
|
|
)]
|
2023-03-25 12:47:54 +01:00
|
|
|
UnknownAction { line: usize, action: String },
|
2025-07-27 15:22:49 +02:00
|
|
|
|
2022-03-24 19:48:41 -03:00
|
|
|
#[error("action string \"{action:?}\" at line {line:?} is invalid: {message:?}")]
|
2025-07-26 15:33:39 +02:00
|
|
|
#[diagnostic(
|
|
|
|
|
code(ips::action_error::manifest::invalid_action),
|
|
|
|
|
help("Check the action format and fix the syntax error.")
|
|
|
|
|
)]
|
2020-05-19 22:14:28 +02:00
|
|
|
InvalidAction {
|
|
|
|
|
line: usize,
|
|
|
|
|
action: String,
|
|
|
|
|
message: String,
|
|
|
|
|
},
|
2020-05-17 01:17:19 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-19 09:35:05 -03:00
|
|
|
#[derive(Parser)]
|
2021-04-19 21:34:40 -03:00
|
|
|
#[grammar = "actions/manifest.pest"]
|
2021-04-19 09:35:05 -03:00
|
|
|
struct ManifestParser;
|
|
|
|
|
|
2021-04-19 23:38:56 -03:00
|
|
|
fn get_action_kind(act: &str) -> ActionKind {
|
2023-03-25 12:47:54 +01:00
|
|
|
match act {
|
2021-04-19 23:38:56 -03:00
|
|
|
"set" => ActionKind::Attr,
|
|
|
|
|
"depend" => ActionKind::Dependency,
|
|
|
|
|
"dir" => ActionKind::Dir,
|
|
|
|
|
"file" => ActionKind::File,
|
|
|
|
|
"license" => ActionKind::License,
|
|
|
|
|
"hardlink" => ActionKind::Link,
|
|
|
|
|
"link" => ActionKind::Link,
|
|
|
|
|
"driver" => ActionKind::Driver,
|
|
|
|
|
"group" => ActionKind::Group,
|
|
|
|
|
"user" => ActionKind::User,
|
|
|
|
|
"legacy" => ActionKind::Legacy,
|
|
|
|
|
"<transform" => ActionKind::Transform,
|
2023-03-25 12:47:54 +01:00
|
|
|
_ => ActionKind::Unknown { action: act.into() },
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_facet(s: String) -> bool {
|
|
|
|
|
s.starts_with("facet.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_facet_key(facet_string: String) -> String {
|
2023-03-25 12:47:54 +01:00
|
|
|
match facet_string.find('.') {
|
|
|
|
|
Some(idx) => facet_string.clone().split_off(idx + 1),
|
|
|
|
|
None => facet_string.clone(),
|
2021-04-19 23:38:56 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-20 19:57:47 -03:00
|
|
|
fn split_property(property_string: String) -> (String, String) {
|
2023-03-25 12:47:54 +01:00
|
|
|
match property_string.find('=') {
|
2021-04-20 19:57:47 -03:00
|
|
|
Some(_) => {
|
2023-03-25 12:47:54 +01:00
|
|
|
let v: Vec<_> = property_string.split('=').collect();
|
2021-04-24 18:56:12 -03:00
|
|
|
(
|
|
|
|
|
String::from(v[0]),
|
2023-03-25 12:47:54 +01:00
|
|
|
String::from(v[1]).replace(['\"', '\\'], ""),
|
2021-04-24 18:56:12 -03:00
|
|
|
)
|
2023-03-25 12:47:54 +01:00
|
|
|
}
|
|
|
|
|
None => (property_string.clone(), String::new()),
|
2021-04-20 19:57:47 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-19 09:35:05 -03:00
|
|
|
fn string_to_bool(orig: &str) -> Result<bool> {
|
2020-05-21 17:49:51 +02:00
|
|
|
match &String::from(orig).trim().to_lowercase()[..] {
|
|
|
|
|
"true" => Ok(true),
|
|
|
|
|
"false" => Ok(false),
|
|
|
|
|
"t" => Ok(true),
|
|
|
|
|
"f" => Ok(false),
|
2023-03-25 12:47:54 +01:00
|
|
|
_ => Err(ActionError::NotBooleanValue(orig.to_owned())),
|
2020-05-21 17:49:51 +02:00
|
|
|
}
|
|
|
|
|
}
|