Client API v2 CLI client: make discriminator subcommand optional#48018
Conversation
This seems good as the descriminator is required. The new behavior seems good. The only thing that may not be clear is combining -f with fields:
The -f is effectively ignored. |
Damn, I should have had a test for it. I didn't think of it, thank you. So I think we can simply support |
22c30c1 to
8c48ffe
Compare
|
Thanks again @shawkins , I have fixed it and added test coverage to make sure it works as expected now. |
Pepo48
left a comment
There was a problem hiding this comment.
@michalvavrik thanks for the PR. Not sure if my concern is right, but it feels like skipIdPositional now controls 3 unrelated things at once - whether a user must pass -f, whether to show positional and whether to add -f option group.
This somehow works with the variant parent command, but I'd say it's more like a coincidence. Not sure about my hypothetical scenario, but what about a future variant, e.g. create needs no body? In other words, what if I want to skip but not require -f?
Regardless, whether the example makes sense, I would not keep it coupled this way - it makes the name skipIdPositional is misleading at least ,when it also means "require file".
Below I tried to suggest a small refactor that eliminates the skipIdPositional parameter and the setFileRequired() setter entirely. Wdyt? I also run the tests, everything seemed to work.
|
|
||
| CommandLine parentCli = new CommandLine(parentSpec); | ||
| groupCommand.setSpec(parentSpec); | ||
| CommandLine parentCli = buildLeafCommand(cmd, null, null, true); |
There was a problem hiding this comment.
| CommandLine parentCli = buildLeafCommand(cmd, null, null, true); | |
| CommandLine parentCli = buildLeafCommand(cmd, null, null); |
| @@ -72,7 +65,14 @@ private static CommandLine buildVariantParentCommand(CommandDescriptor cmd) { | |||
|
|
|||
| private static CommandLine buildLeafCommand(CommandDescriptor cmd, | |||
| List<OptionDescriptor> options, VariantDescriptor variant) { | |||
| return buildLeafCommand(cmd, options, variant, false); | |||
| } | |||
|
|
|||
There was a problem hiding this comment.
| boolean isVariantParent = variant == null | |
| && cmd.getVariants() != null && !cmd.getVariants().isEmpty(); |
There was a problem hiding this comment.
(I did bit refactoring, but essentially it is your suggestion in there)
| @@ -87,7 +87,7 @@ private static CommandLine buildLeafCommand(CommandDescriptor cmd, | |||
| addOutputGroup(spec); | |||
| } | |||
|
|
|||
| if (cmd.isRequiresId()) { | |||
| if (!skipIdPositional && cmd.isRequiresId()) { | |||
There was a problem hiding this comment.
| if (!skipIdPositional && cmd.isRequiresId()) { | |
| if (!isVariantParent && cmd.isRequiresId()) { |
| @@ -97,21 +97,20 @@ private static CommandLine buildLeafCommand(CommandDescriptor cmd, | |||
| .build()); | |||
| } | |||
|
|
|||
| if (options != null && !options.isEmpty()) { | |||
| boolean hasFieldOptions = options != null && !options.isEmpty(); | |||
| if (hasFieldOptions || skipIdPositional) { | |||
There was a problem hiding this comment.
| if (hasFieldOptions || skipIdPositional) { | |
| if (hasFieldOptions || isVariantParent) { |
| @Spec CommandSpec spec; | ||
| private final CommandDescriptor descriptor; | ||
| private final VariantDescriptor variant; | ||
| private boolean fileRequired; |
There was a problem hiding this comment.
| private boolean fileRequired; |
| void setFileRequired(boolean fileRequired) { | ||
| this.fileRequired = fileRequired; |
There was a problem hiding this comment.
| void setFileRequired(boolean fileRequired) { | |
| this.fileRequired = fileRequired; | |
| private boolean isVariantParent() { | |
| return variant == null | |
| && descriptor.getVariants() != null && !descriptor.getVariants().isEmpty(); |
| String body = buildRequestBody(); | ||
| if (body == null && fileRequired) { |
There was a problem hiding this comment.
| if (body == null && fileRequired) { | |
| if (body == null && isVariantParent()) { |
| throw new RuntimeException("Cannot extract '" + descriptor.getResourceName() | ||
| + "' resource ID from file. Use a subcommand that accepts <id> instead."); | ||
| } | ||
| Objects.requireNonNull(body, "Missing required file (-f/--file) with '" + idField + "' field"); |
There was a problem hiding this comment.
| Objects.requireNonNull(body, "Missing required file (-f/--file) with '" + idField + "' field"); |
I guess this is a dead code - fileRequired at line 161-164 always catches body==null for variant parents and PicoCLI enforces for leaf commands.
There was a problem hiding this comment.
I guess this is a dead code - fileRequired at line 161-164 always catches body==null for variant parents and PicoCLI enforces for leaf commands.
good catch
| @@ -11,6 +11,7 @@ | |||
| import java.util.LinkedHashMap; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| import java.util.Objects; | |||
There was a problem hiding this comment.
| import java.util.Objects; |
Pepo48
left a comment
There was a problem hiding this comment.
As for the tests, I think we could cover two more scenarios - for client patch -f bad.json (malformed json) and for client patch -f {"clientId": ""} (blank value).
Also, testClientCreateSamlFromFile doesn't call extractId to verify the created client can be fetched, while testClientCreateOidcFromFile counterpart does that, so I would add it to keep the test symmetry.
certainly, I'll do it. Thanks.
That actually was intentional, I thought we should know that it works as well. I'll make |
|
sorry, I misinterpreted your |
* Closes: keycloak#47463 Signed-off-by: Michal Vavřík <michal.vavrik@aol.com>
8c48ffe to
3ec5368
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the Keycloak Admin CLI v2 “client” commands to allow omitting the protocol discriminator subcommand (oidc/saml) when creating/updating/patching from a JSON file, aligning behavior with list/get which already work without a discriminator.
Changes:
- Make
client create|update|patch -f/--file ...work at the discriminator “parent” command level (nooidc/samlrequired), including extracting the required path<id>from file content when needed. - Improve CLI UX around this mode: help output and shell completion now show
-f/--fileon the variant parent and hide protocol-specific field options there. - Add integration/unit tests covering success paths and failure modes (missing file, duplicate
-f, missing/blank ID in file, malformed JSON).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
rest/admin-v2/tests/src/test/java/org/keycloak/tests/admin/cli/v2/KcAdmV2ClientCLITest.java |
Adds end-to-end integration tests for create/update/patch without discriminator when -f/--file is used, plus negative cases. |
integration/client-cli/admin-cli/src/test/java/org/keycloak/client/admin/cli/commands/v2/KcAdmV2HelpTest.java |
Verifies variant-parent help includes -f/--file, omits <id> and protocol field options, and still shows connection options. |
integration/client-cli/admin-cli/src/test/java/org/keycloak/client/admin/cli/commands/v2/KcAdmV2CompleterTest.java |
Ensures autocomplete suggests -f at the variant parent and does not suggest protocol-specific field options there. |
integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/v2/KcAdmV2RequestExecutor.java |
Implements parent/leaf -f resolution, rejects missing file at variant parent, and extracts <id> from JSON when positional ID is absent. |
integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/v2/KcAdmV2CommandDescriptor.java |
Adds hasVariants() helper to simplify variant detection. |
integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/v2/KcAdmV2CommandBuilder.java |
Builds executable variant-parent commands with -f/--file option and suppresses <id> positional for the parent. |
|
Thank you @michalvavrik, merged based upon my, @Pepo48, and copilot's reviews |
Before this PR, when creating / updating / patching clients you had to specify protocol discriminator to distinguish between
openid-connectandsamlclient protocols. However that is not aligned with what we do forlistorgetcommands where you could omit the discriminator sub-command like this:For that reason, we now allow to omit the discriminator when you create / update / patch the client from a file like this:
However, I did not allow to do this without file (e.g.
kcadm.sh client create --client-id test-client) because we would hit potential name conflicts specific for OIDC / SAML and we would also allow users to mix combinations that are not allowed (like options only specific to OIDC and options only specific to SAML). It would be confusing, if users will require it, we can follow-up in the future.