Skip to content

Commit 1c4508b

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 1c4508b

File tree

24 files changed

+1839
-37
lines changed

24 files changed

+1839
-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: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.quarkus.jsch.deployment;
2+
3+
import io.quarkus.runtime.annotations.ConfigDocSection;
4+
import io.quarkus.runtime.annotations.ConfigGroup;
5+
import io.quarkus.runtime.annotations.ConfigPhase;
6+
import io.quarkus.runtime.annotations.ConfigRoot;
7+
import io.smallrye.config.ConfigMapping;
8+
import io.smallrye.config.WithDefault;
9+
10+
@ConfigMapping(prefix = "quarkus.jsch")
11+
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
12+
public interface JSchBuildTimeConfig {
13+
14+
/**
15+
* The JSch DevService configuration.
16+
*/
17+
@ConfigDocSection
18+
JSchDevServiceConfig devservices();
19+
20+
@ConfigGroup
21+
public interface JSchDevServiceConfig {
22+
23+
/**
24+
* Enable the JSch DevService.
25+
*/
26+
@WithDefault("true")
27+
boolean enabled();
28+
29+
/**
30+
* The image to use for the JSch DevService.
31+
*/
32+
@WithDefault("linuxserver/openssh-server")
33+
String image();
34+
35+
/**
36+
* The port to use for the JSch DevService.
37+
*/
38+
@WithDefault("2222")
39+
int port();
40+
41+
/**
42+
* The username to use for the JSch DevService.
43+
*/
44+
@WithDefault("quarkus")
45+
String username();
46+
47+
/**
48+
* The password to use for the JSch DevService.
49+
*/
50+
@WithDefault("quarkus")
51+
String password();
52+
53+
/**
54+
* Whether to reuse the container for the JSch DevService.
55+
*/
56+
@WithDefault("false")
57+
boolean reuse();
58+
59+
}
60+
}
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)