-
Couldn't load subscription status.
- Fork 54
Add tryWhich() function and enable manifests to have condition
#1194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a tryWhich() function to locate executables in the system PATH and adds conditional manifest loading functionality to skip resources or extensions when conditions aren't met.
Key Changes:
- Adds
tryWhich()function that returns the full path to an executable or null if not found - Enables resource and extension manifests to include a
conditionproperty for conditional loading - Updates the
equals()function to support null comparisons
Reviewed Changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/dsc-lib/src/functions/try_which.rs | Implements the new tryWhich() function with path resolution logic |
| lib/dsc-lib/src/functions/mod.rs | Registers the tryWhich function in the dispatcher |
| lib/dsc-lib/src/functions/equals.rs | Adds null support to the equals() function arguments |
| lib/dsc-lib/src/extensions/extension_manifest.rs | Adds optional condition field to extension manifests |
| lib/dsc-lib/src/extensions/discover.rs | Updates extension discovery to handle conditional manifests |
| lib/dsc-lib/src/dscresources/resource_manifest.rs | Adds optional condition field to resource manifests |
| lib/dsc-lib/src/discovery/command_discovery.rs | Implements condition evaluation logic for manifest loading |
| lib/dsc-lib/locales/en-us.toml | Adds localized strings for condition handling and tryWhich function |
| extensions/bicep/bicep.dsc.extension.json | Uses the new condition feature to check for bicep executable availability |
| dsc/tests/dsc_resource_manifest.tests.ps1 | Tests resource manifest condition evaluation |
| dsc/tests/dsc_functions.tests.ps1 | Tests the tryWhich() function behavior |
| dsc/tests/dsc_extension_manifest.tests.ps1 | Tests extension manifest condition evaluation |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
@SteveL-MSFT - can you perhaps update the following doc? Also add an example like: ### Example 4 - Compare incompatible types
The following example shows that `equals()` returns `null` when comparing incompatible types,
such as comparing a string with an integer or an array with an object.
```yaml
# equals.example.4.dsc.config.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Compare incompatible types
type: Microsoft.DSC.Debug/Echo
properties:
output:
stringAndInteger: "[equals('1', 1)]"
arrayAndObject: "[equals(createArray('a', 'b'), createObject('key', 'value'))]"
stringAndArray: "[equals('hello', createArray('h', 'e', 'l', 'l', 'o'))]"
```
```bash
dsc config get --file equals.example.4.dsc.config.yaml
```
```yaml
results:
- name: Compare incompatible types
type: Microsoft.DSC.Debug/Echo
result:
actualState:
output:
stringAndInteger: null
arrayAndObject: null
stringAndArray: null
messages: []
hadErrors: false
```In the output, I guess you can do: ## Output
The `equals()` function returns `true` if the input values are the same, `false` if they are
different but of compatible types, and `null` if the values are of incompatible types that
cannot be compared.
```yaml
Type: bool | null
```Can you also add a ---
description: Reference for the 'tryWhich' DSC configuration document function
ms.date: 01/17/2025
ms.topic: reference
title: tryWhich
---
# tryWhich
## Synopsis
Returns the full path to an executable if it exists in the system PATH, otherwise returns null.
## Syntax
```Syntax
tryWhich(<executableName>)
```
## Description
The `tryWhich()` function searches for an executable in the system PATH and returns its full
path if found. If the executable doesn't exist in the PATH, the function returns `null` instead
of raising an error. This makes it useful for conditional logic where you need to check if a
command-line tool is available before using it.
This function is particularly useful with the `condition` property in resource and extension
manifests. When the condition evaluates to false, DSC skips that manifest, avoiding warning
messages that would otherwise appear when the specified executable isn't on the system.
## Examples
### Example 1 - Check if a command exists
The following example checks whether the `git` command is available on the system.
```yaml
# tryWhich.example.1.dsc.config.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Check for git
type: Microsoft.DSC.Debug/Echo
properties:
output:
gitPath: "[tryWhich('git')]"
hasGit: "[not(equals(tryWhich('git'), null()))]"
```
```bash
dsc config get --file tryWhich.example.1.dsc.config.yaml
```
```yaml
results:
- name: Check for git
type: Microsoft.DSC.Debug/Echo
result:
actualState:
output:
gitPath: /usr/bin/git
hasGit: true
messages: []
hadErrors: false
```
### Example 2 - Conditional resource based on tool availability
The following example demonstrates using `tryWhich()` with the `if()` function to conditionally
set a property based on whether a tool is installed.
```yaml
# tryWhich.example.2.dsc.config.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Tool availability status
type: Microsoft.DSC.Debug/Echo
properties:
output:
message: "[if(not(equals(tryWhich('docker'), null())), 'Docker is available', 'Docker is not installed')]"
```
```bash
dsc config get --file tryWhich.example.2.dsc.config.yaml
```
```yaml
results:
- name: Tool availability status
type: Microsoft.DSC.Debug/Echo
result:
actualState:
output:
message: Docker is available
messages: []
hadErrors: false
```
### Example 3 - Compare with environment-specific executables
The following example checks for different package managers across operating systems.
```yaml
# tryWhich.example.3.dsc.config.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Package manager detection
type: Microsoft.DSC.Debug/Echo
properties:
output:
hasApt: "[not(equals(tryWhich('apt'), null()))]"
hasBrew: "[not(equals(tryWhich('brew'), null()))]"
hasChoco: "[not(equals(tryWhich('choco'), null()))]"
```
```bash
dsc config get --file tryWhich.example.3.dsc.config.yaml
```
```yaml
results:
- name: Package manager detection
type: Microsoft.DSC.Debug/Echo
result:
actualState:
output:
hasApt: true
hasBrew: false
hasChoco: false
messages: []
hadErrors: false
```
### Example 4 - Build dynamic paths with available tools
The following example uses `tryWhich()` with [`coalesce()`][01] to select the first available
tool from a list.
```yaml
# tryWhich.example.4.dsc.config.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Find available editor
type: Microsoft.DSC.Debug/Echo
properties:
output:
editor: "[coalesce(tryWhich('code'), tryWhich('vim'), tryWhich('nano'), 'notepad')]"
```
```bash
dsc config get --file tryWhich.example.4.dsc.config.yaml
```
```yaml
results:
- name: Find available editor
type: Microsoft.DSC.Debug/Echo
result:
actualState:
output:
editor: /usr/local/bin/code
messages: []
hadErrors: false
```
### Example 5 - Use with manifest conditions
The following example shows how `tryWhich()` can be used in a manifest's `condition` property
to skip resources when required tools aren't available. This prevents warning messages about
missing executables.
```yaml
# tryWhich.example.5.dsc.config.yaml
$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
resources:
- name: Docker container status
type: Docker/Container
condition: "[not(equals(tryWhich('docker'), null()))]"
properties:
name: my-app
image: nginx:latest
```
In this example, the Docker/Container resource is only processed if the `docker` command is
available in the system PATH. If docker is not installed, the condition evaluates to `false`
and DSC skips the resource without showing warning messages about the missing executable.
## Parameters
### executableName
The name of the executable to search for in the system PATH. This should be the command name
as you would type it in a terminal (e.g., `git`, `docker`, `python`). On Windows, you can
specify the name with or without the `.exe` extension.
```yaml
Type: string
Required: true
MinimumCount: 1
MaximumCount: 1
```
## Output
The `tryWhich()` function returns the full path to the executable as a string if it exists in
the system PATH. If the executable is not found, the function returns `null`.
```yaml
Type: string | null
```
## Related functions
- [`envvar()`][02] - Retrieves environment variable values
- [`if()`][03] - Conditional logic
- [`equals()`][04] - Compares two values for equality
- [`null()`][05] - Returns a null value
- [`coalesce()`][01] - Returns the first non-null value
- [`not()`][06] - Logical negation
<!-- Link reference definitions -->
[01]: ./coalesce.md
[02]: ./envvar.md
[03]: ./if.md
[04]: ./equals.md
[05]: ./null.md
[06]: ./not.mdIf you like to separate the doc updates, let me know, then I'll update it after it gets merged. Note I couldn't fully test out the examples, as I was a bit lazy to build your branch. |
|
@Gijsreyn I think we can do the doc updates separately. Thanks for your help though! |
|
@Gijsreyn I'll just add that you do a great job with the doc content, but I would say that's not one of my strengths :) |
c6e12c4 to
ccdda45
Compare
5eb6428 to
4bd4c97
Compare
PR Summary
Adds
tryWhich()function which either returns the full path to an executable ornullif not found.This function is useful with a new
conditionproperty in resource and extension manifests such that if the condition evaluates tofalsethen that manifest is skipped (where currently you might see a warning message because the exe specified isn't on the system). Manifests can use any function in the expression that does not require deployment (likereference(), for example).This change also updates the
bicepextension to use this to check ifbicepis on the system.Finally, the
equals()function was updated to allow checking againstnullsince it appears that the ARM implementation allows for this even though the docs don't specify it.Example resource manifest that detects if running Windows:
{ "`$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", "type": "Test/MyEcho", "version": "1.0.0", "condition": "[equals(context().os.family,'Windows')]", "get": { "executable": "dscecho", "args": [ { "jsonInputArg": "--input", "mandatory": true } ] }, "schema": { "command": { "executable": "dscecho" } } }