Skip to content

Commit 18fd542

Browse files
authored
Merge pull request #1032 from SteveL-MSFT/functions
Add `endsWith`, `startsWith`, `utcNow`, and `uniqueString` functions
2 parents 2c87561 + ba9335d commit 18fd542

File tree

14 files changed

+637
-46
lines changed

14 files changed

+637
-46
lines changed

dsc/Cargo.lock

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dsc/tests/dsc_functions.tests.ps1

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,80 @@ Describe 'tests for function expressions' {
244244
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
245245
($out.results[0].result.actualState.output | Out-String) | Should -BeExactly ($expected | Out-String)
246246
}
247+
248+
It 'utcNow function works for: utcNow(<format>)' -TestCases @(
249+
@{ format = $null}
250+
@{ format = "yyyy-MM-dd"}
251+
@{ format = "yyyy-MM-ddTHH"}
252+
@{ format = "yyyy-MM-ddTHHZ"}
253+
@{ format = "MMM dd, yyyy HH"}
254+
@{ format = "yy-MMMM-dddd tt H" }
255+
@{ format = "MMM ddd zzz" }
256+
@{ format = "YY YYYY MM MMM MMMM" }
257+
) {
258+
param($format)
259+
260+
if ($null -eq $format) {
261+
$expected = (Get-Date -AsUTC).ToString("o")
262+
} else {
263+
$expected = (Get-Date -AsUTC).ToString($format)
264+
$format = "'$format'"
265+
}
266+
267+
$config_yaml = @"
268+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
269+
parameters:
270+
test:
271+
type: string
272+
defaultValue: "[utcNow($format)]"
273+
resources:
274+
- name: Echo
275+
type: Microsoft.DSC.Debug/Echo
276+
properties:
277+
output: "[parameters('test')]"
278+
"@
279+
$out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log
280+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
281+
# ConvertFrom-Json will convert the date to a DateTime object, so we use regex to capture the string
282+
$out -match '"output":"(?<date>.*?)"' | Should -BeTrue -Because "Output should contain a date"
283+
$actual = $matches['date']
284+
# since the datetimes might slightly differ, we remove the seconds and milliseconds
285+
$expected = $expected -replace ':\d+\.\d+Z$', 'Z'
286+
$actual = $actual -replace ':\d+\.\d+Z$', 'Z'
287+
$actual | Should -BeExactly $expected -Because "Expected: '$expected', Actual: '$actual'"
288+
}
289+
290+
It 'utcNow errors if used not as a parameter default' {
291+
$config_yaml = @"
292+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
293+
resources:
294+
- name: Echo
295+
type: Microsoft.DSC.Debug/Echo
296+
properties:
297+
output: "[utcNow()]"
298+
"@
299+
$out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json
300+
$LASTEXITCODE | Should -Be 2 -Because (Get-Content $TestDrive/error.log -Raw)
301+
(Get-Content $TestDrive/error.log -Raw) | Should -Match 'utcNow function can only be used as a parameter default'
302+
}
303+
304+
It 'uniqueString function works for: <expression>' -TestCases @(
305+
@{ expression = "[uniqueString('a')]" ; expected = 'cfvwxu6sc4lqo' }
306+
@{ expression = "[uniqueString('a', 'b', 'c')]" ; expected = 'bhw7m6t6ntwd6' }
307+
@{ expression = "[uniqueString('a', 'b', 'c', 'd')]" ; expected = 'yxzg7ur4qetcy' }
308+
) {
309+
param($expression, $expected)
310+
311+
$config_yaml = @"
312+
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
313+
resources:
314+
- name: Echo
315+
type: Microsoft.DSC.Debug/Echo
316+
properties:
317+
output: "$expression"
318+
"@
319+
$out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json
320+
$LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw)
321+
$out.results[0].result.actualState.output | Should -BeExactly $expected
322+
}
247323
}

dsc_lib/Cargo.lock

Lines changed: 70 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dsc_lib/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ split-debuginfo = "packed" # generates a seperate *.dwp/*.dSYM so the binary ca
1313
strip = "symbols" # See split-debuginfo - allows us to drop the size by ~65%
1414

1515
[dependencies]
16+
base32 = "0.5"
1617
base64 = "0.22"
17-
chrono = "0.4"
18+
chrono = { version = "0.4", features = ["alloc"] }
1819
clap = { version = "4.5", features = ["derive"] }
1920
derive_builder ="0.20"
2021
indicatif = "0.18"
2122
jsonschema = { version = "0.30", default-features = false }
2223
linked-hash-map = "0.5"
24+
murmurhash64 = "0.3"
2325
num-traits = "0.2"
2426
path-absolutize = { version = "3.1" }
2527
regex = "1.11"

dsc_lib/locales/en-us.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ description = "Checks if an array, object, or string is empty"
265265
invoked = "empty function"
266266
invalidArgType = "Invalid argument type, argument must be an array, object, or string"
267267

268+
[functions.endsWith]
269+
description = "Checks if a string ends with a specific suffix"
270+
invoked = "endsWith function"
271+
268272
[functions.envvar]
269273
description = "Retrieves the value of an environment variable"
270274
notFound = "Environment variable not found"
@@ -381,6 +385,10 @@ extensionReturnedError = "Extension '%{extension}': %{error}"
381385
noExtensions = "No extensions supporting secrets was found"
382386
secretNotFound = "Secret '%{name}' not found"
383387

388+
[functions.startsWith]
389+
description = "Checks if a string starts with a specific prefix"
390+
invoked = "startsWith function"
391+
384392
[functions.sub]
385393
description = "Subtracts the second number from the first"
386394
invoked = "sub function"
@@ -398,6 +406,15 @@ description = "Returns a single array or object with all elements from the param
398406
invoked = "union function"
399407
invalidArgType = "All arguments must either be arrays or objects"
400408

409+
[functions.uniqueString]
410+
description = "Returns a deterministic unique string from the given strings"
411+
invoked = "uniqueString function"
412+
413+
[functions.utcNow]
414+
description = "Returns the current UTC time"
415+
invoked = "utcNow function"
416+
onlyUsedAsParameterDefault = "utcNow function can only be used as a parameter default"
417+
401418
[functions.variables]
402419
description = "Retrieves the value of a variable"
403420
invoked = "variables function"

dsc_lib/src/configure/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub struct Context {
2020
pub start_datetime: DateTime<Local>,
2121
pub restart_required: Option<Vec<RestartRequired>>,
2222
pub process_expressions: bool,
23+
pub processing_parameter_defaults: bool,
2324
}
2425

2526
impl Context {
@@ -39,6 +40,7 @@ impl Context {
3940
start_datetime: chrono::Local::now(),
4041
restart_required: None,
4142
process_expressions: true,
43+
processing_parameter_defaults: false,
4244
}
4345
}
4446
}

dsc_lib/src/configure/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,10 @@ impl Configurator {
722722
// default values can be expressions
723723
let value = if default_value.is_string() {
724724
if let Some(value) = default_value.as_str() {
725-
self.statement_parser.parse_and_execute(value, &self.context)?
725+
self.context.processing_parameter_defaults = true;
726+
let result = self.statement_parser.parse_and_execute(value, &self.context)?;
727+
self.context.processing_parameter_defaults = false;
728+
result
726729
} else {
727730
return Err(DscError::Parser(t!("configure.mod.defaultStringNotDefined").to_string()));
728731
}

0 commit comments

Comments
 (0)