Command to download from Maven and commit to dist.apache.org
diff --git a/pom.xml b/pom.xml
index 3fb0e1a..ad30165 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,14 +48,13 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
- <source>1.6</source>
- <target>1.6</target>
+ <source>1.8</source>
+ <target>1.8</target>
</configuration>
</plugin>
<plugin>
- <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
- <version>1.7.1</version>
+ <version>2.1</version>
<executions>
<execution>
<phase>package</phase>
@@ -64,17 +63,46 @@
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
- <shadedClassifierName>jar-with-dependencies</shadedClassifierName>
<transformers>
- <transformer
- implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
- <mainClass>org.apache.openejb.tools.release.Main</mainClass>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <mainClass>org.tomitribe.crest.Main</mainClass>
</transformer>
</transformers>
+ <filters>
+ <filter>
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ <exclude>META-INF/LICENSE</exclude>
+ <exclude>LICENSE</exclude>
+ <!--if this is same as above, not required-->
+ </excludes>
+ </filter>
+ </filters>
</configuration>
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.skife.maven</groupId>
+ <artifactId>really-executable-jar-maven-plugin</artifactId>
+ <version>1.4.0</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>really-executable-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <flags>-Dcmd="$0" $RELEASE_OPTS -Xmx1G</flags>
+ <programFile>release</programFile>
+ <attachProgramFile>true</attachProgramFile>
+ </configuration>
+ </plugin>
</plugins>
</build>
@@ -251,6 +279,21 @@
</exclusions>
</dependency>
+ <dependency>
+ <groupId>org.tomitribe</groupId>
+ <artifactId>tomitribe-crest</artifactId>
+ <version>0.15</version>
+ </dependency>
+ <dependency>
+ <groupId>org.tomitribe</groupId>
+ <artifactId>swizzle</artifactId>
+ <version>1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.tomitribe</groupId>
+ <artifactId>tomitribe-util</artifactId>
+ <version>1.3.18</version>
+ </dependency>
</dependencies>
</project>
diff --git a/src/main/java/org/apache/openejb/tools/release/Loader.java b/src/main/java/org/apache/openejb/tools/release/Loader.java
new file mode 100644
index 0000000..cc2c107
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/Loader.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.openejb.tools.release;
+
+
+import org.apache.openejb.tools.release.cmd.Dist;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class Loader implements org.tomitribe.crest.api.Loader {
+
+ @Override
+ public Iterator<Class<?>> iterator() {
+ return Arrays.asList(
+ Dist.class,
+ Object.class
+ ).iterator();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/openejb/tools/release/cmd/CommandFailedException.java b/src/main/java/org/apache/openejb/tools/release/cmd/CommandFailedException.java
new file mode 100644
index 0000000..cb1405b
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/cmd/CommandFailedException.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.openejb.tools.release.cmd;
+
+import org.tomitribe.crest.api.Exit;
+
+@Exit(1)
+public class CommandFailedException extends RuntimeException {
+ public CommandFailedException(final String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/cmd/Dist.java b/src/main/java/org/apache/openejb/tools/release/cmd/Dist.java
new file mode 100644
index 0000000..b28344c
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/cmd/Dist.java
@@ -0,0 +1,321 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.openejb.tools.release.cmd;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.tomitribe.crest.api.Command;
+import org.tomitribe.crest.api.Default;
+import org.tomitribe.crest.api.Option;
+import org.tomitribe.crest.api.Out;
+import org.tomitribe.swizzle.stream.StreamBuilder;
+import org.tomitribe.util.Files;
+import org.tomitribe.util.Hex;
+import org.tomitribe.util.IO;
+import org.tomitribe.util.dir.Dir;
+import org.tomitribe.util.dir.Filter;
+import org.tomitribe.util.dir.Walk;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.lang.String.format;
+import static org.apache.openejb.tools.release.util.Exec.exec;
+
+@Command
+public class Dist {
+
+ /**
+ * Download binaries from a maven repo and commit them to dist.apache.org dev
+ *
+ * The org/apache/tomee/apache-tomee and org/apache/tomee/tomee-project sections
+ * of the maven repository will be scanned for the version specified and all zip
+ * and tar.gz files will be downloaded along with any associated asc and sha1 files.
+ * After download the sha1 files of each binary will be checked to ensure a complete
+ * download. The sha256 and sha512 file for each binary will be computed and written
+ * to disk.
+ *
+ * If the --dry-run flag is not enabled, the resulting zip, tar.gz, asc, sha256 and
+ * sha512 files will be uploaded to a directory in dist.apache.org dev or the specified
+ * svn repo.
+ *
+ * When ready, the dist.apache.org dev directory can be moved to dist.apache.org release
+ * via the `dist dev-to-release` command.
+ *
+ * @param version The TomEE version being published. Example: 8.0.7
+ * @param tmp The directory under which files can be temporarily downloaded
+ * @param mavenRepoUri The root path of a Nexus staging repository or Maven Central
+ * @param svnRepo The svn directory for tomee where a subdirectory can be created and binaries uploaded
+ * @param dryRun Download the files to local disk, but do not commit them to svn
+ */
+ @Command("maven-to-dev")
+ public void mavenToDev(final String version,
+ @Option("tmp") @Default("/tmp/") final File tmp,
+ @Option("maven-repo") @Default("https://repo1.maven.org/maven2/") final URI mavenRepoUri,
+ @Option("svn-repo") @Default("https://dist.apache.org/repos/dist/dev/tomee/") final URI svnRepo,
+ @Option("dry-run") @Default("false") final boolean dryRun,
+ final @Out PrintStream out) throws IOException {
+
+
+ final String build = buildId(mavenRepoUri);
+ final String tomeeVersionName = "tomee-" + version;
+ final String svnBinaryLocation = format("https://dist.apache.org/repos/dist/dev/tomee/staging-%s/%s", build, tomeeVersionName);
+
+ final File dir = new File(tmp, "tomee-" + version + "-work");
+
+ { // Make and checkout the binaries dir in svn
+ if (!dir.exists()) {
+ Files.mkdirs(dir);
+ }
+
+ if (!dryRun) {
+ exec("svn", "-m", format("[release-tools] staged binary dir for %s", tomeeVersionName), "mkdir", "--parents", svnBinaryLocation);
+ exec("svn", "co", svnBinaryLocation, dir.getAbsolutePath());
+ }
+ }
+
+
+ final MavenRepo repo = new MavenRepo(mavenRepoUri, out);
+
+ final List<URI> binaries = new ArrayList<>();
+ binaries.addAll(repo.binaries("org/apache/tomee/apache-tomee/", version));
+ binaries.addAll(repo.binaries("org/apache/tomee/tomee-project/", version));
+
+ binaries.forEach(repo.downloadTo(dir));
+ out.printf("Downloaded %s binaries to %s%n", binaries.size(), dir.getAbsolutePath());
+
+ final Work work = Dir.of(Work.class, dir);
+
+ final List<Binary> invalid = work.binaries()
+ .filter(((Predicate<Binary>) Binary::verifySha1).negate())
+ .collect(Collectors.toList());
+
+ if (invalid.size() != 0) {
+ invalid.forEach(binary -> out.printf("SHA1 check failed %s%n", binary.get().getAbsolutePath()));
+ throw new CommandFailedException("Remove the invalid files and try again");
+ }
+
+ work.binaries()
+ .peek(Binary::createSha256)
+ .peek(Binary::createSha512)
+ .forEach(binary -> out.println("Hashed " + binary.get().getName()));
+
+ if (!dryRun) {
+
+ Consumer<File> svnAdd = file -> exec("svn", "add", file.getAbsolutePath());
+
+ work.binaries()
+ .peek(binary -> svnAdd.accept(binary.get()))
+ .peek(binary -> svnAdd.accept(binary.asc()))
+ .peek(binary -> svnAdd.accept(binary.sha256()))
+ .peek(binary -> svnAdd.accept(binary.sha512()))
+ .forEach(binary -> out.println("Added " + binary.get().getName()));
+
+ exec("svn", "-m", format("[release-tools] staged binaries for %s", tomeeVersionName), "ci", dir.getAbsolutePath());
+
+ out.printf("Binaries published to %s%n", svnBinaryLocation);
+ }
+ }
+
+ /**
+ * Return the last digits of a Nexus staging repo dir such as orgapachetomee-1136 or
+ * return the month and day as a default.
+ */
+ private String buildId(final URI stagingRepoUri) {
+ final String id = stagingRepoUri.getPath().replaceAll(".*-([0-9]+)/?$", "$1");
+ if (id.matches("^[0-9]+$")) {
+ return id;
+ }
+
+ final SimpleDateFormat format = new SimpleDateFormat("MMdd");
+ return format.format(new Date());
+ }
+
+ @Command("dev-to-release")
+ public void release(final String version,
+ @Option("dev-repo") @Default("https://repo1.maven.org/maven2/") final URI mavenRepo,
+ @Option("release-repo") @Default("https://dist.apache.org/repos/dist/release/tomee/") final URI svnRepo,
+ final @Out PrintStream out) throws IOException {
+
+ }
+
+ public static class MavenRepo {
+ private final CloseableHttpClient client = HttpClientBuilder.create().build();
+ private final URI repo;
+ private final PrintStream out;
+
+ public MavenRepo(final URI repo, final PrintStream out) {
+ this.repo = repo;
+ this.out = out;
+ }
+
+ public List<URI> binaries(final String artifactPath, final String version) throws IOException {
+ final URI artifactDir = this.repo.resolve(artifactPath);
+
+ final URI versionDir = artifactDir.resolve(version + "/");
+ final CloseableHttpResponse response = get(versionDir);
+
+ final List<String> hrefs = new ArrayList<>();
+ StreamBuilder.create(response.getEntity().getContent())
+ .watch("<a href=\"", "\"", hrefs::add)
+ .run();
+
+ final Predicate<String> acceptedExtensions = Pattern.compile("\\.(zip|tar\\.gz)(\\.(asc|sha1))?$").asPredicate();
+ return hrefs.stream()
+ .filter(acceptedExtensions)
+ .map(versionDir::resolve)
+ .collect(Collectors.toList());
+ }
+
+ private CloseableHttpResponse get(final URI uri) throws IOException {
+ final CloseableHttpResponse response = client.execute(new HttpGet(uri));
+ if (response.getStatusLine().getStatusCode() != 200) {
+ EntityUtils.consume(response.getEntity());
+ throw new UnexpectedHttpResponseException("GET", uri, response.getStatusLine());
+ }
+ return response;
+ }
+
+ public Consumer<URI> downloadTo(final File directory) {
+ return downloadTo(directory, false);
+ }
+
+ public Consumer<URI> downloadTo(final File directory, final boolean overwrite) {
+ return uri -> {
+ try {
+ final String name = uri.getPath().replaceAll(".*/", "");
+ final File file = new File(directory, name);
+
+ if (file.exists() && !overwrite) {
+ out.println("Downloaded " + uri);
+ } else {
+ out.println("Downloading " + uri);
+ final CloseableHttpResponse response = get(uri);
+ IO.copy(response.getEntity().getContent(), file);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ };
+ }
+ }
+
+ public interface Work extends Dir {
+ @Walk(maxDepth = 1)
+ @Filter(Binary.Format.class)
+ Stream<Binary> binaries();
+ }
+
+ public interface Binary extends Dir {
+
+ default boolean verifySha1() {
+ final String expectedSha1 = slurp(sha1());
+ final String actualSha1 = hash("SHA-1");
+ return expectedSha1.equals(actualSha1);
+ }
+
+ default void createSha256() {
+ final String sha256 = hash("SHA-256");
+ write(sha256, sha256());
+ }
+
+ default void createSha512() {
+ final String sha256 = hash("SHA-512");
+ write(sha256, sha512());
+ }
+
+ default File asc() {
+ return get(get(), "asc");
+ }
+
+ default File sha1() {
+ return get(get(), "sha1");
+ }
+
+ default File sha256() {
+ return get(get(), "sha256");
+ }
+
+ default File sha512() {
+ return get(get(), "sha512");
+ }
+
+ static void write(String content, File file) {
+ try {
+ IO.copy(IO.read(content), file);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Unable to write to file " + file.getAbsolutePath(), e);
+ }
+ }
+
+ default File get(final File file, final String ext) {
+ return new File(file.getParentFile(), file.getName() + "." + ext);
+ }
+
+ default String hash(final String type) {
+ try {
+ final MessageDigest digest = MessageDigest.getInstance(type);
+ try (final InputStream inputStream = IO.read(get())) {
+ final DigestInputStream digestInputStream = new DigestInputStream(inputStream, digest);
+ IO.copy(digestInputStream, IO.IGNORE_OUTPUT);
+ return Hex.toString(digest.digest());
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("Unknown algorithm " + type, e);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ static String slurp(final File file) {
+ try {
+ return IO.slurp(file);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Cannot read file " + file.getAbsolutePath(), e);
+ }
+ }
+
+ class Format implements FileFilter {
+ @Override
+ public boolean accept(final File file) {
+ final String name = file.getName();
+ return name.endsWith(".zip") || name.endsWith(".tar.gz");
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/openejb/tools/release/cmd/UnexpectedHttpResponseException.java b/src/main/java/org/apache/openejb/tools/release/cmd/UnexpectedHttpResponseException.java
new file mode 100644
index 0000000..5bc1091
--- /dev/null
+++ b/src/main/java/org/apache/openejb/tools/release/cmd/UnexpectedHttpResponseException.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.openejb.tools.release.cmd;
+
+import org.apache.http.StatusLine;
+import org.tomitribe.crest.api.Exit;
+
+import java.net.URI;
+
+@Exit(500)
+public class UnexpectedHttpResponseException extends RuntimeException {
+
+ public UnexpectedHttpResponseException(final String method, final URI uri, final StatusLine statusLine) {
+ super(String.format("%s %s returned unexpected response %s", method, uri, statusLine));
+ }
+}
diff --git a/src/main/resources/META-INF/services/org.tomitribe.crest.api.Loader b/src/main/resources/META-INF/services/org.tomitribe.crest.api.Loader
new file mode 100644
index 0000000..f0af220
--- /dev/null
+++ b/src/main/resources/META-INF/services/org.tomitribe.crest.api.Loader
@@ -0,0 +1 @@
+org.apache.openejb.tools.release.Loader
\ No newline at end of file