Skip to content

Commit

Permalink
refactor: add plugin readiness check for API client generation (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
guqing authored Feb 19, 2025
1 parent 95ac80a commit c89830a
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 97 deletions.
118 changes: 27 additions & 91 deletions src/main/java/run/halo/gradle/openapi/OpenApiDocsGeneratorTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
Expand Down Expand Up @@ -54,11 +55,16 @@
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.jetbrains.annotations.NotNull;
import run.halo.gradle.docker.FrameConsumerResultCallback;
import run.halo.gradle.docker.OutputFrame;
import run.halo.gradle.docker.ToStringConsumer;
import run.halo.gradle.extension.HaloExtension;
import run.halo.gradle.extension.HaloPluginExtension;
import run.halo.gradle.model.Constant;
import run.halo.gradle.steps.HaloSiteOption;
import run.halo.gradle.steps.PluginClient;
import run.halo.gradle.steps.SetupHaloStep;
import run.halo.gradle.utils.Assert;
import run.halo.gradle.utils.FileUtils;
import run.halo.gradle.utils.HaloServerConfigure;
Expand Down Expand Up @@ -124,15 +130,10 @@ public void runRemoteCommand() throws Exception {
.trustStorePassword(trustStorePassword)
.waitTimeInSeconds(waitTimeInSeconds.get())
.build();

System.out.println("Start generating API documentation...");
groupedApiMappings.get().forEach((k, v) -> {
var url = joinUrl(getApiDocsUrl().get(), k);
ReadinessCheck.builder()
.dockerClient(dockerClient)
.containerId(this.containerId.get())
.endpoint(url)
.requestHeaders(requestHeaders.get())
.build()
.awaitReadiness();
apiDocGenerator.generateApiDocs(url, v);
});

Expand Down Expand Up @@ -177,14 +178,10 @@ private void prepareApiDocsServer(DockerClient dockerClient) {
.withLogs(true)
.exec(callback);

ReadinessCheck.builder()
.dockerClient(dockerClient)
.containerId(container.getId())
.endpoint(joinUrl(getApiDocsUrl().get(), "/actuator/health"))
.requestHeaders(requestHeaders.get())
.waitTimeInSeconds(180)
.build()
.awaitReadiness();
var siteOption = createHaloSiteOption();
waitForSetup(siteOption);
waitForPluginReady(siteOption);

} catch (Exception e) {
throw new GradleException("Failed to start Halo application", e);
}
Expand Down Expand Up @@ -213,7 +210,8 @@ public void generateApiDocs(String url, String fileName) {
boolean isYaml = url.toLowerCase(Locale.getDefault()).matches(".+[./]yaml(/.+)*");
try {
SSLContext sslContext = getCustomSslContext();
log.info("Generating OpenApi Docs..");

System.out.println("Generating OpenApi Docs for url: " + url);
HttpURLConnection connection = getHttpURLConnection(url, sslContext);

String response =
Expand Down Expand Up @@ -283,83 +281,21 @@ private String prettifyJson(String response) {
}
}

@Builder
record ReadinessCheck(DockerClient dockerClient, String containerId, String endpoint,
Map<String, String> requestHeaders,
Integer waitTimeInSeconds) {
private static final int MAX_HTTP_STATUS_CODE = 299;
private static final int INITIAL_DELAY = 0;
private static final int PERIOD = 1;
private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
private static final int TIMEOUT = 60;

public ReadinessCheck {
Assert.notNull(endpoint, "Endpoint must not be null");
Assert.notNull(dockerClient, "DockerClient must not be null");
Assert.notNull(containerId, "ContainerId must not be null");
if (requestHeaders == null) {
requestHeaders = Collections.emptyMap();
}
if (waitTimeInSeconds == null) {
waitTimeInSeconds = TIMEOUT;
}
}

private boolean containerIsRunning() {
var containerInfo = dockerClient.inspectContainerCmd(containerId).exec();
return Boolean.TRUE.equals(containerInfo.getState().getRunning());
}

public void awaitReadiness() {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private void waitForSetup(HaloSiteOption siteOption) {
new SetupHaloStep(siteOption).execute();
}

try {
CompletableFuture<Boolean> readinessFuture = new CompletableFuture<>();
ScheduledFuture<?> scheduledFuture = scheduler.scheduleAtFixedRate(() -> {
try {
if (!containerIsRunning()) {
readinessFuture.completeExceptionally(
new GradleException("Container is stopped unexpectedly"));
}
if (isReadiness()) {
readinessFuture.complete(true);
}
} catch (Exception e) {
readinessFuture.completeExceptionally(e);
}
}, INITIAL_DELAY, PERIOD, TIME_UNIT);

readinessFuture.whenComplete((result, throwable) -> {
if (throwable != null) {
log.error("Filed to start Halo application", throwable);
}
scheduledFuture.cancel(true);
});
readinessFuture.get(TIMEOUT, TIME_UNIT);
} catch (TimeoutException e) {
log.error("Timeout to wait for container readiness");
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
} finally {
scheduler.shutdown();
}
}
private void waitForPluginReady(HaloSiteOption siteOption) {
var pluginName = getPluginExtension().getPluginName();
var client = new PluginClient(pluginName, siteOption);
client.checkPluginState();
}

private boolean isReadiness() throws Exception {
URL url = new URL(endpoint);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
requestHeaders.forEach(connection::setRequestProperty);
try {
int responseCode = connection.getResponseCode();
log.trace("apiDocsUrl = {} status code = {}", url, responseCode);
return responseCode < MAX_HTTP_STATUS_CODE;
} catch (SocketException e) {
return false;
} finally {
connection.disconnect();
}
}
private HaloSiteOption createHaloSiteOption() {
var haloExt = getProject().getExtensions().getByType(HaloExtension.class);
var baseUri = URI.create(getApiDocsUrl().get());
return new HaloSiteOption(haloExt.getSuperAdminUsername(),
haloExt.getSuperAdminPassword(), baseUri);
}

private void setContainerCommandConfig(CreateContainerCmd containerCommand) {
Expand Down
12 changes: 7 additions & 5 deletions src/main/java/run/halo/gradle/steps/PluginClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ public class PluginClient {
private final String pluginName;
private final HttpClientFactory clientFactory;
private final HaloSiteOption siteOption;
private final HaloPluginExtension pluginExtension;
private final URI baseUri;

public PluginClient(Project project) {
var haloExt = project.getExtensions().getByType(HaloExtension.class);
this.pluginExtension = project.getExtensions().getByType(HaloPluginExtension.class);
this.pluginName = pluginExtension.getPluginName();
this.siteOption = HaloSiteOption.from(haloExt);
this(project.getExtensions().getByType(HaloPluginExtension.class).getPluginName(),
HaloSiteOption.from(project.getExtensions().getByType(HaloExtension.class)));
}

public PluginClient(String pluginName, HaloSiteOption siteOption) {
this.pluginName = pluginName;
this.siteOption = siteOption;
this.baseUri = siteOption.externalUrl();
this.clientFactory = new HttpClientFactory(siteOption);
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/run/halo/gradle/watch/WatchTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public class WatchTask extends DockerStartContainer {

public WatchTask() {
this.pluginClient = new PluginClient(getProject());
this.pluginExtension = pluginClient.getPluginExtension();
this.pluginExtension = getProject().getExtensions()
.getByType(HaloPluginExtension.class);
}

WatchExecutionParameters getParameters(List<String> buildArgs) {
Expand Down

0 comments on commit c89830a

Please sign in to comment.