feat(baker/finalize): add YAML to shell env var conversion
This commit is contained in:
@@ -1,10 +1,88 @@
|
||||
use crate::finalize::plugin::PluginMetadata;
|
||||
use crate::finalize::plugin::PluginError;
|
||||
use crate::finalize::plugin::PluginMetadata;
|
||||
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct Shell;
|
||||
|
||||
|
||||
// Workaround flexible key in yaml
|
||||
fn parse_key(key: serde_yaml::Value) -> String {
|
||||
match key {
|
||||
serde_yaml::Value::String(s) => s,
|
||||
serde_yaml::Value::Number(n) => n.to_string(),
|
||||
serde_yaml::Value::Bool(b) => b.to_string(),
|
||||
_ => {
|
||||
log::warn!("Unsupported key type in mapping: {:?}", key);
|
||||
String::from("?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_argument(prefix: &str, argument: serde_yaml::Value, in_sequence: bool) -> HashMap<String, String> {
|
||||
let mut envvars: HashMap<String, String> = HashMap::new();
|
||||
match argument {
|
||||
serde_yaml::Value::Mapping(mapping) => {
|
||||
for i in mapping {
|
||||
let key = parse_key(i.0).to_uppercase();
|
||||
let value = i.1.clone();
|
||||
let new_prefix = format!("{}_{}", prefix, key);
|
||||
let subenvvars = convert_argument(new_prefix.as_str(), value, false);
|
||||
envvars.extend(subenvvars);
|
||||
}
|
||||
}
|
||||
serde_yaml::Value::Null => {
|
||||
// NOTE: Null value should be eliminated.
|
||||
}
|
||||
serde_yaml::Value::Bool(value) => {
|
||||
envvars.insert(prefix.to_string(), value.to_string());
|
||||
}
|
||||
serde_yaml::Value::String(value) => {
|
||||
envvars.insert(prefix.to_string(), format!("\"{}\"", value));
|
||||
}
|
||||
serde_yaml::Value::Number(value) => {
|
||||
envvars.insert(prefix.to_string(), value.to_string());
|
||||
},
|
||||
serde_yaml::Value::Tagged(tagged_value) => {
|
||||
let subenvvars = convert_argument(prefix, tagged_value.value, in_sequence);
|
||||
envvars.extend(subenvvars);
|
||||
}
|
||||
serde_yaml::Value::Sequence(seq) => {
|
||||
if in_sequence {
|
||||
log::warn!("Nested sequence in sequence is not supported, skipping: {}", prefix);
|
||||
return envvars;
|
||||
}
|
||||
let mut array = String::new();
|
||||
array.push_str("(");
|
||||
let mut has_mapping = false;
|
||||
let mut _envvars: HashMap<String, String> = HashMap::new();
|
||||
for (index, item) in seq.into_iter().enumerate() {
|
||||
if index > 0 {
|
||||
array.push_str(" ");
|
||||
}
|
||||
let new_prefix = format!("{}_{}", prefix, index);
|
||||
let subenvvars = convert_argument(new_prefix.as_str(), item.clone(), true);
|
||||
if item.is_mapping() {
|
||||
has_mapping = true;
|
||||
}
|
||||
else {
|
||||
array.push_str(subenvvars.get(new_prefix.as_str()).map(|v| v.as_str()).unwrap_or_default());
|
||||
}
|
||||
_envvars.extend(subenvvars);
|
||||
}
|
||||
if has_mapping {
|
||||
envvars.extend(_envvars);
|
||||
}
|
||||
else {
|
||||
array.push_str(")");
|
||||
envvars.insert(prefix.to_string(), array);
|
||||
}
|
||||
}
|
||||
}
|
||||
envvars
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AsyncPluginFn for Shell {
|
||||
async fn call(
|
||||
@@ -14,7 +92,230 @@ impl AsyncPluginFn for Shell {
|
||||
ctx: &ExecutionContext,
|
||||
event_tx: &EventSender,
|
||||
) -> Result<(), PluginError> {
|
||||
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_key_string() {
|
||||
let key = serde_yaml::Value::String("test_key".to_string());
|
||||
assert_eq!(parse_key(key), "test_key");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_key_number() {
|
||||
let key = serde_yaml::Value::Number(42.into());
|
||||
assert_eq!(parse_key(key), "42");
|
||||
|
||||
let key = serde_yaml::Value::Number(3.14.into());
|
||||
assert_eq!(parse_key(key), "3.14");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_key_bool() {
|
||||
let key = serde_yaml::Value::Bool(true);
|
||||
assert_eq!(parse_key(key), "true");
|
||||
|
||||
let key = serde_yaml::Value::Bool(false);
|
||||
assert_eq!(parse_key(key), "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_key_unsupported() {
|
||||
let key = serde_yaml::Value::Null;
|
||||
assert_eq!(parse_key(key), "?");
|
||||
|
||||
let key = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
|
||||
assert_eq!(parse_key(key), "?");
|
||||
|
||||
let key = serde_yaml::Value::Sequence(vec![]);
|
||||
assert_eq!(parse_key(key), "?");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_simple_mapping() {
|
||||
let yaml = serde_yaml::from_str("host: docker.io\nuser: admin").unwrap();
|
||||
let result = convert_argument("PLUGIN", yaml, false);
|
||||
|
||||
// Keys are uppercased in actual call() function
|
||||
assert_eq!(result.get("PLUGIN_HOST"), Some(&"\"docker.io\"".to_string()));
|
||||
assert_eq!(result.get("PLUGIN_USER"), Some(&"\"admin\"".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_nested_mapping() {
|
||||
let yaml = serde_yaml::from_str("
|
||||
from:
|
||||
host: smtp.example.com
|
||||
port: 587
|
||||
").unwrap();
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
|
||||
// Nested keys use uppercase format: CFG_FROM_HOST
|
||||
assert_eq!(result.get("CFG_FROM_HOST"), Some(&"\"smtp.example.com\"".to_string()));
|
||||
assert_eq!(result.get("CFG_FROM_PORT"), Some(&"587".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_different_types() {
|
||||
let yaml = serde_yaml::from_str("
|
||||
enabled: true
|
||||
count: 42
|
||||
ratio: 3.14
|
||||
name: test
|
||||
empty: ~
|
||||
").unwrap();
|
||||
let result = convert_argument("VAR", yaml, false);
|
||||
|
||||
assert_eq!(result.get("VAR_ENABLED"), Some(&"true".to_string()));
|
||||
assert_eq!(result.get("VAR_COUNT"), Some(&"42".to_string()));
|
||||
assert_eq!(result.get("VAR_RATIO"), Some(&"3.14".to_string()));
|
||||
assert_eq!(result.get("VAR_NAME"), Some(&"\"test\"".to_string()));
|
||||
assert!(!result.contains_key("VAR_EMPTY"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_sequence_simple() {
|
||||
let yaml = serde_yaml::from_str("tags:\n - latest\n - v1.0\n - stable").unwrap();
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
|
||||
// Strings in arrays are quoted by convert_argument
|
||||
assert_eq!(result.get("CFG_TAGS"), Some(&"(\"latest\" \"v1.0\" \"stable\")".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_sequence_with_mapping() {
|
||||
let yaml = serde_yaml::from_str("
|
||||
env:
|
||||
- name: KEY1
|
||||
value: val1
|
||||
- name: KEY2
|
||||
value: val2
|
||||
").unwrap();
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
|
||||
assert_eq!(result.get("CFG_ENV_0_NAME"), Some(&"\"KEY1\"".to_string()));
|
||||
assert_eq!(result.get("CFG_ENV_0_VALUE"), Some(&"\"val1\"".to_string()));
|
||||
assert_eq!(result.get("CFG_ENV_1_NAME"), Some(&"\"KEY2\"".to_string()));
|
||||
assert_eq!(result.get("CFG_ENV_1_VALUE"), Some(&"\"val2\"".to_string()));
|
||||
assert!(!result.contains_key("CFG_ENV"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_nested_sequence() {
|
||||
let yaml = serde_yaml::from_str("
|
||||
matrix:
|
||||
- - a
|
||||
- b
|
||||
- - c
|
||||
- d
|
||||
").unwrap();
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
|
||||
// Nested sequences are not supported (line 50-52 logs warning and skips)
|
||||
// So CFG_MATRIX_0 and CFG_MATRIX_1 keys are not created
|
||||
assert!(!result.contains_key("CFG_MATRIX_0"));
|
||||
assert!(!result.contains_key("CFG_MATRIX_1"));
|
||||
// The outer array has empty slots where nested sequences were skipped
|
||||
// (space separator is still added even for empty values)
|
||||
assert_eq!(result.get("CFG_MATRIX"), Some(&"( )".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_empty_mapping() {
|
||||
let yaml = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_empty_sequence() {
|
||||
let yaml = serde_yaml::Value::Sequence(vec![]);
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
assert_eq!(result.get("CFG"), Some(&"()".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_null_value() {
|
||||
let yaml = serde_yaml::from_str("
|
||||
host: docker.io
|
||||
token: ~
|
||||
").unwrap();
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
|
||||
// Keys are uppercased: CFG_HOST
|
||||
assert_eq!(result.get("CFG_HOST"), Some(&"\"docker.io\"".to_string()));
|
||||
// Null value is skipped (not set)
|
||||
assert!(!result.contains_key("CFG_TOKEN"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_number_keys() {
|
||||
let yaml = serde_yaml::from_str("
|
||||
0: first
|
||||
1: second
|
||||
").unwrap();
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
|
||||
// Number keys are preserved but prefixed: CFG_0, CFG_1
|
||||
assert_eq!(result.get("CFG_0"), Some(&"\"first\"".to_string()));
|
||||
assert_eq!(result.get("CFG_1"), Some(&"\"second\"".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_complex_nested() {
|
||||
let yaml = serde_yaml::from_str("
|
||||
plugin:
|
||||
docker-push:
|
||||
host: docker.io
|
||||
image: user/app:tag
|
||||
options:
|
||||
force: true
|
||||
retries: 3
|
||||
tags:
|
||||
- latest
|
||||
- v1.0
|
||||
").unwrap();
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
|
||||
// All keys uppercased for shell environment variables
|
||||
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_HOST"), Some(&"\"docker.io\"".to_string()));
|
||||
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_IMAGE"), Some(&"\"user/app:tag\"".to_string()));
|
||||
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_OPTIONS_FORCE"), Some(&"true".to_string()));
|
||||
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_OPTIONS_RETRIES"), Some(&"3".to_string()));
|
||||
// Strings in sequences are quoted
|
||||
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_TAGS"), Some(&"(\"latest\" \"v1.0\")".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_special_chars_in_values() {
|
||||
let yaml = serde_yaml::from_str("
|
||||
url: https://example.com/path?query=value
|
||||
path: /workspace/output.tar.gz
|
||||
token: 'Bearer abc123'
|
||||
").unwrap();
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
|
||||
// Keys uppercased: CFG_URL, CFG_PATH, CFG_TOKEN
|
||||
assert_eq!(result.get("CFG_URL"), Some(&"\"https://example.com/path?query=value\"".to_string()));
|
||||
assert_eq!(result.get("CFG_PATH"), Some(&"\"/workspace/output.tar.gz\"".to_string()));
|
||||
assert_eq!(result.get("CFG_TOKEN"), Some(&"\"Bearer abc123\"".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_argument_quoted_strings_preserved() {
|
||||
let yaml = serde_yaml::from_str("
|
||||
msg: 'Hello World'
|
||||
cmd: \"echo 'test'\"
|
||||
").unwrap();
|
||||
let result = convert_argument("CFG", yaml, false);
|
||||
|
||||
// Keys uppercased: CFG_MSG, CFG_CMD
|
||||
assert_eq!(result.get("CFG_MSG"), Some(&"\"Hello World\"".to_string()));
|
||||
assert_eq!(result.get("CFG_CMD"), Some(&"\"echo 'test'\"".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user