From e5837e4cb62608b93486422e6479190ffd8023a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Thu, 7 Sep 2023 10:28:10 +0200 Subject: [PATCH 1/5] Cli: add profiles --- conf/cli.yaml | 32 +- .../langstream/admin/client/AdminClient.java | 2 +- .../langstream/cli/LangStreamCLIConfig.java | 14 +- .../java/ai/langstream/cli/NamedProfile.java | 11 + .../main/java/ai/langstream/cli/Profile.java | 17 ++ .../ai/langstream/cli/commands/BaseCmd.java | 52 +++- .../ai/langstream/cli/commands/RootCmd.java | 7 + .../cli/commands/RootProfileCmd.java | 44 +++ .../DownloadApplicationCodeCmd.java | 2 +- .../cli/commands/configure/ConfigureCmd.java | 13 +- .../cli/commands/gateway/BaseGatewayCmd.java | 12 +- .../cli/commands/profiles/BaseProfileCmd.java | 89 ++++++ .../profiles/CreateUpdateProfileCmd.java | 121 ++++++++ .../commands/profiles/DeleteProfileCmd.java | 43 +++ .../profiles/GetCurrentProfileCmd.java | 30 ++ .../cli/commands/profiles/GetProfileCmd.java | 48 +++ .../commands/profiles/ImportProfileCmd.java | 124 ++++++++ .../cli/commands/profiles/ListProfileCmd.java | 66 ++++ .../profiles/SetCurrentProfileCmd.java | 38 +++ .../applications/CommandTestBase.java | 9 + .../applications/ProfilesCmdTest.java | 283 ++++++++++++++++++ 21 files changed, 1016 insertions(+), 41 deletions(-) create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/NamedProfile.java create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/Profile.java create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/commands/RootProfileCmd.java create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/BaseProfileCmd.java create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/CreateUpdateProfileCmd.java create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/DeleteProfileCmd.java create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/GetCurrentProfileCmd.java create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/GetProfileCmd.java create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ImportProfileCmd.java create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ListProfileCmd.java create mode 100644 langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/SetCurrentProfileCmd.java create mode 100644 langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java diff --git a/conf/cli.yaml b/conf/cli.yaml index 0ff2b36ef..fab9f6f2b 100644 --- a/conf/cli.yaml +++ b/conf/cli.yaml @@ -1,19 +1,13 @@ -# -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -webServiceUrl: http://localhost:8090 -apiGatewayUrl: ws://localhost:8091 -tenant: default +--- +webServiceUrl: "http://localhost:8090" +apiGatewayUrl: "ws://localhost:8091" +tenant: "default" +token: null +profiles: + new: + webServiceUrl: "ss" + apiGatewayUrl: null + tenant: "gigio" + token: null + name: "new" +currentProfile: "new" diff --git a/langstream-admin-client/src/main/java/ai/langstream/admin/client/AdminClient.java b/langstream-admin-client/src/main/java/ai/langstream/admin/client/AdminClient.java index 56f93e62d..311ce8bc2 100644 --- a/langstream-admin-client/src/main/java/ai/langstream/admin/client/AdminClient.java +++ b/langstream-admin-client/src/main/java/ai/langstream/admin/client/AdminClient.java @@ -205,7 +205,7 @@ public String tenantAppPath(String uri) { final String tenant = configuration.getTenant(); if (tenant == null) { throw new IllegalStateException( - "Tenant not set. Run 'langstream configure tenant ' to set it."); + "Tenant not set. Please set the tenant in the configuration."); } logger.debug("Using tenant: %s".formatted(tenant)); return "/applications/%s%s".formatted(tenant, uri); diff --git a/langstream-cli/src/main/java/ai/langstream/cli/LangStreamCLIConfig.java b/langstream-cli/src/main/java/ai/langstream/cli/LangStreamCLIConfig.java index 589c9ae55..08186d234 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/LangStreamCLIConfig.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/LangStreamCLIConfig.java @@ -15,20 +15,16 @@ */ package ai.langstream.cli; -import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; +import java.util.TreeMap; import lombok.Getter; import lombok.Setter; @Getter @Setter -public class LangStreamCLIConfig { +public class LangStreamCLIConfig extends Profile { - @JsonProperty(required = true) - private String webServiceUrl; + private Map profiles = new TreeMap<>(); - private String apiGatewayUrl; - - private String tenant; - - private String token; + private String currentProfile = "default"; } diff --git a/langstream-cli/src/main/java/ai/langstream/cli/NamedProfile.java b/langstream-cli/src/main/java/ai/langstream/cli/NamedProfile.java new file mode 100644 index 000000000..2023ec26f --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/NamedProfile.java @@ -0,0 +1,11 @@ +package ai.langstream.cli; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class NamedProfile extends Profile { + + private String name; +} diff --git a/langstream-cli/src/main/java/ai/langstream/cli/Profile.java b/langstream-cli/src/main/java/ai/langstream/cli/Profile.java new file mode 100644 index 000000000..00ec698a6 --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/Profile.java @@ -0,0 +1,17 @@ +package ai.langstream.cli; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Profile { + + @JsonProperty(required = true) + private String webServiceUrl; + + private String apiGatewayUrl; + private String tenant; + private String token; +} diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java index 80c6c9a6a..43d3edd53 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java @@ -19,6 +19,8 @@ import ai.langstream.admin.client.AdminClientConfiguration; import ai.langstream.admin.client.AdminClientLogger; import ai.langstream.cli.LangStreamCLIConfig; +import ai.langstream.cli.NamedProfile; +import ai.langstream.cli.commands.profiles.BaseProfileCmd; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; @@ -44,7 +46,8 @@ public enum Formats { yaml } - private static final ObjectMapper yamlConfigReader = new ObjectMapper(new YAMLFactory()); + protected static final ObjectMapper yamlConfigReader = new ObjectMapper(new YAMLFactory()); + protected static final ObjectMapper jsonConfigReader = new ObjectMapper(); protected static final ObjectMapper jsonPrinter = new ObjectMapper() .enable(SerializationFeature.INDENT_OUTPUT) @@ -81,7 +84,7 @@ public void debug(Object message) { BaseCmd.this.debug(message); } }; - client = new AdminClient(toAdminConfiguration(getConfig()), logger); + client = new AdminClient(toAdminConfiguration(), logger); } return client; } @@ -91,11 +94,46 @@ protected LangStreamCLIConfig getConfig() { return config; } - private static AdminClientConfiguration toAdminConfiguration(LangStreamCLIConfig config) { + protected NamedProfile getDefaultProfile() { + final LangStreamCLIConfig config = getConfig(); + final NamedProfile defaultProfile = + new NamedProfile(); + defaultProfile.setName(BaseProfileCmd.DEFAULT_PROFILE_NAME); + defaultProfile.setTenant(config.getTenant()); + defaultProfile.setToken(config.getToken()); + defaultProfile.setWebServiceUrl(config.getWebServiceUrl()); + defaultProfile.setApiGatewayUrl(config.getApiGatewayUrl()); + return defaultProfile; + } + + protected NamedProfile getCurrentProfile() { + final String profile; + if (getRootCmd().getProfile() != null) { + profile = getRootCmd().getProfile(); + } else { + profile = getConfig().getCurrentProfile(); + } + if (BaseProfileCmd.DEFAULT_PROFILE_NAME.equals(profile)) { + return getDefaultProfile(); + } + final NamedProfile result = getConfig().getProfiles().get(profile); + if (result == null) { + throw new IllegalStateException( + "No profile '%s' defined in configuration".formatted(profile)); + } + return result; + } + + private AdminClientConfiguration toAdminConfiguration() { + final NamedProfile profile = getCurrentProfile(); + if (profile.getWebServiceUrl() == null) { + throw new IllegalStateException( + "No webServiceUrl defined for profile '%s'".formatted(profile.getName())); + } return AdminClientConfiguration.builder() - .webServiceUrl(config.getWebServiceUrl()) - .token(config.getToken()) - .tenant(config.getTenant()) + .webServiceUrl(profile.getWebServiceUrl()) + .token(profile.getToken()) + .tenant(profile.getTenant()) .build(); } @@ -247,7 +285,7 @@ private String computeFormatTemplate(int numColumns) { if (i > 0) { formatTemplate.append(" "); } - formatTemplate.append("%-15.15s"); + formatTemplate.append("%-25.25s"); } return formatTemplate.toString(); } diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/RootCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/RootCmd.java index cdec6df2a..6cecd6100 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/RootCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/RootCmd.java @@ -31,6 +31,7 @@ ConfigureCmd.class, RootTenantCmd.class, RootGatewayCmd.class, + RootProfileCmd.class, AutoComplete.GenerateCompletion.class }) public class RootCmd { @@ -41,6 +42,12 @@ public class RootCmd { @Getter private String configPath; + @CommandLine.Option( + names = {"-p", "--profile"}, + description = "Profile to use with the command.") + @Getter + private String profile; + @CommandLine.Option( names = {"-v", "--verbose"}, defaultValue = "false", diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/RootProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/RootProfileCmd.java new file mode 100644 index 000000000..370d702bc --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/RootProfileCmd.java @@ -0,0 +1,44 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.cli.commands; + +import ai.langstream.cli.commands.profiles.CreateUpdateProfileCmd; +import ai.langstream.cli.commands.profiles.DeleteProfileCmd; +import ai.langstream.cli.commands.profiles.GetCurrentProfileCmd; +import ai.langstream.cli.commands.profiles.ImportProfileCmd; +import ai.langstream.cli.commands.profiles.ListProfileCmd; +import ai.langstream.cli.commands.profiles.SetCurrentProfileCmd; +import ai.langstream.cli.commands.profiles.GetProfileCmd; +import lombok.Getter; +import picocli.CommandLine; + +@CommandLine.Command( + name = "profiles", + header = "Manage local profiles", + subcommands = { + CreateUpdateProfileCmd.CreateProfileCmd.class, + CreateUpdateProfileCmd.UpdateProfileCmd.class, + ListProfileCmd.class, + GetCurrentProfileCmd.class, + SetCurrentProfileCmd.class, + GetProfileCmd.class, + ImportProfileCmd.class, + DeleteProfileCmd.class + }) +@Getter +public class RootProfileCmd { + @CommandLine.ParentCommand private RootCmd rootCmd; +} diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/applications/DownloadApplicationCodeCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/applications/DownloadApplicationCodeCmd.java index cfeaca2ad..0880dbab1 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/applications/DownloadApplicationCodeCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/applications/DownloadApplicationCodeCmd.java @@ -57,7 +57,7 @@ public void run() { } } if (filename == null) { - filename = "%s-%s.zip".formatted(getConfig().getTenant(), applicationId); + filename = "%s-%s.zip".formatted(getCurrentProfile().getTenant(), applicationId); } path = Path.of(filename); } diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/configure/ConfigureCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/configure/ConfigureCmd.java index 8c11a7e73..f8a609d55 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/configure/ConfigureCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/configure/ConfigureCmd.java @@ -17,12 +17,16 @@ import ai.langstream.cli.commands.BaseCmd; import ai.langstream.cli.commands.RootCmd; +import ai.langstream.cli.commands.profiles.BaseProfileCmd; import java.util.Arrays; import lombok.Getter; import lombok.SneakyThrows; import picocli.CommandLine; -@CommandLine.Command(name = "configure", header = "Configure LangStream tenant and authentication") +@CommandLine.Command( + name = "configure", + header = + "Configure LangStream tenant and authentication. DEPRECATED. Use 'langstream profiles' instead.") @Getter public class ConfigureCmd extends BaseCmd { @@ -44,6 +48,9 @@ public enum ConfigKey { @Override @SneakyThrows public void run() { + if (getRootCmd().getProfile() != null) { + throw new IllegalArgumentException("Global profile flag is not allowed here"); + } updateConfig( clientConfig -> { switch (configKey) { @@ -56,6 +63,8 @@ public void run() { .formatted(configKey, Arrays.toString(ConfigKey.values()))); } }); - log("Config updated: %s=%s".formatted(configKey, newValue)); + log( + "profile %s updated: %s=%s" + .formatted(BaseProfileCmd.DEFAULT_PROFILE_NAME, configKey, newValue)); } } diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java index 3b5df439e..ad39f2b01 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/gateway/BaseGatewayCmd.java @@ -88,14 +88,22 @@ protected String validateGatewayAndGetUrl( validateGateway(applicationId, gatewayId, type, params, options, credentials); return "%s/v1/%s/%s/%s/%s?%s" .formatted( - getConfig().getApiGatewayUrl(), + getApiGatewayUrl(), type.toString(), - getConfig().getTenant(), + getTenant(), applicationId, gatewayId, computeQueryString(credentials, params, options)); } + private String getTenant() { + return getCurrentProfile().getTenant(); + } + + private String getApiGatewayUrl() { + return getCurrentProfile().getApiGatewayUrl(); + } + private Map applicationDescriptions = new HashMap<>(); @SneakyThrows diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/BaseProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/BaseProfileCmd.java new file mode 100644 index 000000000..940121dfb --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/BaseProfileCmd.java @@ -0,0 +1,89 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.cli.commands.profiles; + +import ai.langstream.cli.LangStreamCLIConfig; +import ai.langstream.cli.NamedProfile; +import ai.langstream.cli.Profile; +import ai.langstream.cli.commands.BaseCmd; +import ai.langstream.cli.commands.RootCmd; +import ai.langstream.cli.commands.RootProfileCmd; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import picocli.CommandLine; + +public abstract class BaseProfileCmd extends BaseCmd { + + public static final String DEFAULT_PROFILE_NAME = "default"; + @CommandLine.ParentCommand private RootProfileCmd cmd; + + @Override + protected RootCmd getRootCmd() { + return cmd.getRootCmd(); + } + + protected void checkGlobalFlags() { + if (getRootCmd().getProfile() != null) { + throw new IllegalArgumentException( + "Global profile flag is not allowed for profiles commands"); + } + } + + protected void validateProfile(Profile profile) { + if (StringUtils.isBlank(profile.getWebServiceUrl())) { + throw new IllegalArgumentException("webServiceUrl is required"); + } + } + + protected List listAllProfiles() { + final LangStreamCLIConfig config = getConfig(); + List all = new ArrayList<>(); + final NamedProfile defaultProfile = getDefaultProfile(); + all.add(defaultProfile); + final Collection values = config.getProfiles().values(); + all.addAll(values); + return all; + } + + protected NamedProfile getProfileOrThrow(String name) { + if (DEFAULT_PROFILE_NAME.equals(name)) { + return getDefaultProfile(); + } + final NamedProfile profile = getConfig().getProfiles().get(name); + if (profile == null) { + throw new IllegalArgumentException(getProfileNotFoundMessage(name)); + } + return profile; + } + + protected String getProfileNotFoundMessage(String name) { + return "Profile %s not found, maybe you meant one of these: %s" + .formatted( + name, + listAllProfiles().stream() + .map(NamedProfile::getName) + .collect(Collectors.joining(", "))); + } + + protected void checkProfileName(String name) { + if (DEFAULT_PROFILE_NAME.equals(name)) { + throw new IllegalArgumentException("Profile name %s is reserved".formatted(name)); + } + } +} diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/CreateUpdateProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/CreateUpdateProfileCmd.java new file mode 100644 index 000000000..a1fd817a0 --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/CreateUpdateProfileCmd.java @@ -0,0 +1,121 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.cli.commands.profiles; + +import ai.langstream.cli.NamedProfile; +import lombok.SneakyThrows; +import picocli.CommandLine; + +public abstract class CreateUpdateProfileCmd extends BaseProfileCmd { + + @CommandLine.Parameters(description = "Name of the profile") + private String name; + + @CommandLine.Option( + names = {"--set-current"}, + description = "Set this profile as current") + private boolean setAsCurrent; + + @CommandLine.Option( + names = {"--web-service-url"}, + description = "webServiceUrl of the profile") + private String webServiceUrl; + + @CommandLine.Option( + names = {"--api-gateway-url"}, + description = "apiGatewayUrl of the profile") + private String apiGatewayUrl; + + @CommandLine.Option( + names = {"--tenant"}, + description = "tenant of the profile") + private String tenant; + + @CommandLine.Option( + names = {"--token"}, + description = "token of the profile") + private String token; + + @Override + @SneakyThrows + public void run() { + checkGlobalFlags(); + checkProfileName(name); + + NamedProfile profile = getConfig().getProfiles().get(name); + if (isCreate()) { + if (profile != null) { + throw new IllegalArgumentException("Profile %s already exists".formatted(name)); + } + profile = new NamedProfile(); + profile.setName(name); + } else { + if (profile == null) { + throw new IllegalArgumentException(getProfileNotFoundMessage(name)); + } + } + + if (webServiceUrl != null) { + profile.setWebServiceUrl(webServiceUrl); + } + if (apiGatewayUrl != null) { + profile.setApiGatewayUrl(apiGatewayUrl); + } + if (tenant != null) { + profile.setTenant(tenant); + } + if (token != null) { + profile.setToken(token); + } + validateProfile(profile); + + final NamedProfile finalProfile = profile; + + updateConfig( + langStreamCLIConfig -> { + langStreamCLIConfig.getProfiles().put(name, finalProfile); + if (isCreate()) { + log("profile %s created".formatted(name)); + } else { + log("profile %s updated".formatted(name)); + } + if (setAsCurrent) { + langStreamCLIConfig.setCurrentProfile(name); + log("profile %s set as current".formatted(name)); + } + }); + } + + protected abstract boolean isCreate(); + + @CommandLine.Command(name = "create", header = "Create a new profile") + public static class CreateProfileCmd extends CreateUpdateProfileCmd { + + @Override + protected boolean isCreate() { + return true; + } + } + + @CommandLine.Command(name = "update", header = "Update an existing profile") + public static class UpdateProfileCmd extends CreateUpdateProfileCmd { + + @Override + protected boolean isCreate() { + return false; + } + } +} diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/DeleteProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/DeleteProfileCmd.java new file mode 100644 index 000000000..3cce139fa --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/DeleteProfileCmd.java @@ -0,0 +1,43 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.cli.commands.profiles; + +import lombok.SneakyThrows; +import picocli.CommandLine; + +@CommandLine.Command(name = "delete", header = "Delete an existing profile") +public class DeleteProfileCmd extends BaseProfileCmd { + + @CommandLine.Parameters(description = "Name of the profile") + private String name; + + @Override + @SneakyThrows + public void run() { + checkGlobalFlags(); + checkProfileName(name); + getProfileOrThrow(name); + updateConfig( + langStreamCLIConfig -> { + if (name.equals(langStreamCLIConfig.getCurrentProfile())) { + throw new IllegalArgumentException("Cannot delete the current profile"); + } + + langStreamCLIConfig.getProfiles().remove(name); + log("profile %s deleted".formatted(name)); + }); + } +} diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/GetCurrentProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/GetCurrentProfileCmd.java new file mode 100644 index 000000000..1a6db99c2 --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/GetCurrentProfileCmd.java @@ -0,0 +1,30 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.cli.commands.profiles; + +import lombok.SneakyThrows; +import picocli.CommandLine; + +@CommandLine.Command(name = "get-current", header = "Get current profile") +public class GetCurrentProfileCmd extends BaseProfileCmd { + + @Override + @SneakyThrows + public void run() { + checkGlobalFlags(); + log(getConfig().getCurrentProfile()); + } +} diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/GetProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/GetProfileCmd.java new file mode 100644 index 000000000..47a37e81c --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/GetProfileCmd.java @@ -0,0 +1,48 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.cli.commands.profiles; + +import ai.langstream.cli.LangStreamCLIConfig; +import ai.langstream.cli.NamedProfile; +import java.util.List; +import lombok.SneakyThrows; +import picocli.CommandLine; + +@CommandLine.Command(name = "get", header = "Get an existing profile configuration") +public class GetProfileCmd extends BaseProfileCmd { + + @CommandLine.Parameters(description = "Name of the profile") + private String name; + + @CommandLine.Option( + names = {"-o"}, + description = "Output format") + private Formats format = Formats.raw; + + @Override + @SneakyThrows + public void run() { + checkGlobalFlags(); + final NamedProfile profile = getProfileOrThrow(name); + final LangStreamCLIConfig config = getConfig(); + final Object object = format == Formats.raw ? List.of(profile) : profile; + print( + format, + jsonPrinter.writeValueAsString(object), + ListProfileCmd.COLUMNS_FOR_RAW, + ListProfileCmd.rawMapping(config)); + } +} diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ImportProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ImportProfileCmd.java new file mode 100644 index 000000000..416e96f24 --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ImportProfileCmd.java @@ -0,0 +1,124 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.cli.commands.profiles; + +import ai.langstream.cli.NamedProfile; +import ai.langstream.cli.Profile; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Base64; +import lombok.SneakyThrows; +import picocli.CommandLine; + +@CommandLine.Command(name = "import", header = "Import profile from file or inline json") +public class ImportProfileCmd extends BaseProfileCmd { + + @CommandLine.Parameters(description = "Name of the profile") + private String name; + + @CommandLine.Option( + names = {"-f", "--file"}, + description = "Import profile from file") + private String fromFile; + + @CommandLine.Option( + names = {"-i", "--inline"}, + description = "Import profile from inline json") + private String inline; + + @CommandLine.Option( + names = {"-u", "--update"}, + description = "Allow updating the profile if it already exists") + private boolean allowUpdate; + + @CommandLine.Option( + names = {"--set-current"}, + description = "Set this profile as current") + private boolean setAsCurrent; + + @Override + @SneakyThrows + public void run() { + checkGlobalFlags(); + checkProfileName(name); + + if (fromFile == null && inline == null) { + throw new IllegalArgumentException("Either --file or --inline must be specified"); + } + if (fromFile != null && inline != null) { + throw new IllegalArgumentException("Only one of --file or --inline must be specified"); + } + + final Profile profile; + + if (fromFile != null) { + + final File file = new File(fromFile); + if (!Files.isRegularFile(file.toPath())) { + throw new IllegalArgumentException("File %s does not exist".formatted(fromFile)); + } + profile = + yamlConfigReader.readValue( + new File(fromFile), Profile.class); + } else if (inline != null) { + String jsonProfile; + if (inline.startsWith("base64:")) { + final String base64Value = inline.substring("base64:".length()); + jsonProfile = + new String(Base64.getDecoder().decode(base64Value), StandardCharsets.UTF_8); + } else { + jsonProfile = inline; + } + profile = + jsonConfigReader.readValue(jsonProfile, Profile.class); + } else { + throw new IllegalStateException(); + } + + final NamedProfile newProfile = new NamedProfile(); + newProfile.setName(name); + newProfile.setTenant(profile.getTenant()); + newProfile.setApiGatewayUrl(profile.getApiGatewayUrl()); + newProfile.setWebServiceUrl(profile.getWebServiceUrl()); + newProfile.setToken(profile.getToken()); + + validateProfile(newProfile); + + updateConfig( + langStreamCLIConfig -> { + final NamedProfile existing = + langStreamCLIConfig.getProfiles().get(name); + if (!allowUpdate && existing != null) { + throw new IllegalArgumentException( + "Profile %s already exists".formatted(name)); + } + + + langStreamCLIConfig.getProfiles().put(name, newProfile); + if (existing == null) { + log("profile %s created".formatted(name)); + } else { + log("profile %s updated".formatted(name)); + } + + if (setAsCurrent) { + langStreamCLIConfig.setCurrentProfile(name); + log("profile %s set as current".formatted(name)); + } + }); + } +} diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ListProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ListProfileCmd.java new file mode 100644 index 000000000..65f2434ca --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ListProfileCmd.java @@ -0,0 +1,66 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.cli.commands.profiles; + +import ai.langstream.cli.LangStreamCLIConfig; +import com.fasterxml.jackson.databind.JsonNode; +import java.util.function.BiFunction; +import lombok.SneakyThrows; +import picocli.CommandLine; + +@CommandLine.Command(name = "list", header = "List profiles") +public class ListProfileCmd extends BaseProfileCmd { + + static final String[] COLUMNS_FOR_RAW = { + "profile", "webServiceUrl", "tenant", "token", "current" + }; + + @CommandLine.Option( + names = {"-o"}, + description = "Output format") + private Formats format = Formats.raw; + + @Override + @SneakyThrows + public void run() { + checkGlobalFlags(); + final LangStreamCLIConfig config = getConfig(); + final Object object = format == Formats.raw ? listAllProfiles() : config; + + print(format, jsonPrinter.writeValueAsString(object), COLUMNS_FOR_RAW, rawMapping(config)); + } + + static BiFunction rawMapping(LangStreamCLIConfig config) { + return (jsonNode, s) -> { + switch (s) { + case "profile": + return searchValueInJson(jsonNode, "name"); + case "webServiceUrl": + return searchValueInJson(jsonNode, "webServiceUrl"); + case "tenant": + return searchValueInJson(jsonNode, "tenant"); + case "token": + final Object token = searchValueInJson(jsonNode, "token"); + return token == null ? null : "********"; + case "current": + final Object name = searchValueInJson(jsonNode, "name"); + return name.equals(config.getCurrentProfile()) ? "*" : ""; + default: + return null; + } + }; + } +} diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/SetCurrentProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/SetCurrentProfileCmd.java new file mode 100644 index 000000000..ff1501bf6 --- /dev/null +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/SetCurrentProfileCmd.java @@ -0,0 +1,38 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.cli.commands.profiles; + +import lombok.SneakyThrows; +import picocli.CommandLine; + +@CommandLine.Command(name = "set-current", header = "Set current profile") +public class SetCurrentProfileCmd extends BaseProfileCmd { + + @CommandLine.Parameters(description = "Name of the profile") + private String name; + + @Override + @SneakyThrows + public void run() { + checkGlobalFlags(); + getProfileOrThrow(name); + updateConfig( + langStreamCLIConfig -> { + langStreamCLIConfig.setCurrentProfile(name); + log("profile %s set as current".formatted(name)); + }); + } +} diff --git a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/CommandTestBase.java b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/CommandTestBase.java index a5e8de5b0..239190a61 100644 --- a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/CommandTestBase.java +++ b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/CommandTestBase.java @@ -16,6 +16,9 @@ package ai.langstream.cli.commands.applications; import ai.langstream.cli.LangStreamCLI; +import ai.langstream.cli.LangStreamCLIConfig; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; @@ -25,6 +28,7 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.stream.Stream; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.io.TempDir; @@ -105,4 +109,9 @@ protected CommandResult executeCommand(String... args) { log.info(outRes); return new CommandResult(exitCode, outRes, errRes); } + + @SneakyThrows + protected LangStreamCLIConfig getConfig() { + return new ObjectMapper(new YAMLFactory()).readValue(cliYaml.toFile(), LangStreamCLIConfig.class); + } } diff --git a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java new file mode 100644 index 000000000..9240804f2 --- /dev/null +++ b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java @@ -0,0 +1,283 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.langstream.cli.commands.applications; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import ai.langstream.cli.NamedProfile; +import com.github.tomakehurst.wiremock.client.WireMock; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Base64; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class ProfilesCmdTest extends CommandTestBase { + + @Test + public void testCrud() { + CommandResult result = executeCommand("profiles", "create", "new", "--web-service-url", "http://my.localhost:8080", "--tenant", "t", "--token", "tok", "--api-gateway-url", "http://my.localhost:8091"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("profile new created", result.out()); + NamedProfile newProfile = getConfig().getProfiles().get("new"); + assertEquals("new", newProfile.getName()); + assertEquals("http://my.localhost:8080", newProfile.getWebServiceUrl()); + assertEquals("t", newProfile.getTenant()); + assertEquals("tok", newProfile.getToken()); + assertEquals("http://my.localhost:8091", newProfile.getApiGatewayUrl()); + + result = executeCommand("profiles", "update", "new", "--token", "tok2"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("profile new updated", result.out()); + newProfile = getConfig().getProfiles().get("new"); + assertEquals("new", newProfile.getName()); + assertEquals("http://my.localhost:8080", newProfile.getWebServiceUrl()); + assertEquals("t", newProfile.getTenant()); + assertEquals("tok2", newProfile.getToken()); + assertEquals("http://my.localhost:8091", newProfile.getApiGatewayUrl()); + + result = executeCommand("profiles", "get", "new"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals(""" + PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s + new http://my.localhost:8080 t ******** """, result.out()); + + result = executeCommand("profiles", "get", "new", "-o", "json"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals(""" + { + "webServiceUrl" : "http://my.localhost:8080", + "apiGatewayUrl" : "http://my.localhost:8091", + "tenant" : "t", + "token" : "tok2", + "name" : "new" + }""", result.out()); + + result = executeCommand("profiles", "get", "new", "-o", "yaml"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals(""" + --- + webServiceUrl: "http://my.localhost:8080" + apiGatewayUrl: "http://my.localhost:8091" + tenant: "t" + token: "tok2" + name: "new" """, result.out()); + + result = executeCommand("profiles", "get", "notexists"); + assertEquals(1, result.exitCode()); + assertEquals("Profile notexists not found, maybe you meant one of these: default, new", result.err()); + assertEquals("", result.out()); + + + result = executeCommand("profiles", "list"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals(""" + PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s + default %s my-tenant * \s + new http://my.localhost:8080 t ******** """.formatted(getConfig().getWebServiceUrl()), result.out()); + + result = executeCommand("profiles", "get-current"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("default", result.out()); + + result = executeCommand("profiles", "set-current", "new"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("profile new set as current", result.out()); + assertEquals("new", getConfig().getCurrentProfile()); + + + result = executeCommand("profiles", "delete", "new"); + assertEquals(1, result.exitCode()); + assertEquals("Cannot delete the current profile", result.err()); + assertEquals("", result.out()); + assertEquals("new", getConfig().getCurrentProfile()); + + result = executeCommand("profiles", "set-current", "default"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("profile default set as current", result.out()); + assertEquals("new", getConfig().getCurrentProfile()); + + + result = executeCommand("profiles", "delete", "new"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("profile new deleted", result.out()); + assertEquals("default", getConfig().getCurrentProfile()); + + } + + @Test + public void testCreateAndSet() { + CommandResult result = executeCommand("profiles", "create", "new", "--web-service-url", "http://my.localhost:8080", "--set-current"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals(""" + profile new created + profile new set as current""", result.out()); + assertEquals("new", getConfig().getCurrentProfile()); + + result = executeCommand("profiles", "create", "new1", "--web-service-url", "http://my.localhost:8080"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("profile new1 created", result.out()); + assertEquals("new", getConfig().getCurrentProfile()); + + result = executeCommand("profiles", "update", "new1", "--web-service-url", "http://my.localhost:8080", "--set-current"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals(""" + profile new1 updated + profile new1 set as current""", result.out()); + assertEquals("new1", getConfig().getCurrentProfile()); + + } + + + + @ParameterizedTest + @ValueSource(strings = {"file", "json", "base64"}) + public void testImport(String input) throws IOException { + + final String json = "{\"webServiceUrl\":\"http://my.localhost:8080\",\"apiGatewayUrl\":\"http://my.localhost:8091\",\"tenant\":\"t\",\"token\":\"tok\"}"; + String paramName = switch (input) { + case "file" -> "--file"; + case "json", "base64" -> "--inline"; + default -> throw new IllegalArgumentException("Unexpected value: " + input); + }; + + + final File file = Files.createTempFile("test", ".json").toFile(); + Files.writeString(file.toPath(), json); + String paramValue = switch (input) { + case "file" -> file.getAbsolutePath(); + case "json" -> json; + case "base64" -> "base64:" + Base64.getEncoder().encodeToString(json.getBytes()); + default -> throw new IllegalArgumentException("Unexpected value: " + input); + }; + + CommandResult result = executeCommand("profiles", "import", "new", paramName, paramValue, "--set-current"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals(""" + profile new created + profile new set as current""", result.out()); + assertEquals("new", getConfig().getCurrentProfile()); + + NamedProfile newProfile = getConfig().getProfiles().get("new"); + assertEquals("new", newProfile.getName()); + assertEquals("http://my.localhost:8080", newProfile.getWebServiceUrl()); + assertEquals("t", newProfile.getTenant()); + assertEquals("tok", newProfile.getToken()); + assertEquals("http://my.localhost:8091", newProfile.getApiGatewayUrl()); + + result = executeCommand("profiles", "import", "new", paramName, paramValue, "--set-current"); + assertEquals(1, result.exitCode()); + assertEquals("Profile new already exists", result.err()); + assertEquals("", result.out()); + + result = executeCommand("profiles", "import", "new", paramName, paramValue, "--set-current", "-u"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals(""" + profile new updated + profile new set as current""", result.out()); + + + } + + @Test + public void testDefaultProfile() { + + CommandResult result = executeCommand("profiles", "get", "default"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals(""" + PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s + default %s my-tenant * """.formatted(getConfig().getWebServiceUrl()), result.out()); + + result = executeCommand("profiles", "get-current"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("default", result.out()); + + result = executeCommand("profiles", "create", "default"); + assertEquals(1, result.exitCode()); + assertEquals("Profile name default is reserved", result.err()); + assertEquals("", result.out()); + + + result = executeCommand("profiles", "update", "default"); + assertEquals(1, result.exitCode()); + assertEquals("Profile name default is reserved", result.err()); + assertEquals("", result.out()); + + result = executeCommand("profiles", "import", "default"); + assertEquals(1, result.exitCode()); + assertEquals("Profile name default is reserved", result.err()); + assertEquals("", result.out()); + + result = executeCommand("profiles", "delete", "default"); + assertEquals(1, result.exitCode()); + assertEquals("Profile name default is reserved", result.err()); + assertEquals("", result.out()); + + + } + + @Test + public void testGlobalParam() { + wireMock.register( + WireMock.get("/api/applications/" + TENANT) + .willReturn(WireMock.ok("[]"))); + + CommandResult result = executeCommand("apps", "list", "-o", "json"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("[ ]", result.out()); + + + result = executeCommand("profiles", "create", "new", "--web-service-url", "http://my.localhost:8080", "--set-current"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("profile new created\n" + + "profile new set as current", result.out()); + + result = executeCommand("apps", "list", "-o", "json"); + assertEquals(1, result.exitCode()); + assertEquals("Tenant not set. Please set the tenant in the configuration.", result.err()); + assertEquals("", result.out()); + + result = executeCommand("-p", "default", "apps", "list", "-o", "json"); + assertEquals(0, result.exitCode()); + assertEquals("", result.err()); + assertEquals("[ ]", result.out()); + + result = executeCommand("-p", "notexists", "apps", "list", "-o", "json"); + assertEquals(1, result.exitCode()); + assertEquals("No profile 'notexists' defined in configuration", result.err()); + assertEquals("", result.out()); + } +} From 74a798d1225df298b6a421591c3799d34a75ec2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Thu, 7 Sep 2023 10:28:40 +0200 Subject: [PATCH 2/5] spotless --- conf/cli.yaml | 16 ++ .../java/ai/langstream/cli/NamedProfile.java | 15 ++ .../main/java/ai/langstream/cli/Profile.java | 15 ++ .../ai/langstream/cli/commands/BaseCmd.java | 3 +- .../cli/commands/RootProfileCmd.java | 2 +- .../commands/profiles/ImportProfileCmd.java | 11 +- .../applications/CommandTestBase.java | 3 +- .../applications/ProfilesCmdTest.java | 170 ++++++++++++------ 8 files changed, 165 insertions(+), 70 deletions(-) diff --git a/conf/cli.yaml b/conf/cli.yaml index fab9f6f2b..ad06902a3 100644 --- a/conf/cli.yaml +++ b/conf/cli.yaml @@ -1,3 +1,19 @@ +# +# Copyright DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + --- webServiceUrl: "http://localhost:8090" apiGatewayUrl: "ws://localhost:8091" diff --git a/langstream-cli/src/main/java/ai/langstream/cli/NamedProfile.java b/langstream-cli/src/main/java/ai/langstream/cli/NamedProfile.java index 2023ec26f..fa2b4b4b4 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/NamedProfile.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/NamedProfile.java @@ -1,3 +1,18 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ai.langstream.cli; import lombok.Getter; diff --git a/langstream-cli/src/main/java/ai/langstream/cli/Profile.java b/langstream-cli/src/main/java/ai/langstream/cli/Profile.java index 00ec698a6..18eaea4dc 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/Profile.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/Profile.java @@ -1,3 +1,18 @@ +/* + * Copyright DataStax, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package ai.langstream.cli; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java index 43d3edd53..73096d089 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java @@ -96,8 +96,7 @@ protected LangStreamCLIConfig getConfig() { protected NamedProfile getDefaultProfile() { final LangStreamCLIConfig config = getConfig(); - final NamedProfile defaultProfile = - new NamedProfile(); + final NamedProfile defaultProfile = new NamedProfile(); defaultProfile.setName(BaseProfileCmd.DEFAULT_PROFILE_NAME); defaultProfile.setTenant(config.getTenant()); defaultProfile.setToken(config.getToken()); diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/RootProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/RootProfileCmd.java index 370d702bc..b245ec55d 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/RootProfileCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/RootProfileCmd.java @@ -18,10 +18,10 @@ import ai.langstream.cli.commands.profiles.CreateUpdateProfileCmd; import ai.langstream.cli.commands.profiles.DeleteProfileCmd; import ai.langstream.cli.commands.profiles.GetCurrentProfileCmd; +import ai.langstream.cli.commands.profiles.GetProfileCmd; import ai.langstream.cli.commands.profiles.ImportProfileCmd; import ai.langstream.cli.commands.profiles.ListProfileCmd; import ai.langstream.cli.commands.profiles.SetCurrentProfileCmd; -import ai.langstream.cli.commands.profiles.GetProfileCmd; import lombok.Getter; import picocli.CommandLine; diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ImportProfileCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ImportProfileCmd.java index 416e96f24..ae1e59cee 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ImportProfileCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/profiles/ImportProfileCmd.java @@ -71,9 +71,7 @@ public void run() { if (!Files.isRegularFile(file.toPath())) { throw new IllegalArgumentException("File %s does not exist".formatted(fromFile)); } - profile = - yamlConfigReader.readValue( - new File(fromFile), Profile.class); + profile = yamlConfigReader.readValue(new File(fromFile), Profile.class); } else if (inline != null) { String jsonProfile; if (inline.startsWith("base64:")) { @@ -83,8 +81,7 @@ public void run() { } else { jsonProfile = inline; } - profile = - jsonConfigReader.readValue(jsonProfile, Profile.class); + profile = jsonConfigReader.readValue(jsonProfile, Profile.class); } else { throw new IllegalStateException(); } @@ -100,14 +97,12 @@ public void run() { updateConfig( langStreamCLIConfig -> { - final NamedProfile existing = - langStreamCLIConfig.getProfiles().get(name); + final NamedProfile existing = langStreamCLIConfig.getProfiles().get(name); if (!allowUpdate && existing != null) { throw new IllegalArgumentException( "Profile %s already exists".formatted(name)); } - langStreamCLIConfig.getProfiles().put(name, newProfile); if (existing == null) { log("profile %s created".formatted(name)); diff --git a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/CommandTestBase.java b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/CommandTestBase.java index 239190a61..895a9de42 100644 --- a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/CommandTestBase.java +++ b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/CommandTestBase.java @@ -112,6 +112,7 @@ protected CommandResult executeCommand(String... args) { @SneakyThrows protected LangStreamCLIConfig getConfig() { - return new ObjectMapper(new YAMLFactory()).readValue(cliYaml.toFile(), LangStreamCLIConfig.class); + return new ObjectMapper(new YAMLFactory()) + .readValue(cliYaml.toFile(), LangStreamCLIConfig.class); } } diff --git a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java index 9240804f2..d8169bd0d 100644 --- a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java +++ b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java @@ -16,6 +16,7 @@ package ai.langstream.cli.commands.applications; import static org.junit.jupiter.api.Assertions.assertEquals; + import ai.langstream.cli.NamedProfile; import com.github.tomakehurst.wiremock.client.WireMock; import java.io.File; @@ -30,7 +31,19 @@ class ProfilesCmdTest extends CommandTestBase { @Test public void testCrud() { - CommandResult result = executeCommand("profiles", "create", "new", "--web-service-url", "http://my.localhost:8080", "--tenant", "t", "--token", "tok", "--api-gateway-url", "http://my.localhost:8091"); + CommandResult result = + executeCommand( + "profiles", + "create", + "new", + "--web-service-url", + "http://my.localhost:8080", + "--tenant", + "t", + "--token", + "tok", + "--api-gateway-url", + "http://my.localhost:8091"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); assertEquals("profile new created", result.out()); @@ -55,46 +68,56 @@ public void testCrud() { result = executeCommand("profiles", "get", "new"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); - assertEquals(""" + assertEquals( + """ PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s - new http://my.localhost:8080 t ******** """, result.out()); + new http://my.localhost:8080 t ******** """, + result.out()); result = executeCommand("profiles", "get", "new", "-o", "json"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); - assertEquals(""" + assertEquals( + """ { "webServiceUrl" : "http://my.localhost:8080", "apiGatewayUrl" : "http://my.localhost:8091", "tenant" : "t", "token" : "tok2", "name" : "new" - }""", result.out()); + }""", + result.out()); result = executeCommand("profiles", "get", "new", "-o", "yaml"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); - assertEquals(""" + assertEquals( + """ --- webServiceUrl: "http://my.localhost:8080" apiGatewayUrl: "http://my.localhost:8091" tenant: "t" token: "tok2" - name: "new" """, result.out()); + name: "new" """, + result.out()); result = executeCommand("profiles", "get", "notexists"); assertEquals(1, result.exitCode()); - assertEquals("Profile notexists not found, maybe you meant one of these: default, new", result.err()); + assertEquals( + "Profile notexists not found, maybe you meant one of these: default, new", + result.err()); assertEquals("", result.out()); - result = executeCommand("profiles", "list"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); - assertEquals(""" + assertEquals( + """ PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s default %s my-tenant * \s - new http://my.localhost:8080 t ******** """.formatted(getConfig().getWebServiceUrl()), result.out()); + new http://my.localhost:8080 t ******** """ + .formatted(getConfig().getWebServiceUrl()), + result.out()); result = executeCommand("profiles", "get-current"); assertEquals(0, result.exitCode()); @@ -107,7 +130,6 @@ public void testCrud() { assertEquals("profile new set as current", result.out()); assertEquals("new", getConfig().getCurrentProfile()); - result = executeCommand("profiles", "delete", "new"); assertEquals(1, result.exitCode()); assertEquals("Cannot delete the current profile", result.err()); @@ -120,70 +142,95 @@ public void testCrud() { assertEquals("profile default set as current", result.out()); assertEquals("new", getConfig().getCurrentProfile()); - result = executeCommand("profiles", "delete", "new"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); assertEquals("profile new deleted", result.out()); assertEquals("default", getConfig().getCurrentProfile()); - } @Test public void testCreateAndSet() { - CommandResult result = executeCommand("profiles", "create", "new", "--web-service-url", "http://my.localhost:8080", "--set-current"); + CommandResult result = + executeCommand( + "profiles", + "create", + "new", + "--web-service-url", + "http://my.localhost:8080", + "--set-current"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); - assertEquals(""" + assertEquals( + """ profile new created - profile new set as current""", result.out()); + profile new set as current""", + result.out()); assertEquals("new", getConfig().getCurrentProfile()); - result = executeCommand("profiles", "create", "new1", "--web-service-url", "http://my.localhost:8080"); + result = + executeCommand( + "profiles", + "create", + "new1", + "--web-service-url", + "http://my.localhost:8080"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); assertEquals("profile new1 created", result.out()); assertEquals("new", getConfig().getCurrentProfile()); - result = executeCommand("profiles", "update", "new1", "--web-service-url", "http://my.localhost:8080", "--set-current"); + result = + executeCommand( + "profiles", + "update", + "new1", + "--web-service-url", + "http://my.localhost:8080", + "--set-current"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); - assertEquals(""" + assertEquals( + """ profile new1 updated - profile new1 set as current""", result.out()); + profile new1 set as current""", + result.out()); assertEquals("new1", getConfig().getCurrentProfile()); - } - - @ParameterizedTest @ValueSource(strings = {"file", "json", "base64"}) public void testImport(String input) throws IOException { - final String json = "{\"webServiceUrl\":\"http://my.localhost:8080\",\"apiGatewayUrl\":\"http://my.localhost:8091\",\"tenant\":\"t\",\"token\":\"tok\"}"; - String paramName = switch (input) { - case "file" -> "--file"; - case "json", "base64" -> "--inline"; - default -> throw new IllegalArgumentException("Unexpected value: " + input); - }; - + final String json = + "{\"webServiceUrl\":\"http://my.localhost:8080\",\"apiGatewayUrl\":\"http://my.localhost:8091\",\"tenant\":\"t\",\"token\":\"tok\"}"; + String paramName = + switch (input) { + case "file" -> "--file"; + case "json", "base64" -> "--inline"; + default -> throw new IllegalArgumentException("Unexpected value: " + input); + }; final File file = Files.createTempFile("test", ".json").toFile(); Files.writeString(file.toPath(), json); - String paramValue = switch (input) { - case "file" -> file.getAbsolutePath(); - case "json" -> json; - case "base64" -> "base64:" + Base64.getEncoder().encodeToString(json.getBytes()); - default -> throw new IllegalArgumentException("Unexpected value: " + input); - }; - - CommandResult result = executeCommand("profiles", "import", "new", paramName, paramValue, "--set-current"); + String paramValue = + switch (input) { + case "file" -> file.getAbsolutePath(); + case "json" -> json; + case "base64" -> "base64:" + + Base64.getEncoder().encodeToString(json.getBytes()); + default -> throw new IllegalArgumentException("Unexpected value: " + input); + }; + + CommandResult result = + executeCommand("profiles", "import", "new", paramName, paramValue, "--set-current"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); - assertEquals(""" + assertEquals( + """ profile new created - profile new set as current""", result.out()); + profile new set as current""", + result.out()); assertEquals("new", getConfig().getCurrentProfile()); NamedProfile newProfile = getConfig().getProfiles().get("new"); @@ -193,19 +240,22 @@ public void testImport(String input) throws IOException { assertEquals("tok", newProfile.getToken()); assertEquals("http://my.localhost:8091", newProfile.getApiGatewayUrl()); - result = executeCommand("profiles", "import", "new", paramName, paramValue, "--set-current"); + result = + executeCommand("profiles", "import", "new", paramName, paramValue, "--set-current"); assertEquals(1, result.exitCode()); assertEquals("Profile new already exists", result.err()); assertEquals("", result.out()); - result = executeCommand("profiles", "import", "new", paramName, paramValue, "--set-current", "-u"); + result = + executeCommand( + "profiles", "import", "new", paramName, paramValue, "--set-current", "-u"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); - assertEquals(""" + assertEquals( + """ profile new updated - profile new set as current""", result.out()); - - + profile new set as current""", + result.out()); } @Test @@ -214,9 +264,12 @@ public void testDefaultProfile() { CommandResult result = executeCommand("profiles", "get", "default"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); - assertEquals(""" + assertEquals( + """ PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s - default %s my-tenant * """.formatted(getConfig().getWebServiceUrl()), result.out()); + default %s my-tenant * """ + .formatted(getConfig().getWebServiceUrl()), + result.out()); result = executeCommand("profiles", "get-current"); assertEquals(0, result.exitCode()); @@ -228,7 +281,6 @@ public void testDefaultProfile() { assertEquals("Profile name default is reserved", result.err()); assertEquals("", result.out()); - result = executeCommand("profiles", "update", "default"); assertEquals(1, result.exitCode()); assertEquals("Profile name default is reserved", result.err()); @@ -243,27 +295,29 @@ public void testDefaultProfile() { assertEquals(1, result.exitCode()); assertEquals("Profile name default is reserved", result.err()); assertEquals("", result.out()); - - } @Test public void testGlobalParam() { wireMock.register( - WireMock.get("/api/applications/" + TENANT) - .willReturn(WireMock.ok("[]"))); + WireMock.get("/api/applications/" + TENANT).willReturn(WireMock.ok("[]"))); CommandResult result = executeCommand("apps", "list", "-o", "json"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); assertEquals("[ ]", result.out()); - - result = executeCommand("profiles", "create", "new", "--web-service-url", "http://my.localhost:8080", "--set-current"); + result = + executeCommand( + "profiles", + "create", + "new", + "--web-service-url", + "http://my.localhost:8080", + "--set-current"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); - assertEquals("profile new created\n" - + "profile new set as current", result.out()); + assertEquals("profile new created\n" + "profile new set as current", result.out()); result = executeCommand("apps", "list", "-o", "json"); assertEquals(1, result.exitCode()); From d518250e3a98a4f128d2be72a9ce96e12165a60b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Thu, 7 Sep 2023 10:28:47 +0200 Subject: [PATCH 3/5] spotless --- conf/cli.yaml | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/conf/cli.yaml b/conf/cli.yaml index ad06902a3..4a2d5fd97 100644 --- a/conf/cli.yaml +++ b/conf/cli.yaml @@ -14,16 +14,6 @@ # limitations under the License. # ---- -webServiceUrl: "http://localhost:8090" -apiGatewayUrl: "ws://localhost:8091" -tenant: "default" -token: null -profiles: - new: - webServiceUrl: "ss" - apiGatewayUrl: null - tenant: "gigio" - token: null - name: "new" -currentProfile: "new" +webServiceUrl: http://localhost:8090 +apiGatewayUrl: ws://localhost:8091 +tenant: default \ No newline at end of file From d2752ba1b659da931014ba9edb3bcd215adf08d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Thu, 7 Sep 2023 11:24:52 +0200 Subject: [PATCH 4/5] fix tests --- .../ai/langstream/cli/commands/BaseCmd.java | 51 +++++++++++++------ .../commands/applications/AppsCmdTest.java | 6 +-- .../applications/ProfilesCmdTest.java | 18 ++++--- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java index 73096d089..099aad41c 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java @@ -30,7 +30,8 @@ import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -224,34 +225,38 @@ protected void print( case raw: default: { - printRawHeader(columnsForRaw); + List rows = new ArrayList<>(); + + rows.add(prepareHeaderRow(columnsForRaw)); if (readValue.isArray()) { readValue .elements() .forEachRemaining( element -> - printRawRow(element, columnsForRaw, valueSupplier)); + rows.add(prepareRawRow(element, columnsForRaw, valueSupplier))); } else { - printRawRow(readValue, columnsForRaw, valueSupplier); + rows.add(prepareRawRow(readValue, columnsForRaw, valueSupplier)); } + printRows(rows); break; } } } - private void printRawHeader(String[] columnsForRaw) { - final String template = computeFormatTemplate(columnsForRaw.length); - final Object[] columns = Arrays.stream(columnsForRaw).map(String::toUpperCase).toArray(); - final String header = String.format(template, columns); - log(header); + private String[] prepareHeaderRow(String[] columnsForRaw) { + String[] result = new String[columnsForRaw.length]; + for (int i = 0; i < columnsForRaw.length; i++) { + result[i] = columnsForRaw[i].toUpperCase(); + } + return result; } - private void printRawRow( + private String[] prepareRawRow( JsonNode readValue, String[] columnsForRaw, BiFunction valueSupplier) { final int numColumns = columnsForRaw.length; - String formatTemplate = computeFormatTemplate(numColumns); + String[] row = new String[numColumns]; for (int i = 0; i < numColumns; i++) { @@ -274,17 +279,33 @@ private void printRawRow( } row[i] = strColumn; } - final String result = String.format(formatTemplate, row); - log(result); + return row; + } + + private void printRows(List rows) { + + int countMax = 0; + + for (String[] row : rows) { + for (int i = 0; i < row.length; i++) { + countMax = Math.max(countMax, row[i].length()); + } + } + String formatTemplate = computeFormatTemplate(rows.get(0).length, countMax); + + for (String[] row : rows) { + final String result = String.format(formatTemplate, row); + log(result); + } } - private String computeFormatTemplate(int numColumns) { + private String computeFormatTemplate(int numColumns, int width) { StringBuilder formatTemplate = new StringBuilder(); for (int i = 0; i < numColumns; i++) { if (i > 0) { formatTemplate.append(" "); } - formatTemplate.append("%-25.25s"); + formatTemplate.append("%-" + width + "." + width + "s"); } return formatTemplate.toString(); } diff --git a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/AppsCmdTest.java b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/AppsCmdTest.java index 6f0fac825..6c3d168be 100644 --- a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/AppsCmdTest.java +++ b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/AppsCmdTest.java @@ -1055,8 +1055,8 @@ public void testGet() throws Exception { Assertions.assertEquals("", result.err()); Assertions.assertEquals( """ - ID STREAMING COMPUTE STATUS EXECUTORS REPLICAS \s - test kafka kubernetes DEPLOYED 2/2 2/2""", + ID STREAMING COMPUTE STATUS EXECUTORS REPLICAS \s + test kafka kubernetes DEPLOYED 2/2 2/2""", result.out()); ObjectMapper jsonPrinter = new ObjectMapper() @@ -1105,7 +1105,7 @@ public void testList() { Assertions.assertEquals(0, result.exitCode()); Assertions.assertEquals("", result.err()); Assertions.assertEquals( - "ID STREAMING COMPUTE STATUS EXECUTORS REPLICAS", + "ID STREAMING COMPUTE STATUS EXECUTORS REPLICAS", result.out()); result = executeCommand("apps", "list", "-o", "json"); Assertions.assertEquals(0, result.exitCode()); diff --git a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java index d8169bd0d..0e4f6a1d0 100644 --- a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java +++ b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/ProfilesCmdTest.java @@ -16,6 +16,7 @@ package ai.langstream.cli.commands.applications; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import ai.langstream.cli.NamedProfile; import com.github.tomakehurst.wiremock.client.WireMock; @@ -70,8 +71,8 @@ public void testCrud() { assertEquals("", result.err()); assertEquals( """ - PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s - new http://my.localhost:8080 t ******** """, + PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s + new http://my.localhost:8080 t ******** """, result.out()); result = executeCommand("profiles", "get", "new", "-o", "json"); @@ -113,9 +114,9 @@ public void testCrud() { assertEquals("", result.err()); assertEquals( """ - PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s - default %s my-tenant * \s - new http://my.localhost:8080 t ******** """ + PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s + default %s my-tenant * \s + new http://my.localhost:8080 t ********""" .formatted(getConfig().getWebServiceUrl()), result.out()); @@ -140,13 +141,14 @@ public void testCrud() { assertEquals(0, result.exitCode()); assertEquals("", result.err()); assertEquals("profile default set as current", result.out()); - assertEquals("new", getConfig().getCurrentProfile()); + assertEquals("default", getConfig().getCurrentProfile()); result = executeCommand("profiles", "delete", "new"); assertEquals(0, result.exitCode()); assertEquals("", result.err()); assertEquals("profile new deleted", result.out()); assertEquals("default", getConfig().getCurrentProfile()); + assertNull(getConfig().getProfiles().get("new")); } @Test @@ -266,8 +268,8 @@ public void testDefaultProfile() { assertEquals("", result.err()); assertEquals( """ - PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s - default %s my-tenant * """ + PROFILE WEBSERVICEURL TENANT TOKEN CURRENT \s + default %s my-tenant *""" .formatted(getConfig().getWebServiceUrl()), result.out()); From 68d2352280212147baeb355c0a646b52698f7390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Thu, 7 Sep 2023 11:26:20 +0200 Subject: [PATCH 5/5] spotless --- .../src/main/java/ai/langstream/cli/commands/BaseCmd.java | 7 +++++-- .../langstream/cli/commands/applications/AppsCmdTest.java | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java index 099aad41c..787387db8 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/BaseCmd.java @@ -233,7 +233,11 @@ protected void print( .elements() .forEachRemaining( element -> - rows.add(prepareRawRow(element, columnsForRaw, valueSupplier))); + rows.add( + prepareRawRow( + element, + columnsForRaw, + valueSupplier))); } else { rows.add(prepareRawRow(readValue, columnsForRaw, valueSupplier)); } @@ -257,7 +261,6 @@ private String[] prepareRawRow( BiFunction valueSupplier) { final int numColumns = columnsForRaw.length; - String[] row = new String[numColumns]; for (int i = 0; i < numColumns; i++) { final String column = columnsForRaw[i]; diff --git a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/AppsCmdTest.java b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/AppsCmdTest.java index 6c3d168be..3c9e935d9 100644 --- a/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/AppsCmdTest.java +++ b/langstream-cli/src/test/java/ai/langstream/cli/commands/applications/AppsCmdTest.java @@ -1105,8 +1105,7 @@ public void testList() { Assertions.assertEquals(0, result.exitCode()); Assertions.assertEquals("", result.err()); Assertions.assertEquals( - "ID STREAMING COMPUTE STATUS EXECUTORS REPLICAS", - result.out()); + "ID STREAMING COMPUTE STATUS EXECUTORS REPLICAS", result.out()); result = executeCommand("apps", "list", "-o", "json"); Assertions.assertEquals(0, result.exitCode()); Assertions.assertEquals("", result.err());