From 2da051417cf7c85466ebf33f1124ffc0b5a52208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Onimus?= Date: Sat, 18 Feb 2023 21:43:05 +0100 Subject: [PATCH] feat: upgrade to spring boot 3 and spring shell 3 (#278) --- .github/workflows/release.yml | 4 +- .github/workflows/reusable-build.yml | 4 +- README.md | 6 + pom.xml | 13 +- samples/basic/pom.xml | 2 +- samples/complete/pom.xml | 2 +- ...ration.java => CompleteConfiguration.java} | 17 ++- ...vider.java => CompletePromptProvider.java} | 2 +- ...emoSecurity.java => CompleteSecurity.java} | 56 +++---- .../src/main/resources/application.yml | 4 - starter/pom.xml | 2 +- .../ssh/shell/ExtendedCompletionProposal.java | 6 +- .../fonimus/ssh/shell/ExtendedShell.java | 6 +- .../ssh/shell/SshShellAutoConfiguration.java | 2 +- .../fonimus/ssh/shell/SshShellRunnable.java | 4 +- .../fonimus/ssh/shell/SshShellUtils.java | 143 +++++++----------- .../shell/commands/AvailabilityException.java | 5 + .../ssh/shell/commands/ColorAligner.java | 2 +- .../ssh/shell/commands/DatasourceCommand.java | 1 - .../ssh/shell/commands/JmxCommand.java | 21 +-- .../ssh/shell/commands/TasksCommand.java | 26 ++-- .../commands/actuator/ActuatorCommand.java | 49 +++--- .../shell/commands/system/SystemCommand.java | 49 ++---- .../listeners/SshShellListenerService.java | 2 +- .../shell/manage/SshShellSessionManager.java | 2 +- .../ExtendedResultHandlerService.java | 2 +- .../postprocess/PostProcessorException.java | 5 + .../provided/SavePostProcessor.java | 2 +- .../main/resources/META-INF/spring.factories | 2 - ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../ssh/shell/AbstractCommandTest.java | 17 +-- .../ssh/shell/SshShellApplicationTest.java | 19 ++- .../fonimus/ssh/shell/SshShellHelperTest.java | 10 +- .../ssh/shell/commands/ScriptCommandTest.java | 2 +- .../SshShellSecurityConfigurationTest.java | 56 +++---- .../ssh/shell/conf/TaskServiceTest.java | 15 ++ .../PrettyJsonPostProcessorTest.java | 8 +- 37 files changed, 283 insertions(+), 286 deletions(-) rename samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/{DemoConfiguration.java => CompleteConfiguration.java} (91%) rename samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/{DemoPromptProvider.java => CompletePromptProvider.java} (94%) rename samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/{DemoSecurity.java => CompleteSecurity.java} (53%) delete mode 100755 starter/src/main/resources/META-INF/spring.factories create mode 100644 starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 starter/src/test/java/com/github/fonimus/ssh/shell/conf/TaskServiceTest.java diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eba2ae92..0760778e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,10 +29,10 @@ jobs: uses: actions/checkout@v3 with: persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3.10.0 with: - java-version: 11 + java-version: 17 distribution: temurin cache: maven - name: Import GPG key diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 361b6d56..bb4633af 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -27,10 +27,10 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{ inputs.ref }} - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3.10.0 with: - java-version: 11 + java-version: 17 distribution: temurin cache: maven diff --git a/README.md b/README.md index 335163ea..beb47ffa 100755 --- a/README.md +++ b/README.md @@ -797,6 +797,12 @@ public class ApplicationTest { ## Release notes +### 3.0.0 + +* Bump spring-boot.version 2.7.5 to 3.0.2 +* Bump spring-shell-starter 2.1.5 to 3.0.0 +* Java 17 minimum is required + ### 2.0.3 * Bump spring-boot.version from 2.7.3 to 2.7.5 diff --git a/pom.xml b/pom.xml index 447ed2c7..9bd1a284 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.github.fonimus ssh-shell-spring-boot-parent - 2.0.4-SNAPSHOT + 3.0.0-SNAPSHOT pom @@ -51,12 +51,12 @@ - 1.8 - 1.8 + 17 + 17 UTF-8 - 2.7.5 - 2.1.5 + 3.0.2 + 3.0.0 2.9.2 5.9.2 @@ -202,6 +202,9 @@ maven-compiler-plugin 3.10.1 + + -parameters + true diff --git a/samples/basic/pom.xml b/samples/basic/pom.xml index 0db54e9e..0c136b36 100755 --- a/samples/basic/pom.xml +++ b/samples/basic/pom.xml @@ -19,7 +19,7 @@ ssh-shell-spring-boot-parent com.github.fonimus - 2.0.4-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/samples/complete/pom.xml b/samples/complete/pom.xml index 2fda6bf3..c8fe2668 100755 --- a/samples/complete/pom.xml +++ b/samples/complete/pom.xml @@ -19,7 +19,7 @@ ssh-shell-spring-boot-parent com.github.fonimus - 2.0.4-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/DemoConfiguration.java b/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompleteConfiguration.java similarity index 91% rename from samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/DemoConfiguration.java rename to samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompleteConfiguration.java index 9d6f7490..6c467070 100644 --- a/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/DemoConfiguration.java +++ b/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompleteConfiguration.java @@ -20,6 +20,8 @@ import com.github.fonimus.ssh.shell.listeners.SshShellListener; import com.github.fonimus.ssh.shell.postprocess.PostProcessor; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository; +import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -41,17 +43,17 @@ */ @Slf4j @Configuration -public class DemoConfiguration implements SchedulingConfigurer { +public class CompleteConfiguration implements SchedulingConfigurer { private final TasksCommand tasksCommand; - public DemoConfiguration(TasksCommand tasksCommand) { + public CompleteConfiguration(TasksCommand tasksCommand) { this.tasksCommand = tasksCommand; } @Bean public PostProcessor quotePostProcessor() { - return new PostProcessor() { + return new PostProcessor<>() { @Override public String getName() { @@ -72,7 +74,7 @@ public String process(String input, List parameters) { @Bean public PostProcessor datePostProcessor() { - return new PostProcessor() { + return new PostProcessor<>() { @Override public String getName() { @@ -93,7 +95,7 @@ public ZonedDateTime process(String input, List parameters) { @Bean public PostProcessor uctPostProcessor() { - return new PostProcessor() { + return new PostProcessor<>() { @Override public String getName() { @@ -166,6 +168,11 @@ public TaskScheduler threadPoolTaskExecutor2() { return threadPoolTaskScheduler; } + @Bean + public HttpExchangeRepository httpTraceRepository() { + return new InMemoryHttpExchangeRepository(); + } + @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); diff --git a/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/DemoPromptProvider.java b/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompletePromptProvider.java similarity index 94% rename from samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/DemoPromptProvider.java rename to samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompletePromptProvider.java index 81b6bbb2..5cadc9a4 100644 --- a/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/DemoPromptProvider.java +++ b/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompletePromptProvider.java @@ -24,7 +24,7 @@ import static org.jline.utils.AttributedStyle.DEFAULT; @Component -public class DemoPromptProvider implements PromptProvider { +public class CompletePromptProvider implements PromptProvider { @Override public AttributedString getPrompt() { diff --git a/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/DemoSecurity.java b/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompleteSecurity.java similarity index 53% rename from samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/DemoSecurity.java rename to samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompleteSecurity.java index 9371a78c..2c070f59 100644 --- a/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/DemoSecurity.java +++ b/samples/complete/src/main/java/com/github/fonimus/ssh/shell/complete/CompleteSecurity.java @@ -20,37 +20,30 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; /** * Security configuration */ @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) -public class DemoSecurity - extends WebSecurityConfigurerAdapter { +@EnableMethodSecurity +public class CompleteSecurity { - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() + @Bean + public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authManager) throws Exception { + http.authorizeHttpRequests() + .requestMatchers("/ping").permitAll() .requestMatchers(EndpointRequest.to("info")).permitAll() - .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR"); - } - - @Bean("customAuthManager") - @Override - public AuthenticationManager authenticationManager() throws Exception { - return super.authenticationManager(); + .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR") + .and().authenticationManager(authManager); + return http.build(); } @Bean @@ -59,14 +52,21 @@ public PasswordEncoder passwordEncoder() { } @Bean - public UserDetailsService userDetailsService() { - InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); - PasswordEncoder encoder = passwordEncoder(); - manager.createUser(User.withUsername("user").password(encoder.encode("password")).roles("USER").build()); - manager.createUser(User.withUsername("actuator").password(encoder.encode("password")).roles("ACTUATOR").build - ()); - manager.createUser(User.withUsername("admin").password(encoder.encode("admin")).roles("ADMIN", "ACTUATOR") - .build()); - return manager; + public AuthenticationManager authManager(HttpSecurity http) throws Exception { + AuthenticationManagerBuilder authenticationManagerBuilder = + http.getSharedObject(AuthenticationManagerBuilder.class); + authenticationManagerBuilder.inMemoryAuthentication() + .withUser("user") + .password(passwordEncoder().encode("password")) + .roles("USER") + .and() + .withUser("actuator") + .password(passwordEncoder().encode("password")) + .roles("ACTUATOR") + .and() + .withUser("admin") + .password(passwordEncoder().encode("admin")) + .roles("ADMIN", "ACTUATOR"); + return authenticationManagerBuilder.build(); } } diff --git a/samples/complete/src/main/resources/application.yml b/samples/complete/src/main/resources/application.yml index 133003b3..464baf64 100755 --- a/samples/complete/src/main/resources/application.yml +++ b/samples/complete/src/main/resources/application.yml @@ -5,13 +5,11 @@ spring: url: jdbc:h2:mem:testdb second-datasource: url: jdbc:h2:mem:testdb2 - shell.interactive.enabled: true main.lazy-initialization: true ssh: shell: authentication: security - auth-provider-bean-name: customAuthManager authorized-public-keys: classpath:.ssh/authorized.keys commands: actuator: @@ -37,8 +35,6 @@ management: endpoint: shutdown: enabled: true - threaddump: - enabled: false health: group: nocommands: diff --git a/starter/pom.xml b/starter/pom.xml index 3f72278f..3b595a2d 100644 --- a/starter/pom.xml +++ b/starter/pom.xml @@ -19,7 +19,7 @@ ssh-shell-spring-boot-parent com.github.fonimus - 2.0.4-SNAPSHOT + 3.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedCompletionProposal.java b/starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedCompletionProposal.java index 8e165b5a..b8acf307 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedCompletionProposal.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedCompletionProposal.java @@ -16,18 +16,20 @@ package com.github.fonimus.ssh.shell; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; import org.springframework.shell.CompletionProposal; /** * Extended completion proposal to be able to set complete attribute of proposal */ -@Data public class ExtendedCompletionProposal extends CompletionProposal { /** * If should add space after proposed proposal */ + @Getter + @Setter private boolean complete; /** diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedShell.java b/starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedShell.java index 4e64a686..ab2decb7 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedShell.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/ExtendedShell.java @@ -58,7 +58,7 @@ public class ExtendedShell extends Shell { * @param exitCodeMappings exit code mappipngs * @param postProcessors post processors */ - public ExtendedShell( + protected ExtendedShell( ResultHandlerService resultHandlerService, CommandCatalog commandRegistry, Terminal terminal, ShellContext shellContext, ExitCodeMappings exitCodeMappings, List> postProcessors @@ -66,7 +66,7 @@ public ExtendedShell( super(resultHandlerService, commandRegistry, terminal, shellContext, exitCodeMappings); this.resultHandlerService = resultHandlerService; if (postProcessors != null) { - this.postProcessorNames.addAll(postProcessors.stream().map(PostProcessor::getName).collect(Collectors.toList())); + this.postProcessorNames.addAll(postProcessors.stream().map(PostProcessor::getName).toList()); } } @@ -118,7 +118,7 @@ public Object evaluate(Input input) { } if (isKeyCharInList(words)) { List indexes = - IntStream.range(0, words.size()).filter(i -> KEY_CHARS.contains(words.get(i))).boxed().collect(Collectors.toList()); + IntStream.range(0, words.size()).filter(i -> KEY_CHARS.contains(words.get(i))).boxed().toList(); for (Integer index : indexes) { if (words.size() > index + 1) { String keyChar = words.get(index); diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellAutoConfiguration.java b/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellAutoConfiguration.java index ba115166..6fd98393 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellAutoConfiguration.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellAutoConfiguration.java @@ -88,7 +88,7 @@ "org.springframework.boot.actuate.autoconfigure.metrics.MetricsEndpointAutoConfiguration", "org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksEndpointAutoConfiguration", "org.springframework.boot.actuate.autoconfigure.session.SessionsEndpointAutoConfiguration", - "org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceEndpointAutoConfiguration", + "org.springframework.boot.actuate.autoconfigure.web.exchanges.HttpExchangesAutoConfiguration", "org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration" }) @ComponentScan(basePackages = {"com.github.fonimus.ssh.shell"}) diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellRunnable.java b/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellRunnable.java index 3c897520..aa150f77 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellRunnable.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellRunnable.java @@ -104,7 +104,7 @@ public void run() { terminalBuilder.type(sshEnv.getEnv().get(SSH_ENV_TERM)); } try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintStream ps = new PrintStream(baos, true, StandardCharsets.UTF_8.name()); + PrintStream ps = new PrintStream(baos, true, StandardCharsets.UTF_8); Terminal terminal = terminalBuilder.build() ) { @@ -127,7 +127,7 @@ public void run() { } DefaultResultHandler resultHandler = new DefaultResultHandler(terminal); - resultHandler.handleResult(new String(baos.toByteArray(), StandardCharsets.UTF_8)); + resultHandler.handleResult(baos.toString(StandardCharsets.UTF_8)); resultHandler.handleResult("Please type `help` to see available commands"); LineReader reader = LineReaderBuilder.builder() diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellUtils.java b/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellUtils.java index 0f23ef5d..a0b94eb7 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellUtils.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/SshShellUtils.java @@ -39,94 +39,61 @@ private SshShellUtils() { public static void fill(Attributes attr, Map ptyModes) { for (Map.Entry e : ptyModes.entrySet()) { switch (e.getKey()) { - case VINTR: - attr.setControlChar(Attributes.ControlChar.VINTR, e.getValue()); - break; - case VQUIT: - attr.setControlChar(Attributes.ControlChar.VQUIT, e.getValue()); - break; - case VERASE: - attr.setControlChar(Attributes.ControlChar.VERASE, e.getValue()); - break; - case VKILL: - attr.setControlChar(Attributes.ControlChar.VKILL, e.getValue()); - break; - case VEOF: - attr.setControlChar(Attributes.ControlChar.VEOF, e.getValue()); - break; - case VEOL: - attr.setControlChar(Attributes.ControlChar.VEOL, e.getValue()); - break; - case VEOL2: - attr.setControlChar(Attributes.ControlChar.VEOL2, e.getValue()); - break; - case VSTART: - attr.setControlChar(Attributes.ControlChar.VSTART, e.getValue()); - break; - case VSTOP: - attr.setControlChar(Attributes.ControlChar.VSTOP, e.getValue()); - break; - case VSUSP: - attr.setControlChar(Attributes.ControlChar.VSUSP, e.getValue()); - break; - case VDSUSP: - attr.setControlChar(Attributes.ControlChar.VDSUSP, e.getValue()); - break; - case VREPRINT: - attr.setControlChar(Attributes.ControlChar.VREPRINT, e.getValue()); - break; - case VWERASE: - attr.setControlChar(Attributes.ControlChar.VWERASE, e.getValue()); - break; - case VLNEXT: - attr.setControlChar(Attributes.ControlChar.VLNEXT, e.getValue()); - break; - /* - case VFLUSH: - attr.setControlChar(Attributes.ControlChar.VMIN, e.getValue()); - break; - case VSWTCH: - attr.setControlChar(Attributes.ControlChar.VTIME, e.getValue()); - break; - */ - case VSTATUS: - attr.setControlChar(Attributes.ControlChar.VSTATUS, e.getValue()); - break; - case VDISCARD: - attr.setControlChar(Attributes.ControlChar.VDISCARD, e.getValue()); - break; - case ECHO: - attr.setLocalFlag(Attributes.LocalFlag.ECHO, e.getValue() != 0); - break; - case ICANON: - attr.setLocalFlag(Attributes.LocalFlag.ICANON, e.getValue() != 0); - break; - case ISIG: - attr.setLocalFlag(Attributes.LocalFlag.ISIG, e.getValue() != 0); - break; - case ICRNL: - attr.setInputFlag(Attributes.InputFlag.ICRNL, e.getValue() != 0); - break; - case INLCR: - attr.setInputFlag(Attributes.InputFlag.INLCR, e.getValue() != 0); - break; - case IGNCR: - attr.setInputFlag(Attributes.InputFlag.IGNCR, e.getValue() != 0); - break; - case OCRNL: - attr.setOutputFlag(Attributes.OutputFlag.OCRNL, e.getValue() != 0); - break; - case ONLCR: - attr.setOutputFlag(Attributes.OutputFlag.ONLCR, e.getValue() != 0); - break; - case ONLRET: - attr.setOutputFlag(Attributes.OutputFlag.ONLRET, e.getValue() != 0); - break; - case OPOST: - attr.setOutputFlag(Attributes.OutputFlag.OPOST, e.getValue() != 0); - break; - default: - // nothing to do + case VINTR -> + attr.setControlChar(Attributes.ControlChar.VINTR, e.getValue()); + case VQUIT -> + attr.setControlChar(Attributes.ControlChar.VQUIT, e.getValue()); + case VERASE -> + attr.setControlChar(Attributes.ControlChar.VERASE, e.getValue()); + case VKILL -> + attr.setControlChar(Attributes.ControlChar.VKILL, e.getValue()); + case VEOF -> + attr.setControlChar(Attributes.ControlChar.VEOF, e.getValue()); + case VEOL -> + attr.setControlChar(Attributes.ControlChar.VEOL, e.getValue()); + case VEOL2 -> + attr.setControlChar(Attributes.ControlChar.VEOL2, e.getValue()); + case VSTART -> + attr.setControlChar(Attributes.ControlChar.VSTART, e.getValue()); + case VSTOP -> + attr.setControlChar(Attributes.ControlChar.VSTOP, e.getValue()); + case VSUSP -> + attr.setControlChar(Attributes.ControlChar.VSUSP, e.getValue()); + case VDSUSP -> + attr.setControlChar(Attributes.ControlChar.VDSUSP, e.getValue()); + case VREPRINT -> + attr.setControlChar(Attributes.ControlChar.VREPRINT, e.getValue()); + case VWERASE -> + attr.setControlChar(Attributes.ControlChar.VWERASE, e.getValue()); + case VLNEXT -> + attr.setControlChar(Attributes.ControlChar.VLNEXT, e.getValue()); + case VSTATUS -> + attr.setControlChar(Attributes.ControlChar.VSTATUS, e.getValue()); + case VDISCARD -> + attr.setControlChar(Attributes.ControlChar.VDISCARD, e.getValue()); + case ECHO -> + attr.setLocalFlag(Attributes.LocalFlag.ECHO, e.getValue() != 0); + case ICANON -> + attr.setLocalFlag(Attributes.LocalFlag.ICANON, e.getValue() != 0); + case ISIG -> + attr.setLocalFlag(Attributes.LocalFlag.ISIG, e.getValue() != 0); + case ICRNL -> + attr.setInputFlag(Attributes.InputFlag.ICRNL, e.getValue() != 0); + case INLCR -> + attr.setInputFlag(Attributes.InputFlag.INLCR, e.getValue() != 0); + case IGNCR -> + attr.setInputFlag(Attributes.InputFlag.IGNCR, e.getValue() != 0); + case OCRNL -> + attr.setOutputFlag(Attributes.OutputFlag.OCRNL, e.getValue() != 0); + case ONLCR -> + attr.setOutputFlag(Attributes.OutputFlag.ONLCR, e.getValue() != 0); + case ONLRET -> + attr.setOutputFlag(Attributes.OutputFlag.ONLRET, e.getValue() != 0); + case OPOST -> + attr.setOutputFlag(Attributes.OutputFlag.OPOST, e.getValue() != 0); + default -> { + } + // nothing to do } } } diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/AvailabilityException.java b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/AvailabilityException.java index 7d876bbb..8e4980d7 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/AvailabilityException.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/AvailabilityException.java @@ -16,12 +16,17 @@ package com.github.fonimus.ssh.shell.commands; +import java.io.Serial; + /** * Availability */ public class AvailabilityException extends Exception { + @Serial + private static final long serialVersionUID = -8323343028474225670L; + public AvailabilityException(String message) { super(message); } diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/ColorAligner.java b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/ColorAligner.java index ecf0c6e9..cc0567c6 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/ColorAligner.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/ColorAligner.java @@ -25,7 +25,7 @@ */ public class ColorAligner implements Aligner { - private PromptColor color; + private final PromptColor color; /** * Default constructor diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/DatasourceCommand.java b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/DatasourceCommand.java index c6f60c46..89921eff 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/DatasourceCommand.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/DatasourceCommand.java @@ -46,7 +46,6 @@ @Slf4j @SshShellComponent @ShellCommandGroup("Datasource Commands") -@ConditionalOnBean(DataSource.class) @ConditionalOnClass(DataSource.class) @ConditionalOnProperty( name = SshShellProperties.SSH_SHELL_PREFIX + ".commands." + DatasourceCommand.GROUP + ".create", diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/JmxCommand.java b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/JmxCommand.java index 83631def..69e53a99 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/JmxCommand.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/JmxCommand.java @@ -71,7 +71,7 @@ public void jmxList( SimpleTable.SimpleTableBuilder builder = SimpleTable.builder().column("Object name").column("Class name"); Set result = server.queryMBeans(patternName, null); for (ObjectInstance objectInstance : - result.stream().sorted(Comparator.comparing(ObjectInstance::getObjectName)).collect(Collectors.toList())) { + result.stream().sorted(Comparator.comparing(ObjectInstance::getObjectName)).toList()) { builder.line(Arrays.asList(objectInstance.getObjectName().toString(), objectInstance.getClassName())); } helper.print(helper.renderTable(builder.build())); @@ -207,18 +207,13 @@ public Object jmxInvoke( } private Object impact(int impact) { - switch (impact) { - case MBeanOperationInfo.ACTION: - return "action"; - case MBeanOperationInfo.ACTION_INFO: - return "action/info"; - case MBeanOperationInfo.INFO: - return "info"; - case MBeanOperationInfo.UNKNOWN: - return "unknown"; - default: - return "(" + impact + ")"; - } + return switch (impact) { + case MBeanOperationInfo.ACTION -> "action"; + case MBeanOperationInfo.ACTION_INFO -> "action/info"; + case MBeanOperationInfo.INFO -> "info"; + case MBeanOperationInfo.UNKNOWN -> "unknown"; + default -> "(" + impact + ")"; + }; } private Availability jmxListAvailability() { diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/TasksCommand.java b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/TasksCommand.java index 3c979f49..9da7b86f 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/TasksCommand.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/TasksCommand.java @@ -40,7 +40,7 @@ import org.springframework.stereotype.Component; import java.lang.reflect.Method; -import java.time.Duration; +import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.*; @@ -55,7 +55,7 @@ */ @SshShellComponent @ShellCommandGroup("Tasks Commands") -@ConditionalOnBean({ScheduledTaskHolder.class}) +@ConditionalOnBean(ScheduledTaskHolder.class) @ConditionalOnProperty( name = SshShellProperties.SSH_SHELL_PREFIX + ".commands." + TasksCommand.GROUP + ".create", havingValue = "true", matchIfMissing = true @@ -143,13 +143,12 @@ public String tasksList( if (state.getScheduledTask() != null) { line.add(state.getStatus()); Task task = state.getScheduledTask().getTask(); - if (task instanceof CronTask) { + if (task instanceof CronTask cronTask) { line.add("cron"); - CronTask cronTask = ((CronTask) task); line.add("expression : " + cronTask.getExpression()); - Date next = cronTask.getTrigger().nextExecutionTime(new SimpleTriggerContext()); + Instant next = cronTask.getTrigger().nextExecution(new SimpleTriggerContext()); line.add(next == null ? "-" : - FORMATTER.format(next.toInstant().atOffset(ZoneOffset.UTC).toLocalDateTime())); + FORMATTER.format(next.atOffset(ZoneOffset.UTC).toLocalDateTime())); } else if (task instanceof FixedDelayTask) { line.add("fixed-delay"); line.add(getTrigger((FixedDelayTask) task)); @@ -264,10 +263,10 @@ public String tasksRestart( } else if (taskObj instanceof FixedDelayTask) { future = taskScheduler().scheduleWithFixedDelay(state.getScheduledTask().getTask().getRunnable(), - ((FixedDelayTask) taskObj).getInterval()); + ((FixedDelayTask) taskObj).getIntervalDuration()); } else if (taskObj instanceof FixedRateTask) { future = taskScheduler().scheduleAtFixedRate(state.getScheduledTask().getTask().getRunnable(), - ((FixedRateTask) taskObj).getInterval()); + ((FixedRateTask) taskObj).getIntervalDuration()); } else { helper.printWarning("Task [" + taskName + "] of class [" + taskObj.getClass().getName() + "] " + "cannot be restarted."); @@ -310,7 +309,7 @@ public String tasksSingle( try { String executionId = taskName + "-" + generateExecutionId(); // Will run the Runnable immediately or as soon as possible - ScheduledFuture future = taskScheduler().schedule(state.getScheduledTask().getTask().getRunnable(), new Date()); + ScheduledFuture future = taskScheduler().schedule(state.getScheduledTask().getTask().getRunnable(), Instant.now()); statesByName.put(executionId, new TaskState(executionId, null, TaskStatus.running, future)); started.add(executionId); } catch (TaskRejectedException e) { @@ -334,7 +333,7 @@ private List listTasks(boolean all, String task, boolean running) { if (all) { TaskStatus filter = running ? TaskStatus.running : TaskStatus.stopped; result.addAll(this.statesByName.entrySet().stream().filter(e -> e.getValue().getStatus() == filter) - .map(Map.Entry::getKey).collect(Collectors.toList())); + .map(Map.Entry::getKey).toList()); } else { if (task == null || task.isEmpty()) { throw new IllegalArgumentException("You need to set either all option or task one"); @@ -348,9 +347,10 @@ private List listTasks(boolean all, String task, boolean running) { } private static String getTrigger(IntervalTask task) { - String initialDelay = Duration.ofMillis(task.getInitialDelay()).toString(); - String interval = Duration.ofMillis(task.getInterval()).toString(); - return "interval : " + interval + " (" + task.getInterval() + "), init-delay : " + initialDelay + " (" + task.getInitialDelay() + ")"; + return "interval : " + task.getIntervalDuration() + + " (" + task.getIntervalDuration().toMillis() + + "), init-delay : " + task.getInitialDelayDuration() + + " (" + task.getInitialDelayDuration().toMillis() + ")"; } private static String getTaskName(Runnable runnable) { diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/actuator/ActuatorCommand.java b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/actuator/ActuatorCommand.java index c8852281..89e2a272 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/actuator/ActuatorCommand.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/actuator/ActuatorCommand.java @@ -37,7 +37,7 @@ import org.springframework.boot.actuate.metrics.MetricsEndpoint; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint; import org.springframework.boot.actuate.session.SessionsEndpoint; -import org.springframework.boot.actuate.trace.http.HttpTraceEndpoint; +import org.springframework.boot.actuate.web.exchanges.HttpExchangesEndpoint; import org.springframework.boot.actuate.web.mappings.MappingsEndpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -85,7 +85,7 @@ public class ActuatorCommand extends AbstractCommand { private final HealthEndpoint health; - private final HttpTraceEndpoint httptrace; + private final HttpExchangesEndpoint httpExchanges; private final InfoEndpoint info; @@ -109,7 +109,7 @@ public ActuatorCommand(ApplicationContext applicationContext, Environment enviro @Lazy ConfigurationPropertiesReportEndpoint configprops, @Lazy EnvironmentEndpoint env, @Lazy HealthEndpoint health, - @Lazy HttpTraceEndpoint httptrace, + @Lazy HttpExchangesEndpoint httpExchanges, @Lazy InfoEndpoint info, @Lazy LoggersEndpoint loggers, @Lazy MetricsEndpoint metrics, @@ -126,7 +126,7 @@ public ActuatorCommand(ApplicationContext applicationContext, Environment enviro this.configprops = configprops; this.env = env; this.health = health; - this.httptrace = httptrace; + this.httpExchanges = httpExchanges; this.info = info; this.loggers = loggers; this.metrics = metrics; @@ -166,7 +166,7 @@ public Availability auditAvailability() { */ @ShellMethod(key = "beans", value = "Display beans endpoint.") @ShellMethodAvailability("beansAvailability") - public BeansEndpoint.ApplicationBeans beans() { + public BeansEndpoint.BeansDescriptor beans() { return beans.beans(); } @@ -184,8 +184,8 @@ public Availability beansAvailability() { */ @ShellMethod(key = "conditions", value = "Display conditions endpoint.") @ShellMethodAvailability("conditionsAvailability") - public ConditionsReportEndpoint.ApplicationConditionEvaluation conditions() { - return conditions.applicationConditionEvaluation(); + public ConditionsReportEndpoint.ConditionsDescriptor conditions() { + return conditions.conditions(); } /** @@ -202,7 +202,7 @@ public Availability conditionsAvailability() { */ @ShellMethod(key = "configprops", value = "Display configprops endpoint.") @ShellMethodAvailability("configpropsAvailability") - public ConfigurationPropertiesReportEndpoint.ApplicationConfigurationProperties configprops() { + public ConfigurationPropertiesReportEndpoint.ConfigurationPropertiesDescriptor configprops() { return configprops.configurationProperties(); } @@ -276,17 +276,17 @@ public Availability healthAvailability() { * * @return httptrace */ - @ShellMethod(key = "httptrace", value = "Display httptrace endpoint.") - @ShellMethodAvailability("httptraceAvailability") - public HttpTraceEndpoint.HttpTraceDescriptor httptrace() { - return httptrace.traces(); + @ShellMethod(key = "httpexchanges", value = "Display httpexchanges endpoint.") + @ShellMethodAvailability("httpExchangesAvailability") + public HttpExchangesEndpoint.HttpExchangesDescriptor httptrace() { + return httpExchanges.httpExchanges(); } /** - * @return whether `httptrace` command is available + * @return whether `httpexchanges` command is available */ - public Availability httptraceAvailability() { - return availability("httptrace", HttpTraceEndpoint.class); + public Availability httpExchangesAvailability() { + return availability("httpexchanges", HttpExchangesEndpoint.class); } /** @@ -325,18 +325,21 @@ public Object loggers( throw new IllegalArgumentException("Logger name is mandatory for '" + action + "' action"); } switch (action) { - case get: - LoggersEndpoint.LoggerLevels levels = loggers.loggerLevels(loggerName); + case get -> { + LoggersEndpoint.LoggerLevelsDescriptor levels = loggers.loggerLevels(loggerName); return "Logger named [" + loggerName + "] : [configured: " + levels.getConfiguredLevel() + "]"; - case conf: + } + case conf -> { if (loggerLevel == null) { throw new IllegalArgumentException("Logger level is mandatory for '" + action + "' action"); } loggers.configureLogLevel(loggerName, loggerLevel); return "Logger named [" + loggerName + "] now configured to level [" + loggerLevel + "]"; - default: + } + default -> { // list return loggers.loggers(); + } } } @@ -361,7 +364,7 @@ public Object metrics( @ShellOption(help = "Tags (key=value, separated by coma)", defaultValue = ShellOption.NULL) String tags ) { if (name != null) { - MetricsEndpoint.MetricResponse result = metrics.metric(name, tags != null ? Arrays.asList(tags.split(",") + MetricsEndpoint.MetricDescriptor result = metrics.metric(name, tags != null ? Arrays.asList(tags.split(",") ) : null); if (result == null) { String tagsStr = tags != null ? " and tags: " + tags : ""; @@ -386,7 +389,7 @@ public Availability metricsAvailability() { */ @ShellMethod(key = "mappings", value = "Display mappings endpoint.") @ShellMethodAvailability("mappingsAvailability") - public MappingsEndpoint.ApplicationMappings mappings() { + public MappingsEndpoint.ApplicationMappingsDescriptor mappings() { return mappings.mappings(); } @@ -404,7 +407,7 @@ public Availability mappingsAvailability() { */ @ShellMethod(key = "sessions", value = "Display sessions endpoint.") @ShellMethodAvailability("sessionsAvailability") - public SessionsEndpoint.SessionsReport sessions() { + public SessionsEndpoint.SessionsDescriptor sessions() { return applicationContext.getBean(SessionsEndpoint.class).sessionsForUsername(null); } @@ -422,7 +425,7 @@ public Availability sessionsAvailability() { */ @ShellMethod(key = "scheduledtasks", value = "Display scheduledtasks endpoint.") @ShellMethodAvailability("scheduledtasksAvailability") - public ScheduledTasksEndpoint.ScheduledTasksReport scheduledtasks() { + public ScheduledTasksEndpoint.ScheduledTasksDescriptor scheduledtasks() { return scheduledtasks.scheduledTasks(); } diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/system/SystemCommand.java b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/system/SystemCommand.java index f8a6a5d0..9f3eeb1b 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/commands/system/SystemCommand.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/commands/system/SystemCommand.java @@ -220,28 +220,14 @@ private Thread get(Long threadId) { } private Comparator comparator(ThreadColumn orderBy, boolean reverseOrder) { - Comparator c; - switch (orderBy) { - - case priority: - c = Comparator.comparingDouble(Thread::getPriority); - break; - case state: - c = Comparator.comparing(e -> e.getState().name()); - break; - case interrupted: - c = Comparator.comparing(Thread::isAlive); - break; - case daemon: - c = Comparator.comparing(Thread::isDaemon); - break; - case name: - c = Comparator.comparing(Thread::getName); - break; - default: - c = Comparator.comparingDouble(Thread::getId); - break; - } + Comparator c = switch (orderBy) { + case priority -> Comparator.comparingDouble(Thread::getPriority); + case state -> Comparator.comparing(e -> e.getState().name()); + case interrupted -> Comparator.comparing(Thread::isAlive); + case daemon -> Comparator.comparing(Thread::isDaemon); + case name -> Comparator.comparing(Thread::getName); + default -> Comparator.comparingDouble(Thread::getId); + }; if (reverseOrder) { c = c.reversed(); } @@ -249,19 +235,12 @@ private Comparator comparator(ThreadColumn orderBy, boolean reve } private PromptColor color(Thread.State state) { - switch (state) { - case RUNNABLE: - return PromptColor.GREEN; - case BLOCKED: - case TERMINATED: - return PromptColor.RED; - case WAITING: - case TIMED_WAITING: - return PromptColor.CYAN; - default: - return PromptColor.WHITE; - - } + return switch (state) { + case RUNNABLE -> PromptColor.GREEN; + case BLOCKED, TERMINATED -> PromptColor.RED; + case WAITING, TIMED_WAITING -> PromptColor.CYAN; + default -> PromptColor.WHITE; + }; } private String table(ThreadColumn orderBy, boolean reverseOrder, boolean fullscreen) { diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/listeners/SshShellListenerService.java b/starter/src/main/java/com/github/fonimus/ssh/shell/listeners/SshShellListenerService.java index f322906e..9f9ed448 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/listeners/SshShellListenerService.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/listeners/SshShellListenerService.java @@ -28,7 +28,7 @@ @Slf4j public class SshShellListenerService { - private List listeners; + private final List listeners; public SshShellListenerService(List listeners) { this.listeners = listeners == null ? new ArrayList<>() : listeners; diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/manage/SshShellSessionManager.java b/starter/src/main/java/com/github/fonimus/ssh/shell/manage/SshShellSessionManager.java index 6ffb965c..4a6c43d4 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/manage/SshShellSessionManager.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/manage/SshShellSessionManager.java @@ -29,7 +29,7 @@ @Component public class SshShellSessionManager { - private SshShellCommandFactory commandFactory; + private final SshShellCommandFactory commandFactory; /** * Ssh shell session manager diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/ExtendedResultHandlerService.java b/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/ExtendedResultHandlerService.java index 97afa31f..8945d165 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/ExtendedResultHandlerService.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/ExtendedResultHandlerService.java @@ -66,7 +66,7 @@ public void handle(Object result) { handle(result, TypeDescriptor.forObject(result)); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) @Override public void handle(Object result, TypeDescriptor resultType) { if (result == null) { diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/PostProcessorException.java b/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/PostProcessorException.java index 6461e61b..7cef4674 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/PostProcessorException.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/PostProcessorException.java @@ -16,12 +16,17 @@ package com.github.fonimus.ssh.shell.postprocess; +import java.io.Serial; + /** * Post processor exception */ public class PostProcessorException extends Exception { + @Serial + private static final long serialVersionUID = 150227794242813079L; + public PostProcessorException(String message) { super(message); } diff --git a/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/provided/SavePostProcessor.java b/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/provided/SavePostProcessor.java index 3454e46e..68ffc02d 100644 --- a/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/provided/SavePostProcessor.java +++ b/starter/src/main/java/com/github/fonimus/ssh/shell/postprocess/provided/SavePostProcessor.java @@ -65,7 +65,7 @@ public String process(Object result, List parameters) throws PostProcess File file = new File(path); try { String toWrite = string(result).replaceAll(REPLACE_REGEX, "") + "\n"; - Files.write(file.toPath(), toWrite.getBytes(StandardCharsets.UTF_8), CREATE, APPEND); + Files.writeString(file.toPath(), toWrite, CREATE, APPEND); return "Result saved to file: " + file.getAbsolutePath(); } catch (IOException e) { LOGGER.debug("Unable to write to file: " + file.getAbsolutePath(), e); diff --git a/starter/src/main/resources/META-INF/spring.factories b/starter/src/main/resources/META-INF/spring.factories deleted file mode 100755 index 0c9c8aec..00000000 --- a/starter/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -com.github.fonimus.ssh.shell.SshShellAutoConfiguration diff --git a/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..b161714f --- /dev/null +++ b/starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.github.fonimus.ssh.shell.SshShellAutoConfiguration diff --git a/starter/src/test/java/com/github/fonimus/ssh/shell/AbstractCommandTest.java b/starter/src/test/java/com/github/fonimus/ssh/shell/AbstractCommandTest.java index 271ece95..ce79fee6 100644 --- a/starter/src/test/java/com/github/fonimus/ssh/shell/AbstractCommandTest.java +++ b/starter/src/test/java/com/github/fonimus/ssh/shell/AbstractCommandTest.java @@ -43,11 +43,8 @@ public abstract class AbstractCommandTest protected void commonCommandAvailability() { assertAll( - // since spring boot 2.2 audit,httptrace disabled by default - // more info: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2 - // .2-Release-Notes#actuator-http-trace-and-auditing-are-disabled-by-default () -> assertFalse(cmd.auditAvailability().isAvailable()), - () -> assertFalse(cmd.httptraceAvailability().isAvailable()), + () -> assertFalse(cmd.httpExchangesAvailability().isAvailable()), // all available except for shutdown () -> assertTrue(cmd.beansAvailability().isAvailable()), () -> assertTrue(cmd.conditionsAvailability().isAvailable()), @@ -71,7 +68,7 @@ protected void testBeans() { @Test void testConditions() { - assertEquals(conditions.applicationConditionEvaluation().getContexts().size(), + assertEquals(conditions.conditions().getContexts().size(), cmd.conditions().getContexts().size()); } @@ -133,19 +130,19 @@ void testConfigureLogger() { @Test void testMetrics() { assertEquals(metrics.listNames().getNames().size(), - ((MetricsEndpoint.ListNamesResponse) cmd.metrics(null, null)).getNames().size()); + ((MetricsEndpoint.MetricNamesDescriptor) cmd.metrics(null, null)).getNames().size()); } @Test void testMetricsName() { assertEquals(metrics.metric("jvm.memory.max", null).getName(), - ((MetricsEndpoint.MetricResponse) cmd.metrics("jvm.memory.max", null)).getName()); + ((MetricsEndpoint.MetricDescriptor) cmd.metrics("jvm.memory.max", null)).getName()); } @Test void testMetricsTags() { assertEquals(metrics.metric("jvm.memory.max", Collections.singletonList("area:heap")).getName(), - ((MetricsEndpoint.MetricResponse) cmd.metrics("jvm.memory.max", + ((MetricsEndpoint.MetricDescriptor) cmd.metrics("jvm.memory.max", "area:heap")).getName()); } @@ -167,8 +164,8 @@ void testSessions() { @Test void testScheduled() { - ScheduledTasksEndpoint.ScheduledTasksReport actual = cmd.scheduledtasks(); - ScheduledTasksEndpoint.ScheduledTasksReport expected = scheduledtasks.scheduledTasks(); + ScheduledTasksEndpoint.ScheduledTasksDescriptor actual = cmd.scheduledtasks(); + ScheduledTasksEndpoint.ScheduledTasksDescriptor expected = scheduledtasks.scheduledTasks(); assertEquals(expected.getCron().size(), actual.getCron().size()); assertEquals(expected.getFixedDelay().size(), actual.getFixedDelay().size()); assertEquals(expected.getFixedRate().size(), actual.getFixedRate().size()); diff --git a/starter/src/test/java/com/github/fonimus/ssh/shell/SshShellApplicationTest.java b/starter/src/test/java/com/github/fonimus/ssh/shell/SshShellApplicationTest.java index c12027d6..bf35936e 100644 --- a/starter/src/test/java/com/github/fonimus/ssh/shell/SshShellApplicationTest.java +++ b/starter/src/test/java/com/github/fonimus/ssh/shell/SshShellApplicationTest.java @@ -19,15 +19,20 @@ import com.github.fonimus.ssh.shell.commands.DatasourceCommand; import com.github.fonimus.ssh.shell.commands.JmxCommand; import com.github.fonimus.ssh.shell.commands.TasksCommand; +import com.github.fonimus.ssh.shell.conf.SshShellSessionConfigurationTest; +import com.github.fonimus.ssh.shell.conf.TaskServiceTest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpoint; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; import org.springframework.test.annotation.DirtiesContext; import static org.junit.jupiter.api.Assertions.*; -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = SshShellApplicationTest.class, +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, + classes = {SshShellApplicationTest.class, SshShellSessionConfigurationTest.class}, properties = { "ssh.shell.port=2345", "ssh.shell.password=pass", @@ -49,13 +54,19 @@ public class SshShellApplicationTest @Autowired(required = false) protected TasksCommand tasks; + @Autowired(required = false) + protected ApplicationContext context; + + @Autowired(required = false) + protected ConditionsReportEndpoint conditionsReportEndpoint; + @Test void testCommandAvailability() { setActuatorRole(); super.commonCommandAvailability(); - assertFalse(cmd.httptraceAvailability().isAvailable()); + assertFalse(cmd.httpExchangesAvailability().isAvailable()); } @Test @@ -78,7 +89,9 @@ void testJmxCommand() { @Test void testTasksCommand() { assertNotNull(tasks); - assertNotNull(tasks.tasksList(null, true)); + String tasksListResult = tasks.tasksList(null, true); + assertNotNull(tasksListResult); + assertTrue(tasksListResult.contains(TaskServiceTest.class.getName() + ".test")); assertThrows(IllegalArgumentException.class, () -> tasks.tasksStop(false, "unknown")); assertThrows(IllegalArgumentException.class, () -> tasks.tasksRestart(false, "unknown")); assertThrows(IllegalArgumentException.class, () -> tasks.tasksSingle(false, "unknown")); diff --git a/starter/src/test/java/com/github/fonimus/ssh/shell/SshShellHelperTest.java b/starter/src/test/java/com/github/fonimus/ssh/shell/SshShellHelperTest.java index ad09e291..018b1efb 100644 --- a/starter/src/test/java/com/github/fonimus/ssh/shell/SshShellHelperTest.java +++ b/starter/src/test/java/com/github/fonimus/ssh/shell/SshShellHelperTest.java @@ -245,10 +245,12 @@ void table() { String headers = top + "│ col1 │ col2 │ col3 │\n"; String middle = "├──────────┼──────────┼──────────┤\n"; - String body = "│line1 col1│line1 col2│line1 col3│\n" + - "├──────────┼──────────┼──────────┤\n" + - "│line2 col1│line2 col2│line2 col3│\n" + - "└──────────┴──────────┴──────────┘\n"; + String body = """ + │line1 col1│line1 col2│line1 col3│ + ├──────────┼──────────┼──────────┤ + │line2 col1│line2 col2│line2 col3│ + └──────────┴──────────┴──────────┘ + """; SimpleTable.SimpleTableBuilder builder = SimpleTable.builder() .column("col1") .column("col2") diff --git a/starter/src/test/java/com/github/fonimus/ssh/shell/commands/ScriptCommandTest.java b/starter/src/test/java/com/github/fonimus/ssh/shell/commands/ScriptCommandTest.java index 5c4f518f..3f843603 100644 --- a/starter/src/test/java/com/github/fonimus/ssh/shell/commands/ScriptCommandTest.java +++ b/starter/src/test/java/com/github/fonimus/ssh/shell/commands/ScriptCommandTest.java @@ -56,7 +56,7 @@ void setUp() { cmd = new ScriptCommand(parser, sshHelper, new SshShellProperties()); ApplicationContext context = mock(ApplicationContext.class); cmd.setApplicationContext(context); - when(context.getBeanProvider(Shell.class)).thenReturn(new ObjectProvider() { + when(context.getBeanProvider(Shell.class)).thenReturn(new ObjectProvider<>() { @Override public Shell getObject(Object... objects) throws BeansException { return null; diff --git a/starter/src/test/java/com/github/fonimus/ssh/shell/conf/SshShellSecurityConfigurationTest.java b/starter/src/test/java/com/github/fonimus/ssh/shell/conf/SshShellSecurityConfigurationTest.java index b7499e03..0230ca1f 100644 --- a/starter/src/test/java/com/github/fonimus/ssh/shell/conf/SshShellSecurityConfigurationTest.java +++ b/starter/src/test/java/com/github/fonimus/ssh/shell/conf/SshShellSecurityConfigurationTest.java @@ -20,33 +20,28 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession; @Configuration @EnableWebSecurity -@EnableGlobalMethodSecurity(prePostEnabled = true) -public class SshShellSecurityConfigurationTest - extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests() - .requestMatchers(EndpointRequest.to("info")).permitAll() - .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR"); - } +@EnableMethodSecurity +@EnableJdbcHttpSession +public class SshShellSecurityConfigurationTest { @Bean - @Override - public AuthenticationManager authenticationManager() throws Exception { - return super.authenticationManager(); + public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authManager) throws Exception { + http.authorizeHttpRequests() + .requestMatchers(EndpointRequest.to("info")).permitAll() + .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR") + .and().authenticationManager(authManager); + return http.build(); } @Bean @@ -55,14 +50,21 @@ public PasswordEncoder passwordEncoder() { } @Bean - public UserDetailsService userDetailsService() { - InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); - PasswordEncoder encoder = passwordEncoder(); - manager.createUser(User.withUsername("user").password(encoder.encode("password")).roles("USER").build()); - manager.createUser(User.withUsername("actuator").password(encoder.encode("password")).roles("ACTUATOR").build - ()); - manager.createUser(User.withUsername("admin").password(encoder.encode("admin")).roles("ADMIN", "ACTUATOR") - .build()); - return manager; + public AuthenticationManager authManager(HttpSecurity http) throws Exception { + AuthenticationManagerBuilder authenticationManagerBuilder = + http.getSharedObject(AuthenticationManagerBuilder.class); + authenticationManagerBuilder.inMemoryAuthentication() + .withUser("user") + .password(passwordEncoder().encode("password")) + .roles("USER") + .and() + .withUser("actuator") + .password(passwordEncoder().encode("password")) + .roles("ACTUATOR") + .and() + .withUser("admin") + .password(passwordEncoder().encode("admin")) + .roles("ADMIN", "ACTUATOR"); + return authenticationManagerBuilder.build(); } } diff --git a/starter/src/test/java/com/github/fonimus/ssh/shell/conf/TaskServiceTest.java b/starter/src/test/java/com/github/fonimus/ssh/shell/conf/TaskServiceTest.java new file mode 100644 index 00000000..26f2644c --- /dev/null +++ b/starter/src/test/java/com/github/fonimus/ssh/shell/conf/TaskServiceTest.java @@ -0,0 +1,15 @@ +package com.github.fonimus.ssh.shell.conf; + +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +@EnableScheduling +public class TaskServiceTest { + + @Scheduled(cron = "0 0 0 * * *") + public void test() { + // test + } +} diff --git a/starter/src/test/java/com/github/fonimus/ssh/shell/postprocess/PrettyJsonPostProcessorTest.java b/starter/src/test/java/com/github/fonimus/ssh/shell/postprocess/PrettyJsonPostProcessorTest.java index ff230290..948f4c15 100644 --- a/starter/src/test/java/com/github/fonimus/ssh/shell/postprocess/PrettyJsonPostProcessorTest.java +++ b/starter/src/test/java/com/github/fonimus/ssh/shell/postprocess/PrettyJsonPostProcessorTest.java @@ -20,6 +20,8 @@ import com.github.fonimus.ssh.shell.postprocess.provided.PrettyJsonPostProcessor; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.actuate.health.Health; import java.util.Collections; @@ -51,12 +53,12 @@ void process() throws Exception { assertThrows(PostProcessorException.class, () -> processor.process(new NotSerializableObject("test"), null)); } - public class NotSerializableObject { + public static class NotSerializableObject { - private final String test; + public static final Logger LOGGER = LoggerFactory.getLogger(NotSerializableObject.class); public NotSerializableObject(String test) { - this.test = test; + LOGGER.info(test); } } }