Add ability to save report to local folder

Updated the plugin to receive the "protocol"-like tag in the destination, so that you can choose whether to upload to GCS or just save it locally.

Possibly we might expand this in the future, but for now the goal was to allow saving our "internal" builds locally until we find a secure way to store AND BROWSE them remotely.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=238055136
This commit is contained in:
guyben 2019-03-12 11:10:30 -07:00 committed by Ben McIlwain
parent bc3bdc7347
commit a03d10ce22
11 changed files with 229 additions and 147 deletions

View file

@ -16,10 +16,9 @@
# "CREDZ" and "REPORT_GCS_BUCKET" environment variables in your travis # "CREDZ" and "REPORT_GCS_BUCKET" environment variables in your travis
# repository. # repository.
# #
# The report bucket id should just be the bucket id without any scheme or path # The report destination can be any GCS path (e.g. "gcs://bucket-name/path").
# (e.g. "my-bucket-name", not "gcs://my-bucket-name"). You'll want to select # You'll want to select "Display value in build log", otherwise travis will
# "Display value in build log", otherwise travis will hide the bucket name in # hide the bucket name in the URL that is displayed.
# the URL that is displayed.
# #
# The CREDZ variable should be the contents of a json credentials file for # The CREDZ variable should be the contents of a json credentials file for
# a service account with write access to the bucket, escaped for bash shell # a service account with write access to the bucket, escaped for bash shell
@ -56,4 +55,4 @@ env:
# output, instead of the default 10. # output, instead of the default 10.
# See notes on the CREDZ and REPORT_GCS_BUCKET environment variable in the # See notes on the CREDZ and REPORT_GCS_BUCKET environment variable in the
# comments at the top of the file. # comments at the top of the file.
script: cd gradle && echo "$CREDZ" >credz.json && chmod 755 ./gradlew && travis_wait 45 ./gradlew build --continue -P gcsBucket="$REPORT_GCS_BUCKET" -P gcsCredentialsFile=credz.json -P gcsMultithreadedUpload=yes script: cd gradle && echo "$CREDZ" >credz.json && chmod 755 ./gradlew && travis_wait 45 ./gradlew build --continue -P uploaderDestination="$REPORT_GCS_DESTINATION" -P uploaderCredentialsFile=credz.json -P uploaderMultithreadedUpload=yes

View file

@ -23,14 +23,14 @@ plugins {
id "com.moowork.node" version "1.2.0" id "com.moowork.node" version "1.2.0"
} }
apply plugin: google.registry.gradle.plugin.GcsReportUploaderPlugin apply plugin: google.registry.gradle.plugin.ReportUploaderPlugin
gcsReportUploader { reportUploader {
// Set the bucket here to upload build results to a GCS bucket. // Set the location where we want to upload the build results.
// e.g. -P gcsBucket=domain-registry-alpha-build-result-test // e.g. -P uploaderDestination=gcs://domain-registry-alpha-build-result-test
// //
// If no bucket it set - the uploading will be skipped. // If not set - the upload will be skipped
bucket = gcsBucket destination = uploaderDestination
// The location of the file containing the OAuth2 Google Cloud credentials. // The location of the file containing the OAuth2 Google Cloud credentials.
// //
@ -39,11 +39,11 @@ gcsReportUploader {
// supported by the Cloud SDK. // supported by the Cloud SDK.
// //
// If no file is given - the default credentials are used. // If no file is given - the default credentials are used.
credentialsFile = gcsCredentialsFile credentialsFile = uploaderCredentialsFile
// If set to 'yes', each file will be uploaded to GCS in a separate thread. // If set to 'yes', each file will be uploaded to GCS in a separate thread.
// This is MUCH faster. // This is MUCH faster.
multithreadedUpload = gcsMultithreadedUpload multithreadedUpload = uploaderMultithreadedUpload
} }
apply from: 'dependencies.gradle' apply from: 'dependencies.gradle'

View file

@ -46,6 +46,8 @@ final class CoverPageGenerator {
/** List of all resource files that will be uploaded as-is. */ /** List of all resource files that will be uploaded as-is. */
private static final ImmutableSet<Path> STATIC_RESOURCE_FILES = private static final ImmutableSet<Path> STATIC_RESOURCE_FILES =
ImmutableSet.of(Paths.get("css", "style.css")); ImmutableSet.of(Paths.get("css", "style.css"));
/** Name of the entry-point file that will be created. */
private static final Path ENTRY_POINT = Paths.get("index.html");
private final ProjectData projectData; private final ProjectData projectData;
private final ImmutableSetMultimap<TaskData.State, TaskData> tasksByState; private final ImmutableSetMultimap<TaskData.State, TaskData> tasksByState;
@ -63,30 +65,19 @@ final class CoverPageGenerator {
projectData.tasks().stream().collect(toImmutableSetMultimap(TaskData::state, task -> task)); projectData.tasks().stream().collect(toImmutableSetMultimap(TaskData::state, task -> task));
} }
/**
* The (relative) entry point for the cover page.
*
* <p>A file with this relative path is guaranteed to be returned from {@link #getFilesToUpload},
* and a browser pointing to this page will have access to all the data generated by {@link
* #getFilesToUpload}.
*/
Path getEntryPoint() {
return Paths.get("index.html");
}
/** /**
* Returns all the files that need uploading for the cover page to work. * Returns all the files that need uploading for the cover page to work.
* *
* <p>This includes all the report files as well, to make sure that the link works. * <p>This includes all the report files as well, to make sure that the link works.
*/ */
ImmutableMap<Path, Supplier<byte[]>> getFilesToUpload() { FilesWithEntryPoint getFilesToUpload() {
ImmutableMap.Builder<Path, Supplier<byte[]>> builder = new ImmutableMap.Builder<>(); ImmutableMap.Builder<Path, Supplier<byte[]>> builder = new ImmutableMap.Builder<>();
// Add all the static resource pages // Add all the static resource pages
STATIC_RESOURCE_FILES.stream().forEach(file -> builder.put(file, resourceLoader(file))); STATIC_RESOURCE_FILES.stream().forEach(file -> builder.put(file, resourceLoader(file)));
// Create the cover page // Create the cover page
// Note that the ByteArraySupplier here is lazy - the createCoverPage function is only called // Note that the ByteArraySupplier here is lazy - the createCoverPage function is only called
// when the resulting Supplier's get function is called. // when the resulting Supplier's get function is called.
builder.put(getEntryPoint(), toByteArraySupplier(this::createCoverPage)); builder.put(ENTRY_POINT, toByteArraySupplier(this::createCoverPage));
// Add all the files from the tasks // Add all the files from the tasks
tasksByState.values().stream() tasksByState.values().stream()
.flatMap(task -> task.reports().values().stream()) .flatMap(task -> task.reports().values().stream())
@ -96,7 +87,7 @@ final class CoverPageGenerator {
.filter(task -> task.log().isPresent()) .filter(task -> task.log().isPresent())
.forEach(task -> builder.put(getLogPath(task), task.log().get())); .forEach(task -> builder.put(getLogPath(task), task.log().get()));
return builder.build(); return FilesWithEntryPoint.create(builder.build(), ENTRY_POINT);
} }
/** Renders the cover page. */ /** Renders the cover page. */

View file

@ -0,0 +1,59 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// 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
//
// http://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 google.registry.gradle.plugin;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import java.nio.file.Path;
import java.util.function.Supplier;
/**
* Holds a set of files with a browser-friendly entry point to those files.
*
* <p>The file data is lazily generated.
*
* <p>If there is at least one file, it's guaranteed that the entry point is one of these files.
*/
@AutoValue
abstract class FilesWithEntryPoint {
/**
* All files that are part of this report, keyed from their path to a supplier of their content.
*
* <p>The reason we use a supplier instead of loading the content is in case the content is very
* large...
*
* <p>Also, no point in doing IO before we need it!
*/
abstract ImmutableMap<Path, Supplier<byte[]>> files();
/**
* The file that gives access (links...) to all the data in the report.
*
* <p>Guaranteed to be a key in {@link #files} if and only if files isn't empty.
*/
abstract Path entryPoint();
static FilesWithEntryPoint create(ImmutableMap<Path, Supplier<byte[]>> files, Path entryPoint) {
checkArgument(files.isEmpty() || files.containsKey(entryPoint));
return new AutoValue_FilesWithEntryPoint(files, entryPoint);
}
static FilesWithEntryPoint createSingleFile(Path path, Supplier<byte[]> data) {
return create(ImmutableMap.of(path, data), path);
}
}

View file

@ -24,7 +24,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams; import com.google.common.collect.Streams;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import google.registry.gradle.plugin.ProjectData.TaskData.ReportFiles;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -123,10 +122,10 @@ final class GcsPluginUtils {
} }
/** /**
* Reads all the files generated by a Report into a ReportFiles object. * Reads all the files generated by a Report into a FilesWithEntryPoint object.
* *
* <p>Every ReportFiles must have a single link "entry point" that gives users access to all the * <p>Every FilesWithEntryPoint must have a single link "entry point" that gives users access to
* files. If the report generated just one file - we will just link to that file. * all the files. If the report generated just one file - we will just link to that file.
* *
* <p>However, if the report generated more than one file - the only thing we can safely do is to * <p>However, if the report generated more than one file - the only thing we can safely do is to
* zip all the files and link to the zip file. * zip all the files and link to the zip file.
@ -144,20 +143,21 @@ final class GcsPluginUtils {
* directory, and (c) {@code entryPointHint.get()} is one of the files nested inside of the * directory, and (c) {@code entryPointHint.get()} is one of the files nested inside of the
* {@code destination} directory. * {@code destination} directory.
*/ */
static ReportFiles createReportFiles( static FilesWithEntryPoint readFilesWithEntryPoint(
File destination, Optional<File> entryPointHint, Path rootDir) { File destination, Optional<File> entryPointHint, Path rootDir) {
Path destinationPath = rootDir.relativize(toNormalizedPath(destination)); Path destinationPath = rootDir.relativize(toNormalizedPath(destination));
if (destination.isFile()) { if (destination.isFile()) {
// The destination is a single file - find its root, and add this single file to the // The destination is a single file - find its root, and add this single file to the
// ReportFiles. // FilesWithEntryPoint.
return ReportFiles.createSingleFile(destinationPath, toByteArraySupplier(destination)); return FilesWithEntryPoint.createSingleFile(
destinationPath, toByteArraySupplier(destination));
} }
if (!destination.isDirectory()) { if (!destination.isDirectory()) {
// This isn't a file nor a directory - so it doesn't exist! Return empty ReportFiles // This isn't a file nor a directory - so it doesn't exist! Return empty FilesWithEntryPoint
return ReportFiles.create(ImmutableMap.of(), destinationPath); return FilesWithEntryPoint.create(ImmutableMap.of(), destinationPath);
} }
// The destination is a directory - find all the actual files first // The destination is a directory - find all the actual files first
@ -170,13 +170,13 @@ final class GcsPluginUtils {
file -> toByteArraySupplier(file))); file -> toByteArraySupplier(file)));
if (files.isEmpty()) { if (files.isEmpty()) {
// The directory exists, but is empty. Return empty ReportFiles // The directory exists, but is empty. Return empty FilesWithEntryPoint
return ReportFiles.create(ImmutableMap.of(), destinationPath); return FilesWithEntryPoint.create(ImmutableMap.of(), destinationPath);
} }
if (files.size() == 1) { if (files.size() == 1) {
// We got a directory, but it only has a single file. We can link to that. // We got a directory, but it only has a single file. We can link to that.
return ReportFiles.create(files, getOnlyElement(files.keySet())); return FilesWithEntryPoint.create(files, getOnlyElement(files.keySet()));
} }
// There are multiple files in the report! We need to check the entryPointHint // There are multiple files in the report! We need to check the entryPointHint
@ -185,13 +185,13 @@ final class GcsPluginUtils {
if (entryPointPath.isPresent() && files.containsKey(entryPointPath.get())) { if (entryPointPath.isPresent() && files.containsKey(entryPointPath.get())) {
// We were given the entry point! Use it! // We were given the entry point! Use it!
return ReportFiles.create(files, entryPointPath.get()); return FilesWithEntryPoint.create(files, entryPointPath.get());
} }
// We weren't given an appropriate entry point. But we still need a single link to all this data // We weren't given an appropriate entry point. But we still need a single link to all this data
// - so we'll zip it and just host a single file. // - so we'll zip it and just host a single file.
Path zipFilePath = destinationPath.resolve(destinationPath.getFileName().toString() + ".zip"); Path zipFilePath = destinationPath.resolve(destinationPath.getFileName().toString() + ".zip");
return ReportFiles.createSingleFile(zipFilePath, createZippedByteArraySupplier(files)); return FilesWithEntryPoint.createSingleFile(zipFilePath, createZippedByteArraySupplier(files));
} }
static Supplier<byte[]> createZippedByteArraySupplier(Map<Path, Supplier<byte[]>> files) { static Supplier<byte[]> createZippedByteArraySupplier(Map<Path, Supplier<byte[]>> files) {

View file

@ -14,14 +14,10 @@
package google.registry.gradle.plugin; package google.registry.gradle.plugin;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import google.registry.gradle.plugin.ProjectData.TaskData; import google.registry.gradle.plugin.ProjectData.TaskData;
import google.registry.gradle.plugin.ProjectData.TaskData.ReportFiles;
import java.nio.file.Path;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -107,11 +103,11 @@ abstract class ProjectData {
abstract Optional<Supplier<byte[]>> log(); abstract Optional<Supplier<byte[]>> log();
/** /**
* Returns the ReportFiles for every report, keyed on the report type. * Returns the FilesWithEntryPoint for every report, keyed on the report type.
* *
* <p>The "html" report type is the most interesting, but there are other report formats. * <p>The "html" report type is the most interesting, but there are other report formats.
*/ */
abstract ImmutableMap<String, ReportFiles> reports(); abstract ImmutableMap<String, FilesWithEntryPoint> reports();
abstract Builder toBuilder(); abstract Builder toBuilder();
@ -129,45 +125,14 @@ abstract class ProjectData {
abstract Builder setLog(Supplier<byte[]> log); abstract Builder setLog(Supplier<byte[]> log);
abstract ImmutableMap.Builder<String, ReportFiles> reportsBuilder(); abstract ImmutableMap.Builder<String, FilesWithEntryPoint> reportsBuilder();
Builder putReport(String type, ReportFiles reportFiles) { Builder putReport(String type, FilesWithEntryPoint reportFiles) {
reportsBuilder().put(type, reportFiles); reportsBuilder().put(type, reportFiles);
return this; return this;
} }
abstract TaskData build(); abstract TaskData build();
} }
/** The files for a single format of a specific Task. */
@AutoValue
abstract static class ReportFiles {
/**
* All files generated by this report, keyed from their path to a supplier of their content.
*
* <p>The reason we use a supplier instead of loading the content is in case the content is
* very large...
*
* <p>Also, no point in doing IO before we need it!
*/
abstract ImmutableMap<Path, Supplier<byte[]>> files();
/**
* The file that gives access (links...) to all the data in the report.
*
* <p>Guaranteed to be a key in {@link #files} if and only if files isn't empty.
*/
abstract Path entryPoint();
static ReportFiles create(ImmutableMap<Path, Supplier<byte[]>> files, Path entryPoint) {
checkArgument(files.isEmpty() || files.containsKey(entryPoint));
return new AutoValue_ProjectData_TaskData_ReportFiles(files, entryPoint);
}
static ReportFiles createSingleFile(Path path, Supplier<byte[]> data) {
return create(ImmutableMap.of(path, data), path);
}
}
} }
} }

View file

@ -15,9 +15,9 @@
package google.registry.gradle.plugin; package google.registry.gradle.plugin;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.gradle.plugin.GcsPluginUtils.createReportFiles; import static google.registry.gradle.plugin.GcsPluginUtils.readFilesWithEntryPoint;
import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier; import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier;
import static google.registry.gradle.plugin.GcsPluginUtils.toNormalizedPath; import static google.registry.gradle.plugin.GcsPluginUtils.toNormalizedPath;
import static google.registry.gradle.plugin.GcsPluginUtils.uploadFileToGcs; import static google.registry.gradle.plugin.GcsPluginUtils.uploadFileToGcs;
@ -27,6 +27,7 @@ import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.storage.Storage; import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions; import com.google.cloud.storage.StorageOptions;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import google.registry.gradle.plugin.ProjectData.TaskData; import google.registry.gradle.plugin.ProjectData.TaskData;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -38,6 +39,7 @@ import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Optional; import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.gradle.api.DefaultTask; import org.gradle.api.DefaultTask;
import org.gradle.api.Project; import org.gradle.api.Project;
@ -51,20 +53,32 @@ import org.gradle.api.tasks.TaskAction;
/** /**
* A task that uploads the Reports generated by other tasks to GCS. * A task that uploads the Reports generated by other tasks to GCS.
*/ */
public class GcsReportUploader extends DefaultTask { public class ReportUploader extends DefaultTask {
private static final SecureRandom secureRandom = new SecureRandom(); private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static final ImmutableMap<String, BiConsumer<ReportUploader, String>> UPLOAD_FUNCTIONS =
ImmutableMap.of(
"file://", ReportUploader::saveResultsToLocalFolder,
"gcs://", ReportUploader::uploadResultsToGcs);
private final ArrayList<Task> tasks = new ArrayList<>(); private final ArrayList<Task> tasks = new ArrayList<>();
private final HashMap<String, StringBuilder> logs = new HashMap<>(); private final HashMap<String, StringBuilder> logs = new HashMap<>();
private Project project; private Project project;
private String bucket = null; private String destination = null;
private String credentialsFile = null; private String credentialsFile = null;
private String multithreadedUpload = null; private String multithreadedUpload = null;
public void setBucket(String bucket) { /** Sets the destination of the reports.
this.bucket = bucket; *
* Currently supports two types of destinations:
*
* - file://[absulute local path], e.g. file:///tmp/buildOutputs/
*
* - gcs://[bucket name]/[optional path], e.g. gcs://my-bucket/buildOutputs/
*/
public void setDestination(String destination) {
this.destination = destination;
} }
public void setCredentialsFile(String credentialsFile) { public void setCredentialsFile(String credentialsFile) {
@ -144,34 +158,85 @@ public class GcsReportUploader extends DefaultTask {
: Optional.empty(); : Optional.empty();
builder builder
.reportsBuilder() .reportsBuilder()
.put(type, createReportFiles(destination, entryPointHint, rootDir)); .put(type, readFilesWithEntryPoint(destination, entryPointHint, rootDir));
}); });
} }
return builder.build(); return builder.build();
} }
private FilesWithEntryPoint generateFilesToUpload() {
ProjectData projectData = createProjectData();
CoverPageGenerator coverPageGenerator = new CoverPageGenerator(projectData);
return coverPageGenerator.getFilesToUpload();
}
@TaskAction @TaskAction
void uploadResults() { void uploadResults() {
System.out.format("GcsReportUploader: bucket= '%s'\n", bucket); System.out.format("ReportUploader: destination= '%s'\n", destination);
if (isNullOrEmpty(bucket)) {
System.out.format("GcsReportUploader: no bucket defined. Skipping upload\n"); try {
if (isNullOrEmpty(destination)) {
System.out.format("ReportUploader: no destination given, skipping...\n");
return; return;
} }
try { for (String key : UPLOAD_FUNCTIONS.keySet()) {
uploadResultsToGcs(); if (destination.startsWith(key)) {
UPLOAD_FUNCTIONS.get(key).accept(this, destination.substring(key.length()));
return;
}
}
System.out.format(
"ReportUploader: given destination '%s' doesn't start with one of %s."
+ " Defaulting to saving in /tmp\n",
destination, UPLOAD_FUNCTIONS.keySet());
saveResultsToLocalFolder("/tmp/");
} catch (Throwable e) { } catch (Throwable e) {
System.out.format("GcsReportUploader: Encountered error %s\n", e); System.out.format("ReportUploader: Encountered error %s\n", e);
e.printStackTrace(System.out); e.printStackTrace(System.out);
System.out.format("GcsReportUploader: skipping upload\n"); System.out.format("ReportUploader: skipping upload\n");
} }
} }
void uploadResultsToGcs() { private void saveResultsToLocalFolder(String absoluteFolderName) {
checkNotNull(bucket); Path folder = Paths.get(absoluteFolderName, createUniqueFolderName());
ProjectData projectData = createProjectData(); checkArgument(
folder.isAbsolute(),
"Local files destination must be an absolute path, but is %s",
absoluteFolderName);
FilesWithEntryPoint filesToUpload = generateFilesToUpload();
System.out.format(
"ReportUploader: going to save %s files to %s\n",
filesToUpload.files().size(), folder);
filesToUpload
.files()
.forEach((path, dataSupplier) -> saveFile(folder.resolve(path), dataSupplier));
System.out.format(
"ReportUploader: report saved to file://%s\n",
folder.resolve(filesToUpload.entryPoint()));
}
Path folder = Paths.get(createUniqueFolderName()); private void saveFile(Path path, Supplier<byte[]> dataSupplier) {
File dir = path.getParent().toFile();
if (!dir.isDirectory()) {
checkState(dir.mkdirs(), "Couldn't create directory %s", dir);
}
try {
Files.write(dataSupplier.get(), path.toFile());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void uploadResultsToGcs(String destination) {
checkArgument(
!destination.isEmpty(),
"destination must include at least the bucket name, but is empty");
Path bucketWithFolder = Paths.get(destination, createUniqueFolderName());
String bucket = bucketWithFolder.getName(0).toString();
Path folder = bucketWithFolder.subpath(1, bucketWithFolder.getNameCount());
StorageOptions.Builder storageOptions = StorageOptions.newBuilder(); StorageOptions.Builder storageOptions = StorageOptions.newBuilder();
if (!isNullOrEmpty(credentialsFile)) { if (!isNullOrEmpty(credentialsFile)) {
@ -184,26 +249,25 @@ public class GcsReportUploader extends DefaultTask {
} }
Storage storage = storageOptions.build().getService(); Storage storage = storageOptions.build().getService();
CoverPageGenerator coverPageGenerator = new CoverPageGenerator(projectData); FilesWithEntryPoint filesToUpload = generateFilesToUpload();
ImmutableMap<Path, Supplier<byte[]>> filesToUpload = coverPageGenerator.getFilesToUpload();
System.out.format( System.out.format(
"GcsReportUploader: going to upload %s files to %s/%s\n", "ReportUploader: going to upload %s files to %s/%s\n",
filesToUpload.size(), bucket, folder); filesToUpload.files().size(), bucket, folder);
if ("yes".equals(multithreadedUpload)) { if ("yes".equals(multithreadedUpload)) {
System.out.format("GcsReportUploader: multi-threaded upload\n"); System.out.format("ReportUploader: multi-threaded upload\n");
uploadFilesToGcsMultithread(storage, bucket, folder, filesToUpload); uploadFilesToGcsMultithread(storage, bucket, folder, filesToUpload.files());
} else { } else {
System.out.format("GcsReportUploader: single threaded upload\n"); System.out.format("ReportUploader: single threaded upload\n");
filesToUpload.forEach( filesToUpload.files().forEach(
(path, dataSupplier) -> { (path, dataSupplier) -> {
System.out.format("GcsReportUploader: Uploading %s\n", path); System.out.format("ReportUploader: Uploading %s\n", path);
uploadFileToGcs(storage, bucket, folder.resolve(path), dataSupplier); uploadFileToGcs(storage, bucket, folder.resolve(path), dataSupplier);
}); });
} }
System.out.format( System.out.format(
"GcsReportUploader: report uploaded to https://storage.googleapis.com/%s/%s\n", "ReportUploader: report uploaded to https://storage.googleapis.com/%s/%s\n",
bucket, folder.resolve(coverPageGenerator.getEntryPoint())); bucket, folder.resolve(filesToUpload.entryPoint()));
} }
void setProject(Project project) { void setProject(Project project) {
@ -215,7 +279,7 @@ public class GcsReportUploader extends DefaultTask {
} }
private void addTask(Task task) { private void addTask(Task task) {
if (task instanceof GcsReportUploader) { if (task instanceof ReportUploader) {
return; return;
} }
tasks.add(task); tasks.add(task);
@ -241,9 +305,9 @@ public class GcsReportUploader extends DefaultTask {
private String createUniqueFolderName() { private String createUniqueFolderName() {
return String.format( return String.format(
"%h-%h-%h-%h", "%h-%h-%h-%h",
secureRandom.nextInt(), SECURE_RANDOM.nextInt(),
secureRandom.nextInt(), SECURE_RANDOM.nextInt(),
secureRandom.nextInt(), SECURE_RANDOM.nextInt(),
secureRandom.nextInt()); SECURE_RANDOM.nextInt());
} }
} }

View file

@ -18,20 +18,20 @@ import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
/** /**
* Plugin setting up the GcsReportUploader task. * Plugin setting up the ReportUploader task.
* *
* <p>It goes over all the tasks in a project and pass them on to the GcsReportUploader task for set * <p>It goes over all the tasks in a project and pass them on to the ReportUploader task for set
* up. * up.
* *
* <p>Note that since we're passing in all the projects' tasks - this includes the GcsReportUploader * <p>Note that since we're passing in all the projects' tasks - this includes the ReportUploader
* itself! It's up to the GcsReportuploader to take care of not having "infinite loops" caused by * itself! It's up to the ReportUploader to take care of not having "infinite loops" caused by
* waiting for itself to end before finishing. * waiting for itself to end before finishing.
*/ */
public class GcsReportUploaderPlugin implements Plugin<Project> { public class ReportUploaderPlugin implements Plugin<Project> {
public void apply(Project project) { public void apply(Project project) {
GcsReportUploader reportUploader = ReportUploader reportUploader =
project.getTasks().create("gcsReportUploader", GcsReportUploader.class, task -> { project.getTasks().create("reportUploader", ReportUploader.class, task -> {
task.setDescription("Uploads the reports to GCS bucket"); task.setDescription("Uploads the reports to GCS bucket");
task.setGroup("uploads"); task.setGroup("uploads");
}); });

View file

@ -23,10 +23,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import google.registry.gradle.plugin.ProjectData.TaskData; import google.registry.gradle.plugin.ProjectData.TaskData;
import google.registry.gradle.plugin.ProjectData.TaskData.ReportFiles;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.function.Supplier;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.JUnit4; import org.junit.runners.JUnit4;
@ -68,8 +65,8 @@ public final class CoverPageGeneratorTest {
private ImmutableMap<String, String> getGeneratedFiles(ProjectData project) { private ImmutableMap<String, String> getGeneratedFiles(ProjectData project) {
CoverPageGenerator coverPageGenerator = new CoverPageGenerator(project); CoverPageGenerator coverPageGenerator = new CoverPageGenerator(project);
ImmutableMap<Path, Supplier<byte[]>> files = coverPageGenerator.getFilesToUpload(); FilesWithEntryPoint files = coverPageGenerator.getFilesToUpload();
return files.entrySet().stream() return files.files().entrySet().stream()
.collect( .collect(
toImmutableMap( toImmutableMap(
entry -> entry.getKey().toString(), entry -> entry.getKey().toString(),
@ -87,9 +84,10 @@ public final class CoverPageGeneratorTest {
} }
@Test @Test
public void testGetFilesToUpload_getEntryPoint_isIndexHtml() { public void testGetFilesToUpload_entryPoint_isIndexHtml() {
CoverPageGenerator coverPageGenerator = new CoverPageGenerator(EMPTY_PROJECT); CoverPageGenerator coverPageGenerator = new CoverPageGenerator(EMPTY_PROJECT);
assertThat(coverPageGenerator.getEntryPoint()).isEqualTo(Paths.get("index.html")); assertThat(coverPageGenerator.getFilesToUpload().entryPoint())
.isEqualTo(Paths.get("index.html"));
} }
@Test @Test
@ -220,7 +218,7 @@ public final class CoverPageGeneratorTest {
.toBuilder() .toBuilder()
.putReport( .putReport(
"someReport", "someReport",
ReportFiles.create( FilesWithEntryPoint.create(
ImmutableMap.of( ImmutableMap.of(
Paths.get("path", "report.txt"), Paths.get("path", "report.txt"),
toByteArraySupplier("report content")), toByteArraySupplier("report content")),
@ -242,7 +240,8 @@ public final class CoverPageGeneratorTest {
.toBuilder() .toBuilder()
.putReport( .putReport(
"someReport", "someReport",
ReportFiles.create(ImmutableMap.of(), Paths.get("path", "report.txt"))) FilesWithEntryPoint.create(
ImmutableMap.of(), Paths.get("path", "report.txt")))
.build()) .build())
.build()); .build());
assertThat(files).doesNotContainKey("path/report.txt"); assertThat(files).doesNotContainKey("path/report.txt");
@ -263,7 +262,7 @@ public final class CoverPageGeneratorTest {
.setLog(toByteArraySupplier("log data")) .setLog(toByteArraySupplier("log data"))
.putReport( .putReport(
"filledReport", "filledReport",
ReportFiles.create( FilesWithEntryPoint.create(
ImmutableMap.of( ImmutableMap.of(
Paths.get("path-filled", "report.txt"), Paths.get("path-filled", "report.txt"),
toByteArraySupplier("report content"), toByteArraySupplier("report content"),
@ -272,7 +271,7 @@ public final class CoverPageGeneratorTest {
Paths.get("path-filled", "report.txt"))) Paths.get("path-filled", "report.txt")))
.putReport( .putReport(
"emptyReport", "emptyReport",
ReportFiles.create( FilesWithEntryPoint.create(
ImmutableMap.of(), Paths.get("path-empty", "report.txt"))) ImmutableMap.of(), Paths.get("path-empty", "report.txt")))
.build()) .build())
.build()); .build());

View file

@ -16,8 +16,8 @@ package google.registry.gradle.plugin;
import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.gradle.plugin.GcsPluginUtils.createReportFiles;
import static google.registry.gradle.plugin.GcsPluginUtils.getContentType; import static google.registry.gradle.plugin.GcsPluginUtils.getContentType;
import static google.registry.gradle.plugin.GcsPluginUtils.readFilesWithEntryPoint;
import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier; import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier;
import static google.registry.gradle.plugin.GcsPluginUtils.toNormalizedPath; import static google.registry.gradle.plugin.GcsPluginUtils.toNormalizedPath;
import static google.registry.gradle.plugin.GcsPluginUtils.uploadFileToGcs; import static google.registry.gradle.plugin.GcsPluginUtils.uploadFileToGcs;
@ -30,7 +30,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage; import com.google.cloud.storage.Storage;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import google.registry.gradle.plugin.ProjectData.TaskData.ReportFiles;
import java.io.File; import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -136,7 +135,7 @@ public final class GcsPluginUtilsTest {
assertThat(toByteArraySupplier(file).get()).isEqualTo("some data".getBytes(UTF_8)); assertThat(toByteArraySupplier(file).get()).isEqualTo("some data".getBytes(UTF_8));
} }
private ImmutableMap<String, String> readAllFiles(ReportFiles reportFiles) { private ImmutableMap<String, String> readAllFiles(FilesWithEntryPoint reportFiles) {
return reportFiles.files().entrySet().stream() return reportFiles.files().entrySet().stream()
.collect( .collect(
toImmutableMap( toImmutableMap(
@ -153,7 +152,8 @@ public final class GcsPluginUtilsTest {
// Since the entry point is obvious here - any hint given is just ignored. // Since the entry point is obvious here - any hint given is just ignored.
File ignoredHint = folder.newFile("my/root/ignored.txt"); File ignoredHint = folder.newFile("my/root/ignored.txt");
ReportFiles files = createReportFiles(destination, Optional.of(ignoredHint), root); FilesWithEntryPoint files =
readFilesWithEntryPoint(destination, Optional.of(ignoredHint), root);
assertThat(files.entryPoint().toString()).isEqualTo("some/path/file.txt"); assertThat(files.entryPoint().toString()).isEqualTo("some/path/file.txt");
assertThat(readAllFiles(files)).containsExactly("some/path/file.txt", "some data"); assertThat(readAllFiles(files)).containsExactly("some/path/file.txt", "some data");
@ -168,7 +168,8 @@ public final class GcsPluginUtilsTest {
// Since there are not files, any hint given is obvioulsy wrong and will be ignored. // Since there are not files, any hint given is obvioulsy wrong and will be ignored.
File ignoredHint = folder.newFile("my/root/ignored.txt"); File ignoredHint = folder.newFile("my/root/ignored.txt");
ReportFiles files = createReportFiles(destination, Optional.of(ignoredHint), root); FilesWithEntryPoint files =
readFilesWithEntryPoint(destination, Optional.of(ignoredHint), root);
assertThat(files.entryPoint().toString()).isEqualTo("non/existing.txt"); assertThat(files.entryPoint().toString()).isEqualTo("non/existing.txt");
assertThat(files.files()).isEmpty(); assertThat(files.files()).isEmpty();
@ -183,7 +184,8 @@ public final class GcsPluginUtilsTest {
// Since there are not files, any hint given is obvioulsy wrong and will be ignored. // Since there are not files, any hint given is obvioulsy wrong and will be ignored.
File ignoredHint = folder.newFile("my/root/ignored.txt"); File ignoredHint = folder.newFile("my/root/ignored.txt");
ReportFiles files = createReportFiles(destination, Optional.of(ignoredHint), root); FilesWithEntryPoint files =
readFilesWithEntryPoint(destination, Optional.of(ignoredHint), root);
assertThat(files.entryPoint().toString()).isEqualTo("some/path"); assertThat(files.entryPoint().toString()).isEqualTo("some/path");
assertThat(files.files()).isEmpty(); assertThat(files.files()).isEmpty();
@ -200,7 +202,8 @@ public final class GcsPluginUtilsTest {
// Since the entry point is obvious here - any hint given is just ignored. // Since the entry point is obvious here - any hint given is just ignored.
File ignoredHint = folder.newFile("my/root/ignored.txt"); File ignoredHint = folder.newFile("my/root/ignored.txt");
ReportFiles files = createReportFiles(destination, Optional.of(ignoredHint), root); FilesWithEntryPoint files =
readFilesWithEntryPoint(destination, Optional.of(ignoredHint), root);
assertThat(files.entryPoint().toString()).isEqualTo("some/path/a/file.txt"); assertThat(files.entryPoint().toString()).isEqualTo("some/path/a/file.txt");
assertThat(readAllFiles(files)).containsExactly("some/path/a/file.txt", "some data"); assertThat(readAllFiles(files)).containsExactly("some/path/a/file.txt", "some data");
@ -227,7 +230,7 @@ public final class GcsPluginUtilsTest {
Files.write( Files.write(
folder.newFile("my/root/some/path/my_image.png").toPath(), "images".getBytes(UTF_8)); folder.newFile("my/root/some/path/my_image.png").toPath(), "images".getBytes(UTF_8));
ReportFiles files = createReportFiles(destination, Optional.empty(), root); FilesWithEntryPoint files = readFilesWithEntryPoint(destination, Optional.empty(), root);
assertThat(files.entryPoint().toString()).isEqualTo("some/path/path.zip"); assertThat(files.entryPoint().toString()).isEqualTo("some/path/path.zip");
assertThat(readAllFiles(files).keySet()).containsExactly("some/path/path.zip"); assertThat(readAllFiles(files).keySet()).containsExactly("some/path/path.zip");
@ -255,7 +258,8 @@ public final class GcsPluginUtilsTest {
Files.write( Files.write(
folder.newFile("my/root/some/path/my_image.png").toPath(), "images".getBytes(UTF_8)); folder.newFile("my/root/some/path/my_image.png").toPath(), "images".getBytes(UTF_8));
ReportFiles files = createReportFiles(destination, Optional.of(badEntryPoint), root); FilesWithEntryPoint files =
readFilesWithEntryPoint(destination, Optional.of(badEntryPoint), root);
assertThat(files.entryPoint().toString()).isEqualTo("some/path/path.zip"); assertThat(files.entryPoint().toString()).isEqualTo("some/path/path.zip");
assertThat(readAllFiles(files).keySet()).containsExactly("some/path/path.zip"); assertThat(readAllFiles(files).keySet()).containsExactly("some/path/path.zip");
@ -278,7 +282,8 @@ public final class GcsPluginUtilsTest {
Files.write( Files.write(
folder.newFile("my/root/some/path/my_image.png").toPath(), "images".getBytes(UTF_8)); folder.newFile("my/root/some/path/my_image.png").toPath(), "images".getBytes(UTF_8));
ReportFiles files = createReportFiles(destination, Optional.of(goodEntryPoint), root); FilesWithEntryPoint files =
readFilesWithEntryPoint(destination, Optional.of(goodEntryPoint), root);
assertThat(files.entryPoint().toString()).isEqualTo("some/path/index.html"); assertThat(files.entryPoint().toString()).isEqualTo("some/path/index.html");
assertThat(readAllFiles(files)) assertThat(readAllFiles(files))

View file

@ -1,6 +1,6 @@
repositoryUrl= repositoryUrl=
publishUrl= publishUrl=
gcsBucket= uploaderDestination=
gcsCredentialsFile= uploaderCredentialsFile=
gcsMultithreadedUpload= uploaderMultithreadedUpload=
mavenCoordinateFile= mavenCoordinateFile=