Skip to content

Commit e4bc68c

Browse files
michaeltlombardiGijsreyn
authored andcommitted
(SCHEMA) Enhance docs and validation for secret in extension manifest schema
This change makes some corrections to the documentation for the `secret` field in the extension manifest schema: - Moves the documentation about the capability contract from `secret.executable` up to `secret`. - Redefines the documentation for `secret.executable` to indicate defining the path to an executable file. - Rewrites the documentation for `secret.args` to clarify the requirement to define a secret name input argument exactly once and the implications of not defining the vault input argument. The new documentation also shows examples of manifest snippets and the effective invocation. - Adds annotation keywords to document the various valid argument types. This change makes enhancements to the validation for the `secret` field: - It makes `args` required, even though it isn't required in the schema for the struct, because there's no functional way to pass the name of a secret to the extension without defining a secret name input argument. In `process_secret_args`, DSC considers it valid to define `secret` without any arguments and without the secret name input argument, but doing so means the extension is never sent the name of the secret to retrieve. The same is true for manifests that don't define the vault input argument, though conceivably an extension could be implemented without support for retrieving secrets from a named vault. https://github.com/PowerShell/DSC/blob/4750f779ecb27eb480f7ab37b8e8a526ed5054f5/dsc_lib/src/extensions/secret.rs#L108-L134 - It changes the `anyOf` in `secret.args.items` to `oneOf`, which is more strictly correct. An item must be _either_ a string argument _or_ a secret name input argument _or_ a vault input argument. - It ensures that the minimum length for a defined argument is `1`, preventing the accidental defining of an empty string. Arguably, we should also use the `pattern` keyword to forbid spacing characters, but this change doesn't add that stricter validation. - Replaces usage of `additionalProperties` with `unevaluatedProperties` to better support integrating tools and schema extensions. - Adds the `allOf` keyword to the top level of `secret` to provide validation for defining the correct number of secret name input arguments and vault name input arguments. The author must define exactly one secret name input argument in `secret.args` and zero or one vault input argument. The subschemas use the VS Code extended vocabulary keyword `errorMessage` to provide specific messaging when an author defines too few or too many of the secret name input argument and too many of the vault input argument. Finally, this change also updates the default snippets to remove the invalid snippets, which didn't define the secret name input argument, and to reword the documentation for those snippets. It also corrects the numbering of the snippet placeholders.
1 parent 967af27 commit e4bc68c

File tree

1 file changed

+244
-74
lines changed

1 file changed

+244
-74
lines changed

schemas/src/extension/manifest.secret.yaml

Lines changed: 244 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -10,126 +10,296 @@ markdownDescription: | # VS Code only
1010
[_Online Documentation_][00]
1111
***
1212
13-
Defines how DSC must call the DSC extension to retrieve a secret value. An
14-
extension that defines this field in its manifest has the `secret` capability.
13+
Defines how DSC must call the DSC extension to retrieve a secret value. An extension that defines
14+
this field in its manifest has the `secret` capability.
1515
16-
The secret operation is expected to output a single line to stdout containing
17-
the secret text. If the extension outputs no data to stdout, DSC treats it as
18-
"no secret returned". If the extension outputs multiple lines, DSC treats that
19-
as an error.
16+
DSC expects extensions implementing the `secret` capability to adhere to the following contract:
2017
21-
For details about the output format, see
22-
[Secret extension operation stdout][01].
18+
1. If the extension retrieves the secret, the extension must emit the secret to stdout as a
19+
single line of plaintext and exit with code `0`. DSC consumes the emitted output and makes the
20+
secret available in the configuration document.
21+
22+
If the extension emits more than one line to stdout, DSC raises an error.
23+
24+
1. If the extension cannot retrieve the secret because the secret doesn't exist, the extension
25+
must not emit any text to stdout and must exit with code `0`. DSC interprets this result as
26+
the secret not existing in the vault.
27+
28+
1. If the extension cannot retrieve the secret for any other reason, such as invalid credentials
29+
or an API error, the extension should emit a descriptive error message as a JSON Line to
30+
stderr and exit with a nonzero exit code. DSC interprets the nonzero exit code as an
31+
operational failure and surfaces that information and any emitted error messages to the user.
32+
33+
When the exit code for the operation is `0`, DSC interprets the operation as completing without
34+
errors. For extensions, failure to retrieve a secret because it doesn't exist is _not_ an error.
35+
Failure to retrieve a secret for any other reason _is_ an error and the extension should exit
36+
with a nonzero code. For an improved user experience, the extension should define the `exitCodes`
37+
field in the extension manifest to indicate what the nonzero exit code means.
38+
39+
For more information about how DSC validates the data for stdout, see
40+
[Secret extension operation stdout][01]. For more information about defining exit codes for the
41+
extension, see [`exitCodes`][02] in the extension manifest schema reference.
2342
2443
[00]: <DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>
2544
[01]: <DOCS_BASE_URL>/reference/schemas/extension/stdout/secret?<DOCS_VERSION_PIN>
45+
[02]: <DOCS_BASE_URL>/reference/schemas/extension/manifest/root?<DOCS_VERSION_PIN>#exitcodes
2646
2747
type: object
2848
required:
2949
- executable
50+
- args
3051
properties:
3152
executable:
3253
$ref: /<PREFIX>/<VERSION>/definitions/commandExecutable.yaml
3354
markdownDescription: |
3455
***
35-
[_Online Documentation_][01]
56+
[_Online Documentation_][00]
3657
***
3758
38-
DSC expects extensions implementing the `secret` capability to adhere to the
39-
following contract:
40-
41-
1. If the extension retrieves the secret, the extension must emit the secret
42-
to stdout as a single line of plaintext and exit with code `0`. DSC
43-
consumes the emitted output and makes the secret available in the
44-
configuration document.
45-
46-
If the extension emits more than one line to stdout, DSC raises an error.
47-
1. If the extension cannot retrieve the secret because the secret doesn't
48-
exist, the extension must not emit any text to stdout and must exit with
49-
code `0`. DSC interprets this result as the secret not existing in the
50-
vault.
51-
1. If the extension cannot retrieve the secret for any other reason, such
52-
as invalid credentials or an API error, the extension should emit
53-
a descriptive error message as a JSON Line to stderr and exit with a
54-
nonzero exit code. DSC interprets the nonzero exit code as an operational
55-
failure and surfaces that information and any emitted error messages to
56-
the user.
57-
58-
When the exit code for the operation is `0`, DSC interprets the operation as
59-
completing without errors. For extensions, failure to retrieve a secret
60-
because it doesn't exist is _not_ an error. Failure to retrieve a secret
61-
for any other reason _is_ an error and the extension should exit with a
62-
nonzero code. For an improved user experience, the extension should define
63-
the `exitCodes` field in the extension manifest to indicate what the nonzero
64-
exit code means.
65-
66-
For more information about how DSC validates the data for stdout, see
67-
[Secret extension operation stdout][01]. For more information about defining
68-
exit codes for the extension, see [`exitCodes`][02] in the extension manifest
69-
schema reference.
70-
71-
[00]: <DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>
72-
[01]: <DOCS_BASE_URL>/reference/schemas/extension/stdout/secret?<DOCS_VERSION_PIN>
73-
[02]: <DOCS_BASE_URL>/reference/schemas/extension/manifest/root?<DOCS_VERSION_PIN>#exitcodes
59+
Defines the name of the command to run. The value must be the name of a command discoverable
60+
in the system's `PATH` environment variable or the full path to the command. A file extension
61+
is only required when the command isn't recognizable by the operating system as an
62+
executable.
63+
64+
[00]: <DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>#executable
7465
args:
7566
title: Arguments
7667
description: >-
7768
Defines an ordered list of arguments to pass to the command.
7869
markdownDescription: |
7970
***
80-
[_Online Documentation_][01]
71+
[_Online Documentation_][00]
8172
***
8273
83-
Defines an ordered list of arguments to pass to the command. Items can be plain strings or
84-
structured entries indicating which argument name should receive the secret name or the vault
85-
name.
74+
Defines an ordered list of arguments to pass to the command. DSC passes each defined item to
75+
the executable in the order you define them. Items can be string arguments, the secret name
76+
input argument, or the vault input argument.
77+
78+
In order for DSC to retrieve a secret with the extension, the manifest must define the secret
79+
name input argument. For DSC to retreieve a secret from a specific vault with the extension,
80+
the manifest must define the vault input argument.
81+
82+
For example, given the following manifest snippet:
83+
84+
```jsonc
85+
{
86+
// ellided extension manifest fields
87+
"secret": {
88+
"executable": "my_secret_extension",
89+
"args": [
90+
"get",
91+
{ "nameArg": "--secretName" }
92+
]
93+
}
94+
}
95+
```
8696
87-
DSC expands structured entries as follows:
97+
When DSC invokes the extension to retrieve a secret named `apiToken`, it constructs
98+
the following effective command:
8899
89-
- `{ "nameArg": "<flag>" }` expands to `"<flag>", "<secret-name>"`.
90-
- `{ "vaultArg": "<flag>" }` expands to `"<flag>", "<vault-name>"` (only when a vault is
91-
specified by the caller).
100+
```bash
101+
my_secret_extension get --secretName apiToken
102+
```
92103
93-
[01]: <DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>#args
104+
If the user needs to retrieve a secret from a specific vault, DSC is unable to pass the vault
105+
name to the previously defined snippet. The following snippet supports passing the vault name
106+
to the extension as well as the secret name:
107+
108+
```jsonc
109+
{
110+
// ellided extension manifest fields
111+
"secret": {
112+
"executable": "my_secret_extension",
113+
"args": [
114+
"get",
115+
{ "nameArg": "--secretName" },
116+
{ "vaultArg": "--vaultName" }
117+
]
118+
}
119+
}
120+
```
121+
122+
When DSC invokes the extension to retrieve a secret named `apiToken` from the `services`
123+
vault, it constructs the following effective command:
124+
125+
```bash
126+
my_secret_extension get --secretName apiToken --vaultName services
127+
```
128+
129+
[00]: <DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>#args
94130
type: array
95131
items:
96-
anyOf:
97-
- type: string
132+
oneOf:
133+
- title: String argument
134+
description: >-
135+
Any item in the argument array can be a string representing a static argument to pass
136+
to the command, like `get` or `--quiet`.
137+
markdownDescription: |-
138+
***
139+
[_Online Documentation_][00]
140+
***
141+
142+
Any item in the argument array can be a string representing a static argument to pass
143+
to the command, like `get` or `--quiet`.
144+
145+
[00]: <DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>#string-arguments
146+
type: string
147+
minLength: 1
98148
- type: object
99-
additionalProperties: false
149+
title: Secret name input argument
150+
description: >-
151+
Defines the argument for the command that accepts the name of a secret.
152+
markdownDescription: |-
153+
***
154+
[_Online Documentation_][00]
155+
***
156+
157+
Defines the argument for the command that accepts the name of a secret. DSC passes the
158+
name of the secret as a string after the defined argument. You must define this
159+
argument as an object with the `nameArg` property set to the appropriate argument for
160+
the extension command, like `--secret` or `--secret-name`.
161+
162+
An extension implementing the `secret` capability _must_ define a secret name input
163+
argument exactly once.
164+
165+
[00]: <DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>#secret-name-input-argument
166+
unevaluatedProperties: false
100167
required:
101168
- nameArg
102169
properties:
103170
nameArg:
171+
title: Secret name input argument definition
172+
description: >-
173+
Defines the literal string for the argument that accepts the name of the secret to
174+
retrieve, like `--name` or `--secret-name`.
175+
markdownDescription: |-
176+
Defines the literal string for the argument that accepts the name of the secret to
177+
retrieve, like `--name` or `--secret-name`.
104178
type: string
179+
minLength: 1
105180
- type: object
106-
additionalProperties: false
181+
title: Vault input argument
182+
description: >-
183+
Defines the argument for the command that accepts the name of a specific vault to
184+
retrieve a secret from.
185+
markdownDescription: |-
186+
***
187+
[_Online Documentation_][00]
188+
***
189+
190+
Defines the argument for the command that accepts the name of a specific vault to
191+
retrieve a secret from. Define this argument as an object with the `vaultArg` property
192+
set to the appropriate argument for the extension command, like `--vault` or
193+
`--vault-name`.
194+
195+
To support retrieving secrets from a specific vault, an extension implementing the
196+
`secret` capability _must_ define a vault input argument exactly once.
197+
198+
[00]: <DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>#vault-input-argument
199+
unevaluatedProperties: false
107200
required:
108201
- vaultArg
109202
properties:
110203
vaultArg:
204+
title: Vault input argument definition
205+
description: >-
206+
Defines the literal string for the argument that accepts the name of the vault to
207+
retrieve a secret from, like `--vault` or `--vault-name`.
208+
markdownDescription: |-
209+
Defines the literal string for the argument that accepts the name of the vault to
210+
retrieve a secret from, like `--vault` or `--vault-name`.
111211
type: string
212+
minLength: 1
213+
214+
# Need to use an allOf with multiple possibilities to capture the requirement to define the secret
215+
# name input argument exactly once and the vault input argument no more than once. Note that
216+
# the YAML language server in VS Code currently doesn't understand the `minContains` and
217+
# `maxContains` keywords, so when defining the extension manifest in YAML with the schema, the
218+
# language server identifies the vault input argument as required. When defining the extension
219+
# manifest in JSON, the language server correctly identifies the minimum and maximum number of
220+
# arguments to use.
221+
#
222+
# Unfortunately, because we only get one error message per item in the `allOf` keyword, we have to
223+
# define two entries for the secret name input argument to provide better validation error messages.
224+
#
225+
# We use long lines for error messages, which can't use Markdown, so line breaks are literal.
226+
allOf:
227+
- title: Missing secret name input argument
228+
$comment: >-
229+
This validation subschema ensures that `secret.args` contains a secret name input argument.
230+
Without this argument, DSC can't pass the name of the secret to retrieve when invoking the
231+
extension's secret command.
232+
233+
We define this separately from the maximum contains validation subschema to improve the error
234+
messaging in VS Code.
235+
properties:
236+
args:
237+
errorMessage: |-
238+
The `secret` command doesn't define the secret name input argument. If you don't define the secret name input argument, DSC can't pass the secret name to the extension for retrieval. You can only define one secret name input argument for the command.
239+
240+
You must define one argument in `secret.args` as a JSON object with the `nameArg` property. For more information, see:
241+
242+
<DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>
243+
contains:
244+
type: object
245+
required: [nameArg]
246+
minContains: 1
247+
- title: Multiple secret name input arguments
248+
$comment: >-
249+
This validation subschema ensures that `secret.args` doesn't contain more than one secret
250+
name input argument. Defining more than one secret name input argument is invalid.
251+
252+
We define this separately from the minimum contains validation subschema to improve the error
253+
messaging in VS Code.
254+
properties:
255+
args:
256+
errorMessage: |-
257+
The `secret` command defines the secret name input argument more than once. You can only define one secret name input argument for the command.
258+
259+
You must define one argument in `secret.args` as a JSON object with the `nameArg` property and remove the additional secret name input arguments. For more information, see:
260+
261+
<DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>
262+
contains:
263+
type: object
264+
required: [nameArg]
265+
maxContains: 1
266+
- title: Multiple vault input arguments
267+
$comment: >-
268+
This validation subschema ensures that `secret.args` contains zero or one vault input
269+
arguments. Defining a vault input argument is optional. Extensions that don't define the
270+
vault input argument don't support retrieving secrets from a specific vault. Defining
271+
more than one vault input argument is invalid.
272+
properties:
273+
args:
274+
errorMessage: |-
275+
The `secret` command defines the vault input argument more than once. If you don't define the vault input argument, DSC can't pass the vault to the extension for retrieving a secret from a specific vault. You can only define one vault input argument for the command.
276+
277+
You can define one argument in `secret.args` as a JSON object with the `vaultArg` property. For more information, see:
278+
279+
<DOCS_BASE_URL>/reference/schemas/extension/manifest/secret?<DOCS_VERSION_PIN>
280+
contains:
281+
type: object
282+
required: [vaultArg]
283+
minContains: 0
284+
maxContains: 1
112285

113286
defaultSnippets: # VS Code only
114-
- label: ' Define without arguments'
115-
markdownDescription: |
116-
Define the `secret` command for the extension when no arguments are required.
117-
body:
118-
executable: ${2:executable_name}
119-
- label: ' Define with arguments (flags + name)'
287+
- label: ' Define with arguments (static string and secret name input arguments)'
120288
markdownDescription: |-
121-
Define the `secret` command where the secret name is passed to a specific flag.
289+
Define the `secret` command where the secret name is passed to the command. Use this
290+
snippet if the extension doesn't support retrieving a secret from a specific vault.
122291
body:
123-
executable: ${2:executable_name}
292+
executable: ${1:executable_name}
124293
args:
125-
- ${3:--get-secret}
126-
- nameArg: ${4:--name}
127-
- label: ' Define with arguments (flags + name + vault)'
294+
- ${2:get}
295+
- nameArg: ${3:--secret-name}
296+
- label: ' Define with arguments (static string, secret name, and vault input arguments)'
128297
markdownDescription: |-
129-
Define the `secret` command where both the secret name and the vault name are passed.
298+
Define the `secret` command where both the secret name and the vault name are passed to the
299+
command. Use this snippet if the extension supports retrieving a secret from a specific vault.
130300
body:
131-
executable: ${2:executable_name}
301+
executable: ${1:executable_name}
132302
args:
133-
- ${3:--get-secret}
134-
- nameArg: ${4:--name}
135-
- vaultArg: ${5:--vault}
303+
- ${2:get}
304+
- nameArg: ${3:--secret-name}
305+
- vaultArg: ${4:--vault-name}

0 commit comments

Comments
 (0)