Skip to content

Commit 62debd7

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 62debd7

File tree

23 files changed

+1291
-35
lines changed

23 files changed

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

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

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,38 @@
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.RequestScoped;
8+
9+
import org.jboss.jandex.AnnotationInstance;
10+
import org.jboss.jandex.AnnotationValue;
11+
import org.jboss.jandex.DotName;
12+
import org.jboss.jandex.IndexView;
13+
14+
import com.jcraft.jsch.Session;
15+
16+
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
17+
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
18+
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
19+
import io.quarkus.deployment.annotations.BuildProducer;
320
import io.quarkus.deployment.annotations.BuildStep;
21+
import io.quarkus.deployment.annotations.ExecutionTime;
22+
import io.quarkus.deployment.annotations.Record;
423
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
524
import io.quarkus.deployment.builditem.FeatureBuildItem;
25+
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
626
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
727
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
28+
import io.quarkus.jsch.JSchSessions;
29+
import io.quarkus.jsch.JSchSession;
30+
import io.quarkus.jsch.runtime.JSchSessionRecorder;
831
import io.quarkus.jsch.runtime.PortWatcherRunTime;
932

1033
class JSchProcessor {
1134

12-
private static final String FEATURE = "jsch";
35+
public static final String FEATURE = "jsch";
1336

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

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)