Skip to content

Commit

Permalink
Add developer joy !
Browse files Browse the repository at this point in the history
* DevService
* Auto configure jsch logs
* JSchSession annotation:
   * configurable by properties
   * named sessions
   * auto connect/disconnect
  • Loading branch information
ggrebert committed Oct 25, 2024
1 parent b389640 commit 3326cca
Show file tree
Hide file tree
Showing 27 changed files with 1,904 additions and 37 deletions.
14 changes: 14 additions & 0 deletions deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-deployment</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.jsch.deployment;

import io.quarkus.runtime.annotations.ConfigDocSection;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.smallrye.config.ConfigMapping;

@ConfigMapping(prefix = "quarkus.jsch")
@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
public interface JSchBuildTimeConfig {

/**
* The JSch DevService configuration.
*/
@ConfigDocSection
JSchDevServiceConfig devservices();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.quarkus.jsch.deployment;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.smallrye.config.WithDefault;

@ConfigGroup
public interface JSchDevServiceConfig {

/**
* Enable the JSch DevService.
*/
@WithDefault("true")
boolean enabled();

/**
* The image to use for the JSch DevService.
*/
@WithDefault("linuxserver/openssh-server")
String image();

/**
* The port to use for the JSch DevService.
*/
@WithDefault("2222")
int port();

/**
* The username to use for the JSch DevService.
*/
@WithDefault("quarkus")
String username();

/**
* The password to use for the JSch DevService.
*/
@WithDefault("quarkus")
String password();

/**
* Whether to reuse the container for the JSch DevService.
*/
@WithDefault("false")
boolean reuse();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.quarkus.jsch.deployment;

import java.util.Map;

import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;

import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.BuildSteps;
import io.quarkus.deployment.builditem.DevServicesResultBuildItem;
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;

@BuildSteps(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class)
public class JSchDevServiceProcessor {

@BuildStep
DevServicesResultBuildItem startDevServices(JSchBuildTimeConfig config) {
if (!config.devservices().enabled()) {
return null;
}

DockerImageName dockerImageName = DockerImageName.parse(config.devservices().image());
GenericContainer container = new GenericContainer<>(dockerImageName)
.withEnv("USER_NAME", config.devservices().username())
.withEnv("USER_PASSWORD", config.devservices().password())
.withEnv("PASSWORD_ACCESS", "true")
.withExposedPorts(config.devservices().port())
.withReuse(config.devservices().reuse());

container.start();

Map<String, String> configOverrides = Map.of(
"quarkus.jsch.session.host", container.getHost(),
"quarkus.jsch.session.port", container.getMappedPort(config.devservices().port()).toString(),
"quarkus.jsch.session.username", config.devservices().username(),
"quarkus.jsch.session.password", config.devservices().password(),
"quarkus.jsch.session.config.StrictHostKeyChecking", "no");

return new DevServicesResultBuildItem.RunningDevService(JSchProcessor.FEATURE, container.getContainerId(),
container::close, configOverrides)
.toBuildItem();
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
package io.quarkus.jsch.deployment;

import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.RequestScoped;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;

import com.jcraft.jsch.Session;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
import io.quarkus.jsch.JSchSession;
import io.quarkus.jsch.JSchSessions;
import io.quarkus.jsch.runtime.JSchSessionRecorder;
import io.quarkus.jsch.runtime.PortWatcherRunTime;

class JSchProcessor {

private static final String FEATURE = "jsch";
public static final String FEATURE = "jsch";

@BuildStep
FeatureBuildItem feature() {
Expand Down Expand Up @@ -131,4 +155,65 @@ ReflectiveClassBuildItem reflection() {
"com.jcraft.jsch.UserAuthPublicKey")
.fields().methods().build();
}

@BuildStep
void registerAdditionalBeans(BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
additionalBeans.produce(AdditionalBeanBuildItem.builder()
.addBeanClass(JSchSessions.class)
.setUnremovable()
.setDefaultScope(DotName.createSimple(Dependent.class))
.build());

additionalBeans.produce(AdditionalBeanBuildItem.builder()
.addBeanClass(JSchSession.class)
.build());
}

@BuildStep
void produceSessions(
BuildProducer<JSchSessionBuildItem> jschSessionBuildItemBuildProducer,
BeanArchiveIndexBuildItem indexBuildItem) {
IndexView index = indexBuildItem.getIndex();
Collection<AnnotationInstance> jschSessionAnnotations = index.getAnnotations(JSchSession.class);

if (jschSessionAnnotations.isEmpty()) {
// No @JschSession annotations found
return;
}

for (AnnotationInstance annotation : jschSessionAnnotations) {
AnnotationValue value = annotation.value();
String name = value != null ? value.asString() : JSchSession.DEFAULT_SESSION_NAME;
jschSessionBuildItemBuildProducer.produce(new JSchSessionBuildItem(name));
}
}

@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
void sessionRecorder(JSchSessionRecorder recorder,
List<JSchSessionBuildItem> sessionBuildItems,
ShutdownContextBuildItem shutdown,
BuildProducer<SyntheticBeanBuildItem> syntheticBeans) {
if (sessionBuildItems.isEmpty()) {
return;
}

for (JSchSessionBuildItem sessionBuildItem : sessionBuildItems) {
// create session
Supplier<Session> sessionSupplier = recorder.jschSessionSupplier(sessionBuildItem.name());
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
.configure(Session.class)
.scope(RequestScoped.class)
.setRuntimeInit()
.unremovable()
.supplier(sessionSupplier);

configurator.addQualifier()
.annotation(JSchSession.class)
.addValue("value", sessionBuildItem.name())
.done();

syntheticBeans.produce(configurator.done());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.quarkus.jsch.deployment;

import io.quarkus.builder.item.MultiBuildItem;

/**
* A build item that registers a JSch session created by annotation.
*/
public final class JSchSessionBuildItem extends MultiBuildItem {

private final String name;

public JSchSessionBuildItem(String name) {
this.name = name;
}

public String name() {
return name;
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}

if (!(obj instanceof JSchSessionBuildItem)) {
return false;
}

JSchSessionBuildItem other = (JSchSessionBuildItem) obj;
return name.equals(other.name);
}

@Override
public int hashCode() {
return name.hashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import io.quarkus.test.QuarkusDevModeTest;

public class JschDevModeTest {
public class JSchDevModeTest {

// Start hot reload (DevMode) test with your extension loaded
@RegisterExtension
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import io.quarkus.test.QuarkusUnitTest;

public class JschTest {
public class JSchTest {

// Start unit test with your extension loaded
@RegisterExtension
Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/includes/attributes.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
:quarkus-version: 3.14.4
:quarkus-version: 3.15.1
:quarkus-jsch-version: 3.0.11

:quarkus-org-url: https://github.com/quarkusio
Expand Down
Loading

0 comments on commit 3326cca

Please sign in to comment.