Skip to content

Commit

Permalink
Merge branch 'main' into issue/864/copy-sockets
Browse files Browse the repository at this point in the history
  • Loading branch information
meowjesty authored Jul 23, 2024
2 parents a9743e2 + a0d950f commit 028d31a
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 102 deletions.
9 changes: 9 additions & 0 deletions changelog.d/2068.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Add fs mapping, under `feature.fs.mapping` now it's possible to specify regex match and replace for paths while running mirrord exec.

Example:

```toml
[feature.fs.mapping]
"/var/app/temp" = "/tmp" # Will replace all calls to read/write/scan for "/var/app/temp/sample.txt" to "/tmp/sample.txt"
"/var/app/.cache" = "/workspace/mirrord$0" # Will replace "/var/app/.cache/sample.txt" to "/workspace/mirrord/var/app/.cache/sample.txt" see [Regex::replace](https://docs.rs/regex/latest/regex/struct.Regex.html#method.replace)
```
13 changes: 12 additions & 1 deletion mirrord-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
"additionalProperties": false,
"definitions": {
"AdvancedFsUserConfig": {
"description": "Allows the user to specify the default behavior for file operations:\n\n1. `\"read\"` - Read from the remote file system (default) 2. `\"write\"` - Read/Write from the remote file system. 3. `\"local\"` - Read from the local file system. 4. `\"localwithoverrides\"` - perform fs operation locally, unless the path matches a pre-defined or user-specified exception.\n\n> Note: by default, some paths are read locally or remotely, regardless of the selected FS mode. > This is described in further detail below.\n\nBesides the default behavior, the user can specify behavior for specific regex patterns. Case insensitive.\n\n1. `\"read_write\"` - List of patterns that should be read/write remotely. 2. `\"read_only\"` - List of patterns that should be read only remotely. 3. `\"local\"` - List of patterns that should be read locally. 4. `\"not_found\"` - List of patters that should never be read nor written. These files should be treated as non-existent.\n\nThe logic for choosing the behavior is as follows:\n\n1. Check if one of the patterns match the file path, do the corresponding action. There's no specified order if two lists match the same path, we will use the first one (and we do not guarantee what is first).\n\n**Warning**: Specifying the same path in two lists is unsupported and can lead to undefined behaviour.\n\n2. There are pre-defined exceptions to the set FS mode. 1. Paths that match [the patterns defined here](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/read_local_by_default.rs) are read locally by default. 2. Paths that match [the patterns defined here](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/read_remote_by_default.rs) are read remotely by default when the mode is `localwithoverrides`. 3. Paths that match [the patterns defined here](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/not_found_by_default.rs) under the running user's home directory will not be found by the application when the mode is not `local`.\n\nIn order to override that default setting for a path, or a pattern, include it the appropriate pattern set from above. E.g. in order to read files under `/etc/` remotely even though it is covered by [the set of patterns that are read locally by default](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/read_local_by_default.rs), add `\"^/etc/.\"` to the `read_only` set.\n\n3. If none of the above match, use the default behavior (mode).\n\nFor more information, check the file operations [technical reference](https://mirrord.dev/docs/reference/fileops/).\n\n```json { \"feature\": { \"fs\": { \"mode\": \"write\", \"read_write\": \".+\\\\.json\" , \"read_only\": [ \".+\\\\.yaml\", \".+important-file\\\\.txt\" ], \"local\": [ \".+\\\\.js\", \".+\\\\.mjs\" ], \"not_found\": [ \"\\\\.config/gcloud\" ] } } } ```",
"description": "Allows the user to specify the default behavior for file operations:\n\n1. `\"read\"` - Read from the remote file system (default) 2. `\"write\"` - Read/Write from the remote file system. 3. `\"local\"` - Read from the local file system. 4. `\"localwithoverrides\"` - perform fs operation locally, unless the path matches a pre-defined or user-specified exception.\n\n> Note: by default, some paths are read locally or remotely, regardless of the selected FS mode. > This is described in further detail below.\n\nBesides the default behavior, the user can specify behavior for specific regex patterns. Case insensitive.\n\n1. `\"read_write\"` - List of patterns that should be read/write remotely. 2. `\"read_only\"` - List of patterns that should be read only remotely. 3. `\"local\"` - List of patterns that should be read locally. 4. `\"not_found\"` - List of patters that should never be read nor written. These files should be treated as non-existent. 4. `\"mapping\"` - Map of patterns and their corresponding replacers. The replacement happens before any specific behavior as defined above or mode (uses [`Regex::replace`](https://docs.rs/regex/latest/regex/struct.Regex.html#method.replace))\n\nThe logic for choosing the behavior is as follows:\n\n1. Check agains \"mapping\" if path needs to be replaced, if matched then continue to next step with new path after replacements otherwise continue as usual. 2. Check if one of the patterns match the file path, do the corresponding action. There's no specified order if two lists match the same path, we will use the first one (and we do not guarantee what is first).\n\n**Warning**: Specifying the same path in two lists is unsupported and can lead to undefined behaviour.\n\n3. There are pre-defined exceptions to the set FS mode. 1. Paths that match [the patterns defined here](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/read_local_by_default.rs) are read locally by default. 2. Paths that match [the patterns defined here](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/read_remote_by_default.rs) are read remotely by default when the mode is `localwithoverrides`. 3. Paths that match [the patterns defined here](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/not_found_by_default.rs) under the running user's home directory will not be found by the application when the mode is not `local`.\n\nIn order to override that default setting for a path, or a pattern, include it the appropriate pattern set from above. E.g. in order to read files under `/etc/` remotely even though it is covered by [the set of patterns that are read locally by default](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/read_local_by_default.rs), add `\"^/etc/.\"` to the `read_only` set.\n\n4. If none of the above match, use the default behavior (mode).\n\nFor more information, check the file operations [technical reference](https://mirrord.dev/docs/reference/fileops/).\n\n```json { \"feature\": { \"fs\": { \"mode\": \"write\", \"read_write\": \".+\\\\.json\" , \"read_only\": [ \".+\\\\.yaml\", \".+important-file\\\\.txt\" ], \"local\": [ \".+\\\\.js\", \".+\\\\.mjs\" ], \"not_found\": [ \"\\\\.config/gcloud\" ] } } } ```",
"type": "object",
"properties": {
"local": {
Expand All @@ -166,6 +166,17 @@
}
]
},
"mapping": {
"title": "feature.fs.mapping {#feature-fs-mapping}",
"description": "Specify map of patterns that if matched will replace the path according to specification.\n\n*Capture groups are allowed.*\n\nExample: ```json { \"^/home/(?<user>\\S+)/dev/tomcat\": \"/etc/tomcat\" \"^/home/(?<user>\\S+)/dev/config/(?<app>\\S+)\": \"/mnt/configs/${user}-$app\" } ``` Will do the next replacements for any io operaton\n\n`/home/johndoe/dev/tomcat/context.xml` => `/etc/tomcat/context.xml` `/home/johndoe/dev/config/api/app.conf` => `/mnt/configs/johndoe-api/app.conf`",
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "string"
}
},
"mode": {
"title": "feature.fs.mode {#feature-fs-mode}",
"anyOf": [
Expand Down
2 changes: 2 additions & 0 deletions mirrord/config/src/feature/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ impl MirrordConfig for FsUserConfig {
.source_value(context)
.transpose()?,
not_found: None,
mapping: None,
},
FsUserConfig::Advanced(advanced) => advanced.generate_config(context)?,
};
Expand Down Expand Up @@ -117,6 +118,7 @@ impl MirrordToggleableConfig for FsUserConfig {
read_only,
local,
not_found: None,
mapping: None,
})
}
}
Expand Down
32 changes: 29 additions & 3 deletions mirrord/config/src/feature/fs/advanced.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashMap;

use mirrord_analytics::{AnalyticValue, CollectAnalytics};
use mirrord_config_derive::MirrordConfig;
use schemars::JsonSchema;
Expand Down Expand Up @@ -31,17 +33,21 @@ use crate::{
/// 3. `"local"` - List of patterns that should be read locally.
/// 4. `"not_found"` - List of patters that should never be read nor written. These files should be
/// treated as non-existent.
/// 4. `"mapping"` - Map of patterns and their corresponding replacers. The replacement happens before any specific behavior as defined above or mode (uses [`Regex::replace`](https://docs.rs/regex/latest/regex/struct.Regex.html#method.replace))
///
/// The logic for choosing the behavior is as follows:
///
/// 1. Check if one of the patterns match the file path, do the corresponding action. There's
///
/// 1. Check agains "mapping" if path needs to be replaced, if matched then continue to next step
/// with new path after replacements otherwise continue as usual.
/// 2. Check if one of the patterns match the file path, do the corresponding action. There's
/// no specified order if two lists match the same path, we will use the first one (and we
/// do not guarantee what is first).
///
/// **Warning**: Specifying the same path in two lists is unsupported and can lead to undefined
/// behaviour.
///
/// 2. There are pre-defined exceptions to the set FS mode.
/// 3. There are pre-defined exceptions to the set FS mode.
/// 1. Paths that match [the patterns defined here](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/read_local_by_default.rs)
/// are read locally by default.
/// 2. Paths that match [the patterns defined here](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/read_remote_by_default.rs)
Expand All @@ -55,7 +61,7 @@ use crate::{
/// though it is covered by [the set of patterns that are read locally by default](https://github.com/metalbear-co/mirrord/tree/latest/mirrord/layer/src/file/filter/read_local_by_default.rs),
/// add `"^/etc/."` to the `read_only` set.
///
/// 3. If none of the above match, use the default behavior (mode).
/// 4. If none of the above match, use the default behavior (mode).
///
/// For more information, check the file operations
/// [technical reference](https://mirrord.dev/docs/reference/fileops/).
Expand Down Expand Up @@ -106,6 +112,25 @@ pub struct FsConfig {
///
/// Specify file path patterns that if matched will be treated as non-existent.
pub not_found: Option<VecOrSingle<String>>,

/// ### feature.fs.mapping {#feature-fs-mapping}
///
/// Specify map of patterns that if matched will replace the path according to specification.
///
/// *Capture groups are allowed.*
///
/// Example:
/// ```json
/// {
/// "^/home/(?<user>\S+)/dev/tomcat": "/etc/tomcat"
/// "^/home/(?<user>\S+)/dev/config/(?<app>\S+)": "/mnt/configs/${user}-$app"
/// }
/// ```
/// Will do the next replacements for any io operaton
///
/// `/home/johndoe/dev/tomcat/context.xml` => `/etc/tomcat/context.xml`
/// `/home/johndoe/dev/config/api/app.conf` => `/mnt/configs/johndoe-api/app.conf`
pub mapping: Option<HashMap<String, String>>,
}

impl MirrordToggleableConfig for AdvancedFsUserConfig {
Expand All @@ -127,6 +152,7 @@ impl MirrordToggleableConfig for AdvancedFsUserConfig {
read_only,
local,
not_found: None,
mapping: None,
})
}
}
Expand Down
18 changes: 15 additions & 3 deletions mirrord/layer/src/detour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use core::{
convert,
ops::{FromResidual, Residual, Try},
};
use std::{cell::RefCell, ops::Deref, os::unix::prelude::*, path::PathBuf, sync::OnceLock};
use std::{
cell::RefCell, ffi::CString, ops::Deref, os::unix::prelude::*, path::PathBuf, sync::OnceLock,
};

#[cfg(target_os = "macos")]
use libc::c_char;
Expand Down Expand Up @@ -150,10 +152,10 @@ pub(crate) enum Bypass {
FileOperationInMirrordBinTempDir(*const c_char),

/// File [`PathBuf`] should be ignored (used for tests).
IgnoredFile(PathBuf),
IgnoredFile(CString),

/// Some operations only handle absolute [`PathBuf`]s.
RelativePath(PathBuf),
RelativePath(CString),

/// Started mirrord with [`FsModeConfig`](mirrord_config::feature::fs::mode::FsModeConfig) set
/// to [`FsModeConfig::Read`](mirrord_config::feature::fs::FsModeConfig::Read), but
Expand Down Expand Up @@ -209,6 +211,16 @@ pub(crate) enum Bypass {
LocalDns,
}

impl Bypass {
pub fn relative_path(path: impl Into<Vec<u8>>) -> Self {
Bypass::RelativePath(CString::new(path).expect("Should be CStringable"))
}

pub fn ignored_file(path: impl Into<Vec<u8>>) -> Self {
Bypass::IgnoredFile(CString::new(path).expect("Should be CStringable"))
}
}

/// [`ControlFlow`](std::ops::ControlFlow)-like enum to be used by hooks.
///
/// Conversion from `Result`:
Expand Down
1 change: 1 addition & 0 deletions mirrord/layer/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use mirrord_protocol::file::{GetDEnts64Request, GetDEnts64Response};

pub(crate) mod filter;
pub(crate) mod hooks;
pub(crate) mod mapper;
pub(crate) mod open_dirs;
pub(crate) mod ops;

Expand Down
10 changes: 5 additions & 5 deletions mirrord/layer/src/file/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ impl FileFilter {
local,
mode,
not_found,
..
} = fs_config;

let read_write =
Expand Down Expand Up @@ -399,12 +400,12 @@ mod tests {
local,
not_found,
mode,
mapping: None,
};

let file_filter = FileFilter::new(fs_config);

let res =
file_filter.continue_or_bypass_with(path, write, || Bypass::IgnoredFile("".into()));
let res = file_filter.continue_or_bypass_with(path, write, || Bypass::ignored_file(""));
println!("filter result: {res:?}");
assert_eq!(res.kind(), expected);
}
Expand Down Expand Up @@ -439,8 +440,7 @@ mod tests {

let file_filter = FileFilter::new(fs_config);

let res =
file_filter.continue_or_bypass_with(path, write, || Bypass::IgnoredFile("".into()));
let res = file_filter.continue_or_bypass_with(path, write, || Bypass::ignored_file(""));
println!("filter result: {res:?}");

assert_eq!(res.kind(), expected);
Expand All @@ -464,7 +464,7 @@ mod tests {
#[case("/root/.nuget/packages/microsoft.azure.amqp", DetourKind::Success)]
fn not_found_set(#[case] path: &str, #[case] expected: DetourKind) {
let filter = FileFilter::new(Default::default());
let res = filter.continue_or_bypass_with(path, false, || Bypass::IgnoredFile("".into()));
let res = filter.continue_or_bypass_with(path, false, || Bypass::ignored_file(""));
println!("filter result: {res:?}");

assert_eq!(res.kind(), expected);
Expand Down
Loading

0 comments on commit 028d31a

Please sign in to comment.