8000 Client API v2 CLI client: rename `update` command to `apply` to reflect that it can create resources as well as update them by michalvavrik · Pull Request #48134 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class KcAdmV2DescriptorBuilder {
PathItem.HttpMethod.POST, "create",
PathItem.HttpMethod.PATCH, "patch",
PathItem.HttpMethod.DELETE, "delete",
PathItem.HttpMethod.PUT, "update"
PathItem.HttpMethod.PUT, "apply"
);

public static KcAdmV2CommandDescriptor convert(OpenAPI openApi) {
Expand Down Expand Up @@ -168,7 +168,8 @@ private static boolean hasResponseBody(Operation operation) {
}

private static void populateOptionsAndVariants(
KcAdmV2CommandDescriptor.CommandDescriptor cmd, Operation operation, OpenAPI openApi) {
KcAdmV2CommandDescriptor.CommandDescriptor cmd, Operation operation,
OpenAPI openApi) {
Schema schema = extractRequestBodySchema(operation, openApi);

if (schema == null && operation.getRequestBody() != null) {
Expand Down Expand Up @@ -226,6 +227,10 @@ private static List<KcAdmV2CommandDescriptor.OptionDescriptor> toOptionDescripto

Schema resolved = propSchema.getRef() != null ? resolveSchema(propSchema, openApi) : propSchema;

if (Boolean.TRUE.equals(resolved.getReadOnly())) {
continue;
}

if (isObjectType(resolved)) {
flattenNestedObject(fieldName, resolved, openApi, options);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void testResourceGroupShowsCommands() {
assertTrue("Should suggest 'create'", candidates.contains("create"));
assertTrue("Should suggest 'get'", candidates.contains("get"));
assertTrue("Should suggest 'patch'", candidates.contains("patch"));
assertTrue("Should suggest 'update'", candidates.contains("update"));
assertTrue("Should suggest 'apply'", candidates.contains("apply"));
assertTrue("Should suggest 'delete'", candidates.contains("delete"));
}

Expand Down Expand Up @@ -103,6 +103,7 @@ public void testOidcOptionsInAutocompleteForCreateOidc() {
assertTrue("Should suggest '--web-origins'", candidates.contains("--web-origins"));
assertTrue("Should suggest '--auth-method'", candidates.contains("--auth-method"));
assertFalse("Should not suggest '--sign-documents'", candidates.contains("--sign-documents"));
assertFalse("Should not suggest '--uuid' on create: " + candidates, candidates.contains("--uuid"));
}

@Test
Expand Down Expand Up @@ -138,19 +139,20 @@ public void testOutputOptionsNotInAutocompleteForDelete() {
}

@Test
public void testUpdateVariantsInAutocomplete() {
List<String> candidates = complete("client", "update", "");
public void testApplyVariantsInAutocomplete() {
List<String> candidates = complete("client", "apply", "");
assertTrue("Should suggest 'oidc'", candidates.contains("oidc"));
assertTrue("Should suggest 'saml'", candidates.contains("saml"));
}

@Test
public void testUpdateOidcOptionsInAutocomplete() {
List<String> candidates = complete("client", "update", "oidc", "--");
public void testApplyOidcOptionsInAutocomplete() {
List<String> candidates = complete("client", "apply", "oidc", "--");
assertTrue("Should suggest '--client-id'", candidates.contains("--client-id"));
assertTrue("Should suggest '--login-flows'", candidates.contains("--login-flows"));
assertTrue("Should suggest '--compressed'", candidates.contains("--compressed"));
assertFalse("Should not suggest '--sign-documents'", candidates.contains("--sign-documents"));
assertFalse("Should not suggest '--uuid' on apply: " + candidates, candidates.contains("--uuid"));
}

@Test
Expand Down Expand Up @@ -200,7 +202,7 @@ private void assertNoSubcommands(List<String> candidates) {
}

private void assertSubcommandsDoNotContain(List<String> candidates) {
for (String name : new String[]{"create", "get", "patch", "update", "delete"}) {
for (String name : new String[]{"create", "get", "patch", "apply", "delete"}) {
if (candidates.contains(name)) {
throw new AssertionError("Should not contain '" + name + "' but did");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public void testClientHelpShowsAllCommands() {
CommandLine clientCli = cli.getSubcommands().get("client");

String help = clientCli.getUsageMessage();
for (String cmd : List.of("list", "create", "get", "patch", "update", "delete")) {
for (String cmd : List.of("list", "create", "get", "patch", "apply", "delete")) {
assertTrue("Client help should list '" + cmd + "'", help.contains(cmd));
}
}
Expand All @@ -83,6 +83,7 @@ public void testCreateOidcShowsOidcOptions() {
assertTrue("should have --service-account-roles", help.contains("--service-account-roles"));
assertTrue("should have -f", help.contains("-f"));
assertFalse("should not have --sign-documents", help.contains("--sign-documents"));
assertFalse("create should not expose readOnly --uuid: " + help, help.contains("--uuid"));
}

@Test
Expand Down Expand Up @@ -118,6 +119,7 @@ public void testPatchOidcShowsOidcOptions() {
assertTrue("should have --login-flows", help.contains("--login-flows"));
assertTrue("should have -f", help.contains("-f"));
assertFalse("should not have --sign-documents", help.contains("--sign-documents"));
assertFalse("patch should not expose readOnly --uuid: " + help, help.contains("--uuid"));
}

@Test
Expand All @@ -128,47 +130,48 @@ public void testPatchSamlShowsSamlOptions() {
}

@Test
public void testUpdateHasProtocolVariants() {
public void testApplyHasProtocolVariants() {
CommandLine cli = createCli();
CommandLine updateCli = cli.getSubcommands().get("client").getSubcommands().get("update");
assertTrue("update should have 'oidc' subcommand", updateCli.getSubcommands().containsKey("oidc"));
assertTrue("update should have 'saml' subcommand", updateCli.getSubcommands().containsKey("saml"));
CommandLine applyCli = cli.getSubcommands().get("client").getSubcommands().get("apply");
assertTrue("apply should have 'oidc' subcommand", applyCli.getSubcommands().containsKey("oidc"));
assertTrue("apply should have 'saml' subcommand", applyCli.getSubcommands().containsKey("saml"));
}

@Test
public void testUpdateOidcShowsOidcOptions() {
String help = getVariantHelp("update", "oidc");
public void testApplyOidcShowsOidcOptions() {
String help = getVariantHelp("apply", "oidc");
assertTrue("should have --login-flows", help.contains("--login-flows"));
assertTrue("should have -f", help.contains("-f"));
assertTrue("should have <id> positional", help.contains("<id>"));
assertFalse("should not have --sign-documents", help.contains("--sign-documents"));
assertFalse("apply should not expose readOnly --uuid: " + help, help.contains("--uuid"));
}

@Test
public void testUpdateSamlShowsSamlOptions() {
String help = getVariantHelp("update", "saml");
public void testApplySamlShowsSamlOptions() {
String help = getVariantHelp("apply", "saml");
assertTrue("should have --sign-documents", help.contains("--sign-documents"));
assertTrue("should have <id> positional", help.contains("<id>"));
assertFalse("should not have --login-flows", help.contains("--login-flows"));
}

@Test
public void testUpdateHasOutputOptions() {
String help = getVariantHelp("update", "oidc");
assertTrue("update (200 response) should have Output options", help.contains("Output options:"));
public void testApplyHasOutputOptions() {
String help = getVariantHelp("apply", "oidc");
assertTrue("apply (200 response) should have Output options", help.contains("Output options:"));
assertTrue("should have --compressed", help.contains("--compressed"));
}

@Test
public void testHelpOnUpdateVariantLeafWithRequiredId() {
public void testHelpOnApplyVariantLeafWithRequiredId() {
CommandLine cli = createCli();
StringWriter out = new StringWriter();
StringWriter err = new StringWriter();
cli.setOut(new PrintWriter(out));
cli.setErr(new PrintWriter(err));

int exitCode = cli.execute("client", "update", "oidc", "--help");
assertEquals("--help on update variant should exit with 0, err: " + err, 0, exitCode);
int exitCode = cli.execute("client", "apply", "oidc", "--help");
assertEquals("--help on apply variant should exit with 0, err: " + err, 0, exitCode);
assertTrue("should show help with --client-id option", out.toString().contains("--client-id"));
}

Expand Down Expand Up @@ -328,11 +331,11 @@ public void testVariantParentShowsFileOptionForCreate() {
}

@Test
public void testVariantParentShowsFileOptionForUpdate() {
String help = getVariantParentHelp("update");
assertTrue("update parent should show -f option: " + help, help.contains("-f"));
assertTrue("update parent should show --file option: " + help, help.contains("--file"));
assertFalse("update parent should not show <id>: " + help, help.contains("<id>"));
public void testVariantParentShowsFileOptionForApply() {
String help = getVariantParentHelp("apply");
assertTrue("apply parent should show -f option: " + help, help.contains("-f"));
assertTrue("apply parent should show --file option: " + help, help.contains("--file"));
assertFalse("apply parent should not show <id>: " + help, help.contains("<id>"));
}

@Test
Expand Down
AE5C
Original file line number Diff line number Diff line change
Expand Up @@ -474,55 +474,55 @@ void testGetClientWithSpecialCharsInId() {
}

@Test
void testUpdateCreatesNewClient() throws Exception {
void testApplyCreatesNewClient() throws Exception {
Path jsonFile = new File(tempDir, "put-create.json").toPath();
Files.writeString(jsonFile, """
{"clientId": "put-created", "protocol": "openid-connect", "enabled": true}
""");

CommandResult result = kcAdmV2Cmd("client", "update", "oidc", "put-created", "-f", jsonFile.toString());
CommandResult result = kcAdmV2Cmd("client", "apply", "oidc", "put-created", "-f", jsonFile.toString());
assertThat("PUT create should succeed: " + result.err(), result.exitCode(), is(0));
assertThat(result.out(), containsString("\"clientId\" : \"put-created\""));
assertThat(result.out(), containsString("\"enabled\" : true"));
}

@Test
< 10000 span class='blob-code-inner blob-code-marker js-skip-tagsearch' data-code-marker="-"> void testUpdateExistingClient() throws Exception {
void testApplyExistingClient() throws Exception {
CommandResult createResult = kcAdmV2Cmd("client", "create", "oidc",
"--client-id", "put-existing", "--enabled", "true", "--description", "original");
assertThat("setup: create should succeed", createResult.exitCode(), is(0));

Path jsonFile = new File(tempDir, "put-update.json").toPath();
Path jsonFile = new File(tempDir, "put-apply.json").toPath();
Files.writeString(jsonFile, """
{"clientId": "put-existing", "protocol": "openid-connect", "enabled": false, "description": "updated"}
""");

CommandResult updateResult = kcAdmV2Cmd("client", "update", "oidc", "put-existing", "-f", jsonFile.toString());
assertThat("PUT update should succeed: " + updateResult.err(), updateResult.exitCode(), is(0));
assertThat(updateResult.out(), containsString("\"enabled\" : false"));
assertThat(updateResult.out(), containsString("\"description\" : \"updated\""));
assertThat("PUT replaces the whole resource", updateResult.out(), containsString("\"clientId\" : \"put-existing\""));
CommandResult applyResult = kcAdmV2Cmd("client", "apply", "oidc", "put-existing", "-f", jsonFile.toString());
assertThat("PUT apply should succeed: " + applyResult.err(), applyResult.exitCode(), is(0));
assertThat(applyResult.out(), containsString("\"enabled\" : false"));
assertThat(applyResult.out(), containsString("\"description\" : \"updated\""));
assertThat("PUT replaces the whole resource", applyResult.out(), containsString("\"clientId\" : \"put-existing\""));
}

@Test
void testUpdateWithFieldOptions() {
void testApplyWithFieldOptions() {
kcAdmV2Cmd("client", "create", "oidc", "--client-id", "put-with-options", "--enabled", "true");

CommandResult result = kcAdmV2Cmd("client", "update", "oidc", "put-with-options",
CommandResult result = kcAdmV2Cmd("client", "apply", "oidc", "put-with-options",
"--client-id", "put-with-options", "--enabled", "false");
assertThat("PUT with field options should succeed: " + result.err(), result.exitCode(), is(0));
assertThat(result.out(), containsString("\"enabled\" : false"));
assertThat(result.out(), containsString("\"clientId\" : \"put-with-options\""));
}

@Test
void testUpdateWithoutClientIdFails() throws Exception {
void testApplyWithoutClientIdFails() throws Exception {
Path jsonFile = new File(tempDir, "put-no-clientid.json").toPath();
Files.writeString(jsonFile, """
{"protocol": "openid-connect", "enabled": true}
""");

CommandResult result = kcAdmV2Cmd("client", "update", "oidc", "some-id", "-f", jsonFile.toString());
CommandResult result = kcAdmV2Cmd("client", "apply", "oidc", "some-id", "-f", jsonFile.toString());
assertThat("PUT without clientId should fail", result.exitCode(), is(not(0)));
assertThat(result.err(), is("""
Provided data is invalid:
Expand All @@ -531,30 +531,30 @@ void testUpdateWithoutClientIdFails() throws Exception {
}

@Test
void testUpdateWithMismatchedClientIdFails() throws Exception {
void testApplyWithMismatchedClientIdFails() throws Exception {
Path jsonFile = new File(tempDir, "put-mismatch.json").toPath();
Files.writeString(jsonFile, """
{"clientId": "wrong-id", "protocol": "openid-connect"}
""");

CommandResult result = kcAdmV2Cmd("client", "update", "oidc", "correct-id", "-f", jsonFile.toString());
CommandResult result = kcAdmV2Cmd("client", "apply", "oidc", "correct-id", "-f", jsonFile.toString());
assertThat("PUT with mismatched clientId should fail", result.exitCode(), is(not(0)));
assertThat(result.err(), containsString("does not match"));
}

@Test
void testUpdateWithMalformedJsonFile() throws Exception {
void testApplyWithMalformedJsonFile() throws Exception {
Path jsonFile = new File(tempDir, "put-bad.json").toPath();
Files.writeString(jsonFile, "not json at all");

CommandResult result = kcAdmV2Cmd("client", "update", "oidc", "any-id", "-f", jsonFile.toString());
CommandResult result = kcAdmV2Cmd("client", "apply", "oidc", "any-id", "-f", jsonFile.toString());
assertThat("PUT with malformed JSON should fail", result.exitCode(), is(not(0)));
assertThat(result.err(), containsString("Cannot parse the JSON"));
}

@Test
void testUpdateNonExistentFile() {
CommandResult result = kcAdmV2Cmd("client", "update", "oidc", "any-id", "-f", "/nonexistent/file.json");
void testApplyNonExistentFile() {
CommandResult result = kcAdmV2Cmd("client", "apply", "oidc", "any-id", "-f", "/nonexistent/file.json");
assertThat("PUT with missing file should fail", result.exitCode(), is(not(0)));
assertThat(result.err(), containsString("File not found"));
}
Expand Down Expand Up @@ -637,18 +637,18 @@ void testClientCreateSamlFromFile() throws Exception {
}

@Test
void testClientUpdateFromFile() throws Exception {
void testClientApplyFromFile() throws Exception {
CommandResult createResult = kcAdmV2Cmd("client", "create", "oidc",
"--client-id", "update-no-disc", "--enabled", "true");
"--client-id", "apply-no-disc", "--enabled", "true");
assertThat("setup: create should succeed", createResult.exitCode(), is(0));

Path jsonFile = new File(tempDir, "update-from-file.json").toPath();
Path jsonFile = new File(tempDir, "apply-from-file.json").toPath();
Files.writeString(jsonFile, """
{"clientId": "update-no-disc", "protocol": "openid-connect", "enabled": false}
{"clientId": "apply-no-disc", "protocol": "openid-connect", "enabled": false}
""");

CommandResult result = kcAdmV2Cmd("client", "update", "-f", jsonFile.toString());
assertThat("'client update -f' should succeed: " + result.err(), result.exitCode(), is(0));
CommandResult result = kcAdmV2Cmd("client", "apply", "-f", jsonFile.toString());
assertThat("'client apply -f' should succeed: " + result.err(), result.exitCode(), is(0));
assertThat(result.out(), containsString("\"enabled\" : false"));
}

Expand Down Expand Up @@ -764,7 +764,7 @@ void testClientPatchFromFileBeforeSubcommand() throws Exception {
void testFileBeforeSubcommandAndFieldOptionsMutuallyExclusive() {
// -f on parent + field options on leaf should still be rejected as mutually exclusive
// no real file needed — mutual exclusivity check fires before file access
CommandResult result = kcAdmV2Cmd("client", "update", "-f", "/any/path.json",
CommandResult result = kcAdmV2Cmd("client", "apply", "-f", "/any/path.json",
"oidc", "exclusive-test", "--client-id", "exclusive-test");
assertThat("-f before subcommand with field options should fail, err: " + result.err()
+ ", out: " + result.out(),
Expand Down
Loading
0