Skip to content

Commit 02ab602

Browse files
committed
Add developer joy !
* DevService * Auto configure jsch logs * JSchSession annotation: * configurable by properties * named sessions * auto connect/disconnect
1 parent b389640 commit 02ab602

File tree

27 files changed

+1904
-37
lines changed

27 files changed

+1904
-37
lines changed

deployment/pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@
2323
<artifactId>quarkus-junit5-internal</artifactId>
2424
<scope>test</scope>
2525
</dependency>
26+
<dependency>
27+
<groupId>io.quarkus</groupId>
28+
<artifactId>quarkus-devservices-deployment</artifactId>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.testcontainers</groupId>
32+
<artifactId>testcontainers</artifactId>
33+
<exclusions>
34+
<exclusion>
35+
<groupId>junit</groupId>
36+
<artifactId>junit</artifactId>
37+
</exclusion>
38+
</exclusions>
39+
</dependency>
2640
</dependencies>
2741
<build>
2842
<plugins>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.quarkus.jsch.deployment;
2+
3+
import io.quarkus.runtime.annotations.ConfigDocSection;
4+
import io.quarkus.runtime.annotations.ConfigPhase;
5+
import io.quarkus.runtime.annotations.ConfigRoot;
6+
import io.smallrye.config.ConfigMapping;
7+
8+
@ConfigMapping(prefix = "quarkus.jsch")
9+
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
10+
public interface JSchBuildTimeConfig {
11+
12+
/**
13+
* The JSch DevService configuration.
14+
*/
15+
@ConfigDocSection
16+
JSchDevServiceConfig devservices();
17+
18+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.quarkus.jsch.deployment;
2+
3+
import io.quarkus.runtime.annotations.ConfigGroup;
4+
import io.smallrye.config.WithDefault;
5+
6+
@ConfigGroup
7+
public interface JSchDevServiceConfig {
8+
9+
/**
10+
* Enable the JSch DevService.
11+
*/
12+
@WithDefault("true")
13+
boolean enabled();
14+
15+
/**
16+
* The image to use for the JSch DevService.
17+
*/
18+
@WithDefault("linuxserver/openssh-server")
19+
String image();
20+
21+
/**
22+
* The port to use for the JSch DevService.
23+
*/
24+
@WithDefault("2222")
25+
int port();
26+
27+
/**
28+
* The username to use for the JSch DevService.
29+
*/
30+
@WithDefault("quarkus")
31+
String username();
32+
33+
/**
34+
* The password to use for the JSch DevService.
35+
*/
36+
@WithDefault("quarkus")
37+
String password();
38+
39+
/**
40+
* Whether to reuse the container for the JSch DevService.
41+
*/
42+
@WithDefault("false")
43+
boolean reuse();
44+
45+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.quarkus.jsch.deployment;
2+
3+
import java.util.Map;
4+
5+
import org.testcontainers.containers.GenericContainer;
6+
import org.testcontainers.utility.DockerImageName;
7+
8+
import io.quarkus.deployment.IsNormal;
9+
import io.quarkus.deployment.annotations.BuildStep;
10+
import io.quarkus.deployment.annotations.BuildSteps;
11+
import io.quarkus.deployment.builditem.DevServicesResultBuildItem;
12+
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
13+
14+
@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class)
15+
public class JSchDevServiceProcessor {
16+
17+
@BuildStep
18+
DevServicesResultBuildItem startDevServices(JSchBuildTimeConfig config) {
19+
if (!config.devservices().enabled()) {
20+
return null;
21+
}
22+
23+
DockerImageName dockerImageName = DockerImageName.parse(config.devservices().image());
24+
GenericContainer container = new GenericContainer<>(dockerImageName)
25+
.withEnv("USER_NAME", config.devservices().username())
26+
.withEnv("USER_PASSWORD", config.devservices().password())
27+
.withEnv("PASSWORD_ACCESS", "true")
28+
.withExposedPorts(config.devservices().port())
29+
.withReuse(config.devservices().reuse());
30+
31+
container.start();
32+
33+
Map<String, String> configOverrides = Map.of(
34+
"quarkus.jsch.session.host", container.getHost(),
35+
"quarkus.jsch.session.port", container.getMappedPort(config.devservices().port()).toString(),
36+
"quarkus.jsch.session.username", config.devservices().username(),
37+
"quarkus.jsch.session.password", config.devservices().password(),
38+
"quarkus.jsch.session.config.StrictHostKeyChecking", "no");
39+
40+
return new DevServicesResultBuildItem.RunningDevService(JSchProcessor.FEATURE, container.getContainerId(),
41+
container::close, configOverrides)
42+
.toBuildItem();
43+
}
44+
}

deployment/src/main/java/io/quarkus/jsch/deployment/JSchProcessor.java

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
package io.quarkus.jsch.deployment;
22

3+
import java.util.Collection;
4+
import java.util.List;
5+
import java.util.function.Supplier;
6+
7+
import jakarta.enterprise.context.Dependent;
8+
import jakarta.enterprise.context.RequestScoped;
9+
10+
import org.jboss.jandex.AnnotationInstance;
11+
import org.jboss.jandex.AnnotationValue;
12+
import org.jboss.jandex.DotName;
13+
import org.jboss.jandex.IndexView;
14+
15+
import com.jcraft.jsch.Session;
16+
17+
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
18+
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
19+
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
20+
import io.quarkus.deployment.annotations.BuildProducer;
321
import io.quarkus.deployment.annotations.BuildStep;
22+
import io.quarkus.deployment.annotations.ExecutionTime;
23+
import io.quarkus.deployment.annotations.Record;
424
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
525
import io.quarkus.deployment.builditem.FeatureBuildItem;
26+
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
627
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
728
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
29+
import io.quarkus.jsch.JSchSession;
30+
import io.quarkus.jsch.JSchSessions;
31+
import io.quarkus.jsch.runtime.JSchSessionRecorder;
832
import io.quarkus.jsch.runtime.PortWatcherRunTime;
933

1034
class JSchProcessor {
1135

12-
private static final String FEATURE = "jsch";
36+
public static final String FEATURE = "jsch";
1337

1438
@BuildStep
1539
FeatureBuildItem feature() {
@@ -131,4 +155,65 @@ ReflectiveClassBuildItem reflection() {
131155
"com.jcraft.jsch.UserAuthPublicKey")
132156
.fields().methods().build();
133157
}
158+
159+
@BuildStep
160+
void registerAdditionalBeans(BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
161+
additionalBeans.produce(AdditionalBeanBuildItem.builder()
162+
.addBeanClass(JSchSessions.class)
163+
.setUnremovable()
164+
.setDefaultScope(DotName.createSimple(Dependent.class))
165+
.build());
166+
167+
additionalBeans.produce(AdditionalBeanBuildItem.builder()
168+
.addBeanClass(JSchSession.class)
169+
.build());
170+
}
171+
172+
@BuildStep
173+
void produceSessions(
174+
BuildProducer<JSchSessionBuildItem> jschSessionBuildItemBuildProducer,
175+
BeanArchiveIndexBuildItem indexBuildItem) {
176+
IndexView index = indexBuildItem.getIndex();
177+
Collection<AnnotationInstance> jschSessionAnnotations = index.getAnnotations(JSchSession.class);
178+
179+
if (jschSessionAnnotations.isEmpty()) {
180+
// No @JschSession annotations found
181+
return;
182+
}
183+
184+
for (AnnotationInstance annotation : jschSessionAnnotations) {
185+
AnnotationValue value = annotation.value();
186+
String name = value != null ? value.asString() : JSchSession.DEFAULT_SESSION_NAME;
187+
jschSessionBuildItemBuildProducer.produce(new JSchSessionBuildItem(name));
188+
}
189+
}
190+
191+
@Record(ExecutionTime.RUNTIME_INIT)
192+
@BuildStep
193+
void sessionRecorder(JSchSessionRecorder recorder,
194+
List<JSchSessionBuildItem> sessionBuildItems,
195+
ShutdownContextBuildItem shutdown,
196+
BuildProducer<SyntheticBeanBuildItem> syntheticBeans) {
197+
if (sessionBuildItems.isEmpty()) {
198+
return;
199+
}
200+
201+
for (JSchSessionBuildItem sessionBuildItem : sessionBuildItems) {
202+
// create session
203+
Supplier<Session> sessionSupplier = recorder.jschSessionSupplier(sessionBuildItem.name());
204+
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
205+
.configure(Session.class)
206+
.scope(RequestScoped.class)
207+
.setRuntimeInit()
208+
.unremovable()
209+
.supplier(sessionSupplier);
210+
211+
configurator.addQualifier()
212+
.annotation(JSchSession.class)
213+
.addValue("value", sessionBuildItem.name())
214+
.done();
215+
216+
syntheticBeans.produce(configurator.done());
217+
}
218+
}
134219
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.quarkus.jsch.deployment;
2+
3+
import io.quarkus.builder.item.MultiBuildItem;
4+
5+
/**
6+
* A build item that registers a JSch session created by annotation.
7+
*/
8+
public final class JSchSessionBuildItem extends MultiBuildItem {
9+
10+
private final String name;
11+
12+
public JSchSessionBuildItem(String name) {
13+
this.name = name;
14+
}
15+
16+
public String name() {
17+
return name;
18+
}
19+
20+
@Override
21+
public boolean equals(Object obj) {
22+
if (obj == this) {
23+
return true;
24+
}
25+
26+
if (!(obj instanceof JSchSessionBuildItem)) {
27+
return false;
28+
}
29+
30+
JSchSessionBuildItem other = (JSchSessionBuildItem) obj;
31+
return name.equals(other.name);
32+
}
33+
34+
@Override
35+
public int hashCode() {
36+
return name.hashCode();
37+
}
38+
}

deployment/src/test/java/io/quarkiverse/jsch/test/JschDevModeTest.java renamed to deployment/src/test/java/io/quarkiverse/jsch/test/JSchDevModeTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import io.quarkus.test.QuarkusDevModeTest;
1010

11-
public class JschDevModeTest {
11+
public class JSchDevModeTest {
1212

1313
// Start hot reload (DevMode) test with your extension loaded
1414
@RegisterExtension

deployment/src/test/java/io/quarkiverse/jsch/test/JschTest.java renamed to deployment/src/test/java/io/quarkiverse/jsch/test/JSchTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import io.quarkus.test.QuarkusUnitTest;
1010

11-
public class JschTest {
11+
public class JSchTest {
1212

1313
// Start unit test with your extension loaded
1414
@RegisterExtension

docs/modules/ROOT/pages/includes/attributes.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
:quarkus-version: 3.14.4
1+
:quarkus-version: 3.15.1
22
:quarkus-jsch-version: 3.0.11
33

44
:quarkus-org-url: https://github.com/quarkusio

0 commit comments

Comments
 (0)