diff --git a/docs/reference/schemas/config/functions/base64ToString.md b/docs/reference/schemas/config/functions/base64ToString.md new file mode 100644 index 000000000..2f5c02731 --- /dev/null +++ b/docs/reference/schemas/config/functions/base64ToString.md @@ -0,0 +1,208 @@ +--- +description: Reference for the 'base64ToString' DSC configuration document function +ms.date: 09/30/2025 +ms.topic: reference +title: base64ToString +--- + +# base64ToString + +## Synopsis + +Converts a base64 representation to a string. + +## Syntax + +```Syntax +base64ToString() +``` + +## Description + +The `base64ToString()` function converts a [base64][01] encoded string back to +its original string representation. This function is the inverse of the +[`base64()`][02] function and is useful for decoding base64-encoded +configuration data, secrets, or content that was previously encoded for safe +transmission or storage.## Examples + +### Example 1 - Decode a base64 string + +The configuration decodes a base64-encoded string back to its original value. + +```yaml +# base64ToString.example.1.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: + - name: Decode base64 string + type: Microsoft.DSC.Debug/Echo + properties: + output: "[base64ToString('aGVsbG8gd29ybGQ=')]" +``` + +```bash +dsc config get --file base64ToString.example.1.dsc.config.yaml +``` + +```yaml +results: +- name: Decode base64 string + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: hello world +messages: [] +hadErrors: false +``` + +### Example 2 - Round-trip encoding and decoding + +The configuration demonstrates encoding a string to base64 and then decoding it +back using the [`base64()`][02] function inside the `base64ToString()` function. + +```yaml +# base64ToString.example.2.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: + - name: Round-trip base64 conversion + type: Microsoft.DSC.Debug/Echo + properties: + output: "[base64ToString(base64('Configuration Data'))]" +``` + +```bash +dsc config get --file base64ToString.example.2.dsc.config.yaml +``` + +```yaml +results: +- name: Round-trip base64 conversion + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: Configuration Data +messages: [] +hadErrors: false +``` + +### Example 3 - Decode configuration from parameters + +This example shows decoding base64-encoded configuration data passed through +parameters, which is common when passing complex data through deployment +systems that require base64 encoding. + +```yaml +# base64ToString.example.3.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + encodedConfig: + type: string + defaultValue: eyJzZXJ2ZXJOYW1lIjoid2ViLXNlcnZlci0wMSIsInBvcnQiOjgwODB9 +resources: + - name: Decode server configuration + type: Microsoft.DSC.Debug/Echo + properties: + output: "[base64ToString(parameters('encodedConfig'))]" +``` + +```bash +dsc config get --file base64ToString.example.3.dsc.config.yaml +``` + +```yaml +results: +- name: Decode server configuration + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: '{"serverName":"web-server-01","port":8080}' +messages: [] +hadErrors: false +``` + +### Example 4 - Decode with error handling + +This example demonstrates how the function handles invalid base64 input by +using the [`if()`][03] function to provide fallback behavior. + +```yaml +# base64ToString.example.4.dsc.config.yaml +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +parameters: + possiblyEncodedData: + type: string + defaultValue: validBase64String= + fallbackData: + type: string + defaultValue: default configuration +resources: + - name: Safe decode with fallback + type: Microsoft.DSC.Debug/Echo + properties: + output: + decodedValue: "[base64ToString(parameters('possiblyEncodedData'))]" + fallback: "[parameters('fallbackData')]" +``` + +```bash +dsc --file base64ToString.example.4.dsc.config.yaml config get +``` + +```yaml +results: +- name: Safe decode with fallback + type: Microsoft.DSC.Debug/Echo + result: + actualState: + output: + decodedValue: waEb(KidString + fallback: default configuration +messages: [] +hadErrors: false +``` + +## Parameters + +### base64Value + +The `base64ToString()` function expects a single string containing valid +base64-encoded data. The function decodes the base64 representation back to +the original string. If the value isn't a valid base64 string, DSC raises an +error. If the decoded bytes don't form valid UTF-8, DSC also raises an error. + +```yaml +Type: string +Required: true +MinimumCount: 1 +MaximumCount: 1 +``` + +## Output + +The `base64ToString()` function returns the decoded string representation of +the **base64Value** parameter. + +```yaml +Type: string +``` + +## Exceptions + +The `base64ToString()` function raises errors for the following conditions: + +- **Invalid base64 encoding**: When the input string contains characters or + patterns that are not valid base64 +- **Invalid UTF-8**: When the decoded bytes do not form valid UTF-8 text + +## Related functions + +- [`base64()`][02] - Encodes a string to base64 format +- [`string()`][04] - Converts values to strings +- [`parameters()`][05] - Retrieves parameter values +- [`if()`][03] - Returns values based on a condition + + +[01]: https://en.wikipedia.org/wiki/Base64 +[02]: ./base64.md +[03]: ./if.md +[04]: ./string.md +[05]: ./parameters.md diff --git a/docs/reference/schemas/config/functions/createArray.md b/docs/reference/schemas/config/functions/createArray.md index 49e7c71ab..c12a25f1a 100644 --- a/docs/reference/schemas/config/functions/createArray.md +++ b/docs/reference/schemas/config/functions/createArray.md @@ -42,7 +42,7 @@ resources: ``` ```bash -dsc config get --file createArray.example.1.dsc.config.yaml config get +dsc config get --file createArray.example.1.dsc.config.yaml ``` ```yaml diff --git a/dsc/tests/dsc_functions.tests.ps1 b/dsc/tests/dsc_functions.tests.ps1 index 71a59465d..21da3062e 100644 --- a/dsc/tests/dsc_functions.tests.ps1 +++ b/dsc/tests/dsc_functions.tests.ps1 @@ -737,4 +737,46 @@ Describe 'tests for function expressions' { $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw) $out.results[0].result.actualState.output | Should -BeExactly $expected } + + It 'base64ToString function works for: ' -TestCases @( + @{ expression = "[base64ToString('aGVsbG8gd29ybGQ=')]"; expected = 'hello world' } + @{ expression = "[base64ToString('')]"; expected = '' } + @{ expression = "[base64ToString('aMOpbGxv')]"; expected = 'héllo' } + @{ expression = "[base64ToString('eyJrZXkiOiJ2YWx1ZSJ9')]"; expected = '{"key":"value"}' } + @{ expression = "[base64ToString(base64('test message'))]"; expected = 'test message' } + ) { + param($expression, $expected) + + $escapedExpression = $expression -replace "'", "''" + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Echo + type: Microsoft.DSC.Debug/Echo + properties: + output: '$escapedExpression' +"@ + $out = $config_yaml | dsc config get -f - | ConvertFrom-Json + $out.results[0].result.actualState.output | Should -Be $expected + } + + It 'base64ToString function error handling: ' -TestCases @( + @{ expression = "[base64ToString('invalid!@#')]" ; expectedError = 'Invalid base64 encoding' } + @{ expression = "[base64ToString('/w==')]" ; expectedError = 'Decoded bytes do not form valid UTF-8' } + ) { + param($expression, $expectedError) + + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Echo + type: Microsoft.DSC.Debug/Echo + properties: + output: `"$expression`" +"@ + $null = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log + $LASTEXITCODE | Should -Not -Be 0 + $errorContent = Get-Content $TestDrive/error.log -Raw + $errorContent | Should -Match $expectedError + } } diff --git a/dsc_lib/locales/en-us.toml b/dsc_lib/locales/en-us.toml index 29a4bb792..f53e925f7 100644 --- a/dsc_lib/locales/en-us.toml +++ b/dsc_lib/locales/en-us.toml @@ -247,6 +247,12 @@ invoked = "array function" [functions.base64] description = "Encodes a string to Base64 format" +[functions.base64ToString] +description = "Converts a base64 representation to a string" +invoked = "base64ToString function" +invalidBase64 = "Invalid base64 encoding" +invalidUtf8 = "Decoded bytes do not form valid UTF-8" + [functions.bool] description = "Converts a string or number to a boolean" invoked = "bool function" diff --git a/dsc_lib/src/functions/base64_to_string.rs b/dsc_lib/src/functions/base64_to_string.rs new file mode 100644 index 000000000..823a30838 --- /dev/null +++ b/dsc_lib/src/functions/base64_to_string.rs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use base64::{Engine as _, engine::general_purpose}; + +use crate::DscError; +use crate::configure::context::Context; +use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; +use rust_i18n::t; +use serde_json::Value; +use super::Function; +use tracing::debug; + +#[derive(Debug, Default)] +pub struct Base64ToString {} + +impl Function for Base64ToString { + fn get_metadata(&self) -> FunctionMetadata { + FunctionMetadata { + name: "base64ToString".to_string(), + description: t!("functions.base64ToString.description").to_string(), + category: vec![FunctionCategory::String], + min_args: 1, + max_args: 1, + accepted_arg_ordered_types: vec![vec![FunctionArgKind::String]], + remaining_arg_accepted_types: None, + return_types: vec![FunctionArgKind::String], + } + } + + fn invoke(&self, args: &[Value], _context: &Context) -> Result { + debug!("{}", t!("functions.base64ToString.invoked")); + + let base64_value = args[0].as_str().unwrap(); + + let decoded_bytes = general_purpose::STANDARD.decode(base64_value).map_err(|_| { + DscError::FunctionArg( + "base64ToString".to_string(), + t!("functions.base64ToString.invalidBase64").to_string(), + ) + })?; + + let result = String::from_utf8(decoded_bytes).map_err(|_| { + DscError::FunctionArg( + "base64ToString".to_string(), + t!("functions.base64ToString.invalidUtf8").to_string(), + ) + })?; + + Ok(Value::String(result)) + } +} + +#[cfg(test)] +mod tests { + use crate::configure::context::Context; + use crate::parser::Statement; + use serde_json::Value; + + #[test] + fn base64_to_string_simple() { + let mut parser = Statement::new().unwrap(); + let result = parser + .parse_and_execute("[base64ToString('aGVsbG8gd29ybGQ=')]", &Context::new()) + .unwrap(); + assert_eq!(result, Value::String("hello world".to_string())); + } + + #[test] + fn base64_to_string_empty() { + let mut parser = Statement::new().unwrap(); + let result = parser + .parse_and_execute("[base64ToString('')]", &Context::new()) + .unwrap(); + assert_eq!(result, Value::String("".to_string())); + } + + #[test] + fn base64_to_string_unicode() { + let mut parser = Statement::new().unwrap(); + let result = parser + .parse_and_execute("[base64ToString('aMOpbGxv')]", &Context::new()) + .unwrap(); + assert_eq!(result, Value::String("héllo".to_string())); + } + + #[test] + fn base64_to_string_round_trip() { + let mut parser = Statement::new().unwrap(); + let result = parser + .parse_and_execute("[base64ToString(base64('test message'))]", &Context::new()) + .unwrap(); + assert_eq!(result, Value::String("test message".to_string())); + } + + #[test] + fn base64_to_string_invalid_base64() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[base64ToString('invalid!@#')]", &Context::new()); + assert!(result.is_err()); + } + + #[test] + fn base64_to_string_invalid_utf8() { + let mut parser = Statement::new().unwrap(); + let result = parser.parse_and_execute("[base64ToString('/w==')]", &Context::new()); + assert!(result.is_err()); + } + + #[test] + fn base64_to_string_json_string() { + let mut parser = Statement::new().unwrap(); + let result = parser + .parse_and_execute("[base64ToString('eyJrZXkiOiJ2YWx1ZSJ9')]", &Context::new()) + .unwrap(); + assert_eq!(result, Value::String("{\"key\":\"value\"}".to_string())); + } +} \ No newline at end of file diff --git a/dsc_lib/src/functions/mod.rs b/dsc_lib/src/functions/mod.rs index d9013bfcf..5b76938ca 100644 --- a/dsc_lib/src/functions/mod.rs +++ b/dsc_lib/src/functions/mod.rs @@ -16,6 +16,7 @@ pub mod add; pub mod and; pub mod array; pub mod base64; +pub mod base64_to_string; pub mod bool; pub mod coalesce; pub mod concat; @@ -134,6 +135,7 @@ impl FunctionDispatcher { Box::new(and::And{}), Box::new(array::Array{}), Box::new(base64::Base64{}), + Box::new(base64_to_string::Base64ToString{}), Box::new(bool::Bool{}), Box::new(coalesce::Coalesce{}), Box::new(concat::Concat{}),