Skip to content

Commit

Permalink
add gitlab and github token Auth support #14 (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg committed Feb 12, 2021
2 parents 55eac1d + e50316f commit 33fe34c
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 17 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/gradle-wrapper-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: "Validate Gradle Wrapper"
on: [push, pull_request]

jobs:
validation:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: gradle/[email protected]
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
- Support for gitlab public repo #13
### Added
- Support for GitLab, self-hosted and `gitlab.com` ([#18](https://github.com/diffplug/blowdryer/pull/18)).
- Support for private GitHub and GitLab script repositories via auth tokens ([#18](https://github.com/diffplug/blowdryer/pull/18)).

## [1.0.0] - 2020-01-09
Same as `0.2.0`, just committing to API back-compat from here.

## [0.2.0] - 2020-01-09
### Added
Expand Down
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ plugins {
id 'com.diffplug.blowdryer'
id 'com.diffplug.gradle.spotless'
id 'com.diffplug.spotless-changelog'
id 'com.github.ben-manes.versions'
id 'com.gradle.plugin-publish'
id 'com.jfrog.bintray'
}
Expand Down
25 changes: 22 additions & 3 deletions src/main/java/com/diffplug/blowdryer/Blowdryer.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
Expand Down Expand Up @@ -132,8 +133,9 @@ private static Map<String, String> loadPropertyFile(File file) throws IOExceptio

private static void download(String url, File dst) throws IOException {
OkHttpClient client = new OkHttpClient.Builder().build();
Request req = new Request.Builder().url(url).build();
try (Response response = client.newCall(req).execute()) {
Request.Builder req = new Request.Builder().url(url);
authPlugin.addAuthToken(url, req);
try (Response response = client.newCall(req.build()).execute()) {
if (!response.isSuccessful()) {
throw new IllegalArgumentException(url + "\nreceived http code " + response.code() + "\n" + response.body().string());
}
Expand Down Expand Up @@ -185,16 +187,26 @@ static void assertPluginNotSet(String errorMessage) {
}
}

static void assertPluginNotSet() {
assertPluginNotSet("You already initialized the `blowdryer` plugin, you can't do this twice.");
}

static void setResourcePluginNull() {
synchronized (Blowdryer.class) {
Blowdryer.plugin = null;
Blowdryer.authPlugin = authPluginNone;
}
}

static void setResourcePlugin(ResourcePlugin plugin) {
setResourcePlugin(plugin, null);
}

static void setResourcePlugin(ResourcePlugin plugin, AuthPlugin authPlugin) {
synchronized (Blowdryer.class) {
assertPluginNotSet("You already initialized the `blowdryer` plugin, you can't do this twice.");
assertPluginNotSet();
Blowdryer.plugin = plugin;
Blowdryer.authPlugin = authPlugin == null ? authPluginNone : authPlugin;
}
}

Expand All @@ -204,6 +216,13 @@ private static void assertInitialized() {
}
}

static interface AuthPlugin {
void addAuthToken(String url, Request.Builder builder) throws MalformedURLException;
}

private static final AuthPlugin authPluginNone = (url, builder) -> {};
private static AuthPlugin authPlugin = authPluginNone;

/** Returns the given resource as a File (as configured by {@link BlowdryerSetup}. */
public static File file(String resourcePath) {
synchronized (Blowdryer.class) {
Expand Down
110 changes: 100 additions & 10 deletions src/main/java/com/diffplug/blowdryer/BlowdryerSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@
import com.diffplug.common.base.Errors;
import groovy.lang.Closure;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Objects;
import java.util.function.Function;
import javax.annotation.Nullable;
import org.jetbrains.annotations.NotNull;

/** Configures where {@link Blowdryer#file(String)} downloads files from. */
public class BlowdryerSetup {
static final String NAME = "blowdryerSetup";

private static final String GITHUB_HOST = "raw.githubusercontent.com";
private static final String GITLAB_HOST = "gitlab.com";

private final File referenceDirectory;

/** Pass in the directory that will be used to resolve string arguments to devLocal. */
Expand Down Expand Up @@ -54,19 +61,94 @@ public enum GitAnchorType {
}

/** Sets the source where we will grab these scripts. */
public void github(String repoOrg, GitAnchorType anchorType, String anchor) {
assertNoLeadingOrTrailingSlash(repoOrg);
assertNoLeadingOrTrailingSlash(anchor);
String root = "https://raw.githubusercontent.com/" + repoOrg + "/" + anchor + "/" + repoSubfolder + "/";
Blowdryer.setResourcePlugin(resource -> root + resource);
public GitHub github(String repoOrg, GitAnchorType anchorType, String anchor) {
// anchorType isn't used right now, but makes it easier to read what "anchor" is
return new GitHub(repoOrg, anchor);
}

public class GitHub {
private String repoOrg;
private String anchor;
private @Nullable String authToken;

private GitHub(String repoOrg, String anchor) {
Blowdryer.assertPluginNotSet();
this.repoOrg = assertNoLeadingOrTrailingSlash(repoOrg);
this.anchor = assertNoLeadingOrTrailingSlash(anchor);
setGlobals();
}

public GitHub authToken(String authToken) {
this.authToken = authToken;
return setGlobals();
}

private GitHub setGlobals() {
Blowdryer.setResourcePluginNull();
String root = "https://" + GITHUB_HOST + "/" + repoOrg + "/" + anchor + "/";
Blowdryer.setResourcePlugin(resource -> root + getFullResourcePath(resource), authToken == null ? null : (url, builder) -> {
if (url.startsWith(root)) {
builder.addHeader("Authorization", "Bearer " + authToken);
}
});
return this;
}
}

/** Sets the source where we will grab these scripts. */
public void gitlab(String repoOrg, GitAnchorType anchorType, String anchor) {
assertNoLeadingOrTrailingSlash(repoOrg);
assertNoLeadingOrTrailingSlash(anchor);
String root = "https://gitlab.com/" + repoOrg + "/-/raw/" + anchor + "/" + repoSubfolder + "/";
Blowdryer.setResourcePlugin(resource -> root + resource);
public GitLab gitlab(String repoOrg, GitAnchorType anchorType, String anchor) {
// anchorType isn't used right now, but makes it easier to read what "anchor" is
return new GitLab(repoOrg, anchor);
}

public class GitLab {
private String repoOrg;
private String anchor;
private @Nullable String authToken;
private String protocol, host;

private GitLab(String repoOrg, String anchor) {
Blowdryer.assertPluginNotSet();
this.repoOrg = assertNoLeadingOrTrailingSlash(repoOrg);
this.anchor = assertNoLeadingOrTrailingSlash(anchor);
customDomainHttps(GITLAB_HOST);
}

public GitLab authToken(String authToken) {
this.authToken = authToken;
return setGlobals();
}

public GitLab customDomainHttp(String domain) {
return customProtocolAndDomain("http://", domain);
}

public GitLab customDomainHttps(String domain) {
return customProtocolAndDomain("https://", domain);
}

private GitLab customProtocolAndDomain(String protocol, String domain) {
this.protocol = protocol;
this.host = domain;
return setGlobals();
}

private GitLab setGlobals() {
Blowdryer.setResourcePluginNull();
String urlStart = protocol + host + "/api/v4/projects/" + encodeUrlPart(repoOrg) + "/repository/files/";
String urlEnd = "/raw?ref=" + encodeUrlPart(anchor);
Blowdryer.setResourcePlugin(resource -> urlStart + encodeUrlPart(getFullResourcePath(resource)) + urlEnd, authToken == null ? null : (url, builder) -> {
if (url.startsWith(urlStart)) {
builder.addHeader("Authorization", "Bearer " + authToken);
}
});
return this;
}
}

@NotNull
private String getFullResourcePath(String resource) {
return (repoSubfolder.isEmpty() ? "" : repoSubfolder + "/") + resource;
}

/** Sets the mapping from `file(String)` to `immutableUrl(String)`. */
Expand Down Expand Up @@ -108,4 +190,12 @@ private static String assertNoLeadingOrTrailingSlash(String input) {
}
return input;
}

private static String encodeUrlPart(String part) {
try {
return URLEncoder.encode(part, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("error encoding part", e);
}
}
}
73 changes: 73 additions & 0 deletions src/test/java/com/diffplug/blowdryer/BlowdryerPluginAuthTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2020 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.blowdryer;


import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.junit.Ignore;
import org.junit.Test;

@Ignore("has to be filled with prvate tokens and repos")
public class BlowdryerPluginAuthTest extends GradleHarness {

private void settingsGitlabAuth(String tag, String... extra) throws IOException {
write("settings.gradle",
"plugins { id 'com.diffplug.blowdryerSetup' }",
"blowdryerSetup { repoSubfolder(''); "
+ "gitlab('private/repo', 'tag', '" + tag + "').authToken('foobar');"
+ " }",
Arrays.stream(extra).collect(Collectors.joining("\n")));
}

private void settingsGithubAuth(String tag, String... extra) throws IOException {
write("settings.gradle",
"plugins { id 'com.diffplug.blowdryerSetup' }",
"blowdryerSetup { github('private/repo', 'tag', '" + tag + "').authToken('foobar');"
+ " }",
Arrays.stream(extra).collect(Collectors.joining("\n")));
}

@Test
public void githubAuthTag() throws IOException {
settingsGithubAuth("master");
write("build.gradle",
"apply plugin: 'com.diffplug.blowdryer'",
"assert 干.file('sample').text == 'a'");
gradleRunner().build();
}

@Test
public void gitlabAuthTag() throws IOException {
settingsGitlabAuth("init-test-for-auth");
write("build.gradle",
"apply plugin: 'com.diffplug.blowdryer'",
"assert 干.file('sample').text == 'a'");
gradleRunner().build();
}

/** Writes the given content to the given path. */
protected File write(String path, String... lines) throws IOException {
File file = file(path);
file.getParentFile().mkdirs();
Files.write(file.toPath(), Arrays.asList(lines), StandardCharsets.UTF_8);
return file;
}
}
71 changes: 69 additions & 2 deletions src/test/java/com/diffplug/blowdryer/BlowdryerPluginTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,21 @@ private void settingsGithub(String tag, String... extra) throws IOException {
private void settingsGitlab(String tag, String... extra) throws IOException {
write("settings.gradle",
"plugins { id 'com.diffplug.blowdryerSetup' }",
"blowdryerSetup { gitlab('vgropp/blowdryer-test', 'tag', '" + tag + "') }",
"blowdryerSetup { gitlab('diffplug/blowdryer', 'tag', '" + tag + "') }",
Arrays.stream(extra).collect(Collectors.joining("\n")));
}

private void settingsCustomGitlab(String tag, String... extra) throws IOException {
write("settings.gradle",
"plugins { id 'com.diffplug.blowdryerSetup' }",
"blowdryerSetup { gitlab('diffplug/blowdryer', 'tag', '" + tag + "').customDomainHttps('gitlab.com') }",
Arrays.stream(extra).collect(Collectors.joining("\n")));
}

private void settingsGitlabRootFolder(String tag, String... extra) throws IOException {
write("settings.gradle",
"plugins { id 'com.diffplug.blowdryerSetup' }",
"blowdryerSetup { repoSubfolder(''); gitlab('diffplug/blowdryer', 'tag', '" + tag + "') }",
Arrays.stream(extra).collect(Collectors.joining("\n")));
}

Expand Down Expand Up @@ -89,6 +103,58 @@ public void gitlabTag() throws IOException {
gradleRunner().buildAndFail();
}

@Test
public void customGitlabTag() throws IOException {
settingsCustomGitlab("test/2/a");
write("build.gradle",
"apply plugin: 'com.diffplug.blowdryer'",
"assert 干.file('sample').text == 'a'",
"assert 干.prop('sample', 'name') == 'test'",
"assert 干.prop('sample', 'ver_spotless') == '1.2.0'");
gradleRunner().build();

settingsCustomGitlab("test/2/b");
write("build.gradle",
"apply plugin: 'com.diffplug.blowdryer'",
"assert 干.file('sample').text == 'b'",
"assert 干.prop('sample', 'name') == 'testB'",
"assert 干.prop('sample', 'group') == 'com.diffplug.gradleB'");
gradleRunner().build();

// double-check that failures do fail
settingsCustomGitlab("test/2/b");
write("build.gradle",
"plugins { id 'com.diffplug.blowdryer' }",
"assert Blowdryer.file('sample').text == 'a'");
gradleRunner().buildAndFail();
}

@Test
public void rootfolderGitlabTag() throws IOException {
settingsGitlabRootFolder("test/2/a");
write("build.gradle",
"apply plugin: 'com.diffplug.blowdryer'",
"assert 干.file('src/main/resources/sample').text == 'a'",
"assert 干.prop('src/main/resources/sample', 'name') == 'test'",
"assert 干.prop('src/main/resources/sample', 'ver_spotless') == '1.2.0'");
gradleRunner().build();

settingsGitlabRootFolder("test/2/b");
write("build.gradle",
"apply plugin: 'com.diffplug.blowdryer'",
"assert 干.file('src/main/resources/sample').text == 'b'",
"assert 干.prop('src/main/resources/sample', 'name') == 'testB'",
"assert 干.prop('src/main/resources/sample', 'group') == 'com.diffplug.gradleB'");
gradleRunner().build();

// double-check that failures do fail
settingsGitlabRootFolder("test/2/b");
write("build.gradle",
"plugins { id 'com.diffplug.blowdryer' }",
"assert Blowdryer.file('src/main/resources/sample').text == 'a'");
gradleRunner().buildAndFail();
}

@Test
public void devLocal() throws IOException {
write("../blowdryer-script/src/main/resources/sample", "c");
Expand All @@ -100,7 +166,8 @@ public void devLocal() throws IOException {
"blowdryerSetup { devLocal('../blowdryer-script') }");
write("build.gradle",
"apply plugin: 'com.diffplug.blowdryer'",
"assert 干.file('sample').text == 'c\\n'",
// .replace('\\r', '') fixes test on windows
"assert 干.file('sample').text.replace('\\r', '') == 'c\\n'",
"assert 干.prop('sample', 'name') == 'test'",
"assert 干.prop('sample', 'group') == 'com.diffplug.gradle'");
gradleRunner().build();
Expand Down

0 comments on commit 33fe34c

Please sign in to comment.