From 5863c60f90ebc97221ea86f662fa9624e644a4e8 Mon Sep 17 00:00:00 2001 From: Gareth Healy Date: Fri, 5 Jan 2024 12:44:37 +0000 Subject: [PATCH] upgraded to quarkus command line app --- .github/workflows/build.yaml | 113 +++++++ .github/workflows/scorecard.yml | 72 ++++ .mvn/wrapper/.gitignore | 1 + .mvn/wrapper/MavenWrapperDownloader.java | 98 ++++++ .mvn/wrapper/maven-wrapper.properties | 18 + mvnw | 308 ++++++++++++++++++ mvnw.cmd | 205 ++++++++++++ pom.xml | 214 +++++++----- renovate.json | 7 + src/main/docker/Dockerfile.jvm | 97 ++++++ src/main/docker/Dockerfile.legacy-jar | 93 ++++++ src/main/docker/Dockerfile.native | 27 ++ src/main/docker/Dockerfile.native-micro | 30 ++ src/main/docker/version.json | 3 + .../githubstats/GitHubStatsApplication.java | 30 ++ .../commands/CollectStatsCommand.java | 34 ++ .../commands/CreateWhoAreYouIssueCommand.java | 43 +++ .../githubstats/commands/QuarkusCommand.java | 10 + .../githubstats/model/RepoInfo.java | 88 +---- .../rest/client/BaseGitHubService.java | 34 ++ .../rest/client/CollectStatsService.java | 232 +++++++------ .../client/CreateWhoAreYouIssueService.java | 106 +++--- src/main/resources/application.properties | 0 src/main/resources/log4j2.xml | 28 -- .../rest/client/CollectStatsServiceTest.java | 26 -- .../CreateWhoAreYouIssueServiceTest.java | 24 -- tests/members.csv | 2 + 27 files changed, 1514 insertions(+), 429 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/scorecard.yml create mode 100644 .mvn/wrapper/.gitignore create mode 100644 .mvn/wrapper/MavenWrapperDownloader.java create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100755 mvnw create mode 100755 mvnw.cmd create mode 100644 renovate.json create mode 100644 src/main/docker/Dockerfile.jvm create mode 100644 src/main/docker/Dockerfile.legacy-jar create mode 100644 src/main/docker/Dockerfile.native create mode 100644 src/main/docker/Dockerfile.native-micro create mode 100644 src/main/docker/version.json create mode 100644 src/main/java/com/garethahealy/githubstats/GitHubStatsApplication.java create mode 100644 src/main/java/com/garethahealy/githubstats/commands/CollectStatsCommand.java create mode 100644 src/main/java/com/garethahealy/githubstats/commands/CreateWhoAreYouIssueCommand.java create mode 100644 src/main/java/com/garethahealy/githubstats/commands/QuarkusCommand.java create mode 100644 src/main/java/com/garethahealy/githubstats/rest/client/BaseGitHubService.java create mode 100644 src/main/resources/application.properties delete mode 100644 src/main/resources/log4j2.xml delete mode 100644 src/test/java/com/garethahealy/githubstats/rest/client/CollectStatsServiceTest.java delete mode 100644 src/test/java/com/garethahealy/githubstats/rest/client/CreateWhoAreYouIssueServiceTest.java create mode 100644 tests/members.csv diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..96623cb --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,113 @@ +name: "Build, Analyze and Test" + +on: [push, pull_request] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 + with: + distribution: "temurin" + java-version: 21 + + - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven + restore-keys: | + ${{ runner.os }}-maven + + - name: Build + run: ./mvnw clean install --batch-mode + + - name: Build native + run: ./mvnw clean install -Pnative --batch-mode + + - name: Run help + run: target/github-stats-*-runner help + + - name: Upload target + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4 + with: + name: target + path: target/ + if-no-files-found: error + + - name: Get image tags + id: image_tags + uses: redhat-cop/github-actions/get-image-version@main + with: + IMAGE_CONTEXT_DIR: src/main/docker + + - name: Build image + uses: redhat-actions/buildah-build@b4dc19b4ba891854660ab1f88a097d45aa158f76 # v2 + with: + dockerfiles: src/main/docker/Dockerfile.native-micro + image: github-stats + oci: true + tags: "${{ steps.image_tags.outputs.IMAGE_TAGS }}" + + analyze: + needs: [ build ] + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4 + with: + distribution: "temurin" + java-version: 21 + + - name: Initialize CodeQL + uses: github/codeql-action/init@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3 + with: + languages: java + + - name: Autobuild + uses: github/codeql-action/autobuild@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3 + with: + category: "/language:java" + + test: + needs: [ build ] + runs-on: ubuntu-latest + steps: + - name: Download target + uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4 + with: + name: target + + - name: Make github-stats-*-runner executable + run: chmod +x github-stats-*-runner + + - name: Run collect-stats for myself + env: + GITHUB_LOGIN: ${{ github.repository_owner }} + GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} + run: ./github-stats-*-runner collect-stats --organization=garethahealy + + - name: Run collect-stats for myself + env: + GITHUB_LOGIN: ${{ github.repository_owner }} + GITHUB_OAUTH: ${{ secrets.GITHUB_TOKEN }} + run: echo "todo" #./github-stats-*-runner create-who-are-you-issues --dry-run=true --organization=garethahealy --issue-repo=github-stats --members-csv=tests/members.csv --fail-if-no-vpn=false + + - name: Upload github-output.csv + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4 + with: + name: github-output.csv + path: github-output.csv + if-no-files-found: error diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..73bd066 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,72 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '36 19 * * 3' + push: + branches: [ "main" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 + with: + sarif_file: results.sarif diff --git a/.mvn/wrapper/.gitignore b/.mvn/wrapper/.gitignore new file mode 100644 index 0000000..e72f5e8 --- /dev/null +++ b/.mvn/wrapper/.gitignore @@ -0,0 +1 @@ +maven-wrapper.jar diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..84d1e60 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,98 @@ +/* + * 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. + */ + +import java.io.IOException; +import java.io.InputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +public final class MavenWrapperDownloader +{ + private static final String WRAPPER_VERSION = "3.2.0"; + + private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) ); + + public static void main( String[] args ) + { + log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION ); + + if ( args.length != 2 ) + { + System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" ); + System.exit( 1 ); + } + + try + { + log( " - Downloader started" ); + final URL wrapperUrl = new URL( args[0] ); + final String jarPath = args[1].replace( "..", "" ); // Sanitize path + final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize(); + downloadFileFromURL( wrapperUrl, wrapperJarPath ); + log( "Done" ); + } + catch ( IOException e ) + { + System.err.println( "- Error downloading: " + e.getMessage() ); + if ( VERBOSE ) + { + e.printStackTrace(); + } + System.exit( 1 ); + } + } + + private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath ) + throws IOException + { + log( " - Downloading to: " + wrapperJarPath ); + if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) + { + final String username = System.getenv( "MVNW_USERNAME" ); + final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); + Authenticator.setDefault( new Authenticator() + { + @Override + protected PasswordAuthentication getPasswordAuthentication() + { + return new PasswordAuthentication( username, password ); + } + } ); + } + try ( InputStream inStream = wrapperUrl.openStream() ) + { + Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING ); + } + log( " - Downloader complete" ); + } + + private static void log( String msg ) + { + if ( VERBOSE ) + { + System.out.println( msg ); + } + } + +} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..346d645 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..8d937f4 --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100755 index 0000000..c4586b5 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 9baa31f..4f66cd0 100644 --- a/pom.xml +++ b/pom.xml @@ -1,93 +1,133 @@ - - 4.0.0 - com.garethahealy.githubstats - github-stats - 1.0.0-SNAPSHOT - - - 3.9.0 - 3.0.0-M5 - - 11 - 11 - - UTF-8 - UTF-8 - + + 4.0.0 + com.github.garethahealy + github-stats + 1.0.0-SNAPSHOT + + 3.12.1 + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 3.6.4 + true + 3.2.3 + + - - org.kohsuke - github-api - 1.301 - - - com.squareup.okhttp3 - okhttp - 4.9.3 - - - org.apache.commons - commons-csv - 1.8 - - - org.apache.logging.log4j - log4j-api - 2.17.1 - - - org.apache.logging.log4j - log4j-core - 2.17.1 - - - org.apache.logging.log4j - log4j-jul - 2.17.1 - - - org.apache.logging.log4j - log4j-slf4j-impl - 2.17.1 - - - org.apache.directory.api - api-all - 2.1.0 - - - org.junit.jupiter - junit-jupiter-engine - 5.8.2 - test - + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + - - - - org.apache.maven.plugins - maven-compiler-plugin - ${compiler-plugin.version} - - - org.apache.maven.plugins - maven-surefire-plugin - ${surefire-plugin.version} - - - - java.util.logging.manager - org.apache.logging.log4j.jul.LogManager - - - - - - - + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-picocli + + + io.quarkiverse.githubapi + quarkus-github-api + 1.318.0 + + + org.apache.commons + commons-csv + 1.10.0 + + + org.apache.directory.api + api-all + 2.1.5 + + + io.quarkus + quarkus-junit5 + test + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + native + + + native + + + + false + native + + + diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..01ae43a --- /dev/null +++ b/renovate.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:best-practices", + "schedule:earlyMondays" + ] +} diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..5fe2f04 --- /dev/null +++ b/src/main/docker/Dockerfile.jvm @@ -0,0 +1,97 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./mvnw package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/github-stats-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/github-stats-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/github-stats-jvm +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi8/openjdk-17:1.18-2@sha256:0d12c4097e098b62f78a7a31c0d711d78e1e5a53f4c007b9a5fc6cc6ab4dc018 + +ENV LANGUAGE='en_US:en' + + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] + diff --git a/src/main/docker/Dockerfile.legacy-jar b/src/main/docker/Dockerfile.legacy-jar new file mode 100644 index 0000000..0b3702a --- /dev/null +++ b/src/main/docker/Dockerfile.legacy-jar @@ -0,0 +1,93 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./mvnw package -Dquarkus.package.type=legacy-jar +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/github-stats-legacy-jar . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/github-stats-legacy-jar +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/github-stats-legacy-jar +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi8/openjdk-17:1.18-2@sha256:0d12c4097e098b62f78a7a31c0d711d78e1e5a53f4c007b9a5fc6cc6ab4dc018 + +ENV LANGUAGE='en_US:en' + + +COPY target/lib/* /deployments/lib/ +COPY target/*-runner.jar /deployments/quarkus-run.jar + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..19ed950 --- /dev/null +++ b/src/main/docker/Dockerfile.native @@ -0,0 +1,27 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# +# Before building the container image run: +# +# ./mvnw package -Dnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/github-stats . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/github-stats +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9-1029@sha256:87bcbfedfd70e67aab3875fff103bade460aeff510033ebb36b7efa009ab6639 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/src/main/docker/Dockerfile.native-micro b/src/main/docker/Dockerfile.native-micro new file mode 100644 index 0000000..15dfa22 --- /dev/null +++ b/src/main/docker/Dockerfile.native-micro @@ -0,0 +1,30 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# It uses a micro base image, tuned for Quarkus native executables. +# It reduces the size of the resulting container image. +# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. +# +# Before building the container image run: +# +# ./mvnw package -Dnative +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/github-stats . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/github-stats +# +### +FROM quay.io/quarkus/quarkus-micro-image:2.0@sha256:da7140ede620f67789bf0d17d4b1868265bde19a217cbda2436b0d7208ffef51 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root target/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/src/main/docker/version.json b/src/main/docker/version.json new file mode 100644 index 0000000..8744366 --- /dev/null +++ b/src/main/docker/version.json @@ -0,0 +1,3 @@ +{ + "version": "v1.0.0" +} \ No newline at end of file diff --git a/src/main/java/com/garethahealy/githubstats/GitHubStatsApplication.java b/src/main/java/com/garethahealy/githubstats/GitHubStatsApplication.java new file mode 100644 index 0000000..71edfb2 --- /dev/null +++ b/src/main/java/com/garethahealy/githubstats/GitHubStatsApplication.java @@ -0,0 +1,30 @@ +package com.garethahealy.githubstats; + +import com.garethahealy.githubstats.commands.CollectStatsCommand; +import com.garethahealy.githubstats.commands.CreateWhoAreYouIssueCommand; +import com.garethahealy.githubstats.commands.QuarkusCommand; +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.QuarkusApplication; +import io.quarkus.runtime.annotations.QuarkusMain; +import jakarta.inject.Inject; +import picocli.CommandLine; + +@QuarkusMain +public class GitHubStatsApplication implements QuarkusApplication { + @Inject + CollectStatsCommand collectStatsCommand; + @Inject + CreateWhoAreYouIssueCommand createWhoAreYouIssueCommand; + + public static void main(String[] args) { + Quarkus.run(GitHubStatsApplication.class, args); + } + + @Override + public int run(String... args) throws Exception { + return new CommandLine(new QuarkusCommand()) + .addSubcommand(collectStatsCommand) + .addSubcommand(createWhoAreYouIssueCommand) + .execute(args); + } +} diff --git a/src/main/java/com/garethahealy/githubstats/commands/CollectStatsCommand.java b/src/main/java/com/garethahealy/githubstats/commands/CollectStatsCommand.java new file mode 100644 index 0000000..7ef9fa9 --- /dev/null +++ b/src/main/java/com/garethahealy/githubstats/commands/CollectStatsCommand.java @@ -0,0 +1,34 @@ +package com.garethahealy.githubstats.commands; + +import com.garethahealy.githubstats.rest.client.CollectStatsService; +import jakarta.enterprise.context.Dependent; +import jakarta.inject.Inject; +import picocli.CommandLine; + +import java.io.IOException; + +@Dependent +@CommandLine.Command(name = "collect-stats", mixinStandardHelpOptions = true, description = "Collect the stats in CSV format") +public class CollectStatsCommand implements Runnable { + + @CommandLine.Option(names = {"-org", "--organization"}, description = "GitHub organization", required = true) + String organization; + + @CommandLine.Option(names = {"-cfg", "--validate-org-config"}, description = "Whether to check the 'org/config.yaml'", defaultValue = "false") + boolean validateOrgConfig; + + @CommandLine.Option(names = {"-o", "--csv-output"}, description = "Output location for CSV", defaultValue = "github-output.csv") + String output; + + @Inject + CollectStatsService collectStatsService; + + @Override + public void run() { + try { + collectStatsService.run(organization, validateOrgConfig, output); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/garethahealy/githubstats/commands/CreateWhoAreYouIssueCommand.java b/src/main/java/com/garethahealy/githubstats/commands/CreateWhoAreYouIssueCommand.java new file mode 100644 index 0000000..f86623d --- /dev/null +++ b/src/main/java/com/garethahealy/githubstats/commands/CreateWhoAreYouIssueCommand.java @@ -0,0 +1,43 @@ +package com.garethahealy.githubstats.commands; + +import com.garethahealy.githubstats.rest.client.CreateWhoAreYouIssueService; +import jakarta.enterprise.context.Dependent; +import jakarta.inject.Inject; +import org.apache.directory.api.ldap.model.exception.LdapException; +import picocli.CommandLine; + +import java.io.IOException; + +@Dependent +@CommandLine.Command(name = "create-who-are-you-issues", mixinStandardHelpOptions = true, description = "Creates an issue per user in the org config that isn't in a CVS") +public class CreateWhoAreYouIssueCommand implements Runnable { + + @CommandLine.Option(names = {"-org", "--organization"}, description = "GitHub organization", required = true) + String organization; + + @CommandLine.Option(names = {"-repo", "--issue-repo"}, description = "Repo where the issues should be created, i.e.: 'org'", required = true) + String orgRepo; + + @CommandLine.Option(names = {"-dry", "--dry-run"}, description = "Dry-run aka don't actually create the GitHub issues", required = true) + boolean dryRun; + + @CommandLine.Option(names = {"-i", "--members-csv"}, description = "CSV container current known members", required = true) + String membersCsv; + + @CommandLine.Option(names = {"-vpn", "--fail-if-no-vpn"}, description = "Throw an exception if can't connect to LDAP", required = true) + boolean failNoVpn; + + @Inject + CreateWhoAreYouIssueService createWhoAreYouIssueService; + + @Override + public void run() { + try { + //TODO: for time being, always dry-run + dryRun = true; + createWhoAreYouIssueService.run(organization, orgRepo, dryRun, membersCsv, failNoVpn); + } catch (IOException | LdapException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/garethahealy/githubstats/commands/QuarkusCommand.java b/src/main/java/com/garethahealy/githubstats/commands/QuarkusCommand.java new file mode 100644 index 0000000..a22c5f7 --- /dev/null +++ b/src/main/java/com/garethahealy/githubstats/commands/QuarkusCommand.java @@ -0,0 +1,10 @@ +package com.garethahealy.githubstats.commands; + +import picocli.CommandLine; + +@CommandLine.Command( + name = "github-stats", + description = "GitHub helper utility", + subcommands = {CommandLine.HelpCommand.class}) +public class QuarkusCommand { +} \ No newline at end of file diff --git a/src/main/java/com/garethahealy/githubstats/model/RepoInfo.java b/src/main/java/com/garethahealy/githubstats/model/RepoInfo.java index 1119ee0..62f56c1 100644 --- a/src/main/java/com/garethahealy/githubstats/model/RepoInfo.java +++ b/src/main/java/com/garethahealy/githubstats/model/RepoInfo.java @@ -1,18 +1,13 @@ package com.garethahealy.githubstats.model; +import org.kohsuke.github.*; + import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.List; -import org.kohsuke.github.GHCommit; -import org.kohsuke.github.GHIssue; -import org.kohsuke.github.GHPullRequest; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHRepositoryCloneTraffic; -import org.kohsuke.github.GHRepositoryViewTraffic; - public class RepoInfo { public enum Headers { @@ -31,6 +26,7 @@ public enum Headers { HasCodeOwners, HasWorkflows, HasTravis, + HasRenovate, InConfig, IsArchived } @@ -50,6 +46,7 @@ public enum Headers { private final boolean hasCodeOwners; private final boolean hasWorkflows; private final boolean hasTravis; + private final boolean hasRenovate; private final boolean inConfig; private final boolean isArchived; @@ -66,6 +63,7 @@ public RepoInfo(String repoName, boolean hasCodeOwners, boolean hasWorkflows, boolean hasTravis, + boolean hasRenovate, boolean inConfig, boolean isArchived) throws IOException { DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); @@ -74,10 +72,10 @@ public RepoInfo(String repoName, this.lastCommitAuthor = lastCommit == null || lastCommit.getAuthor() == null ? null : lastCommit.getAuthor().getLogin(); this.lastCommitDate = lastCommit == null ? null : df.format(lastCommit.getCommitDate()); this.cop = topics.stream().filter(topic -> topic.contains("-cop") || topic.contains("gpte")).findFirst().orElse(null); - this.contributorCount = contributors.size(); + this.contributorCount = contributors == null ? 0 : contributors.size(); this.commitCount = commits == null ? 0 : commits.size(); - this.openIssueCount = issues.size(); - this.openPullRequestCount = pullRequests.size(); + this.openIssueCount = issues == null ? 0 : issues.size(); + this.openPullRequestCount = pullRequests == null ? 0 : pullRequests.size(); this.topics = topics; this.clonesInPast14Days = cloneTraffic == null ? 0 : cloneTraffic.getUniques(); this.viewsInPast14Days = viewTraffic == null ? 0 : viewTraffic.getUniques(); @@ -85,78 +83,11 @@ public RepoInfo(String repoName, this.hasCodeOwners = hasCodeOwners; this.hasWorkflows = hasWorkflows; this.hasTravis = hasTravis; + this.hasRenovate = hasRenovate; this.inConfig = inConfig; this.isArchived = isArchived; } - public String getRepoName() { - return repoName; - } - - public String getLastCommitAuthor() { - return lastCommitAuthor; - } - - public String getLastCommitDate() { - return lastCommitDate; - } - - public String getCop() { - return cop; - } - - public int getContributorCount() { - return contributorCount; - } - - public int getCommitCount() { - return commitCount; - } - - public int getOpenIssueCount() { - return openIssueCount; - } - - public int getOpenPullRequestCount() { - return openPullRequestCount; - } - - public List getTopics() { - return topics; - } - - public long getClonesInPast14Days() { - return clonesInPast14Days; - } - - public long getViewsInPast14Days() { - return viewsInPast14Days; - } - - public boolean isHasOwners() { - return hasOwners; - } - - public boolean isHasCodeOwners() { - return hasCodeOwners; - } - - public boolean isHasWorkflows() { - return hasWorkflows; - } - - public boolean isHasTravis() { - return hasTravis; - } - - public boolean isInConfig() { - return inConfig; - } - - public boolean isArchived() { - return isArchived; - } - public List toArray() { return Arrays.asList(repoName, cop, lastCommitDate, lastCommitAuthor, String.valueOf(contributorCount), String.valueOf(commitCount), String.valueOf(openIssueCount), @@ -164,6 +95,7 @@ public List toArray() { String.valueOf(clonesInPast14Days), String.valueOf(viewsInPast14Days), String.valueOf(hasOwners), String.valueOf(hasCodeOwners), String.valueOf(hasWorkflows), String.valueOf(hasTravis), + String.valueOf(hasRenovate), String.valueOf(inConfig), String.valueOf(isArchived)); } } diff --git a/src/main/java/com/garethahealy/githubstats/rest/client/BaseGitHubService.java b/src/main/java/com/garethahealy/githubstats/rest/client/BaseGitHubService.java new file mode 100644 index 0000000..85bbd8e --- /dev/null +++ b/src/main/java/com/garethahealy/githubstats/rest/client/BaseGitHubService.java @@ -0,0 +1,34 @@ +package com.garethahealy.githubstats.rest.client; + +import jakarta.inject.Inject; +import org.jboss.logging.Logger; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; + +import java.io.IOException; + +public abstract class BaseGitHubService { + + @Inject + Logger logger; + + protected GitHub getGitHub() throws IOException { + logger.info("Starting..."); + + GitHub gitHub = GitHubBuilder.fromEnvironment().build(); + if (!gitHub.isCredentialValid()) { + throw new IllegalStateException("isCredentialValid - are GITHUB_LOGIN / GITHUB_OAUTH valid?"); + } + + if (gitHub.isAnonymous()) { + throw new IllegalStateException("isAnonymous - have you set GITHUB_LOGIN / GITHUB_OAUTH ?"); + } + + logger.infof("RateLimit: limit %s, remaining %s, resetDate %s", gitHub.getRateLimit().getLimit(), gitHub.getRateLimit().getRemaining(), gitHub.getRateLimit().getResetDate()); + if (gitHub.getRateLimit().getRemaining() == 0) { + throw new IllegalStateException("RateLimit - is zero, you need to wait until the reset date"); + } + + return gitHub; + } +} diff --git a/src/main/java/com/garethahealy/githubstats/rest/client/CollectStatsService.java b/src/main/java/com/garethahealy/githubstats/rest/client/CollectStatsService.java index 729253e..fe09037 100644 --- a/src/main/java/com/garethahealy/githubstats/rest/client/CollectStatsService.java +++ b/src/main/java/com/garethahealy/githubstats/rest/client/CollectStatsService.java @@ -1,5 +1,14 @@ package com.garethahealy.githubstats.rest.client; +import com.garethahealy.githubstats.model.RepoInfo; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.io.FileUtils; +import org.jboss.logging.Logger; +import org.kohsuke.github.*; + import java.io.File; import java.io.IOException; import java.nio.charset.Charset; @@ -11,153 +20,121 @@ import java.util.List; import java.util.Map; -import com.garethahealy.githubstats.model.RepoInfo; -import okhttp3.Cache; -import okhttp3.OkHttpClient; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVPrinter; -import org.apache.commons.io.FileUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.kohsuke.github.GHCommit; -import org.kohsuke.github.GHContent; -import org.kohsuke.github.GHException; -import org.kohsuke.github.GHFileNotFoundException; -import org.kohsuke.github.GHIssue; -import org.kohsuke.github.GHIssueState; -import org.kohsuke.github.GHOrganization; -import org.kohsuke.github.GHPullRequest; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHRepositoryCloneTraffic; -import org.kohsuke.github.GHRepositoryViewTraffic; -import org.kohsuke.github.GitHub; -import org.kohsuke.github.GitHubBuilder; -import org.kohsuke.github.extras.okhttp3.OkHttpGitHubConnector; - -public class CollectStatsService { - - private static final Logger logger = LogManager.getLogger(CollectStatsService.class); - - public List run() throws IOException { - logger.info("Starting..."); - - List answer = new ArrayList<>(); - - Cache cache = new Cache(new File("/tmp/github-okhttp"), 10 * 1024 * 1024); // 10MB cache - GitHub gitHub = GitHubBuilder.fromEnvironment() - .withConnector(new OkHttpGitHubConnector(new OkHttpClient.Builder().cache(cache).build())) - .build(); +@ApplicationScoped +public class CollectStatsService extends BaseGitHubService { - if (!gitHub.isCredentialValid()) { - throw new IllegalStateException("isCredentialValid - are GITHUB_LOGIN / GITHUB_OAUTH valid?"); - } - - if (gitHub.isAnonymous()) { - throw new IllegalStateException("isAnonymous - have you set GITHUB_LOGIN / GITHUB_OAUTH ?"); - } - - logger.info("Connector with cache created."); - logger.info("RateLimit: limit {}, remaining {}, resetDate {}", gitHub.getRateLimit().getLimit(), gitHub.getRateLimit().getRemaining(), gitHub.getRateLimit().getResetDate()); - - if (gitHub.getRateLimit().getRemaining() == 0) { - throw new IllegalStateException("RateLimit - is zero, you need to wait until the reset date"); - } + @Inject + Logger logger; - GHOrganization org = gitHub.getOrganization("redhat-cop"); + public List run(String organization, boolean validateOrgConfig, String output) throws IOException { + List answer = new ArrayList<>(); - logger.info("Downloading org/config.yaml"); + GitHub gitHub = getGitHub(); + GHOrganization org = gitHub.getOrganization(organization); - GHRepository coreOrg = org.getRepository("org"); - GHContent orgConfig = coreOrg.getFileContent("config.yaml"); - File configOutputFile = new File("target/core-config.yaml"); - FileUtils.copyInputStreamToFile(orgConfig.read(), configOutputFile); - String configContent = FileUtils.readFileToString(configOutputFile, Charset.defaultCharset()); + String configContent = validateOrgConfig ? getOrgConfigYaml(org) : ""; + CSVFormat csvFormat = CSVFormat.Builder.create(CSVFormat.DEFAULT).setHeader((RepoInfo.Headers.class)).build(); LocalDateTime flushAt = LocalDateTime.now(); - try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(Paths.get("target/github-output.csv")), CSVFormat.DEFAULT.withHeader(RepoInfo.Headers.class))) { + try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(Paths.get(output)), csvFormat)) { Map repos = org.getRepositories(); - logger.info("Found {} repos.", repos.size()); + logger.infof("Found %s repos.", repos.size()); int i = 1; for (Map.Entry current : repos.entrySet()) { - logger.info("Working on: {} - {} / {}", current.getValue().getName(), i, repos.size()); + logger.infof("Working on: %s - %s / %s", current.getValue().getName(), i, repos.size()); GHRepository repo = current.getValue(); String repoName = repo.getName(); - logger.info("-> listContributors"); - List contributors = repo.listContributors().toList(); + List contributors = null; List commits = null; - - logger.info("-> listIssues"); - List issues = repo.getIssues(GHIssueState.OPEN); - - logger.info("-> listPullRequests"); - List pullRequests = repo.getPullRequests(GHIssueState.OPEN); - - logger.info("-> listTopics"); - List topics = repo.listTopics(); + List issues = null; + List pullRequests = null; + List topics = new ArrayList<>(); GHCommit lastCommit = null; GHRepositoryCloneTraffic cloneTraffic = null; GHRepositoryViewTraffic viewTraffic = null; boolean inConfig = configContent.contains(repoName); boolean isArchived = repo.isArchived(); - - try { - logger.info("-> listCommits"); - commits = repo.listCommits().toList(); - lastCommit = commits.get(0); - } catch (GHException | IOException ex) { - //ignore - has no commits - } - - try { - logger.info("-> Traffic"); - cloneTraffic = repo.getCloneTraffic(); - viewTraffic = repo.getViewTraffic(); - } catch (GHException | GHFileNotFoundException ex) { - //ignore - token doesnt have access to this repo to get traffic - } - boolean hasOwners = false; boolean hasCodeOwners = false; boolean hasWorkflows = false; boolean hasTravis = false; - - try { - logger.info("-> OWNERS"); - GHContent owners = repo.getFileContent("OWNERS"); - hasOwners = owners != null && owners.isFile(); - } catch (GHFileNotFoundException ex) { - //ignore - file doesnt exist - } - - try { - logger.info("-> CODEOWNERS"); - GHContent codeowners = repo.getFileContent("CODEOWNERS"); - hasCodeOwners = codeowners != null && codeowners.isFile(); - } catch (GHFileNotFoundException ex) { - //ignore - file doesnt exist - } - - try { - logger.info("-> .github/workflows"); - List workflows = repo.getDirectoryContent(".github/workflows"); - hasWorkflows = workflows != null && workflows.size() > 0; - } catch (GHFileNotFoundException ex) { - //ignore - file doesnt exist - } - - try { - logger.info("-> .travis.yml"); - GHContent travis = repo.getFileContent(".travis.yml"); - hasTravis = travis != null && travis.isFile(); - } catch (GHFileNotFoundException ex) { - //ignore - file doesnt exist + boolean hasRenovate = false; + + if (!isArchived) { + logger.info("-> listContributors"); + contributors = repo.listContributors().toList(); + + logger.info("-> listIssues"); + issues = repo.getIssues(GHIssueState.OPEN); + + logger.info("-> listPullRequests"); + pullRequests = repo.getPullRequests(GHIssueState.OPEN); + + logger.info("-> listTopics"); + topics = repo.listTopics(); + + try { + logger.info("-> listCommits"); + commits = repo.listCommits().toList(); + lastCommit = commits.get(0); + } catch (GHException | IOException ex) { + //ignore - has no commits + } + + try { + logger.info("-> Traffic"); + cloneTraffic = repo.getCloneTraffic(); + viewTraffic = repo.getViewTraffic(); + } catch (GHException | GHFileNotFoundException ex) { + //ignore - token doesn't have access to this repo to get traffic + } + + try { + logger.info("-> OWNERS"); + GHContent owners = repo.getFileContent("OWNERS"); + hasOwners = owners != null && owners.isFile(); + } catch (GHFileNotFoundException ex) { + //ignore - file doesn't exist + } + + try { + logger.info("-> CODEOWNERS"); + GHContent codeowners = repo.getFileContent("CODEOWNERS"); + hasCodeOwners = codeowners != null && codeowners.isFile(); + } catch (GHFileNotFoundException ex) { + //ignore - file doesn't exist + } + + try { + logger.info("-> .github/workflows"); + List workflows = repo.getDirectoryContent(".github/workflows"); + hasWorkflows = workflows != null && !workflows.isEmpty(); + } catch (GHFileNotFoundException ex) { + //ignore - file doesn't exist + } + + try { + logger.info("-> .travis.yml"); + GHContent travis = repo.getFileContent(".travis.yml"); + hasTravis = travis != null && travis.isFile(); + } catch (GHFileNotFoundException ex) { + //ignore - file doesn't exist + } + + try { + logger.info("-> renovate.json"); + GHContent renovate = repo.getFileContent("renovate.json"); + hasRenovate = renovate != null && renovate.isFile(); + } catch (GHFileNotFoundException ex) { + //ignore - file doesn't exist + } } RepoInfo repoInfo = new RepoInfo(repoName, lastCommit, contributors, commits, issues, pullRequests, - topics, cloneTraffic, viewTraffic, hasOwners, hasCodeOwners, hasWorkflows, hasTravis, inConfig, isArchived); + topics, cloneTraffic, viewTraffic, hasOwners, hasCodeOwners, hasWorkflows, hasTravis, hasRenovate, inConfig, isArchived); answer.add(repoInfo); @@ -169,7 +146,7 @@ public List run() throws IOException { flushAt = LocalDateTime.now(); csvPrinter.flush(); - logger.info("RateLimit: limit {}, remaining {}, resetDate {}", gitHub.getRateLimit().getLimit(), gitHub.getRateLimit().getRemaining(), gitHub.getRateLimit().getResetDate()); + logger.infof("RateLimit: limit %s, remaining %s, resetDate %s", gitHub.getRateLimit().getLimit(), gitHub.getRateLimit().getRemaining(), gitHub.getRateLimit().getResetDate()); } i++; @@ -177,8 +154,19 @@ public List run() throws IOException { } logger.info("Finished."); - logger.info("RateLimit: limit {}, remaining {}, resetDate {}", gitHub.getRateLimit().getLimit(), gitHub.getRateLimit().getRemaining(), gitHub.getRateLimit().getResetDate()); + logger.infof("RateLimit: limit %s, remaining %s, resetDate %s", gitHub.getRateLimit().getLimit(), gitHub.getRateLimit().getRemaining(), gitHub.getRateLimit().getResetDate()); return answer; } + + private String getOrgConfigYaml(GHOrganization org) throws IOException { + logger.info("Downloading org/config.yaml"); + + GHRepository coreOrg = org.getRepository("org"); + GHContent orgConfig = coreOrg.getFileContent("config.yaml"); + File configOutputFile = new File("target/core-config.yaml"); + FileUtils.copyInputStreamToFile(orgConfig.read(), configOutputFile); + + return FileUtils.readFileToString(configOutputFile, Charset.defaultCharset()); + } } diff --git a/src/main/java/com/garethahealy/githubstats/rest/client/CreateWhoAreYouIssueService.java b/src/main/java/com/garethahealy/githubstats/rest/client/CreateWhoAreYouIssueService.java index f06d6da..db17c41 100644 --- a/src/main/java/com/garethahealy/githubstats/rest/client/CreateWhoAreYouIssueService.java +++ b/src/main/java/com/garethahealy/githubstats/rest/client/CreateWhoAreYouIssueService.java @@ -1,16 +1,7 @@ package com.garethahealy.githubstats.rest.client; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import okhttp3.Cache; -import okhttp3.OkHttpClient; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVRecord; import org.apache.directory.api.ldap.model.cursor.EntryCursor; @@ -21,57 +12,38 @@ import org.apache.directory.ldap.client.api.LdapConnection; import org.apache.directory.ldap.client.api.LdapNetworkConnection; import org.apache.directory.ldap.client.api.exception.InvalidConnectionException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.kohsuke.github.GHIssueBuilder; -import org.kohsuke.github.GHOrganization; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GHUser; -import org.kohsuke.github.GitHub; -import org.kohsuke.github.GitHubBuilder; -import org.kohsuke.github.extras.okhttp3.OkHttpGitHubConnector; - -public class CreateWhoAreYouIssueService { - - private static final Logger logger = LogManager.getLogger(CreateWhoAreYouIssueService.class); - private final boolean isDryRun = true; - - public void run() throws IOException, LdapException { - logger.info("Starting..."); - - Cache cache = new Cache(new File("/tmp/github-okhttp"), 10 * 1024 * 1024); // 10MB cache - GitHub gitHub = GitHubBuilder.fromEnvironment() - .withConnector(new OkHttpGitHubConnector(new OkHttpClient.Builder().cache(cache).build())) - .build(); - - if (!gitHub.isCredentialValid()) { - throw new IllegalStateException("isCredentialValid - are GITHUB_LOGIN / GITHUB_OAUTH valid?"); - } +import org.jboss.logging.Logger; +import org.kohsuke.github.*; - if (gitHub.isAnonymous()) { - throw new IllegalStateException("isAnonymous - have you set GITHUB_LOGIN / GITHUB_OAUTH ?"); - } +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; - logger.info("Connector with cache created."); - logger.info("RateLimit: limit {}, remaining {}, resetDate {}", gitHub.getRateLimit().getLimit(), gitHub.getRateLimit().getRemaining(), gitHub.getRateLimit().getResetDate()); +@ApplicationScoped +public class CreateWhoAreYouIssueService extends BaseGitHubService { - if (gitHub.getRateLimit().getRemaining() == 0) { - throw new IllegalStateException("RateLimit - is zero, you need to wait until the reset date"); - } + @Inject + Logger logger; - GHOrganization org = gitHub.getOrganization("redhat-cop"); - GHRepository orgRepo = org.getRepository("org"); - List members = org.listMembers().toList(); + public void run(String organization, String issueRepo, boolean isDryRun, String membersCsv, boolean failNoVpn) throws IOException, LdapException { + GitHub gitHub = getGitHub(); + GHOrganization org = gitHub.getOrganization(organization); - logger.info("There are {} members", members.size()); + GHRepository orgRepo = org.getRepository(issueRepo); + List members = org.listMembers().toList(); - Set usernamesToIgnore = getUsernamesToIgnore(); + Set usernamesToIgnore = getUsernamesToIgnore(membersCsv); - logger.info("There are {} members we already have emails for who will be ignored", usernamesToIgnore.size()); + logger.infof("There are %s members", members.size()); + logger.infof("There are %s members we already have emails for who will be ignored", usernamesToIgnore.size()); for (GHUser current : members) { if (usernamesToIgnore.contains(current.getLogin())) { - logger.info("Ignoring: {}", current.getLogin()); + logger.infof("Ignoring: %s", current.getLogin()); } else { GHIssueBuilder builder = orgRepo.createIssue("@" + current.getLogin() + " please complete form") .assignee(current) @@ -83,11 +55,11 @@ public void run() throws IOException, LdapException { "https://red.ht/github-redhat-cop-username"); if (isDryRun) { - logger.info("DRY-RUN: Would have created issue for {}", current.getLogin()); + logger.infof("DRY-RUN: Would have created issue for %s in %s", current.getLogin(), orgRepo.getName()); } else { builder.create(); - logger.info("Created issue for {}", current.getLogin()); + logger.infof("Created issue for %s", current.getLogin()); } } } @@ -97,7 +69,7 @@ public void run() throws IOException, LdapException { Set membersLogins = getMembersLogins(members); for (String current : usernamesToIgnore) { if (!membersLogins.contains(current)) { - logger.info("Have a google form response but they are not part the git hub org anymore for {}", current); + logger.infof("Have a google form response but they are not part the github org anymore for %s", current); } } @@ -106,7 +78,7 @@ public void run() throws IOException, LdapException { //ldapsearch -x -h ldap.corp.redhat.com -b dc=redhat,dc=com -s sub 'uid=gahealy' Dn systemDn = new Dn("dc=redhat,dc=com"); try (LdapConnection connection = new LdapNetworkConnection("ldap.corp.redhat.com")) { - for (String current : getCollectedEmails()) { + for (String current : getCollectedEmails(membersCsv)) { String uid = current.split("@")[0]; try (EntryCursor cursor = connection.search(systemDn, "(uid=" + uid + ")", SearchScope.SUBTREE)) { boolean found = false; @@ -116,21 +88,26 @@ public void run() throws IOException, LdapException { } if (!found) { - logger.info("Did not find {} in ldap", uid); + logger.infof("Did not find %s in ldap", uid); } } } } catch (InvalidConnectionException ex) { - logger.error("Unable to search ldap for users. Are you on the VPN?", ex); + if (failNoVpn) { + logger.error("Unable to search ldap for users. Are you on the VPN?", ex); + } else { + logger.warn("Unable to search ldap for users. Are you on the VPN?", ex); + } } logger.info("Ldap Lookup DONE"); } - private Set getUsernamesToIgnore() throws IOException { + private Set getUsernamesToIgnore(String membersCsv) throws IOException { Set answer = new HashSet<>(); - try (Reader in = new FileReader("GitHub Red Hat CoP Members (Responses) - Form Responses 1.csv")) { - Iterable records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(in); + CSVFormat csvFormat = CSVFormat.Builder.create(CSVFormat.DEFAULT).setSkipHeaderRecord(true).build(); + try (Reader in = new FileReader(membersCsv)) { + Iterable records = csvFormat.parse(in); for (CSVRecord record : records) { answer.add(record.get("What is your GitHub username?")); } @@ -139,10 +116,11 @@ private Set getUsernamesToIgnore() throws IOException { return answer; } - private List getCollectedEmails() throws IOException { + private List getCollectedEmails(String membersCsv) throws IOException { List answer = new ArrayList<>(); - try (Reader in = new FileReader("GitHub Red Hat CoP Members (Responses) - Form Responses 1.csv")) { - Iterable records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(in); + CSVFormat csvFormat = CSVFormat.Builder.create(CSVFormat.DEFAULT).setSkipHeaderRecord(true).build(); + try (Reader in = new FileReader(membersCsv)) { + Iterable records = csvFormat.parse(in); for (CSVRecord record : records) { answer.add(record.get("Email Address")); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml deleted file mode 100644 index 9da2875..0000000 --- a/src/main/resources/log4j2.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - %d %p %c{1.} [%t] %m%n - - - - - %d %p %c{1.} [%t] %m%n - - - - - - - - - - - - diff --git a/src/test/java/com/garethahealy/githubstats/rest/client/CollectStatsServiceTest.java b/src/test/java/com/garethahealy/githubstats/rest/client/CollectStatsServiceTest.java deleted file mode 100644 index b2ef14b..0000000 --- a/src/test/java/com/garethahealy/githubstats/rest/client/CollectStatsServiceTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.garethahealy.githubstats.rest.client; - -import java.io.IOException; -import java.util.List; - -import com.garethahealy.githubstats.model.RepoInfo; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class CollectStatsServiceTest { - - private static final Logger logger = LogManager.getLogger(CollectStatsServiceTest.class); - - @Test - public void canRun() throws IOException { - logger.info("Running."); - - CollectStatsService service = new CollectStatsService(); - List answer = service.run(); - - Assertions.assertNotNull(answer); - Assertions.assertTrue(answer.size() > 0); - } -} \ No newline at end of file diff --git a/src/test/java/com/garethahealy/githubstats/rest/client/CreateWhoAreYouIssueServiceTest.java b/src/test/java/com/garethahealy/githubstats/rest/client/CreateWhoAreYouIssueServiceTest.java deleted file mode 100644 index c31b589..0000000 --- a/src/test/java/com/garethahealy/githubstats/rest/client/CreateWhoAreYouIssueServiceTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.garethahealy.githubstats.rest.client; - -import java.io.IOException; - -import org.apache.directory.api.ldap.model.exception.LdapException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class CreateWhoAreYouIssueServiceTest { - - private static final Logger logger = LogManager.getLogger(CreateWhoAreYouIssueServiceTest.class); - - @Test - public void canRun() throws IOException, LdapException { - logger.info("Running."); - - CreateWhoAreYouIssueService service = new CreateWhoAreYouIssueService(); - service.run(); - - Assertions.assertTrue(true); - } -} \ No newline at end of file diff --git a/tests/members.csv b/tests/members.csv new file mode 100644 index 0000000..0e9ff16 --- /dev/null +++ b/tests/members.csv @@ -0,0 +1,2 @@ +Timestamp,Email Address,What is your GitHub username? +1/26/2022 15:37:03,gahealy@redhat.com,garethahealy \ No newline at end of file