Skip to content

Commit

Permalink
feat: Permissive chmod (#285)
Browse files Browse the repository at this point in the history
Also renamed an enum variant
  • Loading branch information
taorepoara authored Oct 1, 2024
1 parent bd36635 commit 41b1b09
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 21 deletions.
50 changes: 42 additions & 8 deletions docs/dofigen.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,48 @@
"Cache": {
"title": "Cache",
"type": "object",
"oneOf": [
{
"type": "object",
"required": [
"fromImage"
],
"properties": {
"fromImage": {
"$ref": "#/definitions/ParsableStruct<ImageName>"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"fromBuilder"
],
"properties": {
"fromBuilder": {
"type": "string"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"fromContext"
],
"properties": {
"fromContext": {
"type": [
"string",
"null"
],
"nullable": true
}
},
"additionalProperties": false
}
],
"properties": {
"chmod": {
"default": null,
Expand Down Expand Up @@ -485,14 +527,6 @@
],
"nullable": true
},
"from": {
"default": null,
"type": [
"string",
"null"
],
"nullable": true
},
"id": {
"default": null,
"type": [
Expand Down
4 changes: 2 additions & 2 deletions docs/struct.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ It can be parsed from string.
| `sharing` | "shared" or "private" or "locked" | The sharing strategy of the cache. |
| `from...` | [FromContext](#fromcontext) | The base of the cache mount. |
| `source` | string | Subpath in the from to mount. |
| `chmod` | string | The permissions of the cache. |
| `chmod` | string or integer | The permissions of the cache. |
| `chown` | [User](#user) | The user and group that own the cache. |

## Bind
Expand Down Expand Up @@ -213,7 +213,7 @@ This represents the options of a COPY/ADD instructions.
| --- | --- | --- |
| `target` | string | The target path of the copied files. |
| `chown` | [User](#user) | The user and group that own the copied files. See https://docs.docker.com/reference/dockerfile/#copy---chown---chmod |
| `chmod` | string | The permissions of the copied files. See https://docs.docker.com/reference/dockerfile/#copy---chown---chmod |
| `chmod` | string or integer | The permissions of the copied files. See https://docs.docker.com/reference/dockerfile/#copy---chown---chmod |
| `link` | boolean | Use of the link flag. See https://docs.docker.com/reference/dockerfile/#copy---link |

## Port
Expand Down
63 changes: 63 additions & 0 deletions src/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,21 @@ where
deserializer.deserialize_any(visitor)
}

#[cfg(feature = "permissive")]
pub(crate) fn deserialize_from_optional_string_or_number<'de, D>(
deserializer: D,
) -> Result<Option<Option<String>>, D::Error>
where
D: Deserializer<'de>,
{
let val: Option<StringOrNumber> = Deserialize::deserialize(deserializer)?;

Ok(Some(val.map(|val| match val {
StringOrNumber::String(s) => s,
StringOrNumber::Number(n) => n.to_string(),
})))
}

fn sort_commands<T, P>(a: &VecDeepPatchCommand<T, P>, b: &VecDeepPatchCommand<T, P>) -> Ordering
where
T: Clone + From<P>,
Expand Down Expand Up @@ -2380,5 +2395,53 @@ mod test {
)
}
}

#[cfg(feature = "permissive")]
mod from_optional_string_or_number {
use super::*;

#[derive(Deserialize, Debug, Clone, PartialEq, Default)]
struct TestStruct {
#[serde(
deserialize_with = "deserialize_from_optional_string_or_number",
default
)]
pub test: Option<Option<String>>,
}

#[test]
fn string() {
let ret: TestStruct = serde_yaml::from_str("test: \"123\"").unwrap();
assert_eq_sorted!(
ret,
TestStruct {
test: Some(Some("123".into()))
}
)
}

#[test]
fn number() {
let ret: TestStruct = serde_yaml::from_str("test: 123").unwrap();
assert_eq_sorted!(
ret,
TestStruct {
test: Some(Some("123".into()))
}
)
}

#[test]
fn null() {
let ret: TestStruct = serde_yaml::from_str("test: null").unwrap();
assert_eq_sorted!(ret, TestStruct { test: Some(None) })
}

#[test]
fn absent() {
let ret: TestStruct = serde_yaml::from_str("").unwrap();
assert_eq_sorted!(ret, TestStruct { test: None })
}
}
}
}
8 changes: 4 additions & 4 deletions src/dockerfile_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct DockerfileInsctruction {

#[derive(Debug, Clone, PartialEq)]
pub enum InstructionOption {
NameOnly(String),
Flag(String),
WithValue(String, String),
WithOptions(String, Vec<InstructionOptionOption>),
}
Expand Down Expand Up @@ -82,7 +82,7 @@ impl DockerfileContent for DockerfileInsctruction {
impl DockerfileContent for InstructionOption {
fn generate_content(&self) -> String {
match self {
InstructionOption::NameOnly(name) => format!("--{}", name),
InstructionOption::Flag(name) => format!("--{}", name),
InstructionOption::WithValue(name, value) => format!("--{}={}", name, value),
InstructionOption::WithOptions(name, options) => format!(
"--{}={}",
Expand Down Expand Up @@ -123,7 +123,7 @@ mod test {
command: "RUN".into(),
content: "echo 'Hello, World!'".into(),
options: vec![
InstructionOption::NameOnly("arg1".into()),
InstructionOption::Flag("arg1".into()),
InstructionOption::WithValue("arg2".into(), "value2".into()),
],
};
Expand All @@ -147,7 +147,7 @@ mod test {

#[test]
fn test_generate_content_name_only_option() {
let option = InstructionOption::NameOnly("arg1".into());
let option = InstructionOption::Flag("arg1".into());
assert_eq_sorted!(option.generate_content(), "--arg1");
}

Expand Down
38 changes: 37 additions & 1 deletion src/dofigen_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ pub struct Cache {
pub source: Option<String>,

/// The permissions of the cache
#[cfg_attr(
feature = "permissive",
patch(attribute(serde(
deserialize_with = "deserialize_from_optional_string_or_number",
default
)))
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub chmod: Option<String>,

Expand Down Expand Up @@ -507,6 +514,13 @@ pub struct CopyOptions {

/// The permissions of the copied files
/// See https://docs.docker.com/reference/dockerfile/#copy---chown---chmod
#[cfg_attr(
feature = "permissive",
patch(attribute(serde(
deserialize_with = "deserialize_from_optional_string_or_number",
default
)))
)]
#[serde(skip_serializing_if = "Option::is_none")]
pub chmod: Option<String>,

Expand Down Expand Up @@ -873,7 +887,6 @@ mod test {
);
}

// #[ignore = "Not managed yet by serde because of multilevel flatten: https://serde.rs/field-attrs.html#flatten"]
#[test]
fn copy_simple() {
let json_data = r#"{
Expand Down Expand Up @@ -903,6 +916,29 @@ mod test {
);
}

#[cfg(feature = "permissive")]
#[test]
fn copy_chmod_int() {
let json_data = r#"{
"paths": ["file1.txt"],
"chmod": 755
}"#;

let copy_resource: CopyPatch = serde_yaml::from_str(json_data).unwrap();

assert_eq_sorted!(
copy_resource,
CopyPatch {
paths: Some(vec!["file1.txt".into()].into_patch()),
options: Some(CopyOptionsPatch {
chmod: Some(Some("755".into())),
..Default::default()
}),
..Default::default()
}
);
}

#[cfg(feature = "permissive")]
#[test]
fn deserialize_copy_from_str() {
Expand Down
35 changes: 34 additions & 1 deletion src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ fn add_copy_options(
inst_options.push(InstructionOption::WithValue("chmod".into(), chmod.into()));
}
if *copy_options.link.as_ref().unwrap_or(&true) {
inst_options.push(InstructionOption::NameOnly("link".into()));
inst_options.push(InstructionOption::Flag("link".into()));
}
}

Expand Down Expand Up @@ -836,6 +836,39 @@ mod test {
}
}

mod copy {
use super::*;

#[test]
fn with_chmod() {
let copy = Copy {
paths: vec!["/path/to/file".into()],
options: CopyOptions {
target: Some("/app/".into()),
chmod: Some("755".into()),
..Default::default()
},
..Default::default()
};

let lines = copy
.generate_dockerfile_lines(&GenerationContext::default())
.unwrap();

assert_eq_sorted!(
lines,
vec![DockerfileLine::Instruction(DockerfileInsctruction {
command: "COPY".into(),
content: "\"/path/to/file\" \"/app/\"".into(),
options: vec![
InstructionOption::WithValue("chmod".into(), "755".into()),
InstructionOption::Flag("link".into())
],
})]
);
}
}

mod image_name {
use super::*;

Expand Down
10 changes: 7 additions & 3 deletions tests/lib_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ arg:
APP_NAME: template-rust
env:
fprocess: /app
artifacts:
copy:
- fromBuilder: builder
source: /home/rust/src/target/x86_64-unknown-linux-musl/release/${APP_NAME}
paths: /home/rust/src/target/x86_64-unknown-linux-musl/release/${APP_NAME}
target: /app
chmod: "555"
- fromImage: ghcr.io/openfaas/of-watchdog:0.9.6
source: /fwatchdog
paths: /fwatchdog
target: /fwatchdog
chmod: 555
expose: 8080
healthcheck:
interval: 3s
Expand Down Expand Up @@ -100,11 +102,13 @@ ENV fprocess="/app"
COPY \
--from=builder \
--chown=1000:1000 \
--chmod=555 \
--link \
"/home/rust/src/target/x86_64-unknown-linux-musl/release/${APP_NAME}" "/app"
COPY \
--from=ghcr.io/openfaas/of-watchdog:0.9.6 \
--chown=1000:1000 \
--chmod=555 \
--link \
"/fwatchdog" "/fwatchdog"
USER 1000:1000
Expand Down
6 changes: 4 additions & 2 deletions tests/regression_test.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::collections::HashMap;

#[cfg(feature = "permissive")]
use dofigen_lib::*;
#[cfg(feature = "permissive")]
use pretty_assertions_sorted::assert_eq_sorted;
#[cfg(feature = "permissive")]
use std::collections::HashMap;

#[cfg(feature = "permissive")]
#[test]
Expand Down

0 comments on commit 41b1b09

Please sign in to comment.