This is an automated email from the ASF dual-hosted git repository. vveider pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push: new da0a7f73f IGNITE-17130 Add CLI profile API implementation (#897) da0a7f73f is described below commit da0a7f73f9eef9c0ae43a4a3ba1d8b663236b269 Author: Mikhail <pochat...@users.noreply.github.com> AuthorDate: Mon Jul 11 17:53:55 2022 +0300 IGNITE-17130 Add CLI profile API implementation (#897) --- modules/cli/pom.xml | 7 ++ .../commands/CliCommandTestIntegrationBase.java | 12 ++ .../src/main/java/org/apache/ignite/cli/Main.java | 5 +- .../ignite/cli/call/cliconfig/CliConfigCall.java | 16 ++- .../cli/call/cliconfig/CliConfigGetCall.java | 16 ++- ...etCallInput.java => CliConfigGetCallInput.java} | 22 ++-- .../cli/call/cliconfig/CliConfigSetCall.java | 16 +-- .../cli/call/cliconfig/CliConfigSetCallInput.java | 9 +- .../profile/CliConfigCreateProfileCall.java | 58 ++++++++++ .../profile/CliConfigCreateProfileCallInput.java | 84 ++++++++++++++ .../CliConfigShowProfileCall.java} | 17 +-- .../CliConfigUseCall.java} | 19 ++-- .../commands/cliconfig/CliConfigGetSubCommand.java | 8 +- .../commands/cliconfig/CliConfigSetSubCommand.java | 6 +- .../commands/cliconfig/CliConfigSubCommand.java | 12 +- .../CliConfigCreateProfileCommand.java} | 38 ++++--- .../CliConfigProfileCommand.java} | 30 ++--- .../CliConfigShowProfileCommand.java} | 23 ++-- .../cli/commands/connect/ConnectCommand.java | 3 +- .../cli/commands/decorators/ConfigDecorator.java | 23 ++-- ...ctory.java => CachedConfigManagerProvider.java} | 16 +-- .../java/org/apache/ignite/cli/config/Config.java | 123 --------------------- .../apache/ignite/cli/config/ConfigConstants.java | 52 +++++++++ .../ConfigDefaultValueProvider.java} | 17 ++- ...ory.java => ConfigInitializationException.java} | 20 ++-- .../{ConfigFactory.java => ConfigManager.java} | 25 +++-- ...nfigFactory.java => ConfigManagerProvider.java} | 13 +-- ...figFactory.java => ConfigStoringException.java} | 19 ++-- .../config/{ConfigFactory.java => Profile.java} | 25 +++-- ...gFactory.java => ProfileNotFoundException.java} | 13 +-- ...ConfigFactory.java => StateFolderProvider.java} | 35 ++++-- .../ignite/cli/config/ini/IniConfigManager.java | 123 +++++++++++++++++++++ .../org/apache/ignite/cli/config/ini/IniFile.java | 98 ++++++++++++++++ .../apache/ignite/cli/config/ini/IniParser.java | 99 +++++++++++++++++ .../apache/ignite/cli/config/ini/IniProfile.java | 72 ++++++++++++ .../apache/ignite/cli/config/ini/IniSection.java | 63 +++++++++++ .../SectionAlreadyExistsException.java} | 15 +-- .../handler/ConfigStoringExceptionHandler.java | 43 +++++++ .../handler/DefaultExceptionHandlers.java | 3 + ...s.java => ProfileNotFoundExceptionHandler.java} | 26 ++--- ...a => SectionAlreadyExistsExceptionHandler.java} | 26 ++--- .../cli/core/repl/SessionDefaultValueProvider.java | 14 +-- .../cli/core/repl/executor/ReplExecutor.java | 4 +- .../ignite/cli/commands/CliCommandTestBase.java | 12 +- .../apache/ignite/cli/commands/PicocliBugTest.java | 88 +++++++++++++++ .../cliconfig/CliConfigActivateCommandTest.java | 68 ++++++++++++ .../CliConfigCreateProfileCommandTest.java | 118 ++++++++++++++++++++ .../cliconfig/CliConfigGetSubCommandTest.java | 35 +++++- .../cliconfig/CliConfigSetSubCommandTest.java | 54 ++++++++- ...t.java => CliConfigShowProfileCommandTest.java} | 35 ++++-- .../cliconfig/CliConfigSubCommandTest.java | 33 +++++- .../cli/commands/cliconfig/ConfigManagerTest.java | 74 +++++++++++++ .../cli/commands/cliconfig/TestConfigFactory.java | 50 --------- .../cliconfig/TestConfigManagerHelper.java | 70 ++++++++++++ ...figTest.java => TestConfigManagerProvider.java} | 28 ++--- modules/cli/src/test/resources/empty.ini | 0 .../cli/src/test/resources/integration_tests.ini | 6 + .../test/resources/two_section_with_internal.ini | 12 ++ .../resources/two_section_without_internal.ini | 10 ++ parent/pom.xml | 2 +- 60 files changed, 1620 insertions(+), 443 deletions(-) diff --git a/modules/cli/pom.xml b/modules/cli/pom.xml index 20699e8d2..d7794ce20 100644 --- a/modules/cli/pom.xml +++ b/modules/cli/pom.xml @@ -259,6 +259,13 @@ <build> + <testResources> + <testResource> + <directory>src/test/resources</directory> + <filtering>true</filtering> + </testResource> + </testResources> + <resources> <resource> <directory>src/main/resources</directory> diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestIntegrationBase.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestIntegrationBase.java index 363787cfd..7d3a86ce0 100644 --- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestIntegrationBase.java +++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestIntegrationBase.java @@ -25,6 +25,10 @@ import jakarta.inject.Inject; import java.io.PrintWriter; import java.io.StringWriter; import org.apache.ignite.cli.IntegrationTestBase; +import org.apache.ignite.cli.commands.cliconfig.TestConfigManagerHelper; +import org.apache.ignite.cli.commands.cliconfig.TestConfigManagerProvider; +import org.apache.ignite.cli.config.ConfigDefaultValueProvider; +import org.apache.ignite.cli.config.ini.IniConfigManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInfo; import picocli.CommandLine; @@ -39,6 +43,12 @@ public class CliCommandTestIntegrationBase extends IntegrationTestBase { @Inject private ApplicationContext context; + @Inject + ConfigDefaultValueProvider configDefaultValueProvider; + + @Inject + TestConfigManagerProvider configManagerProvider; + private CommandLine cmd; private StringWriter sout; private StringWriter serr; @@ -53,7 +63,9 @@ public class CliCommandTestIntegrationBase extends IntegrationTestBase { @BeforeEach public void setUp(TestInfo testInfo) throws Exception { super.setUp(testInfo); + configManagerProvider.configManager = new IniConfigManager(TestConfigManagerHelper.createIntegrationTests()); cmd = new CommandLine(getCommandClass(), new MicronautFactory(context)); + cmd.setDefaultValueProvider(configDefaultValueProvider); sout = new StringWriter(); serr = new StringWriter(); cmd.setOut(new PrintWriter(sout)); diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/Main.java b/modules/cli/src/main/java/org/apache/ignite/cli/Main.java index eeb0f37c9..dac94907d 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/Main.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/Main.java @@ -28,7 +28,7 @@ import java.util.HashMap; import java.util.stream.Collectors; import org.apache.ignite.cli.commands.TopLevelCliCommand; import org.apache.ignite.cli.commands.TopLevelCliReplCommand; -import org.apache.ignite.cli.config.Config; +import org.apache.ignite.cli.config.ConfigDefaultValueProvider; import org.apache.ignite.cli.core.call.CallExecutionPipeline; import org.apache.ignite.cli.core.call.StringCallInput; import org.apache.ignite.cli.core.exception.handler.DefaultExceptionHandlers; @@ -113,8 +113,7 @@ public class Main { private static int executeCommand(String[] args, MicronautFactory micronautFactory) throws Exception { CommandLine cmd = new CommandLine(TopLevelCliCommand.class, micronautFactory); cmd.setExecutionExceptionHandler(new PicocliExecutionExceptionHandler()); - Config config = micronautFactory.create(Config.class); - cmd.setDefaultValueProvider(new CommandLine.PropertiesDefaultProvider(config.getProperties())); + cmd.setDefaultValueProvider(micronautFactory.create(ConfigDefaultValueProvider.class)); return cmd.execute(args); } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java index fb7a4810e..1d8748bf3 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java @@ -19,21 +19,25 @@ package org.apache.ignite.cli.call.cliconfig; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.apache.ignite.cli.config.Config; +import org.apache.ignite.cli.config.ConfigManager; +import org.apache.ignite.cli.config.ConfigManagerProvider; +import org.apache.ignite.cli.config.Profile; import org.apache.ignite.cli.core.call.Call; import org.apache.ignite.cli.core.call.DefaultCallOutput; -import org.apache.ignite.cli.core.call.EmptyCallInput; +import org.apache.ignite.cli.core.call.StringCallInput; /** * Gets entire CLI configuration. */ @Singleton -public class CliConfigCall implements Call<EmptyCallInput, Config> { +public class CliConfigCall implements Call<StringCallInput, Profile> { @Inject - private Config config; + private ConfigManagerProvider configManagerProvider; @Override - public DefaultCallOutput<Config> execute(EmptyCallInput input) { - return DefaultCallOutput.success(config); + public DefaultCallOutput<Profile> execute(StringCallInput input) { + ConfigManager configManager = configManagerProvider.get(); + String profile = input.getString(); + return DefaultCallOutput.success(profile == null ? configManager.getCurrentProfile() : configManager.getProfile(profile)); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCall.java index 3b72ceb8e..65a80cfc0 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCall.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCall.java @@ -19,22 +19,26 @@ package org.apache.ignite.cli.call.cliconfig; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.apache.ignite.cli.config.Config; +import org.apache.ignite.cli.config.ConfigManager; +import org.apache.ignite.cli.config.ConfigManagerProvider; +import org.apache.ignite.cli.config.Profile; import org.apache.ignite.cli.core.call.Call; import org.apache.ignite.cli.core.call.DefaultCallOutput; -import org.apache.ignite.cli.core.call.StringCallInput; /** * Gets CLI configuration parameter. */ @Singleton -public class CliConfigGetCall implements Call<StringCallInput, String> { +public class CliConfigGetCall implements Call<CliConfigGetCallInput, String> { @Inject - private Config config; + private ConfigManagerProvider configManagerProvider; @Override - public DefaultCallOutput<String> execute(StringCallInput input) { - String key = input.getString(); + public DefaultCallOutput<String> execute(CliConfigGetCallInput input) { + ConfigManager configManager = configManagerProvider.get(); + String key = input.getKey(); + String profileName = input.getProfileName(); + Profile config = profileName == null ? configManager.getCurrentProfile() : configManager.getProfile(profileName); String property = config.getProperty(key, ""); return DefaultCallOutput.success(property); } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCallInput.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCallInput.java similarity index 69% copy from modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCallInput.java copy to modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCallInput.java index 9e8ac8683..eaccc8904 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCallInput.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCallInput.java @@ -17,20 +17,26 @@ package org.apache.ignite.cli.call.cliconfig; -import java.util.Map; import org.apache.ignite.cli.core.call.CallInput; /** - * Input for {@link CliConfigSetCall}. + * Get config property parameters. */ -public class CliConfigSetCallInput implements CallInput { - private final Map<String, String> parameters; +public class CliConfigGetCallInput implements CallInput { + private final String key; - public CliConfigSetCallInput(Map<String, String> parameters) { - this.parameters = parameters; + private final String profileName; + + public CliConfigGetCallInput(String key, String profileName) { + this.key = key; + this.profileName = profileName; + } + + public String getKey() { + return key; } - public Map<String, String> getParameters() { - return parameters; + public String getProfileName() { + return profileName; } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCall.java index b2c5a3647..5bbf6623e 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCall.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCall.java @@ -19,8 +19,9 @@ package org.apache.ignite.cli.call.cliconfig; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import java.util.Map.Entry; -import org.apache.ignite.cli.config.Config; +import org.apache.ignite.cli.config.ConfigManager; +import org.apache.ignite.cli.config.ConfigManagerProvider; +import org.apache.ignite.cli.config.Profile; import org.apache.ignite.cli.core.call.Call; import org.apache.ignite.cli.core.call.DefaultCallOutput; @@ -30,15 +31,14 @@ import org.apache.ignite.cli.core.call.DefaultCallOutput; @Singleton public class CliConfigSetCall implements Call<CliConfigSetCallInput, Object> { @Inject - private Config config; + private ConfigManagerProvider configManagerProvider; @Override public DefaultCallOutput<Object> execute(CliConfigSetCallInput input) { - for (Entry<String, String> entry : input.getParameters().entrySet()) { - config.setProperty(entry.getKey(), entry.getValue()); - } - config.saveConfig(); - + ConfigManager configManager = configManagerProvider.get(); + String profileName = input.getProfileName(); + Profile config = profileName == null ? configManager.getCurrentProfile() : configManager.getProfile(profileName); + config.setProperties(input.getParameters()); return DefaultCallOutput.empty(); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCallInput.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCallInput.java index 9e8ac8683..4bc0ee1dd 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCallInput.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigSetCallInput.java @@ -26,11 +26,18 @@ import org.apache.ignite.cli.core.call.CallInput; public class CliConfigSetCallInput implements CallInput { private final Map<String, String> parameters; - public CliConfigSetCallInput(Map<String, String> parameters) { + private final String profileName; + + public CliConfigSetCallInput(Map<String, String> parameters, String profileName) { this.parameters = parameters; + this.profileName = profileName; } public Map<String, String> getParameters() { return parameters; } + + public String getProfileName() { + return profileName; + } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigCreateProfileCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigCreateProfileCall.java new file mode 100644 index 000000000..685fc4f5c --- /dev/null +++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigCreateProfileCall.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.call.cliconfig.profile; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.apache.ignite.cli.config.ConfigManager; +import org.apache.ignite.cli.config.ConfigManagerProvider; +import org.apache.ignite.cli.config.Profile; +import org.apache.ignite.cli.core.call.Call; +import org.apache.ignite.cli.core.call.CallOutput; +import org.apache.ignite.cli.core.call.DefaultCallOutput; + +/** + * Create CLI profile. + */ +@Singleton +public class CliConfigCreateProfileCall implements Call<CliConfigCreateProfileCallInput, String> { + + @Inject + private ConfigManagerProvider configManagerProvider; + + @Override + public CallOutput<String> execute(CliConfigCreateProfileCallInput input) { + ConfigManager configManager = configManagerProvider.get(); + Profile copyFrom = null; + if (input.getCopyFrom() != null) { + copyFrom = configManager.getProfile(input.getCopyFrom()); + } + + String profileName = input.getName(); + Profile newProfile = configManager.createProfile(profileName); + + if (copyFrom != null) { + newProfile.setProperties(copyFrom); + } + + if (input.isActivate()) { + configManager.setCurrentProfile(profileName); + } + return DefaultCallOutput.success("Profile " + profileName + " was created successfully."); + } +} diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigCreateProfileCallInput.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigCreateProfileCallInput.java new file mode 100644 index 000000000..f61ef7bf3 --- /dev/null +++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigCreateProfileCallInput.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.call.cliconfig.profile; + +import org.apache.ignite.cli.core.call.CallInput; + +/** + * Create profile call parameters. + */ +public class CliConfigCreateProfileCallInput implements CallInput { + private final String name; + + private final String copyFrom; + + private final boolean activate; + + private CliConfigCreateProfileCallInput(String name, String copyFrom, boolean activate) { + this.name = name; + this.copyFrom = copyFrom; + this.activate = activate; + } + + public static CliConfigCreateProfileCallInputBuilder builder() { + return new CliConfigCreateProfileCallInputBuilder(); + } + + public String getName() { + return name; + } + + public String getCopyFrom() { + return copyFrom; + } + + public boolean isActivate() { + return activate; + } + + /** + * Builder of {@link CliConfigCreateProfileCallInput}. + */ + public static class CliConfigCreateProfileCallInputBuilder { + private String name; + + private String copyFrom; + + private boolean activate; + + public CliConfigCreateProfileCallInputBuilder profileName(String name) { + this.name = name; + return this; + } + + public CliConfigCreateProfileCallInputBuilder copyFrom(String copyFrom) { + this.copyFrom = copyFrom; + return this; + } + + public CliConfigCreateProfileCallInputBuilder activate(boolean activate) { + this.activate = activate; + return this; + } + + public CliConfigCreateProfileCallInput build() { + return new CliConfigCreateProfileCallInput(name, copyFrom, activate); + } + } + +} diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigShowProfileCall.java similarity index 67% copy from modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java copy to modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigShowProfileCall.java index fb7a4810e..c9115b0c3 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigShowProfileCall.java @@ -15,25 +15,28 @@ * limitations under the License. */ -package org.apache.ignite.cli.call.cliconfig; +package org.apache.ignite.cli.call.cliconfig.profile; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.apache.ignite.cli.config.Config; +import org.apache.ignite.cli.config.ConfigManagerProvider; import org.apache.ignite.cli.core.call.Call; +import org.apache.ignite.cli.core.call.CallOutput; import org.apache.ignite.cli.core.call.DefaultCallOutput; import org.apache.ignite.cli.core.call.EmptyCallInput; /** - * Gets entire CLI configuration. + * Get current profile call. */ @Singleton -public class CliConfigCall implements Call<EmptyCallInput, Config> { +public class CliConfigShowProfileCall implements Call<EmptyCallInput, String> { + @Inject - private Config config; + private ConfigManagerProvider provider; @Override - public DefaultCallOutput<Config> execute(EmptyCallInput input) { - return DefaultCallOutput.success(config); + public CallOutput<String> execute(EmptyCallInput input) { + String profileName = provider.get().getCurrentProfile().getName(); + return DefaultCallOutput.success("Current profile: " + profileName); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigUseCall.java similarity index 65% copy from modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCall.java copy to modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigUseCall.java index 3b72ceb8e..670fba3f0 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigGetCall.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/profile/CliConfigUseCall.java @@ -15,27 +15,28 @@ * limitations under the License. */ -package org.apache.ignite.cli.call.cliconfig; +package org.apache.ignite.cli.call.cliconfig.profile; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.apache.ignite.cli.config.Config; +import org.apache.ignite.cli.config.ConfigManagerProvider; import org.apache.ignite.cli.core.call.Call; +import org.apache.ignite.cli.core.call.CallOutput; import org.apache.ignite.cli.core.call.DefaultCallOutput; import org.apache.ignite.cli.core.call.StringCallInput; /** - * Gets CLI configuration parameter. + * Activate CLI profile as default. */ @Singleton -public class CliConfigGetCall implements Call<StringCallInput, String> { +public class CliConfigUseCall implements Call<StringCallInput, String> { + @Inject - private Config config; + private ConfigManagerProvider configManagerProvider; @Override - public DefaultCallOutput<String> execute(StringCallInput input) { - String key = input.getString(); - String property = config.getProperty(key, ""); - return DefaultCallOutput.success(property); + public CallOutput<String> execute(StringCallInput input) { + configManagerProvider.get().setCurrentProfile(input.getString()); + return DefaultCallOutput.success("Profile " + input.getString() + " was activated successfully."); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java index 9ad7a23b2..46d9388ce 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java @@ -20,10 +20,11 @@ package org.apache.ignite.cli.commands.cliconfig; import jakarta.inject.Inject; import java.util.concurrent.Callable; import org.apache.ignite.cli.call.cliconfig.CliConfigGetCall; +import org.apache.ignite.cli.call.cliconfig.CliConfigGetCallInput; import org.apache.ignite.cli.commands.BaseCommand; import org.apache.ignite.cli.core.call.CallExecutionPipeline; -import org.apache.ignite.cli.core.call.StringCallInput; import picocli.CommandLine.Command; +import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; /** @@ -34,13 +35,16 @@ public class CliConfigGetSubCommand extends BaseCommand implements Callable<Inte @Parameters private String key; + @Option(names = {"--profile", "-p"}, description = "Get property from specified profile.") + private String profileName; + @Inject private CliConfigGetCall call; @Override public Integer call() { return CallExecutionPipeline.builder(call) - .inputProvider(() -> new StringCallInput(key)) + .inputProvider(() -> new CliConfigGetCallInput(key, profileName)) .output(spec.commandLine().getOut()) .errOutput(spec.commandLine().getErr()) .build() diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java index f78b82d78..1bfc8bba6 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java @@ -25,6 +25,7 @@ import org.apache.ignite.cli.call.cliconfig.CliConfigSetCallInput; import org.apache.ignite.cli.commands.BaseCommand; import org.apache.ignite.cli.core.call.CallExecutionPipeline; import picocli.CommandLine.Command; +import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; /** @@ -35,13 +36,16 @@ public class CliConfigSetSubCommand extends BaseCommand implements Callable<Inte @Parameters(arity = "1..*") private Map<String, String> parameters; + @Option(names = {"--profile", "-p"}, description = "Set property in specified profile.") + private String profileName; + @Inject private CliConfigSetCall call; @Override public Integer call() { return CallExecutionPipeline.builder(call) - .inputProvider(() -> new CliConfigSetCallInput(parameters)) + .inputProvider(() -> new CliConfigSetCallInput(parameters, profileName)) .output(spec.commandLine().getOut()) .errOutput(spec.commandLine().getErr()) .build() diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java index a622631db..6e0d2f7cc 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java @@ -22,28 +22,34 @@ import jakarta.inject.Singleton; import java.util.concurrent.Callable; import org.apache.ignite.cli.call.cliconfig.CliConfigCall; import org.apache.ignite.cli.commands.BaseCommand; +import org.apache.ignite.cli.commands.cliconfig.profile.CliConfigProfileCommand; import org.apache.ignite.cli.commands.decorators.ConfigDecorator; import org.apache.ignite.cli.core.call.CallExecutionPipeline; -import org.apache.ignite.cli.core.call.EmptyCallInput; +import org.apache.ignite.cli.core.call.StringCallInput; import picocli.CommandLine.Command; +import picocli.CommandLine.Option; /** * Parent command for CLI configuration commands. */ @Command(name = "config", subcommands = { CliConfigGetSubCommand.class, - CliConfigSetSubCommand.class + CliConfigSetSubCommand.class, + CliConfigProfileCommand.class, }) @Singleton public class CliConfigSubCommand extends BaseCommand implements Callable<Integer> { + @Option(names = {"--profile", "-p"}) + private String profileName; + @Inject private CliConfigCall call; @Override public Integer call() { return CallExecutionPipeline.builder(call) - .inputProvider(EmptyCallInput::new) + .inputProvider(() -> new StringCallInput(profileName)) .output(spec.commandLine().getOut()) .errOutput(spec.commandLine().getErr()) .decorator(new ConfigDecorator()) diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigCreateProfileCommand.java similarity index 52% copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigCreateProfileCommand.java index f78b82d78..848e9cfc1 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigCreateProfileCommand.java @@ -15,36 +15,44 @@ * limitations under the License. */ -package org.apache.ignite.cli.commands.cliconfig; +package org.apache.ignite.cli.commands.cliconfig.profile; import jakarta.inject.Inject; -import java.util.Map; import java.util.concurrent.Callable; -import org.apache.ignite.cli.call.cliconfig.CliConfigSetCall; -import org.apache.ignite.cli.call.cliconfig.CliConfigSetCallInput; +import org.apache.ignite.cli.call.cliconfig.profile.CliConfigCreateProfileCall; +import org.apache.ignite.cli.call.cliconfig.profile.CliConfigCreateProfileCallInput; import org.apache.ignite.cli.commands.BaseCommand; import org.apache.ignite.cli.core.call.CallExecutionPipeline; import picocli.CommandLine.Command; -import picocli.CommandLine.Parameters; +import picocli.CommandLine.Option; /** - * Command to set CLI configuration parameters. + * Command for create CLI profile. */ -@Command(name = "set") -public class CliConfigSetSubCommand extends BaseCommand implements Callable<Integer> { - @Parameters(arity = "1..*") - private Map<String, String> parameters; +@Command(name = "create", description = "Create profile command.") +public class CliConfigCreateProfileCommand extends BaseCommand implements Callable<Integer> { + @Option(names = {"--name", "-n"}, required = true, description = "Name of new profile.") + private String profileName; + + @Option(names = {"--copy-from", "-c"}, description = "Profile whose content will be copied to new one.") + private String copyFrom; + + @Option(names = {"--activate", "-a"}, description = "Activate new profile as current or not.") + private boolean activate; + @Inject - private CliConfigSetCall call; + private CliConfigCreateProfileCall call; @Override public Integer call() { return CallExecutionPipeline.builder(call) - .inputProvider(() -> new CliConfigSetCallInput(parameters)) - .output(spec.commandLine().getOut()) + .inputProvider(CliConfigCreateProfileCallInput.builder() + .profileName(profileName) + .copyFrom(copyFrom) + .activate(activate)::build) .errOutput(spec.commandLine().getErr()) - .build() - .runPipeline(); + .output(spec.commandLine().getOut()) + .build().runPipeline(); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigProfileCommand.java similarity index 62% copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigProfileCommand.java index 9ad7a23b2..dd83cd92e 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigProfileCommand.java @@ -15,35 +15,39 @@ * limitations under the License. */ -package org.apache.ignite.cli.commands.cliconfig; +package org.apache.ignite.cli.commands.cliconfig.profile; import jakarta.inject.Inject; import java.util.concurrent.Callable; -import org.apache.ignite.cli.call.cliconfig.CliConfigGetCall; +import org.apache.ignite.cli.call.cliconfig.profile.CliConfigUseCall; import org.apache.ignite.cli.commands.BaseCommand; import org.apache.ignite.cli.core.call.CallExecutionPipeline; import org.apache.ignite.cli.core.call.StringCallInput; import picocli.CommandLine.Command; -import picocli.CommandLine.Parameters; +import picocli.CommandLine.Option; /** - * Command to get CLI configuration parameters. + * Root profile command. */ -@Command(name = "get") -public class CliConfigGetSubCommand extends BaseCommand implements Callable<Integer> { - @Parameters - private String key; +@Command(name = "profile", + description = "Create profile command.", + subcommands = { + CliConfigCreateProfileCommand.class, + CliConfigShowProfileCommand.class + }) +public class CliConfigProfileCommand extends BaseCommand implements Callable<Integer> { + @Option(names = {"--set-current", "-s"}, description = "Name of profile which should be activated as default.") + private String profileName; @Inject - private CliConfigGetCall call; + private CliConfigUseCall call; @Override public Integer call() { return CallExecutionPipeline.builder(call) - .inputProvider(() -> new StringCallInput(key)) - .output(spec.commandLine().getOut()) + .inputProvider(() -> new StringCallInput(profileName)) .errOutput(spec.commandLine().getErr()) - .build() - .runPipeline(); + .output(spec.commandLine().getOut()) + .build().runPipeline(); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigShowProfileCommand.java similarity index 68% copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigShowProfileCommand.java index 9ad7a23b2..ca300b7e3 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigShowProfileCommand.java @@ -15,32 +15,29 @@ * limitations under the License. */ -package org.apache.ignite.cli.commands.cliconfig; +package org.apache.ignite.cli.commands.cliconfig.profile; import jakarta.inject.Inject; import java.util.concurrent.Callable; -import org.apache.ignite.cli.call.cliconfig.CliConfigGetCall; +import org.apache.ignite.cli.call.cliconfig.profile.CliConfigShowProfileCall; import org.apache.ignite.cli.commands.BaseCommand; import org.apache.ignite.cli.core.call.CallExecutionPipeline; -import org.apache.ignite.cli.core.call.StringCallInput; -import picocli.CommandLine.Command; -import picocli.CommandLine.Parameters; +import org.apache.ignite.cli.core.call.EmptyCallInput; +import picocli.CommandLine; /** - * Command to get CLI configuration parameters. + * Show current profile command. */ -@Command(name = "get") -public class CliConfigGetSubCommand extends BaseCommand implements Callable<Integer> { - @Parameters - private String key; +@CommandLine.Command(name = "show", description = "Show current default profile.") +public class CliConfigShowProfileCommand extends BaseCommand implements Callable<Integer> { @Inject - private CliConfigGetCall call; + private CliConfigShowProfileCall call; @Override - public Integer call() { + public Integer call() throws Exception { return CallExecutionPipeline.builder(call) - .inputProvider(() -> new StringCallInput(key)) + .inputProvider(EmptyCallInput::new) .output(spec.commandLine().getOut()) .errOutput(spec.commandLine().getErr()) .build() diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java index d73a19a49..056cbd400 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java @@ -22,6 +22,7 @@ import jakarta.inject.Singleton; import org.apache.ignite.cli.call.connect.ConnectCall; import org.apache.ignite.cli.call.connect.ConnectCallInput; import org.apache.ignite.cli.commands.BaseCommand; +import org.apache.ignite.cli.config.ConfigConstants; import org.apache.ignite.cli.core.call.CallExecutionPipeline; import picocli.CommandLine.Command; import picocli.CommandLine.Parameters; @@ -38,7 +39,7 @@ public class ConnectCommand extends BaseCommand implements Runnable { */ @Parameters( description = "Ignite node url.", - descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300" + descriptionKey = ConfigConstants.CLUSTER_URL ) private String nodeUrl; diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ConfigDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ConfigDecorator.java index 87c91adf5..3032a073b 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ConfigDecorator.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ConfigDecorator.java @@ -17,26 +17,19 @@ package org.apache.ignite.cli.commands.decorators; -import java.util.Iterator; -import java.util.Map.Entry; +import java.util.stream.Collectors; import org.apache.ignite.cli.commands.decorators.core.Decorator; import org.apache.ignite.cli.commands.decorators.core.TerminalOutput; -import org.apache.ignite.cli.config.Config; +import org.apache.ignite.cli.config.Profile; /** - * Decorator for printing {@link Config}. + * Decorator for printing {@link Profile}. */ -public class ConfigDecorator implements Decorator<Config, TerminalOutput> { +public class ConfigDecorator implements Decorator<Profile, TerminalOutput> { @Override - public TerminalOutput decorate(Config data) { - StringBuilder builder = new StringBuilder(); - for (Iterator<Entry<Object, Object>> iterator = data.getProperties().entrySet().iterator(); iterator.hasNext(); ) { - Entry<Object, Object> entry = iterator.next(); - builder.append(entry.getKey()).append("=").append(entry.getValue()); - if (iterator.hasNext()) { - builder.append(System.lineSeparator()); - } - } - return builder::toString; + public TerminalOutput decorate(Profile data) { + return () -> data.getAll().entrySet().stream() + .map(entry -> entry.getKey() + "=" + entry.getValue()) + .collect(Collectors.joining(System.lineSeparator())); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/CachedConfigManagerProvider.java similarity index 70% copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java copy to modules/cli/src/main/java/org/apache/ignite/cli/config/CachedConfigManagerProvider.java index a8e09d2f3..3c364d3c1 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/CachedConfigManagerProvider.java @@ -17,16 +17,18 @@ package org.apache.ignite.cli.config; -import io.micronaut.context.annotation.Factory; import jakarta.inject.Singleton; +import org.apache.ignite.cli.config.ini.IniConfigManager; /** - * Factory for {@link Config}. + * Provider for {@link ConfigManager}. */ -@Factory -public class ConfigFactory { - @Singleton - public Config fileConfig() { - return new Config(); +@Singleton +public class CachedConfigManagerProvider implements ConfigManagerProvider { + private final ConfigManager configManager = new IniConfigManager(ConfigConstants.getConfigFile()); + + @Override + public ConfigManager get() { + return configManager; } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/Config.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/Config.java deleted file mode 100644 index 04374689b..000000000 --- a/modules/cli/src/main/java/org/apache/ignite/cli/config/Config.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.ignite.cli.config; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Path; -import java.util.Properties; - -/** - * CLI default configuration. - */ -public class Config { - private static final String XDG_CONFIG_HOME = "XDG_CONFIG_HOME"; - private static final String XDG_STATE_HOME = "XDG_STATE_HOME"; - private static final String PARENT_FOLDER_NAME = "ignitecli"; - private static final String CONFIG_FILE_NAME = "defaults"; - - private final File configFile; - private final Properties props = new Properties(); - - public Config(File configFile) { - this.configFile = configFile; - loadConfig(configFile); - } - - /** - * Loads config from the default location specified by the XDG_CONFIG_HOME. - */ - public Config() { - this(getConfigFile()); - } - - public Properties getProperties() { - return props; - } - - public String getProperty(String key) { - return props.getProperty(key); - } - - public String getProperty(String key, String defaultValue) { - return props.getProperty(key, defaultValue); - } - - public void setProperty(String key, String value) { - props.setProperty(key, value); - } - - private void loadConfig(File configFile) { - if (configFile.canRead()) { - try (InputStream is = new FileInputStream(configFile)) { - props.load(is); - } catch (IOException e) { - // todo report error? - } - } - } - - /** - * Saves config to file. - */ - public void saveConfig() { - configFile.getParentFile().mkdirs(); - if (configFile.canWrite()) { - try (OutputStream os = new FileOutputStream(configFile)) { - props.store(os, null); - } catch (IOException e) { - // todo report error? - } - } - } - - private static File getConfigFile() { - return getConfigRoot().resolve(PARENT_FOLDER_NAME).resolve(CONFIG_FILE_NAME).toFile(); - } - - /** - * Gets the path for the state. - * - * @return Folder for state storage. - */ - public static File getStateFolder() { - return getStateRoot().resolve(PARENT_FOLDER_NAME).toFile(); - } - - private static Path getConfigRoot() { - String xdgConfigHome = System.getenv(XDG_CONFIG_HOME); - if (xdgConfigHome != null) { - return Path.of(xdgConfigHome); - } else { - return Path.of(System.getProperty("user.home"), ".config"); - } - } - - private static Path getStateRoot() { - String xdgStateHome = System.getenv(XDG_STATE_HOME); - if (xdgStateHome != null) { - return Path.of(xdgStateHome); - } else { - return Path.of(System.getProperty("user.home"), ".local", "state"); - } - } -} diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigConstants.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigConstants.java new file mode 100644 index 000000000..fcc2f9864 --- /dev/null +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigConstants.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.config; + +import java.io.File; +import java.nio.file.Path; + +/** + * Util class for config CLI. + */ +public final class ConfigConstants { + private static final String XDG_CONFIG_HOME = "XDG_CONFIG_HOME"; + private static final String PARENT_FOLDER_NAME = "ignitecli"; + private static final String CONFIG_FILE_NAME = "defaults"; + + public static final String CURRENT_PROFILE = "current_profile"; + public static final String CLUSTER_URL = "ignite.cluster-url"; + public static final String JDBC_URL = "ignite.jdbc-url"; + + private ConfigConstants() { + + } + + + public static File getConfigFile() { + return getConfigRoot().resolve(PARENT_FOLDER_NAME).resolve(CONFIG_FILE_NAME).toFile(); + } + + private static Path getConfigRoot() { + String xdgConfigHome = System.getenv(XDG_CONFIG_HOME); + if (xdgConfigHome != null) { + return Path.of(xdgConfigHome); + } else { + return Path.of(System.getProperty("user.home"), ".config"); + } + } +} diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigDefaultValueProvider.java similarity index 65% copy from modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java copy to modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigDefaultValueProvider.java index fb7a4810e..37e2fefd0 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/call/cliconfig/CliConfigCall.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigDefaultValueProvider.java @@ -15,25 +15,22 @@ * limitations under the License. */ -package org.apache.ignite.cli.call.cliconfig; +package org.apache.ignite.cli.config; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.apache.ignite.cli.config.Config; -import org.apache.ignite.cli.core.call.Call; -import org.apache.ignite.cli.core.call.DefaultCallOutput; -import org.apache.ignite.cli.core.call.EmptyCallInput; +import picocli.CommandLine; /** - * Gets entire CLI configuration. + * Implementation of {@link CommandLine.IDefaultValueProvider} based on CLI config API. */ @Singleton -public class CliConfigCall implements Call<EmptyCallInput, Config> { +public class ConfigDefaultValueProvider implements CommandLine.IDefaultValueProvider { @Inject - private Config config; + private ConfigManagerProvider configManagerProvider; @Override - public DefaultCallOutput<Config> execute(EmptyCallInput input) { - return DefaultCallOutput.success(config); + public String defaultValue(CommandLine.Model.ArgSpec argSpec) throws Exception { + return configManagerProvider.get().getCurrentProperty(argSpec.descriptionKey()); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigInitializationException.java similarity index 68% copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java copy to modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigInitializationException.java index a8e09d2f3..b8a95af4b 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigInitializationException.java @@ -17,16 +17,18 @@ package org.apache.ignite.cli.config; -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; - /** - * Factory for {@link Config}. + * Config initialization exception. */ -@Factory -public class ConfigFactory { - @Singleton - public Config fileConfig() { - return new Config(); +public class ConfigInitializationException extends RuntimeException { + + /** + * Constructor. + * + * @param configPath path of not initialized config. + * @param e reason. + */ + public ConfigInitializationException(String configPath, Throwable e) { + super("Failed to initialize default config in location: " + configPath, e); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigManager.java similarity index 65% copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java copy to modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigManager.java index a8e09d2f3..681e81ffa 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigManager.java @@ -17,16 +17,23 @@ package org.apache.ignite.cli.config; -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; - /** - * Factory for {@link Config}. + * Manager of CLI config. */ -@Factory -public class ConfigFactory { - @Singleton - public Config fileConfig() { - return new Config(); +public interface ConfigManager { + Profile getCurrentProfile(); + + Profile getProfile(String profile); + + Profile createProfile(String profileName); + + void setCurrentProfile(String profileName); + + default String getCurrentProperty(String key) { + return getCurrentProfile().getProperty(key); + } + + default void setProperty(String key, String value) { + getCurrentProfile().setProperty(key, value); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigManagerProvider.java similarity index 78% copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java copy to modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigManagerProvider.java index a8e09d2f3..921e65138 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigManagerProvider.java @@ -17,16 +17,9 @@ package org.apache.ignite.cli.config; -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; - /** - * Factory for {@link Config}. + * Provider for {@link ConfigManager}. */ -@Factory -public class ConfigFactory { - @Singleton - public Config fileConfig() { - return new Config(); - } +public interface ConfigManagerProvider { + ConfigManager get(); } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigStoringException.java similarity index 74% copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java copy to modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigStoringException.java index a8e09d2f3..f7149dc49 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigStoringException.java @@ -17,16 +17,17 @@ package org.apache.ignite.cli.config; -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; - /** - * Factory for {@link Config}. + * Exception for case when config not stored correctly. */ -@Factory -public class ConfigFactory { - @Singleton - public Config fileConfig() { - return new Config(); +public class ConfigStoringException extends RuntimeException { + + /** + * Constructor. + * + * @param cause cause exception. + */ + public ConfigStoringException(String message, Throwable cause) { + super(message, cause); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/Profile.java similarity index 69% copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java copy to modules/cli/src/main/java/org/apache/ignite/cli/config/Profile.java index a8e09d2f3..31d47a5fb 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/Profile.java @@ -17,16 +17,23 @@ package org.apache.ignite.cli.config; -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; +import java.util.Map; /** - * Factory for {@link Config}. + * Ignite CLI Profile. */ -@Factory -public class ConfigFactory { - @Singleton - public Config fileConfig() { - return new Config(); - } +public interface Profile { + String getName(); + + Map<String, String> getAll(); + + String getProperty(String key); + + String getProperty(String key, String defaultValue); + + void setProperty(String key, String value); + + void setProperties(Map<String, String> values); + + void setProperties(Profile copyFrom); } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ProfileNotFoundException.java similarity index 79% copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java copy to modules/cli/src/main/java/org/apache/ignite/cli/config/ProfileNotFoundException.java index a8e09d2f3..a1e0b671d 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ProfileNotFoundException.java @@ -17,16 +17,11 @@ package org.apache.ignite.cli.config; -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; - /** - * Factory for {@link Config}. + * CLI profile not found. */ -@Factory -public class ConfigFactory { - @Singleton - public Config fileConfig() { - return new Config(); +public class ProfileNotFoundException extends RuntimeException { + public ProfileNotFoundException(String profileName) { + super("Profile " + profileName + " not found."); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/StateFolderProvider.java similarity index 51% copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java copy to modules/cli/src/main/java/org/apache/ignite/cli/config/StateFolderProvider.java index a8e09d2f3..021cd93b5 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/StateFolderProvider.java @@ -17,16 +17,35 @@ package org.apache.ignite.cli.config; -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; +import java.io.File; +import java.nio.file.Path; /** - * Factory for {@link Config}. + * Helper class to access to state folder of CLI. */ -@Factory -public class ConfigFactory { - @Singleton - public Config fileConfig() { - return new Config(); +public final class StateFolderProvider { + private static final String XDG_STATE_HOME = "XDG_STATE_HOME"; + private static final String PARENT_FOLDER_NAME = "ignitecli"; + + private StateFolderProvider() { + + } + + /** + * Gets the path for the state. + * + * @return Folder for state storage. + */ + public static File getStateFolder() { + return getStateRoot().resolve(PARENT_FOLDER_NAME).toFile(); + } + + private static Path getStateRoot() { + String xdgStateHome = System.getenv(XDG_STATE_HOME); + if (xdgStateHome != null) { + return Path.of(xdgStateHome); + } else { + return Path.of(System.getProperty("user.home"), ".local", "state"); + } } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniConfigManager.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniConfigManager.java new file mode 100644 index 000000000..bba9f1739 --- /dev/null +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniConfigManager.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.config.ini; + +import static org.apache.ignite.cli.config.ConfigConstants.CLUSTER_URL; +import static org.apache.ignite.cli.config.ConfigConstants.CURRENT_PROFILE; +import static org.apache.ignite.cli.config.ConfigConstants.JDBC_URL; + +import java.io.File; +import java.io.IOException; +import java.util.NoSuchElementException; +import org.apache.ignite.cli.config.ConfigConstants; +import org.apache.ignite.cli.config.ConfigInitializationException; +import org.apache.ignite.cli.config.ConfigManager; +import org.apache.ignite.cli.config.Profile; +import org.apache.ignite.cli.config.ProfileNotFoundException; +import org.apache.ignite.internal.logger.IgniteLogger; +import org.apache.ignite.internal.logger.Loggers; + +/** + * Implementation of {@link ConfigManager} based on {@link IniFile}. + */ +public class IniConfigManager implements ConfigManager { + private static final IgniteLogger log = Loggers.forClass(IniConfigManager.class); + + private static final String INTERNAL_SECTION_NAME = "ignitecli_internal"; + + private final IniFile configFile; + + private String currentProfileName; + + /** + * Constructor. + * + * @param file ini file. + */ + public IniConfigManager(File file) { + IniFile configFile; + try { + configFile = new IniFile(file); + findCurrentProfile(configFile); + } catch (IOException | NoSuchElementException e) { + log.warn("User config is corrupted or doesn't exist.", e); + configFile = createDefaultConfig(); + } + this.configFile = configFile; + this.currentProfileName = findCurrentProfile(configFile).getProperty(CURRENT_PROFILE); + } + + private static IniSection findCurrentProfile(IniFile configFile) { + IniSection internalSection = configFile.getSection(INTERNAL_SECTION_NAME); + if (internalSection == null) { + IniSection section = configFile.getSections().stream().findFirst().orElseThrow(); + internalSection = configFile.createSection(INTERNAL_SECTION_NAME); + internalSection.setProperty(CURRENT_PROFILE, section.getName()); + } + return internalSection; + } + + @Override + public Profile getCurrentProfile() { + return getProfile(currentProfileName); + } + + @Override + public Profile getProfile(String profile) { + IniSection section = configFile.getSection(profile); + if (section == null) { + throw new ProfileNotFoundException(profile); + } + return new IniProfile(section, configFile::store); + } + + @Override + public Profile createProfile(String profileName) { + return new IniProfile(configFile.createSection(profileName), configFile::store); + } + + @Override + public void setCurrentProfile(String profileName) { + IniSection section = configFile.getSection(profileName); + if (section == null) { + throw new ProfileNotFoundException(profileName); + } + currentProfileName = profileName; + configFile.getSection(INTERNAL_SECTION_NAME).setProperty(CURRENT_PROFILE, profileName); + configFile.store(); + } + + private static IniFile createDefaultConfig() { + File configFile = ConfigConstants.getConfigFile(); + try { + configFile.getParentFile().mkdirs(); + configFile.delete(); + configFile.createNewFile(); + IniFile ini = new IniFile(configFile); + IniSection internal = ini.createSection(INTERNAL_SECTION_NAME); + internal.setProperty("current_profile", "default"); + IniSection defaultSection = ini.createSection("default"); + defaultSection.setProperty(CLUSTER_URL, "http://localhost:10300"); + defaultSection.setProperty(JDBC_URL, "jdbc:ignite:thin://127.0.0.1:10800"); + ini.store(); + return ini; + } catch (IOException e) { + throw new ConfigInitializationException(configFile.getAbsolutePath(), e); + } + } +} diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniFile.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniFile.java new file mode 100644 index 000000000..6dce2340b --- /dev/null +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniFile.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.config.ini; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.ignite.cli.config.ConfigStoringException; + +/** + * Representation of INI file. + */ +public class IniFile { + private final Map<String, IniSection> content = new LinkedHashMap<>(); + private final File file; + + /** + * Constructor. + * + * @param file ini file. + * @throws IOException in case when provided INI file can't be parsed. + */ + public IniFile(File file) throws IOException { + content.putAll(new IniParser().parse(file)); + this.file = file; + } + + public IniSection getSection(String name) { + return content.get(name); + } + + public Collection<IniSection> getSections() { + return content.values(); + } + + /** + * Store current INI file to FS file. + */ + public void store() { + try (OutputStream os = new FileOutputStream(file)) { + store(os); + } catch (IOException e) { + throw new ConfigStoringException("Can't store cli config file " + file.getAbsolutePath(), e); + } + } + + private void store(OutputStream outputStream) throws IOException { + BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); + for (IniSection section : getSections()) { + bufferedWriter.write("[" + section.getName() + "]"); + bufferedWriter.newLine(); + for (Map.Entry<String, String> sectionEntry : section.getAll().entrySet()) { + bufferedWriter.write(sectionEntry.getKey() + " = "); + bufferedWriter.write(sectionEntry.getValue()); + bufferedWriter.newLine(); + } + bufferedWriter.newLine(); + } + bufferedWriter.flush(); + } + + /** + * Create and return new {@link IniSection} with provided name. + * + * @param name of section. + * @return new section. + */ + public IniSection createSection(String name) { + if (content.containsKey(name)) { + throw new SectionAlreadyExistsException(name); + } + IniSection iniSection = new IniSection(name); + content.put(name, iniSection); + return iniSection; + } +} diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniParser.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniParser.java new file mode 100644 index 000000000..c0eb2bb92 --- /dev/null +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniParser.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.config.ini; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * INI file parser. + */ +public class IniParser { + private static final Pattern SECTION_PATTERN = Pattern.compile("\\s*\\[([^]]*)\\]\\s*"); + private static final Pattern KEY_VALUE_PATTER = Pattern.compile("\\s*([^=]*)=(.*)"); + private static final Pattern COMMENT_LINE = Pattern.compile("^[;|#].*"); + + + /** + * Parse incoming input string as {@link Map}. + * + * @param inputStream of INI file. + * @return Map representation of INI file. + * @throws IOException when parsing failed. + */ + public Map<String, IniSection> parse(InputStream inputStream) throws IOException { + if (inputStream == null) { + throw new FileNotFoundException("inputStream is null"); + } + + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { + return parseIniFile(bufferedReader); + } + } + + /** + * Parse incoming file as {@link Map}. + * + * @param file INI file. + * @return Map representation of INI file. + * @throws IOException when parsing failed. + */ + public Map<String, IniSection> parse(File file) throws IOException { + try (FileInputStream input = new FileInputStream(file)) { + return parse(input); + } + } + + private Map<String, IniSection> parseIniFile(BufferedReader bufferedReader) throws IOException { + Map<String, IniSection> map = new LinkedHashMap<>(); + IniSection currentSection = new IniSection("NO_SECTION"); + String line; + while ((line = bufferedReader.readLine()) != null) { + + Matcher commentMatcher = COMMENT_LINE.matcher(line); + if (commentMatcher.matches()) { + continue; + } + + Matcher sectionMather = SECTION_PATTERN.matcher(line); + if (sectionMather.matches()) { + String sectionName = sectionMather.group(1).trim(); + currentSection = new IniSection(sectionName); + map.put(sectionName, currentSection); + continue; + } + + Matcher keyValueMatcher = KEY_VALUE_PATTER.matcher(line); + if (keyValueMatcher.matches()) { + String key = keyValueMatcher.group(1).trim(); + String value = keyValueMatcher.group(2).trim(); + currentSection.setProperty(key, value); + } + } + return map; + } +} diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniProfile.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniProfile.java new file mode 100644 index 000000000..dcda97307 --- /dev/null +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniProfile.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.config.ini; + +import java.util.Map; +import org.apache.ignite.cli.config.Profile; + +/** + * Implementation of {@link Profile} based on {@link IniSection}. + */ +public class IniProfile implements Profile { + private final IniSection section; + private final Runnable saveAction; + + public IniProfile(IniSection section, Runnable saveAction) { + this.section = section; + this.saveAction = saveAction; + } + + @Override + public String getName() { + return section.getName(); + } + + @Override + public Map<String, String> getAll() { + return section.getAll(); + } + + @Override + public String getProperty(String key) { + return section.getProperty(key); + } + + @Override + public String getProperty(String key, String defaultValue) { + return section.getProperty(key, defaultValue); + } + + @Override + public void setProperty(String key, String value) { + section.setProperty(key, value); + saveAction.run(); + } + + @Override + public void setProperties(Map<String, String> values) { + section.setProperties(values); + saveAction.run(); + } + + @Override + public void setProperties(Profile copyFrom) { + section.setProperties(copyFrom); + saveAction.run(); + } +} diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniSection.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniSection.java new file mode 100644 index 000000000..eb6d95b70 --- /dev/null +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/IniSection.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.config.ini; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.ignite.cli.config.Profile; + +/** + * INI section representation. + */ +public class IniSection { + private final String name; + private final Map<String, String> props = new LinkedHashMap<>(); + + public IniSection(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public Map<String, String> getAll() { + return Collections.unmodifiableMap(props); + } + + public String getProperty(String key) { + return props.get(key); + } + + public String getProperty(String key, String defaultValue) { + return props.getOrDefault(key, defaultValue); + } + + public void setProperty(String key, String value) { + props.put(key, value); + } + + public void setProperties(Map<String, String> values) { + props.putAll(values); + } + + public void setProperties(Profile copyFrom) { + props.putAll(copyFrom.getAll()); + } +} diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java similarity index 74% rename from modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java rename to modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java index a8e09d2f3..72b69ffe7 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ConfigFactory.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java @@ -15,18 +15,13 @@ * limitations under the License. */ -package org.apache.ignite.cli.config; - -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; +package org.apache.ignite.cli.config.ini; /** - * Factory for {@link Config}. + * Exception when already created INI section trying to create. */ -@Factory -public class ConfigFactory { - @Singleton - public Config fileConfig() { - return new Config(); +public class SectionAlreadyExistsException extends RuntimeException { + public SectionAlreadyExistsException(String name) { + super("Section " + name + " already exists."); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConfigStoringExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConfigStoringExceptionHandler.java new file mode 100644 index 000000000..0f317483c --- /dev/null +++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConfigStoringExceptionHandler.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.core.exception.handler; + +import org.apache.ignite.cli.config.ConfigStoringException; +import org.apache.ignite.cli.core.exception.ExceptionHandler; +import org.apache.ignite.cli.core.exception.ExceptionWriter; +import org.apache.ignite.internal.logger.IgniteLogger; +import org.apache.ignite.internal.logger.Loggers; + +/** + * Handler for {@link ConfigStoringException}. + */ +public class ConfigStoringExceptionHandler implements ExceptionHandler<ConfigStoringException> { + private static final IgniteLogger log = Loggers.forClass(ConfigStoringExceptionHandler.class); + + @Override + public int handle(ExceptionWriter err, ConfigStoringException e) { + log.error("CLI config storing error: ", e); + err.write("Error happened while saving CLI config " + e.getMessage()); + return 1; + } + + @Override + public Class<ConfigStoringException> applicableException() { + return ConfigStoringException.class; + } +} diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java index ca00a9615..c63071311 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java @@ -34,5 +34,8 @@ public class DefaultExceptionHandlers extends ExceptionHandlers { addExceptionHandler(new IgniteCliExceptionHandler()); addExceptionHandler(new IgniteCliApiExceptionHandler()); addExceptionHandler(new UnknownCommandExceptionHandler()); + addExceptionHandler(new ConfigStoringExceptionHandler()); + addExceptionHandler(new ProfileNotFoundExceptionHandler()); + addExceptionHandler(new SectionAlreadyExistsExceptionHandler()); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ProfileNotFoundExceptionHandler.java similarity index 58% copy from modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ProfileNotFoundExceptionHandler.java index ca00a9615..a410dd0bc 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ProfileNotFoundExceptionHandler.java @@ -17,22 +17,22 @@ package org.apache.ignite.cli.core.exception.handler; -import org.apache.ignite.cli.core.exception.ExceptionHandlers; +import org.apache.ignite.cli.config.ProfileNotFoundException; +import org.apache.ignite.cli.core.exception.ExceptionHandler; +import org.apache.ignite.cli.core.exception.ExceptionWriter; /** - * Default collection of exception handlers. + * Handler for {@link ProfileNotFoundException}. */ -public class DefaultExceptionHandlers extends ExceptionHandlers { +public class ProfileNotFoundExceptionHandler implements ExceptionHandler<ProfileNotFoundException> { + @Override + public int handle(ExceptionWriter err, ProfileNotFoundException e) { + err.write(e.getMessage()); + return 1; + } - /** - * Constructor. - */ - public DefaultExceptionHandlers() { - addExceptionHandler(new SqlExceptionHandler()); - addExceptionHandler(new TimeoutExceptionHandler()); - addExceptionHandler(new IgniteClientExceptionHandler()); - addExceptionHandler(new IgniteCliExceptionHandler()); - addExceptionHandler(new IgniteCliApiExceptionHandler()); - addExceptionHandler(new UnknownCommandExceptionHandler()); + @Override + public Class<ProfileNotFoundException> applicableException() { + return ProfileNotFoundException.class; } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SectionAlreadyExistsExceptionHandler.java similarity index 57% copy from modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java copy to modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SectionAlreadyExistsExceptionHandler.java index ca00a9615..aa868ca8a 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/DefaultExceptionHandlers.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SectionAlreadyExistsExceptionHandler.java @@ -17,22 +17,22 @@ package org.apache.ignite.cli.core.exception.handler; -import org.apache.ignite.cli.core.exception.ExceptionHandlers; +import org.apache.ignite.cli.config.ini.SectionAlreadyExistsException; +import org.apache.ignite.cli.core.exception.ExceptionHandler; +import org.apache.ignite.cli.core.exception.ExceptionWriter; /** - * Default collection of exception handlers. + * Handler for {@link SectionAlreadyExistsException}. */ -public class DefaultExceptionHandlers extends ExceptionHandlers { +public class SectionAlreadyExistsExceptionHandler implements ExceptionHandler<SectionAlreadyExistsException> { + @Override + public int handle(ExceptionWriter err, SectionAlreadyExistsException e) { + err.write(e.getMessage()); + return 1; + } - /** - * Constructor. - */ - public DefaultExceptionHandlers() { - addExceptionHandler(new SqlExceptionHandler()); - addExceptionHandler(new TimeoutExceptionHandler()); - addExceptionHandler(new IgniteClientExceptionHandler()); - addExceptionHandler(new IgniteCliExceptionHandler()); - addExceptionHandler(new IgniteCliApiExceptionHandler()); - addExceptionHandler(new UnknownCommandExceptionHandler()); + @Override + public Class<SectionAlreadyExistsException> applicableException() { + return SectionAlreadyExistsException.class; } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/SessionDefaultValueProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/SessionDefaultValueProvider.java index 68541cfd3..2ac2c495c 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/SessionDefaultValueProvider.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/SessionDefaultValueProvider.java @@ -19,10 +19,10 @@ package org.apache.ignite.cli.core.repl; import jakarta.inject.Singleton; import java.util.Objects; -import org.apache.ignite.cli.config.Config; +import org.apache.ignite.cli.config.ConfigConstants; +import org.apache.ignite.cli.config.ConfigDefaultValueProvider; import picocli.CommandLine.IDefaultValueProvider; import picocli.CommandLine.Model.ArgSpec; -import picocli.CommandLine.PropertiesDefaultProvider; /** * Implementation of {@link IDefaultValueProvider} based on {@link Session}. @@ -32,23 +32,23 @@ public class SessionDefaultValueProvider implements IDefaultValueProvider { private final Session session; - private final IDefaultValueProvider defaultValueProvider; + private final ConfigDefaultValueProvider defaultValueProvider; /** * Constructor. * * @param session session instance. - * @param config config instance. + * @param defaultValueProvider defaults value provider. */ - public SessionDefaultValueProvider(Session session, Config config) { + public SessionDefaultValueProvider(Session session, ConfigDefaultValueProvider defaultValueProvider) { this.session = session; - this.defaultValueProvider = new PropertiesDefaultProvider(config.getProperties()); + this.defaultValueProvider = defaultValueProvider; } @Override public String defaultValue(ArgSpec argSpec) throws Exception { if (session.isConnectedToNode()) { - if (Objects.equals(argSpec.descriptionKey(), "ignite.jdbc-url")) { + if (Objects.equals(argSpec.descriptionKey(), ConfigConstants.JDBC_URL)) { return session.getJdbcUrl(); } } diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/ReplExecutor.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/ReplExecutor.java index 4ab85fd74..812ba991f 100644 --- a/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/ReplExecutor.java +++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/repl/executor/ReplExecutor.java @@ -22,7 +22,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; -import org.apache.ignite.cli.config.Config; +import org.apache.ignite.cli.config.StateFolderProvider; import org.apache.ignite.cli.core.exception.handler.PicocliExecutionExceptionHandler; import org.apache.ignite.cli.core.exception.handler.ReplExceptionHandlers; import org.apache.ignite.cli.core.repl.Repl; @@ -89,7 +89,7 @@ public class ReplExecutor { ? repl.getCompleter() : registry.completer()); if (repl.getHistoryFileName() != null) { - reader.variable(LineReader.HISTORY_FILE, new File(Config.getStateFolder(), repl.getHistoryFileName())); + reader.variable(LineReader.HISTORY_FILE, new File(StateFolderProvider.getStateFolder(), repl.getHistoryFileName())); } RegistryCommandExecutor executor = new RegistryCommandExecutor(registry); diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/CliCommandTestBase.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/CliCommandTestBase.java index dfe2106a3..9bb2eec9b 100644 --- a/modules/cli/src/test/java/org/apache/ignite/cli/commands/CliCommandTestBase.java +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/CliCommandTestBase.java @@ -25,6 +25,8 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import picocli.CommandLine; @@ -33,6 +35,8 @@ import picocli.CommandLine; */ @MicronautTest public abstract class CliCommandTestBase { + private static final Map<Class<?>, CommandLine> cache = new HashMap<>(); + @Inject private ApplicationContext context; @@ -46,7 +50,9 @@ public abstract class CliCommandTestBase { @BeforeEach public void setUp() { - cmd = new CommandLine(getCommandClass(), new MicronautFactory(context)); + //Caching need to prevent bug in Picocli related to default values initialization. + //Please refer to org.apache.ignite.cli.commands.PicocliBugTest + cmd = cache.computeIfAbsent(getCommandClass(), clazz -> new CommandLine(clazz, new MicronautFactory(context))); sout = new StringWriter(); serr = new StringWriter(); cmd.setOut(new PrintWriter(sout)); @@ -55,6 +61,10 @@ public abstract class CliCommandTestBase { protected abstract Class<?> getCommandClass(); + protected void execute(String argsLine) { + execute(argsLine.split(" ")); + } + protected void execute(String... args) { exitCode = cmd.execute(args); } diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/PicocliBugTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/PicocliBugTest.java new file mode 100644 index 000000000..13cbab49b --- /dev/null +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/PicocliBugTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.commands; + +import io.micronaut.configuration.picocli.MicronautFactory; +import io.micronaut.context.ApplicationContext; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import java.io.PrintWriter; +import java.io.StringWriter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import picocli.CommandLine; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.Spec; + +//TODO: Change org.apache.ignite.cli.commands.CliCommandTestBase after fix https://github.com/remkop/picocli/issues/1733 +@MicronautTest +@Disabled +class PicocliBugTest { + @Inject + private ApplicationContext context; + + private CommandLine cmd; + + private StringWriter sout; + + private StringWriter serr; + + private int exitCode = Integer.MIN_VALUE; + + @BeforeEach + public void setUp() { + cmd = new CommandLine(Command.class, new MicronautFactory(context)); + sout = new StringWriter(); + serr = new StringWriter(); + cmd.setOut(new PrintWriter(sout)); + cmd.setErr(new PrintWriter(serr)); + } + + @Test + public void test1() { + cmd.execute("--option", "option"); + Assertions.assertEquals("option", sout.toString()); + } + + @Test + public void test2() { + cmd.execute(); + Assertions.assertEquals("", sout.toString()); + } + + @CommandLine.Command(name = "command") + @Singleton + static class Command implements Runnable { + @Option(names = "--option") + private String option; + + @Spec + CommandSpec spec; + + + @Override + public void run() { + spec.commandLine().getOut().print(option); + spec.commandLine().getOut().flush(); + } + } +} \ No newline at end of file diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigActivateCommandTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigActivateCommandTest.java new file mode 100644 index 000000000..526607d2b --- /dev/null +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigActivateCommandTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.commands.cliconfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import jakarta.inject.Inject; +import org.apache.ignite.cli.commands.CliCommandTestBase; +import org.apache.ignite.cli.commands.cliconfig.profile.CliConfigProfileCommand; +import org.apache.ignite.cli.config.ini.IniConfigManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CliConfigActivateCommandTest extends CliCommandTestBase { + @Inject + private TestConfigManagerProvider configManagerProvider; + + @Override + protected Class<?> getCommandClass() { + return CliConfigProfileCommand.class; + } + + @BeforeEach + public void setup() { + configManagerProvider.configManager = new IniConfigManager(TestConfigManagerHelper.createSectionWithInternalPart()); + } + + @Test + public void activateTest() { + execute("--set-current owner"); + + assertAll( + this::assertExitCodeIsZero, + () -> assertOutputContains("Profile owner was activated successfully"), + this::assertErrOutputIsEmpty, + () -> assertThat(configManagerProvider.get().getCurrentProfile().getName()).isEqualTo("owner") + ); + } + + @Test + public void activateWithNotExistedProfileTest() { + execute("--set-current notExist"); + + assertAll( + () -> assertExitCodeIs(1), + () -> assertErrOutputContains("Profile notExist not found"), + this::assertOutputIsEmpty, + () -> assertThat(configManagerProvider.get().getCurrentProfile().getName()).isEqualTo("database") + ); + } + +} diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigCreateProfileCommandTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigCreateProfileCommandTest.java new file mode 100644 index 000000000..e2f303605 --- /dev/null +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigCreateProfileCommandTest.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.commands.cliconfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import jakarta.inject.Inject; +import org.apache.ignite.cli.commands.CliCommandTestBase; +import org.apache.ignite.cli.commands.cliconfig.profile.CliConfigCreateProfileCommand; +import org.apache.ignite.cli.config.ProfileNotFoundException; +import org.apache.ignite.cli.config.ini.IniConfigManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CliConfigCreateProfileCommandTest extends CliCommandTestBase { + @Inject + TestConfigManagerProvider configManagerProvider; + + @Override + protected Class<?> getCommandClass() { + return CliConfigCreateProfileCommand.class; + } + + @BeforeEach + void configManagerRefresh() { + configManagerProvider.configManager = new IniConfigManager(TestConfigManagerHelper.createSectionWithInternalPart()); + } + + @Test + public void testWithoutProfileName() { + execute(); + + assertAll( + this::assertOutputIsEmpty, + () -> assertErrOutputContains("Missing required option: '--name=<profileName>") + ); + } + + @Test + public void testProfileCreation() { + execute("--name profileName"); + + assertAll( + this::assertErrOutputIsEmpty, + () -> assertOutputContains("Profile profileName was created successfully"), + () -> assertThat(configManagerProvider.get().getCurrentProfile().getName()).isNotEqualTo("profileName") + ); + } + + @Test + public void testProfileCopyFrom() { + execute("--name profileName --copy-from database"); + + assertAll( + this::assertErrOutputIsEmpty, + () -> assertOutputContains("Profile profileName was created successfully"), + () -> assertThat(configManagerProvider.get().getProfile("profileName").getAll()).containsAllEntriesOf( + configManagerProvider.get().getProfile("database").getAll()) + ); + } + + @Test + public void testCopyFromNonExist() { + execute("--name profileName --copy-from notExist"); + + assertAll( + () -> assertExitCodeIs(1), + () -> assertErrOutputContains("Profile notExist not found"), + this::assertOutputIsEmpty, + () -> assertThatThrownBy(() -> configManagerProvider.get().getProfile("profileName")) + .isInstanceOf(ProfileNotFoundException.class) + ); + } + + @Test + public void testProfileActivate() { + execute("--name profileName --activate"); + + assertAll( + () -> assertOutputContains("Profile profileName was created successfully"), + this::assertErrOutputIsEmpty, + () -> assertThat(configManagerProvider.get().getCurrentProfile().getName()).isEqualTo("profileName") + ); + } + + @Test + public void testCreateExistedProfile() { + execute("--name profileName"); + assertAll( + () -> assertOutputContains("Profile profileName was created successfully"), + this::assertErrOutputIsEmpty + ); + + execute("--name profileName"); + + assertAll( + () -> assertErrOutputContains("Section profileName already exist") + ); + } + +} diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommandTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommandTest.java index 26c4c196b..cc00e6987 100644 --- a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommandTest.java +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommandTest.java @@ -19,17 +19,27 @@ package org.apache.ignite.cli.commands.cliconfig; import static org.junit.jupiter.api.Assertions.assertAll; +import jakarta.inject.Inject; import org.apache.ignite.cli.commands.CliCommandTestBase; +import org.apache.ignite.cli.config.ini.IniConfigManager; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class CliConfigGetSubCommandTest extends CliCommandTestBase { + @Inject + private TestConfigManagerProvider configManagerProvider; @Override protected Class<?> getCommandClass() { return CliConfigGetSubCommand.class; } + @BeforeEach + public void setup() { + configManagerProvider.configManager = new IniConfigManager(TestConfigManagerHelper.createSectionWithInternalPart()); + } + @Test @DisplayName("Key is mandatory") void noKey() { @@ -47,11 +57,11 @@ class CliConfigGetSubCommandTest extends CliCommandTestBase { @DisplayName("Displays value for specified key") void singleKey() { // When executed with single key - execute("ignite.cluster-url"); + execute("server"); assertAll( this::assertExitCodeIsZero, - () -> assertOutputIs("test_cluster_url" + System.lineSeparator()), + () -> assertOutputIs("127.0.0.1" + System.lineSeparator()), this::assertErrOutputIsEmpty ); } @@ -81,4 +91,25 @@ class CliConfigGetSubCommandTest extends CliCommandTestBase { () -> assertErrOutputContains("Unmatched argument at index 1") ); } + + @Test + public void testWithNonDefaultProfile() { + execute("organization --profile owner"); + + assertAll( + this::assertErrOutputIsEmpty, + () -> assertOutputContains("Apache Ignite") + ); + + } + + @Test + public void testWithNonNotExistedProfile() { + execute("organization --profile notExist"); + + assertAll( + this::assertOutputIsEmpty, + () -> assertErrOutputContains("Profile notExist not found.") + ); + } } diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommandTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommandTest.java index 2235ffff8..6981dd110 100644 --- a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommandTest.java +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommandTest.java @@ -22,20 +22,28 @@ import static org.junit.jupiter.api.Assertions.assertAll; import jakarta.inject.Inject; import org.apache.ignite.cli.commands.CliCommandTestBase; -import org.apache.ignite.cli.config.Config; +import org.apache.ignite.cli.config.ConfigConstants; +import org.apache.ignite.cli.config.ConfigManager; +import org.apache.ignite.cli.config.ini.IniConfigManager; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class CliConfigSetSubCommandTest extends CliCommandTestBase { @Inject - Config config; + TestConfigManagerProvider configManagerProvider; @Override protected Class<?> getCommandClass() { return CliConfigSetSubCommand.class; } + @BeforeEach + public void setup() { + configManagerProvider.configManager = new IniConfigManager(TestConfigManagerHelper.createSectionWithInternalPart()); + } + @Test @DisplayName("Need at least one parameter") void noKey() { @@ -66,13 +74,13 @@ class CliConfigSetSubCommandTest extends CliCommandTestBase { @DisplayName("Sets single value") void singleKeySet() { // When executed with key - execute("ignite.cluster-url=test"); + execute(ConfigConstants.CLUSTER_URL + "=" + "test"); assertAll( this::assertExitCodeIsZero, this::assertOutputIsEmpty, this::assertErrOutputIsEmpty, - () -> assertThat(config.getProperty("ignite.cluster-url")).isEqualTo("test") + () -> assertThat(configManagerProvider.get().getCurrentProperty(ConfigConstants.CLUSTER_URL)).isEqualTo("test") ); } @@ -82,12 +90,46 @@ class CliConfigSetSubCommandTest extends CliCommandTestBase { // When executed with multiple keys execute("ignite.cluster-url=test", "ignite.jdbc-url=test2"); + ConfigManager configManager = configManagerProvider.get(); + assertAll( this::assertExitCodeIsZero, this::assertOutputIsEmpty, this::assertErrOutputIsEmpty, - () -> assertThat(config.getProperty("ignite.cluster-url")).isEqualTo("test"), - () -> assertThat(config.getProperty("ignite.jdbc-url")).isEqualTo("test2") + () -> assertThat(configManager.getCurrentProperty(ConfigConstants.CLUSTER_URL)).isEqualTo("test"), + () -> assertThat(configManager.getCurrentProperty(ConfigConstants.JDBC_URL)).isEqualTo("test2") + ); + } + + @Test + @DisplayName("Set with profile") + public void testWithProfile() { + // When executed with multiple keys + execute("ignite.cluster-url=test ignite.jdbc-url=test2 --profile owner"); + + ConfigManager configManager = configManagerProvider.get(); + + assertAll( + this::assertExitCodeIsZero, + this::assertOutputIsEmpty, + this::assertErrOutputIsEmpty, + () -> assertThat(configManager.getCurrentProperty(ConfigConstants.CLUSTER_URL)).isNotEqualTo("test"), + () -> assertThat(configManager.getCurrentProperty(ConfigConstants.JDBC_URL)).isNotEqualTo("test2"), + () -> assertThat(configManager.getProfile("owner").getProperty(ConfigConstants.CLUSTER_URL)).isEqualTo("test"), + () -> assertThat(configManager.getProfile("owner").getProperty(ConfigConstants.JDBC_URL)).isEqualTo("test2")); + } + + @Test + public void testWithNotExistedProfile() { + execute("ignite.cluster-url=test ignite.jdbc-url=test2 --profile notExist"); + + ConfigManager configManager = configManagerProvider.get(); + + assertAll( + () -> assertExitCodeIs(1), + this::assertOutputIsEmpty, + () -> assertErrOutputContains("Profile notExist not found"), + () -> assertThat(configManager.getCurrentProperty(ConfigConstants.CLUSTER_URL)).isNotEqualTo("test") ); } } diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommandTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigShowProfileCommandTest.java similarity index 52% copy from modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommandTest.java copy to modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigShowProfileCommandTest.java index b5e979e6d..34847fdca 100644 --- a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommandTest.java +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigShowProfileCommandTest.java @@ -19,26 +19,45 @@ package org.apache.ignite.cli.commands.cliconfig; import static org.junit.jupiter.api.Assertions.assertAll; +import jakarta.inject.Inject; import org.apache.ignite.cli.commands.CliCommandTestBase; -import org.junit.jupiter.api.DisplayName; +import org.apache.ignite.cli.commands.cliconfig.profile.CliConfigShowProfileCommand; +import org.apache.ignite.cli.config.ini.IniConfigManager; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class CliConfigSubCommandTest extends CliCommandTestBase { +class CliConfigShowProfileCommandTest extends CliCommandTestBase { + + @Inject + TestConfigManagerProvider configManagerProvider; @Override protected Class<?> getCommandClass() { - return CliConfigSubCommand.class; + return CliConfigShowProfileCommand.class; + } + + @BeforeEach + void configManagerRefresh() { + configManagerProvider.configManager = new IniConfigManager(TestConfigManagerHelper.createSectionWithInternalPart()); + } + + @Test + public void testWithInternalSection() { + execute(); + + assertAll( + () -> assertOutputContains("Current profile: database"), + this::assertErrOutputIsEmpty + ); } @Test - @DisplayName("Displays all keys") - void noKey() { + public void testWithoutInternalSection() { + configManagerProvider.configManager = new IniConfigManager(TestConfigManagerHelper.createSectionWithoutInternalPart()); execute(); assertAll( - this::assertExitCodeIsZero, - () -> assertOutputContains("ignite.cluster-url=test_cluster_url"), - () -> assertOutputContains("ignite.jdbc-url=test_jdbc_url"), + () -> assertOutputContains("Current profile: owner"), this::assertErrOutputIsEmpty ); } diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommandTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommandTest.java index b5e979e6d..c5505e3f2 100644 --- a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommandTest.java +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommandTest.java @@ -19,12 +19,24 @@ package org.apache.ignite.cli.commands.cliconfig; import static org.junit.jupiter.api.Assertions.assertAll; +import jakarta.inject.Inject; import org.apache.ignite.cli.commands.CliCommandTestBase; +import org.apache.ignite.cli.config.ini.IniConfigManager; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class CliConfigSubCommandTest extends CliCommandTestBase { + @Inject + TestConfigManagerProvider configManagerProvider; + + @BeforeEach + public void setup() { + configManagerProvider.configManager = new IniConfigManager(TestConfigManagerHelper.createSectionWithInternalPart()); + } + + @Override protected Class<?> getCommandClass() { return CliConfigSubCommand.class; @@ -35,10 +47,27 @@ class CliConfigSubCommandTest extends CliCommandTestBase { void noKey() { execute(); + String expectedResult = "server=127.0.0.1" + System.lineSeparator() + + "port=8080" + System.lineSeparator() + + "file=\"apache.ignite\"" + System.lineSeparator(); + + assertAll( + this::assertExitCodeIsZero, + () -> assertOutputIs(expectedResult), + this::assertErrOutputIsEmpty + ); + } + + @Test + public void testWithProfile() { + execute("--profile owner"); + + String expectedResult = "name=John Smith" + System.lineSeparator() + + "organization=Apache Ignite" + System.lineSeparator(); + assertAll( this::assertExitCodeIsZero, - () -> assertOutputContains("ignite.cluster-url=test_cluster_url"), - () -> assertOutputContains("ignite.jdbc-url=test_jdbc_url"), + () -> assertOutputIs(expectedResult), this::assertErrOutputIsEmpty ); } diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/ConfigManagerTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/ConfigManagerTest.java new file mode 100644 index 000000000..2e7058980 --- /dev/null +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/ConfigManagerTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.commands.cliconfig; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import org.apache.ignite.cli.config.ini.IniConfigManager; +import org.junit.jupiter.api.Test; + +class ConfigManagerTest { + @Test + public void testSaveLoadConfig() { + File tempFile = TestConfigManagerHelper.createSectionWithInternalPart(); + IniConfigManager configManager = new IniConfigManager(tempFile); + + configManager.setProperty("ignite.cluster-url", "test"); + + IniConfigManager configAfterSave = new IniConfigManager(tempFile); + assertThat(configAfterSave.getCurrentProperty("ignite.cluster-url")).isEqualTo("test"); + } + + @Test + public void testLoadConfigWithoutInternalSection() { + File tempFile = TestConfigManagerHelper.createSectionWithoutInternalPart(); + IniConfigManager configManager = new IniConfigManager(tempFile); + + + assertThat(configManager.getCurrentProfile().getName()).isEqualTo("owner"); + } + + @Test + public void testEmptyConfigLoad() { + File tempFile = TestConfigManagerHelper.createEmptyConfig(); + IniConfigManager configManager = new IniConfigManager(tempFile); + + assertThat(configManager.getCurrentProfile().getName()).isEqualTo("default"); + } + + + @Test + public void testRemoveConfigFileOnRuntime() { + File tempFile = TestConfigManagerHelper.createSectionWithInternalPart(); + IniConfigManager configManager = new IniConfigManager(tempFile); + + assertThat(configManager.getCurrentProfile().getName()).isEqualTo("database"); + + assertThat(tempFile.delete()).isTrue(); + assertThat(tempFile.exists()).isFalse(); + + assertThat(configManager.getCurrentProfile().getName()).isEqualTo("database"); + + configManager.setProperty("test", "testValue"); + + assertThat(tempFile.exists()).isTrue(); + assertThat(configManager.getCurrentProfile().getProperty("test")).isEqualTo("testValue"); + assertThat(configManager.getCurrentProfile().getName()).isEqualTo("database"); + } +} diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/TestConfigFactory.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/TestConfigFactory.java deleted file mode 100644 index fb2d2b07e..000000000 --- a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/TestConfigFactory.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.ignite.cli.commands.cliconfig; - -import io.micronaut.context.annotation.Factory; -import io.micronaut.context.annotation.Replaces; -import jakarta.inject.Singleton; -import java.io.File; -import java.io.IOException; -import org.apache.ignite.cli.config.Config; -import org.apache.ignite.cli.config.ConfigFactory; - -/** - * Test factory for {@link Config}. - */ -@Factory -@Replaces(factory = ConfigFactory.class) -public class TestConfigFactory { - - /** - * Creates a {@link Config} with some defaults for testing. - * - * @return {@link Config} - * @throws IOException in case temp file couldn't be created - */ - @Singleton - public Config createConfig() throws IOException { - File tempFile = File.createTempFile("cli", null); - tempFile.deleteOnExit(); - Config config = new Config(tempFile); - config.setProperty("ignite.cluster-url", "test_cluster_url"); - config.setProperty("ignite.jdbc-url", "test_jdbc_url"); - return config; - } -} diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/TestConfigManagerHelper.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/TestConfigManagerHelper.java new file mode 100644 index 000000000..d4e6ab748 --- /dev/null +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/TestConfigManagerHelper.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.ignite.cli.commands.cliconfig; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import org.apache.ignite.cli.config.ConfigManager; + +/** + * Test factory for {@link ConfigManager}. + */ +public class TestConfigManagerHelper { + public static final String EMPTY = "empty.ini"; + public static final String TWO_SECTION_WITH_INTERNAL_PART = "two_section_with_internal.ini"; + public static final String TWO_SECTION_WITHOUT_INTERNAL_PART = "two_section_without_internal.ini"; + public static final String INTEGRATION_TESTS = "integration_tests.ini"; + + public static File createEmptyConfig() { + return createIniFile(EMPTY); + } + + public static File createSectionWithInternalPart() { + return createIniFile(TWO_SECTION_WITH_INTERNAL_PART); + } + + public static File createSectionWithoutInternalPart() { + return createIniFile(TWO_SECTION_WITHOUT_INTERNAL_PART); + } + + public static File createIntegrationTests() { + return createIniFile(INTEGRATION_TESTS); + } + + private static File createIniFile(String iniResource) { + try { + File tempFile = File.createTempFile("cli", null); + + try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile)) { + FileChannel dest = fileOutputStream.getChannel(); + InputStream resourceAsStream = TestConfigManagerHelper.class.getClassLoader().getResourceAsStream(iniResource); + ReadableByteChannel src = Channels.newChannel(resourceAsStream); + dest.transferFrom(src, 0, Integer.MAX_VALUE); + tempFile.deleteOnExit(); + return tempFile; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/ConfigTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/TestConfigManagerProvider.java similarity index 58% rename from modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/ConfigTest.java rename to modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/TestConfigManagerProvider.java index 1cd8e5182..ed581a665 100644 --- a/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/ConfigTest.java +++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/cliconfig/TestConfigManagerProvider.java @@ -17,22 +17,22 @@ package org.apache.ignite.cli.commands.cliconfig; -import static org.assertj.core.api.Assertions.assertThat; +import io.micronaut.context.annotation.Replaces; +import jakarta.inject.Singleton; +import org.apache.ignite.cli.config.ConfigManager; +import org.apache.ignite.cli.config.ConfigManagerProvider; -import java.io.File; -import java.io.IOException; -import org.apache.ignite.cli.config.Config; -import org.junit.jupiter.api.Test; +/** + * Test implementation of {@link ConfigManagerProvider}. + */ +@Singleton +@Replaces(ConfigManagerProvider.class) +public class TestConfigManagerProvider implements ConfigManagerProvider { -class ConfigTest { - @Test - public void testSaveLoadConfig() throws IOException { - File tempFile = File.createTempFile("cli", null); - Config config = new Config(tempFile); - config.setProperty("ignite.cluster-url", "test"); - config.saveConfig(); + public ConfigManager configManager; - Config configAfterSave = new Config(tempFile); - assertThat(configAfterSave.getProperty("ignite.cluster-url")).isEqualTo("test"); + @Override + public ConfigManager get() { + return configManager; } } diff --git a/modules/cli/src/test/resources/empty.ini b/modules/cli/src/test/resources/empty.ini new file mode 100644 index 000000000..e69de29bb diff --git a/modules/cli/src/test/resources/integration_tests.ini b/modules/cli/src/test/resources/integration_tests.ini new file mode 100644 index 000000000..6031a5f31 --- /dev/null +++ b/modules/cli/src/test/resources/integration_tests.ini @@ -0,0 +1,6 @@ +[ignitecli_internal] +current_profile = default + +[default] +ignite.cluster-url = http://localhost:10300 +ignite.jdbc-url = jdbc:ignite:thin://127.0.0.1:10800 diff --git a/modules/cli/src/test/resources/two_section_with_internal.ini b/modules/cli/src/test/resources/two_section_with_internal.ini new file mode 100644 index 000000000..1b125c2ff --- /dev/null +++ b/modules/cli/src/test/resources/two_section_with_internal.ini @@ -0,0 +1,12 @@ +[ignitecli_internal] +current_profile = database + +[owner] +name = John Smith +organization = Apache Ignite + +[database] +server = 127.0.0.1 +port = 8080 +file = "apache.ignite" + diff --git a/modules/cli/src/test/resources/two_section_without_internal.ini b/modules/cli/src/test/resources/two_section_without_internal.ini new file mode 100644 index 000000000..98d04ca17 --- /dev/null +++ b/modules/cli/src/test/resources/two_section_without_internal.ini @@ -0,0 +1,10 @@ + +[owner] +name = John Smith +organization = Apache Ignite + +[database] +server = 127.0.0.1 +port = 8080 +file = "apache.ignite" + diff --git a/parent/pom.xml b/parent/pom.xml index 357cbe5d1..0fa404eaf 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -163,7 +163,6 @@ <dependencyManagement> <dependencies> - <dependency> <groupId>io.micronaut.test</groupId> <artifactId>micronaut-test-core</artifactId> @@ -1559,6 +1558,7 @@ <exclude>deliveries/deb/rules</exclude> <exclude>deliveries/deb/control</exclude> <exclude>**/*.bat</exclude> <!-- Files in bat format --> + <exclude>**/*.ini</exclude> <!-- Files in ini format --> <exclude>modules/runner/src/integrationTest/sql/**</exclude> <!-- SQL logic tests. --> </excludes> </configuration>