-
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added github subcommand to download latest release asset (#397)
- Loading branch information
Showing
9 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
### | ||
@org = kettingpowered | ||
@repo = kettinglauncher | ||
GET https://api.github.com/repos/{{org}}/{{repo}}/releases/latest | ||
Accept: Accept: application/vnd.github+json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
src/main/java/me/itzg/helpers/github/DownloadLatestAssetCommand.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package me.itzg.helpers.github; | ||
|
||
import java.nio.file.Path; | ||
import java.util.concurrent.Callable; | ||
import java.util.regex.Pattern; | ||
import lombok.extern.slf4j.Slf4j; | ||
import me.itzg.helpers.errors.InvalidParameterException; | ||
import me.itzg.helpers.http.Fetch; | ||
import me.itzg.helpers.http.SharedFetch; | ||
import me.itzg.helpers.http.SharedFetchArgs; | ||
import picocli.CommandLine; | ||
import picocli.CommandLine.ArgGroup; | ||
import picocli.CommandLine.Command; | ||
import picocli.CommandLine.Option; | ||
import picocli.CommandLine.Parameters; | ||
|
||
@Command(name = "download-latest-asset", | ||
description = "From the latest release, downloads the first matching asset, and outputs the downloaded filename" | ||
) | ||
@Slf4j | ||
public class DownloadLatestAssetCommand implements Callable<Integer> { | ||
|
||
String organization; | ||
String repo; | ||
|
||
@Option(names = "--name-pattern") | ||
Pattern namePattern; | ||
|
||
@Option(names = "--output-directory", defaultValue = ".") | ||
Path outputDirectory; | ||
|
||
@Option(names = "--api-base-url", defaultValue = GithubClient.DEFAULT_API_BASE_URL) | ||
String apiBaseUrl; | ||
|
||
@Parameters(arity = "1", paramLabel = "org/repo") | ||
public void setOrgRepo(String input) { | ||
final String[] parts = input.split("/", 2); | ||
if (parts.length != 2) { | ||
throw new InvalidParameterException("org/repo needs to be slash delimited"); | ||
} | ||
this.organization = parts[0]; | ||
this.repo = parts[1]; | ||
} | ||
|
||
@ArgGroup(exclusive = false) | ||
SharedFetchArgs sharedFetchArgs = new SharedFetchArgs(); | ||
|
||
@Override | ||
public Integer call() throws Exception { | ||
|
||
try (SharedFetch sharedFetch = Fetch.sharedFetch("github download-latest-asset", sharedFetchArgs.options())) { | ||
|
||
final GithubClient client = new GithubClient(sharedFetch, apiBaseUrl); | ||
final Path result = client.downloadLatestAsset(organization, repo, namePattern, outputDirectory) | ||
.block(); | ||
|
||
if (result == null) { | ||
log.error("Unable to locate latest, matching asset from {}/{}", organization, repo); | ||
return CommandLine.ExitCode.USAGE; | ||
} | ||
else { | ||
System.out.println(result); | ||
} | ||
} | ||
|
||
return CommandLine.ExitCode.OK; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package me.itzg.helpers.github; | ||
|
||
import java.net.URI; | ||
import java.nio.file.Path; | ||
import java.util.Collections; | ||
import java.util.regex.Pattern; | ||
import java.util.stream.Collectors; | ||
import lombok.extern.slf4j.Slf4j; | ||
import me.itzg.helpers.github.model.Asset; | ||
import me.itzg.helpers.github.model.Release; | ||
import me.itzg.helpers.http.FailedRequestException; | ||
import me.itzg.helpers.http.SharedFetch; | ||
import me.itzg.helpers.http.UriBuilder; | ||
import org.jetbrains.annotations.Nullable; | ||
import reactor.core.publisher.Mono; | ||
|
||
@Slf4j | ||
public class GithubClient { | ||
|
||
public static final String DEFAULT_API_BASE_URL = "https://api.github.com"; | ||
|
||
private final SharedFetch sharedFetch; | ||
private final UriBuilder uriBuilder; | ||
|
||
public GithubClient(SharedFetch sharedFetch, String apiBaseUrl) { | ||
this.sharedFetch = sharedFetch; | ||
this.uriBuilder = UriBuilder.withBaseUrl(apiBaseUrl); | ||
} | ||
|
||
public Mono<Path> downloadLatestAsset(String org, String repo, @Nullable Pattern namePattern, Path outputDirectory) { | ||
return sharedFetch.fetch( | ||
uriBuilder.resolve("/repos/{org}/{repo}/releases/latest", org, repo) | ||
) | ||
.acceptContentTypes(Collections.singletonList("application/vnd.github+json")) | ||
.toObject(Release.class) | ||
.assemble() | ||
.onErrorResume(throwable -> throwable instanceof FailedRequestException && ((FailedRequestException) throwable).getStatusCode() == 404, | ||
throwable -> Mono.empty() | ||
) | ||
.flatMap(release -> { | ||
if (log.isDebugEnabled()) { | ||
log.debug("Assets in latest release '{}': {}", | ||
release.getName(), | ||
release.getAssets().stream() | ||
.map(Asset::getName) | ||
.collect(Collectors.toList()) | ||
); | ||
} | ||
|
||
return Mono.justOrEmpty( | ||
release.getAssets().stream() | ||
.filter(asset -> namePattern == null || namePattern.matcher(asset.getName()).matches()) | ||
.findFirst() | ||
) | ||
.flatMap(asset -> | ||
sharedFetch.fetch(URI.create(asset.getBrowserDownloadUrl())) | ||
.toDirectory(outputDirectory) | ||
.skipExisting(true) | ||
.assemble() | ||
); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package me.itzg.helpers.github; | ||
|
||
import picocli.CommandLine.Command; | ||
|
||
@Command(name = "github", | ||
subcommands = { | ||
DownloadLatestAssetCommand.class | ||
} | ||
) | ||
public class GithubCommands { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package me.itzg.helpers.github.model; | ||
|
||
import com.fasterxml.jackson.databind.PropertyNamingStrategies; | ||
import com.fasterxml.jackson.databind.annotation.JsonNaming; | ||
import lombok.Data; | ||
|
||
@Data | ||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) | ||
public class Asset { | ||
String name; | ||
String browserDownloadUrl; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package me.itzg.helpers.github.model; | ||
|
||
import com.fasterxml.jackson.databind.PropertyNamingStrategies; | ||
import com.fasterxml.jackson.databind.annotation.JsonNaming; | ||
import java.util.List; | ||
import lombok.Data; | ||
|
||
@Data | ||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) | ||
public class Release { | ||
String name; | ||
List<Asset> assets; | ||
} |
68 changes: 68 additions & 0 deletions
68
src/test/java/me/itzg/helpers/github/DownloadLatestAssetCommandTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package me.itzg.helpers.github; | ||
|
||
import static com.github.tomakehurst.wiremock.client.WireMock.*; | ||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer; | ||
import com.github.tomakehurst.wiremock.junit5.WireMockExtension; | ||
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; | ||
import java.nio.file.Path; | ||
import org.apache.commons.lang3.RandomStringUtils; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
import org.junit.jupiter.api.io.TempDir; | ||
import picocli.CommandLine; | ||
|
||
class DownloadLatestAssetCommandTest { | ||
@RegisterExtension | ||
static WireMockExtension wm = WireMockExtension.newInstance() | ||
.options(wireMockConfig() | ||
.dynamicPort() | ||
.extensions(new ResponseTemplateTransformer(false)) | ||
) | ||
.configureStaticDsl(true) | ||
.build(); | ||
|
||
@Test | ||
void usingNamePattern(@TempDir Path tempDir, WireMockRuntimeInfo wmInfo) { | ||
final String filename = RandomStringUtils.randomAlphabetic(10)+".jar"; | ||
final String fileContent = RandomStringUtils.randomAlphabetic(20); | ||
|
||
stubFor(get("/repos/org/repo/releases/latest") | ||
.willReturn(ok() | ||
.withHeader("Content-Type", "application/json") | ||
.withBodyFile("github/release-with-sources-jar.json") | ||
.withTransformers("response-template") | ||
.withTransformerParameter("filename", filename) | ||
) | ||
); | ||
stubFor(head(urlPathEqualTo("/download/"+filename)) | ||
.willReturn( | ||
ok() | ||
.withHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", filename)) | ||
) | ||
); | ||
stubFor(get("/download/"+filename) | ||
.willReturn( | ||
ok() | ||
.withHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", filename)) | ||
.withBody(fileContent) | ||
) | ||
); | ||
|
||
final int exitCode = new CommandLine(new DownloadLatestAssetCommand()) | ||
.execute( | ||
"--api-base-url", wmInfo.getHttpBaseUrl(), | ||
"--name-pattern", "app-.+?(?<!-sources)\\.jar", | ||
"--output-directory", tempDir.toString(), | ||
"org/repo" | ||
); | ||
|
||
assertThat(exitCode).isEqualTo(0); | ||
|
||
assertThat(tempDir.resolve(filename)) | ||
.exists() | ||
.content().isEqualTo(fileContent); | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/test/resources/__files/github/release-with-sources-jar.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"name": "app v1.3.0", | ||
"assets": [ | ||
{ | ||
"name": "app-1.3.0-sources.jar", | ||
"browser_download_url": "{{request.baseUrl}}/invalid.jar" | ||
}, | ||
{ | ||
"name": "app-1.3.0.jar", | ||
"browser_download_url": "{{request.baseUrl}}/download/{{parameters.filename}}" | ||
} | ||
] | ||
} |