diff --git a/.github/actions/gwy/badges/action.yml b/.github/actions/gwy/badges/action.yml new file mode 100644 index 0000000..b4694d4 --- /dev/null +++ b/.github/actions/gwy/badges/action.yml @@ -0,0 +1,745 @@ +name: 'Generate Badges' +description: 'Generates coverage, license, build, downloads, last commit, contributors, issues, stars, go version, and GWY badges' + +inputs: + token: + description: 'GitHub token for accessing repository data and pushing badges' + required: true + + branch: + description: 'Orphan branch to save badges (, , or branch name)' + required: true + default: '' + + directory: + description: 'Directory in orphan branch to save badges (e.g., images/gwy/badges)' + required: true + default: 'images/gwy/badges' + + url: + description: '' + required: false + default: 'github.com' + +runs: + using: 'composite' + steps: + - name: Badges Generation Bootstrapping + shell: bash + run: | + # Badges Generation Bootstrapping + echo "GWY_BADGES_TMP=$RUNNER_TEMP/gwy-badges" >> $GITHUB_ENV + echo "GWY_BADGES_URL=${{ inputs.url }}" >> $GITHUB_ENV + echo "GWY_BADGES_TITLE=release badges generation" >> $GITHUB_ENV + + SANITIZED_BRANCH=$(echo "$GWY_BRANCH" | tr '/' '-') + echo "SANITIZED_BRANCH=$SANITIZED_BRANCH" >> $GITHUB_ENV + + # Warn if GWY_BADGES_TMP exists + if [ -d "$GWY_BADGES_TMP" ]; then + echo "::warning title=$GWY_BADGES_TITLE::Directory $GWY_BADGES_TMP exists in repo, consider changing GWY_BADGES_TMP" + fi + + # Resolve orphan branch + if [ "${{ inputs.branch }}" = "" ]; then + PAGES_INFO=$(curl -s -H "Authorization: Bearer ${{ inputs.token }}" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/pages") + if [ $? -eq 0 ] && [ -n "$PAGES_INFO" ]; then + GWY_BADGES_BRANCH=$(echo "$PAGES_INFO" | jq -r '.source.branch // "gh-pages"') + echo "Detected GitHub Pages branch: $GWY_BADGES_BRANCH" + else + GWY_BADGES_BRANCH="gh-pages" + echo "Failed to fetch GitHub Pages branch, defaulting to gh-pages" + fi + else + GWY_BADGES_BRANCH="${{ inputs.branch }}" + echo "Using specified badges branch: $GWY_BADGES_BRANCH" + fi + echo "GWY_BADGES_BRANCH=$GWY_BADGES_BRANCH" >> $GITHUB_ENV + + echo -e "\n## Latest Release Badges\n" >> $GITHUB_STEP_SUMMARY + + # Write summary header + if [ "${{ inputs.branch }}" = "" ]; then + echo "Badges were not requested to be commited in an assets branch in the repository, the following badges are linked to \`Shields.io\`. If you want your badges to be automatically updated upon each release merge to master or a daily job to keep your documentation badges counts (downloads, stars, etc) up-to-date, updating transparently your documentation, you need to specify a badges branch. Kindly refer to GWY documentation to learn how to do this." >> $GITHUB_STEP_SUMMARY + else + echo "The displayed badges have been commited to branch \`$GWY_BADGES_BRANCH\`. The following badges are linked to each of these files in the branch so you can use them in your documentation and forget (badges will be updated automatically in every release merge to master or you can run daily this workflow to update them)." >> $GITHUB_STEP_SUMMARY + fi + + - name: Generate Coverage Badge + continue-on-error: true + shell: bash + run: | + # Calculate Coverage + echo "**Coverage:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Default to error value + GWY_COVERAGE="-1" + + # Run tests with coverage, redirect errors to check later + if go test ./... -coverprofile=coverage.out 2>/dev/null; then + # Extract coverage percentage, check if it worked + TOTAL_LINE=$(go tool cover -func=coverage.out | grep "total:" || echo "") + if [ -n "$TOTAL_LINE" ]; then + GWY_COVERAGE=$(echo "$TOTAL_LINE" | awk '{print $3}' | sed 's/%//') + echo "Coverage: $GWY_COVERAGE%" + else + echo "No total coverage line found in coverage.out" + fi + else + echo "go test failed to generate coverage.out" + fi + + # Generate badge if coverage is valid + if [ "$GWY_COVERAGE" != "-1" ]; then + # Determine color based on coverage + GWY_COLOR="red" + if (( $(echo "$GWY_COVERAGE >= ${{ env.BADGES_THRESHOLD_SUCCESS }}" | bc -l) )); then GWY_COLOR="green" + elif (( $(echo "$GWY_COVERAGE >= ${{ env.BADGES_THRESHOLD_WARNING }}" | bc -l) )); then GWY_COLOR="yellow" + elif (( $(echo "$GWY_COVERAGE >= ${{ env.BADGES_THRESHOLD_BAD }}" | bc -l) )); then GWY_COLOR="orange" + fi + + # Output badges for all styles, one per line + SAVE_FAILED=false + for STYLE in flat flat-square plastic for-the-badge; do + SHIELDS_URL="https://img.shields.io/badge/Coverage-$GWY_COVERAGE%25-$GWY_COLOR?style=$STYLE" + if [ "${{ inputs.branch }}" = "" ]; then + BADGE_URL="$SHIELDS_URL" + else + if [ "$GWY_BADGES_URL" = "github.com" ]; then + BADGE_URL="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/coverage-$STYLE.svg" + else + BADGE_URL="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/coverage-$STYLE.svg" + fi + mkdir -p "$GWY_BADGES_TMP" + curl -s "$SHIELDS_URL" > "$GWY_BADGES_TMP/coverage-$STYLE.svg" + if [ ! -f "$GWY_BADGES_TMP/coverage-$STYLE.svg" ]; then + SAVE_FAILED=true + fi + fi + echo " [![coverage-$STYLE]($SHIELDS_URL)]($BADGE_URL)" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$SAVE_FAILED" = "true" ]; then + echo "Failed to save some badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to save some badge files, rely on Shields.io URLs or rerun the workflow" + fi + else + echo " **ERROR** - Failed to generate badge" >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::error generating coverage badge" + echo "" >> $GITHUB_STEP_SUMMARY + fi + + env: + GO_VERSION: ${{ env.GWY_GO_VERSION }} + + - name: Generate License Badge + continue-on-error: true + shell: bash + run: | + # Generate License Badge + + LICENSE_FILE="" + LICENSE_TYPE="" + LICENSE_FILES=("LICENSE" "LICENSE.md" "LICENSE.txt" "license" "license.md" "license.txt") + for file in "${LICENSE_FILES[@]}"; do + if [ -f "$file" ]; then + LICENSE_FILE="$file" + echo "Found license file: $LICENSE_FILE" + break + fi + done + + if [ -n "$LICENSE_FILE" ]; then + if grep -i "MIT License" "$LICENSE_FILE" > /dev/null; then + LICENSE_TYPE="MIT" + elif grep -i "Apache License" "$LICENSE_FILE" > /dev/null; then + LICENSE_TYPE="Apache-2.0" + elif grep -i "GNU General Public License" "$LICENSE_FILE" > /dev/null; then + if grep -i "version 3" "$LICENSE_FILE" > /dev/null; then + LICENSE_TYPE="GPL-3.0" + else + LICENSE_TYPE="GPL-2.0" + fi + elif grep -i "BSD License" "$LICENSE_FILE" > /dev/null; then + if grep -i "3-clause" "$LICENSE_FILE" > /dev/null; then + LICENSE_TYPE="BSD-3-Clause" + else + LICENSE_TYPE="BSD-2-Clause" + fi + else + LICENSE_TYPE="Unknown" + fi + + if [ "$LICENSE_TYPE" != "Unknown" ]; then + echo "**License:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + SAVE_FAILED=false + for STYLE in flat flat-square plastic for-the-badge; do + GREEN_URL="https://img.shields.io/badge/License-$LICENSE_TYPE-green?style=$STYLE" + BLUE_URL="https://img.shields.io/badge/License-$LICENSE_TYPE-blue?style=$STYLE" + ORANGE_URL="https://img.shields.io/badge/License-$LICENSE_TYPE-orange?style=$STYLE" + YELLOW_URL="https://img.shields.io/badge/License-$LICENSE_TYPE-yellow?style=$STYLE" + if [ "${{ inputs.branch }}" = "" ]; then + GREEN_LINK="$GREEN_URL" + BLUE_LINK="$BLUE_URL" + ORANGE_LINK="$ORANGE_URL" + YELLOW_LINK="$YELLOW_URL" + else + if [ "$GWY_BADGES_URL" = "github.com" ]; then + GREEN_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/license-green-$STYLE.svg" + BLUE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/license-blue-$STYLE.svg" + ORANGE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/license-orange-$STYLE.svg" + YELLOW_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/license-yellow-$STYLE.svg" + else + GREEN_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/license-green-$STYLE.svg" + BLUE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/license-blue-$STYLE.svg" + ORANGE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/license-orange-$STYLE.svg" + YELLOW_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/license-yellow-$STYLE.svg" + fi + mkdir -p "$GWY_BADGES_TMP" + curl -s "$GREEN_URL" > "$GWY_BADGES_TMP/license-green-$STYLE.svg" + curl -s "$BLUE_URL" > "$GWY_BADGES_TMP/license-blue-$STYLE.svg" + curl -s "$ORANGE_URL" > "$GWY_BADGES_TMP/license-orange-$STYLE.svg" + curl -s "$YELLOW_URL" > "$GWY_BADGES_TMP/license-yellow-$STYLE.svg" + if [ ! -f "$GWY_BADGES_TMP/license-green-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/license-blue-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/license-orange-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/license-yellow-$STYLE.svg" ]; then + SAVE_FAILED=true + fi + fi + echo " [![license-green-$STYLE]($GREEN_URL)]($GREEN_LINK) " \ + "[![license-blue-$STYLE]($BLUE_URL)]($BLUE_LINK) " \ + "[![license-orange-$STYLE]($ORANGE_URL)]($ORANGE_LINK) " \ + "[![license-yellow-$STYLE]($YELLOW_URL)]($YELLOW_LINK)" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$SAVE_FAILED" = "true" ]; then + echo "Failed to save some badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to save some badge files, rely on Shields.io URLs or rerun the workflow" + fi + else + echo "License type not recognized, skipping badge" + echo "::notice title=$GWY_BADGES_TITLE::license type not recognized, skipping badge generation" + echo "" >> $GITHUB_STEP_SUMMARY + fi + else + echo "No license file found, skipping license badge" + echo "::notice title=$GWY_BADGES_TITLE::no license file found, skipping license badge generation" + echo "" >> $GITHUB_STEP_SUMMARY + fi + + - name: Generate Build Badge + continue-on-error: true + shell: bash + run: | + # Generate Build Badge + echo "**Build:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + SAVE_FAILED=false + for STYLE in flat flat-square plastic for-the-badge; do + GREEN_URL="https://img.shields.io/badge/Build-Passing-green?style=$STYLE" + BLUE_URL="https://img.shields.io/badge/Build-Passing-blue?style=$STYLE" + ORANGE_URL="https://img.shields.io/badge/Build-Passing-orange?style=$STYLE" + YELLOW_URL="https://img.shields.io/badge/Build-Passing-yellow?style=$STYLE" + if [ "${{ inputs.branch }}" = "" ]; then + GREEN_LINK="$GREEN_URL" + BLUE_LINK="$BLUE_URL" + ORANGE_LINK="$ORANGE_URL" + YELLOW_LINK="$YELLOW_URL" + else + if [ "$GWY_BADGES_URL" = "github.com" ]; then + GREEN_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/build-green-$STYLE.svg" + BLUE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/build-blue-$STYLE.svg" + ORANGE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/build-orange-$STYLE.svg" + YELLOW_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/build-yellow-$STYLE.svg" + else + GREEN_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/build-green-$STYLE.svg" + BLUE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/build-blue-$STYLE.svg" + ORANGE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/build-orange-$STYLE.svg" + YELLOW_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/build-yellow-$STYLE.svg" + fi + mkdir -p "$GWY_BADGES_TMP" + curl -s "$GREEN_URL" > "$GWY_BADGES_TMP/build-green-$STYLE.svg" + curl -s "$BLUE_URL" > "$GWY_BADGES_TMP/build-blue-$STYLE.svg" + curl -s "$ORANGE_URL" > "$GWY_BADGES_TMP/build-orange-$STYLE.svg" + curl -s "$YELLOW_URL" > "$GWY_BADGES_TMP/build-yellow-$STYLE.svg" + if [ ! -f "$GWY_BADGES_TMP/build-green-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/build-blue-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/build-orange-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/build-yellow-$STYLE.svg" ]; then + SAVE_FAILED=true + fi + fi + echo " [![build-green-$STYLE]($GREEN_URL)]($GREEN_LINK) " \ + "[![build-blue-$STYLE]($BLUE_URL)]($BLUE_LINK) " \ + "[![build-orange-$STYLE]($ORANGE_URL)]($ORANGE_LINK) " \ + "[![build-yellow-$STYLE]($YELLOW_URL)]($YELLOW_LINK)" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$SAVE_FAILED" = "true" ]; then + echo "Failed to save some badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to save some badge files, rely on Shields.io URLs or rerun the workflow" + fi + + - name: Generate Downloads Badge + continue-on-error: true + shell: bash + run: | + # Generate Downloads Badge + echo "**Downloads:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + DOWNLOADS=$(curl -s -H "Authorization: Bearer ${{ inputs.token }}" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases" | \ + jq -r '[.[] | .assets[] | .download_count] | add // 0') + + if [ -n "$DOWNLOADS" ]; then + SAVE_FAILED=false + for STYLE in flat flat-square plastic for-the-badge; do + GREEN_URL="https://img.shields.io/badge/Downloads-$DOWNLOADS-green?style=$STYLE" + BLUE_URL="https://img.shields.io/badge/Downloads-$DOWNLOADS-blue?style=$STYLE" + ORANGE_URL="https://img.shields.io/badge/Downloads-$DOWNLOADS-orange?style=$STYLE" + YELLOW_URL="https://img.shields.io/badge/Downloads-$DOWNLOADS-yellow?style=$STYLE" + if [ "${{ inputs.branch }}" = "" ]; then + GREEN_LINK="$GREEN_URL" + BLUE_LINK="$BLUE_URL" + ORANGE_LINK="$ORANGE_URL" + YELLOW_LINK="$YELLOW_URL" + else + if [ "$GWY_BADGES_URL" = "github.com" ]; then + GREEN_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/downloads-green-$STYLE.svg" + BLUE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/downloads-blue-$STYLE.svg" + ORANGE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/downloads-orange-$STYLE.svg" + YELLOW_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/downloads-yellow-$STYLE.svg" + else + GREEN_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/downloads-green-$STYLE.svg" + BLUE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/downloads-blue-$STYLE.svg" + ORANGE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/downloads-orange-$STYLE.svg" + YELLOW_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/downloads-yellow-$STYLE.svg" + fi + mkdir -p "$GWY_BADGES_TMP" + curl -s "$GREEN_URL" > "$GWY_BADGES_TMP/downloads-green-$STYLE.svg" + curl -s "$BLUE_URL" > "$GWY_BADGES_TMP/downloads-blue-$STYLE.svg" + curl -s "$ORANGE_URL" > "$GWY_BADGES_TMP/downloads-orange-$STYLE.svg" + curl -s "$YELLOW_URL" > "$GWY_BADGES_TMP/downloads-yellow-$STYLE.svg" + if [ ! -f "$GWY_BADGES_TMP/downloads-green-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/downloads-blue-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/downloads-orange-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/downloads-yellow-$STYLE.svg" ]; then + SAVE_FAILED=true + fi + fi + echo " [![downloads-green-$STYLE]($GREEN_URL)]($GREEN_LINK) " \ + "[![downloads-blue-$STYLE]($BLUE_URL)]($BLUE_LINK) " \ + "[![downloads-orange-$STYLE]($ORANGE_URL)]($ORANGE_LINK) " \ + "[![downloads-yellow-$STYLE]($YELLOW_URL)]($YELLOW_LINK)" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$SAVE_FAILED" = "true" ]; then + echo "Failed to save some badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to save some badge files, rely on Shields.io URLs or rerun the workflow" + fi + else + echo " **ERROR** - Failed to generate badge" >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::error generating downloads badge" + echo "" >> $GITHUB_STEP_SUMMARY + fi + + - name: Generate Release Date Badge + continue-on-error: true + shell: bash + run: | + # Generate Release Date Badge + echo "**Release Date:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + LAST_COMMIT=$(git log -1 --format=%cd --date=short 2>/dev/null || echo "unknown") + LAST_COMMIT=${LAST_COMMIT//-/.} + + if [ -n "$LAST_COMMIT" ] && [ "$LAST_COMMIT" != "unknown" ]; then + SAVE_FAILED=false + for STYLE in flat flat-square plastic for-the-badge; do + GREEN_URL="https://img.shields.io/badge/Release-$LAST_COMMIT-green?style=$STYLE" + BLUE_URL="https://img.shields.io/badge/Release-$LAST_COMMIT-blue?style=$STYLE" + ORANGE_URL="https://img.shields.io/badge/Release-$LAST_COMMIT-orange?style=$STYLE" + YELLOW_URL="https://img.shields.io/badge/Release-$LAST_COMMIT-yellow?style=$STYLE" + if [ "${{ inputs.branch }}" = "" ]; then + GREEN_LINK="$GREEN_URL" + BLUE_LINK="$BLUE_URL" + ORANGE_LINK="$ORANGE_URL" + YELLOW_LINK="$YELLOW_URL" + else + if [ "$GWY_BADGES_URL" = "github.com" ]; then + GREEN_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/last-commit-green-$STYLE.svg" + BLUE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/last-commit-blue-$STYLE.svg" + ORANGE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/last-commit-orange-$STYLE.svg" + YELLOW_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/last-commit-yellow-$STYLE.svg" + else + GREEN_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/last-commit-green-$STYLE.svg" + BLUE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/last-commit-blue-$STYLE.svg" + ORANGE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/last-commit-orange-$STYLE.svg" + YELLOW_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/last-commit-yellow-$STYLE.svg" + fi + mkdir -p "$GWY_BADGES_TMP" + curl -s "$GREEN_URL" > "$GWY_BADGES_TMP/last-commit-green-$STYLE.svg" + curl -s "$BLUE_URL" > "$GWY_BADGES_TMP/last-commit-blue-$STYLE.svg" + curl -s "$ORANGE_URL" > "$GWY_BADGES_TMP/last-commit-orange-$STYLE.svg" + curl -s "$YELLOW_URL" > "$GWY_BADGES_TMP/last-commit-yellow-$STYLE.svg" + if [ ! -f "$GWY_BADGES_TMP/last-commit-green-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/last-commit-blue-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/last-commit-orange-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/last-commit-yellow-$STYLE.svg" ]; then + SAVE_FAILED=true + fi + fi + echo " [![last-commit-green-$STYLE]($GREEN_URL)]($GREEN_LINK) " \ + "[![last-commit-blue-$STYLE]($BLUE_URL)]($BLUE_LINK) " \ + "[![last-commit-orange-$STYLE]($ORANGE_URL)]($ORANGE_LINK) " \ + "[![last-commit-yellow-$STYLE]($YELLOW_URL)]($YELLOW_LINK)" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$SAVE_FAILED" = "true" ]; then + echo "Failed to save some badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to save some badge files, rely on Shields.io URLs or rerun the workflow" + fi + else + echo " **ERROR** - Failed to generate badge" >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::error generating release date badge" + echo "" >> $GITHUB_STEP_SUMMARY + fi + + - name: Generate Contributors Badge + continue-on-error: true + shell: bash + run: | + # Generate Contributors Badge + echo "**Contributors:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + CONTRIBUTORS=$(curl -s -H "Authorization: Bearer ${{ inputs.token }}" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/contributors" | jq -r 'length') + + if [ -n "$CONTRIBUTORS" ]; then + SAVE_FAILED=false + for STYLE in flat flat-square plastic for-the-badge; do + GREEN_URL="https://img.shields.io/badge/Contributors-$CONTRIBUTORS-green?style=$STYLE" + BLUE_URL="https://img.shields.io/badge/Contributors-$CONTRIBUTORS-blue?style=$STYLE" + ORANGE_URL="https://img.shields.io/badge/Contributors-$CONTRIBUTORS-orange?style=$STYLE" + YELLOW_URL="https://img.shields.io/badge/Contributors-$CONTRIBUTORS-yellow?style=$STYLE" + if [ "${{ inputs.branch }}" = "" ]; then + GREEN_LINK="$GREEN_URL" + BLUE_LINK="$BLUE_URL" + ORANGE_LINK="$ORANGE_URL" + YELLOW_LINK="$YELLOW_URL" + else + if [ "$GWY_BADGES_URL" = "github.com" ]; then + GREEN_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/contributors-green-$STYLE.svg" + BLUE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/contributors-blue-$STYLE.svg" + ORANGE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/contributors-orange-$STYLE.svg" + YELLOW_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/contributors-yellow-$STYLE.svg" + else + GREEN_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/contributors-green-$STYLE.svg" + BLUE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/contributors-blue-$STYLE.svg" + ORANGE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/contributors-orange-$STYLE.svg" + YELLOW_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/contributors-yellow-$STYLE.svg" + fi + mkdir -p "$GWY_BADGES_TMP" + curl -s "$GREEN_URL" > "$GWY_BADGES_TMP/contributors-green-$STYLE.svg" + curl -s "$BLUE_URL" > "$GWY_BADGES_TMP/contributors-blue-$STYLE.svg" + curl -s "$ORANGE_URL" > "$GWY_BADGES_TMP/contributors-orange-$STYLE.svg" + curl -s "$YELLOW_URL" > "$GWY_BADGES_TMP/contributors-yellow-$STYLE.svg" + if [ ! -f "$GWY_BADGES_TMP/contributors-green-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/contributors-blue-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/contributors-orange-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/contributors-yellow-$STYLE.svg" ]; then + SAVE_FAILED=true + fi + fi + echo " [![contributors-green-$STYLE]($GREEN_URL)]($GREEN_LINK) " \ + "[![contributors-blue-$STYLE]($BLUE_URL)]($BLUE_LINK) " \ + "[![contributors-orange-$STYLE]($ORANGE_URL)]($ORANGE_LINK) " \ + "[![contributors-yellow-$STYLE]($YELLOW_URL)]($YELLOW_LINK)" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$SAVE_FAILED" = "true" ]; then + echo "Failed to save some badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to save some badge files, rely on Shields.io URLs or rerun the workflow" + fi + else + echo " **ERROR** - Failed to generate badge" >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::error generating contributors badge" + echo "" >> $GITHUB_STEP_SUMMARY + fi + + - name: Generate Issues Badge + continue-on-error: true + shell: bash + run: | + # Generate Issues Badge + echo "**Issues:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + ISSUES=$(curl -s -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ inputs.token }}" \ + "https://api.github.com/search/issues?q=repo:${GITHUB_REPOSITORY}+is:issue+state:open" \ + | jq .total_count) + + if [ -n "$ISSUES" ]; then + SAVE_FAILED=false + for STYLE in flat flat-square plastic for-the-badge; do + GREEN_URL="https://img.shields.io/badge/Issues-$ISSUES-green?style=$STYLE" + BLUE_URL="https://img.shields.io/badge/Issues-$ISSUES-blue?style=$STYLE" + ORANGE_URL="https://img.shields.io/badge/Issues-$ISSUES-orange?style=$STYLE" + YELLOW_URL="https://img.shields.io/badge/Issues-$ISSUES-yellow?style=$STYLE" + if [ "${{ inputs.branch }}" = "" ]; then + GREEN_LINK="$GREEN_URL" + BLUE_LINK="$BLUE_URL" + ORANGE_LINK="$ORANGE_URL" + YELLOW_LINK="$YELLOW_URL" + else + if [ "$GWY_BADGES_URL" = "github.com" ]; then + GREEN_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/issues-green-$STYLE.svg" + BLUE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/issues-blue-$STYLE.svg" + ORANGE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/issues-orange-$STYLE.svg" + YELLOW_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/issues-yellow-$STYLE.svg" + else + GREEN_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/issues-green-$STYLE.svg" + BLUE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/issues-blue-$STYLE.svg" + ORANGE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/issues-orange-$STYLE.svg" + YELLOW_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/issues-yellow-$STYLE.svg" + fi + mkdir -p "$GWY_BADGES_TMP" + curl -s "$GREEN_URL" > "$GWY_BADGES_TMP/issues-green-$STYLE.svg" + curl -s "$BLUE_URL" > "$GWY_BADGES_TMP/issues-blue-$STYLE.svg" + curl -s "$ORANGE_URL" > "$GWY_BADGES_TMP/issues-orange-$STYLE.svg" + curl -s "$YELLOW_URL" > "$GWY_BADGES_TMP/issues-yellow-$STYLE.svg" + if [ ! -f "$GWY_BADGES_TMP/issues-green-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/issues-blue-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/issues-orange-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/issues-yellow-$STYLE.svg" ]; then + SAVE_FAILED=true + fi + fi + echo " [![issues-green-$STYLE]($GREEN_URL)]($GREEN_LINK) " \ + "[![issues-blue-$STYLE]($BLUE_URL)]($BLUE_LINK) " \ + "[![issues-orange-$STYLE]($ORANGE_URL)]($ORANGE_LINK) " \ + "[![issues-yellow-$STYLE]($YELLOW_URL)]($YELLOW_LINK)" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$SAVE_FAILED" = "true" ]; then + echo "Failed to save some badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to save some badge files, rely on Shields.io URLs or rerun the workflow" + fi + else + echo " **ERROR** - Failed to generate badge" >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::error generating issues badge" + echo "" >> $GITHUB_STEP_SUMMARY + fi + + - name: Generate Stars Badge + continue-on-error: true + shell: bash + run: | + # Generate Stars Badge + echo "**Stars:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + STARS=$(curl -s -H "Authorization: Bearer ${{ inputs.token }}" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}" | jq -r '.stargazers_count') + + if [ -n "$STARS" ]; then + SAVE_FAILED=false + for STYLE in flat flat-square plastic for-the-badge; do + GREEN_URL="https://img.shields.io/badge/Stars-$STARS-green?style=$STYLE" + BLUE_URL="https://img.shields.io/badge/Stars-$STARS-blue?style=$STYLE" + ORANGE_URL="https://img.shields.io/badge/Stars-$STARS-orange?style=$STYLE" + YELLOW_URL="https://img.shields.io/badge/Stars-$STARS-yellow?style=$STYLE" + if [ "${{ inputs.branch }}" = "" ]; then + GREEN_LINK="$GREEN_URL" + BLUE_LINK="$BLUE_URL" + ORANGE_LINK="$ORANGE_URL" + YELLOW_LINK="$YELLOW_URL" + else + if [ "$GWY_BADGES_URL" = "github.com" ]; then + GREEN_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/stars-green-$STYLE.svg" + BLUE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/stars-blue-$STYLE.svg" + ORANGE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/stars-orange-$STYLE.svg" + YELLOW_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/stars-yellow-$STYLE.svg" + else + GREEN_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/stars-green-$STYLE.svg" + BLUE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/stars-blue-$STYLE.svg" + ORANGE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/stars-orange-$STYLE.svg" + YELLOW_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/stars-yellow-$STYLE.svg" + fi + mkdir -p "$GWY_BADGES_TMP" + curl -s "$GREEN_URL" > "$GWY_BADGES_TMP/stars-green-$STYLE.svg" + curl -s "$BLUE_URL" > "$GWY_BADGES_TMP/stars-blue-$STYLE.svg" + curl -s "$ORANGE_URL" > "$GWY_BADGES_TMP/stars-orange-$STYLE.svg" + curl -s "$YELLOW_URL" > "$GWY_BADGES_TMP/stars-yellow-$STYLE.svg" + if [ ! -f "$GWY_BADGES_TMP/stars-green-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/stars-blue-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/stars-orange-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/stars-yellow-$STYLE.svg" ]; then + SAVE_FAILED=true + fi + fi + echo " [![stars-green-$STYLE]($GREEN_URL)]($GREEN_LINK) " \ + "[![stars-blue-$STYLE]($BLUE_URL)]($BLUE_LINK) " \ + "[![stars-orange-$STYLE]($ORANGE_URL)]($ORANGE_LINK) " \ + "[![stars-yellow-$STYLE]($YELLOW_URL)]($YELLOW_LINK)" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$SAVE_FAILED" = "true" ]; then + echo "Failed to save some badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to save some badge files, rely on Shields.io URLs or rerun the workflow" + fi + else + echo " **ERROR** - Failed to generate badge" >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::error generating stars badge" + echo "" >> $GITHUB_STEP_SUMMARY + fi + + - name: Generate Go Version Badge + continue-on-error: true + shell: bash + run: | + # Generate Go Version Badge + echo "**Go Version:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + GO_VERSION="${{ env.GWY_GO_VERSION }}" + if [ -z "$GO_VERSION" ]; then + GO_VERSION="unknown" + fi + + if [ "$GO_VERSION" != "unknown" ]; then + SAVE_FAILED=false + for STYLE in flat flat-square plastic for-the-badge; do + GREEN_URL="https://img.shields.io/badge/Go-$GO_VERSION-green?style=$STYLE" + BLUE_URL="https://img.shields.io/badge/Go-$GO_VERSION-blue?style=$STYLE" + ORANGE_URL="https://img.shields.io/badge/Go-$GO_VERSION-orange?style=$STYLE" + YELLOW_URL="https://img.shields.io/badge/Go-$GO_VERSION-yellow?style=$STYLE" + if [ "${{ inputs.branch }}" = "" ]; then + GREEN_LINK="$GREEN_URL" + BLUE_LINK="$BLUE_URL" + ORANGE_LINK="$ORANGE_URL" + YELLOW_LINK="$YELLOW_URL" + else + if [ "$GWY_BADGES_URL" = "github.com" ]; then + GREEN_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/go-version-green-$STYLE.svg" + BLUE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/go-version-blue-$STYLE.svg" + ORANGE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/go-version-orange-$STYLE.svg" + YELLOW_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/go-version-yellow-$STYLE.svg" + else + GREEN_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/go-version-green-$STYLE.svg" + BLUE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/go-version-blue-$STYLE.svg" + ORANGE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/go-version-orange-$STYLE.svg" + YELLOW_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/go-version-yellow-$STYLE.svg" + fi + mkdir -p "$GWY_BADGES_TMP" + curl -s "$GREEN_URL" > "$GWY_BADGES_TMP/go-version-green-$STYLE.svg" + curl -s "$BLUE_URL" > "$GWY_BADGES_TMP/go-version-blue-$STYLE.svg" + curl -s "$ORANGE_URL" > "$GWY_BADGES_TMP/go-version-orange-$STYLE.svg" + curl -s "$YELLOW_URL" > "$GWY_BADGES_TMP/go-version-yellow-$STYLE.svg" + if [ ! -f "$GWY_BADGES_TMP/go-version-green-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/go-version-blue-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/go-version-orange-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/go-version-yellow-$STYLE.svg" ]; then + SAVE_FAILED=true + fi + fi + echo " [![go-version-green-$STYLE]($GREEN_URL)]($GREEN_LINK) " \ + "[![go-version-blue-$STYLE]($BLUE_URL)]($BLUE_LINK) " \ + "[![go-version-orange-$STYLE]($ORANGE_URL)]($ORANGE_LINK) " \ + "[![go-version-yellow-$STYLE]($YELLOW_URL)]($YELLOW_LINK)" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$SAVE_FAILED" = "true" ]; then + echo "Failed to save some badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to save some badge files, rely on Shields.io URLs or rerun the workflow" + fi + else + echo " **ERROR** - Failed to generate badge" >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::error generating go version badge" + echo "" >> $GITHUB_STEP_SUMMARY + fi + + - name: Generate GWY Badge + continue-on-error: true + shell: bash + run: | + # Generate GWY Badge + echo "**Powered by:**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + SAVE_FAILED=false + for STYLE in flat flat-square plastic for-the-badge; do + GREEN_URL="https://img.shields.io/badge/Powered%20by-GWY-green?style=$STYLE" + BLUE_URL="https://img.shields.io/badge/Powered%20by-GWY-blue?style=$STYLE" + ORANGE_URL="https://img.shields.io/badge/Powered%20by-GWY-orange?style=$STYLE" + YELLOW_URL="https://img.shields.io/badge/Powered%20by-GWY-yellow?style=$STYLE" + if [ "${{ inputs.branch }}" = "" ]; then + GREEN_LINK="$GREEN_URL" + BLUE_LINK="$BLUE_URL" + ORANGE_LINK="$ORANGE_URL" + YELLOW_LINK="$YELLOW_URL" + else + if [ "$GWY_BADGES_URL" = "github.com" ]; then + GREEN_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/gwy-green-$STYLE.svg" + BLUE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/gwy-blue-$STYLE.svg" + ORANGE_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/gwy-orange-$STYLE.svg" + YELLOW_LINK="https://github.com/${GITHUB_REPOSITORY}/blob/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/gwy-yellow-$STYLE.svg" + else + GREEN_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/gwy-green-$STYLE.svg" + BLUE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/gwy-blue-$STYLE.svg" + ORANGE_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/gwy-orange-$STYLE.svg" + YELLOW_LINK="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/$GWY_BADGES_BRANCH/${{ inputs.directory }}/$SANITIZED_BRANCH/gwy-yellow-$STYLE.svg" + fi + mkdir -p "$GWY_BADGES_TMP" + curl -s "$GREEN_URL" > "$GWY_BADGES_TMP/gwy-green-$STYLE.svg" + curl -s "$BLUE_URL" > "$GWY_BADGES_TMP/gwy-blue-$STYLE.svg" + curl -s "$ORANGE_URL" > "$GWY_BADGES_TMP/gwy-orange-$STYLE.svg" + curl -s "$YELLOW_URL" > "$GWY_BADGES_TMP/gwy-yellow-$STYLE.svg" + if [ ! -f "$GWY_BADGES_TMP/gwy-green-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/gwy-blue-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/gwy-orange-$STYLE.svg" ] || \ + [ ! -f "$GWY_BADGES_TMP/gwy-yellow-$STYLE.svg" ]; then + SAVE_FAILED=true + fi + fi + echo " [![gwy-green-$STYLE]($GREEN_URL)]($GREEN_LINK) " \ + "[![gwy-blue-$STYLE]($BLUE_URL)]($BLUE_LINK) " \ + "[![gwy-orange-$STYLE]($ORANGE_URL)]($ORANGE_LINK) " \ + "[![gwy-yellow-$STYLE]($YELLOW_URL)]($YELLOW_LINK)" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + if [ "$SAVE_FAILED" = "true" ]; then + echo "Failed to save some badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to save some badge files, rely on Shields.io URLs or rerun the workflow" + fi + + - name: Publish Badges + if: inputs.branch != '' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ inputs.token }} + publish_dir: ${{ env.GWY_BADGES_TMP }} + publish_branch: ${{ env.GWY_BADGES_BRANCH }} + destination_dir: ${{ inputs.directory }}/${{ env.SANITIZED_BRANCH }} + commit_message: "[GWY/CI] latest release badges generation" + continue-on-error: true + id: publish-badges + + - name: Check Publish Status + if: inputs.branch != '' + shell: bash + run: | + if [ "${{ steps.publish-badges.outcome }}" != "success" ]; then + echo "Failed to commit badge files, rely on Shields.io URLs or rerun the workflow." >> $GITHUB_STEP_SUMMARY + echo "::error title=$GWY_BADGES_TITLE::Failed to commit badge files, rely on Shields.io URLs or rerun the workflow" + fi diff --git a/.github/actions/gwy/coverage/action.yml b/.github/actions/gwy/coverage/action.yml new file mode 100644 index 0000000..d2b0ccd --- /dev/null +++ b/.github/actions/gwy/coverage/action.yml @@ -0,0 +1,154 @@ +name: 'Unit Tests & Coverage' +description: 'Runs Go tests, checks coverage and updates badge' + +inputs: + threshold: + description: 'Minimum coverage threshold (%)' + required: true + + token: + description: 'GitHub token for pushing badges' + required: false + + summary-threshold: + description: 'Minimum coverage threshold (%) of functions showed in summary' + required: false + default: '51' + +outputs: + result: + description: 'Result of the coverage check (success or failed)' + value: ${{ steps.check-coverage.outputs.result }} + +runs: + using: 'composite' + steps: + - name: Run Tests with Coverage + shell: bash + run: | + # Running unit tests and coverage + GWY_COVER=cover.out + echo "GWY_COVER=$GWY_COVER" >> $GITHUB_ENV + + echo "GWY_ARTIFACT=$RUNNER_TEMP/coverage-report.md" >> $GITHUB_ENV + echo "GWY_SUMMARY=$RUNNER_TEMP/coverage-summary.md" >> $GITHUB_ENV + + # set annotations reusable title + echo "GWY_TITLE=coverage threshold check" >> $GITHUB_ENV + + # run unit tests and generate coverage dump + GWY_GO_TEST="$(timeout $GWY_TIMEOUT_SECONDS go test ./... -coverprofile="$GWY_COVER" -covermode=atomic -coverpkg=./... 2>&1)" + echo "GWY_GO_TEST<> $GITHUB_ENV + echo "$GWY_GO_TEST" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + echo "$GWY_GO_TEST" + + # process coverage dump + GWY_GO_TOOL=$(go tool cover -func="$GWY_COVER" 2>&1) + echo "GWY_GO_TOOL<> $GITHUB_ENV + echo "$GWY_GO_TOOL" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + echo "$GWY_GO_TOOL" + + # process coverage results + # timeout $GWY_TIMEOUT_SECONDS go tool cover -func=$GWY_COVER | grep "total:" | awk '{print "Total Coverage: " $3}' + GWY_COVERAGE=$(echo "$GWY_GO_TOOL" | grep 'total:' | awk '{print $3}' | sed 's/%//' | xargs printf "%.0f") + echo "GWY_COVERAGE=$GWY_COVERAGE" >> $GITHUB_ENV + + echo "Coverage Total: $GWY_COVERAGE" + + - name: Check Coverage Threshold + id: check-coverage + continue-on-error: true + + shell: bash + run: | + # Checking Coverage Threshold + GWY_THRESHOLD=${{ inputs.threshold }} + GWY_TOTAL_COVERAGE=${{ env.GWY_COVERAGE }} + + if (( $(echo "$GWY_TOTAL_COVERAGE < $GWY_THRESHOLD" | bc -l) )); then + echo "::error title=$GWY_TITLE: NOT MET::coverage ($GWY_TOTAL_COVERAGE%) is below threshold ($GWY_THRESHOLD%)" + echo "Coverage ($GWY_TOTAL_COVERAGE%) is below threshold ($GWY_THRESHOLD%)" + echo "result=failed" >> $GITHUB_OUTPUT + else + echo "::notice title=$GWY_TITLE: success::coverage ($GWY_TOTAL_COVERAGE%) meets threshold ($GWY_THRESHOLD%)" + echo "congratulations! coverage ($GWY_TOTAL_COVERAGE%) meets threshold ($GWY_THRESHOLD%)" + echo "result=success" >> $GITHUB_OUTPUT + fi + + - name: Add Coverage Summary to Job + if: always() + continue-on-error: true + shell: bash + run: | + # Generating Coverage Summary + echo "## Code Coverage" >> $GWY_SUMMARY + + # Capture coverage output + GWY_COVERAGE_LINES=$(echo "$GWY_GO_TOOL" | grep -v "total:") + + # Check if GWY_COVERAGE_LINES is empty + if [ -z "$GWY_COVERAGE_LINES" ]; then + echo " - no tests found in repository" >> $GWY_SUMMARY + else + echo "$GWY_COVERAGE_LINES" | while IFS=: read -r full_path line rest; do + file=$(basename "$full_path") + relative_path=$(echo "$full_path" | sed 's|github.com/[^/]\+/[^/]\+/||') + func_percent=$(echo "$rest" | sed 's/^\s*//') + func=$(echo "$func_percent" | awk '{print $1}') + percent=$(echo "$func_percent" | awk '{print $2}' | sed 's/%$//') # Strip % for comparison + # Compare coverage against threshold (convert to int for -lt) + if [ $(echo "$percent < ${{ inputs.summary-threshold }}" | bc) -eq 1 ]; then + url="https://github.com/${GITHUB_REPOSITORY}/blob/${GWY_BRANCH}/${relative_path}#L${line}" + printf ' - `%-8s` [%s()](%s)\n' "${percent}%" "$func" "$url" >> $GWY_SUMMARY + fi + done + + echo -e " - \`COVERAGE\` ${{ env.GWY_COVERAGE }}%" >> $GWY_SUMMARY + + if [ ${{ env.GWY_COVERAGE }} -lt ${{ inputs.threshold }} ]; then + echo -e "\n \n![RESULT:FAILED](https://img.shields.io/badge/result-failed-red?style=for-the-badge)" >> $GWY_SUMMARY + else + echo -e "\n \n![RESULT:SUCCESS](https://img.shields.io/badge/result-success-green?style=for-the-badge)" >> $GWY_SUMMARY + fi + fi + + cat $GWY_SUMMARY >> $GITHUB_STEP_SUMMARY + + # append to artifact coverage output + + cat $GWY_SUMMARY_HEADING > $GWY_ARTIFACT + cat $GWY_SUMMARY >> $GWY_ARTIFACT + + echo -e "\n\n## Coverage Output\n" >> $GWY_ARTIFACT + echo -e "### go test\n \n\`\`\`\n$GWY_GO_TEST\n\`\`\`\n" >> $GWY_ARTIFACT + echo -e "### go tool cover\n \n\`\`\`\n$GWY_GO_TOOL\n\`\`\`\n" >> $GWY_ARTIFACT + + echo -e "### cover.out\n \n\`\`\`\n" >> $GWY_ARTIFACT + cat $GWY_COVER >> $GWY_ARTIFACT + echo -e "\n\`\`\`\n" >> $GWY_ARTIFACT + + - name: Upload Coverage Summary Artifact + if: always() + continue-on-error: true + + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: ${{ env.GWY_ARTIFACT }} + + - name: Action clean-up + shell: bash + run: | + # Cleaning Action Environment + unset GWY_TITLE + unset GWY_COLOR + unset GWY_COVERAGE + unset GWY_THRESHOLD + unset GWY_TOTAL_COVERAGE + unset GWY_COVERAGE_LINES + + rm -f cover.out diff --git a/.github/actions/gwy/dependencies/action.yml b/.github/actions/gwy/dependencies/action.yml new file mode 100644 index 0000000..c4f40ad --- /dev/null +++ b/.github/actions/gwy/dependencies/action.yml @@ -0,0 +1,162 @@ +name: 'Dependencies Scan' +description: 'Scans for outdated Go dependencies and optionally creates a PR with updates' + +inputs: + token: + description: 'Repository token for PR creation (required if create-pr is true)' + required: false + + create-pr: + description: 'Create a PR with outdated dependency updates?' + required: true + default: 'true' + +outputs: + result: + description: 'Result of the action (success or failed)' + value: ${{ steps.check-dependencies.outputs.result }} + +runs: + using: 'composite' + steps: + - name: Initialize Dependencies Scanning Environment + shell: bash + run: | + # Initialize Dependencies Scanning Environment + GWY_SUMMARY=$RUNNER_TEMP/dependencies-summary.txt + echo -e "## Outdated Dependencies Scan\n" >> $GWY_SUMMARY + + # set annotations reusable title + echo "GWY_TITLE=outdated dependencies scan" >> $GITHUB_ENV + + # export reusable environments + echo "GWY_SUMMARY=$GWY_SUMMARY" >> $GITHUB_ENV + echo "GWY_DEPENDENCIES_COUNT=0" >> $GITHUB_ENV + echo "GWY_ARTIFACT=$RUNNER_TEMP/dependencies-report.md" >> $GITHUB_ENV + echo "GWY_BRANCH=$(git branch --show-current)" >> $GITHUB_ENV + sync + + - name: Check Outdated Dependencies + id: check-dependencies + continue-on-error: true + + # Allow pipeline to keep going on errors (+e) + shell: bash --noprofile --norc +e -o pipefail {0} + run: | + # Check Outdated Dependencies + + # Update dependencies and tidy, suppress output + timeout $GWY_TIMEOUT_SECONDS sh -c "go get -u ./... 2>&1 | grep '^go: upgraded ' | sed 's/go: upgraded //'" + + # Save dependency updates found for report + GWY_DEPENDENCIES_FOUND=$(git diff -U0 go.mod | grep -E '^\-[^+-]') + + # GWY_DEPENDENCIES_COUNT=$(echo "$GWY_DEPENDENCIES_FOUND" | wc -l) + # process outdated dependencies count + if [ -z "$GWY_DEPENDENCIES_FOUND" ]; then + GWY_DEPENDENCIES_COUNT=0 + else + GWY_DEPENDENCIES_COUNT=$(echo "$GWY_DEPENDENCIES_FOUND" | wc -l) + fi + + echo "GWY_DEPENDENCIES_COUNT=$GWY_DEPENDENCIES_COUNT" >> $GITHUB_ENV + + # set action result based in outdated dependencies count + if [ "$GWY_DEPENDENCIES_COUNT" -ne 0 ]; then + echo "result=failed" >> $GITHUB_OUTPUT + else + echo "result=success" >> $GITHUB_OUTPUT + fi + + # Write diff to artifact + git diff go.mod > $GWY_ARTIFACT + + # remove go.mod changes if no pr creation was requested + if [ "${{ inputs.create-pr }}" != "true" ]; then + echo "unstaging changes" + git restore go.mod go.sum + fi + + # Enhance summary, annotations, and debug output + if [ $GWY_DEPENDENCIES_COUNT -ne 0 ]; then + # Error annotation with count + echo "::error title=$GWY_TITLE: $GWY_DEPENDENCIES_COUNT OUTDATED DEPENDENCIES!::check summary or artifact report for more details" + + # Add to summary + echo "$GWY_DEPENDENCIES_FOUND" | sed 's/^/ /' >> $GWY_SUMMARY + + else + # Success annotation + echo "::notice title=$GWY_TITLE: up-to-date::code dependencies in go.mod are up-to-date" + + # Add to summary + echo " - no outdated dependencies detected" >> $GWY_SUMMARY + fi + + cat $GWY_SUMMARY >> $GITHUB_STEP_SUMMARY + + # append to artifact dependencies scan output + + echo -e "\n\n## Dependencies Scan Output\n\n\`\`\`" >> $GWY_SUMMARY + cat $GWY_ARTIFACT >> $GWY_SUMMARY + echo -e "\`\`\`\n" >> $GWY_SUMMARY + + cat $GWY_SUMMARY_HEADING > $GWY_ARTIFACT + cat $GWY_SUMMARY >> $GWY_ARTIFACT + + - name: Create PR with Updates + id: create-pr + if: env.GWY_DEPENDENCIES_COUNT != '0' && inputs.create-pr == 'true' + + continue-on-error: true + uses: peter-evans/create-pull-request@v5 + + with: + token: ${{ inputs.token }} + title: "[GWY/CI] Automatic outdated dependencies fix (${{ env.GWY_BRANCH }})" + body: "Automatic update of ${{ env.GWY_DEPENDENCIES_COUNT }} outdated dependencies" + commit-message: "[GWY/CI] automatic fix of ${{ env.GWY_DEPENDENCIES_COUNT }} outdated dependencies" + branch: ${{ env.GWY_BRANCH }}-dependencies-fix + + - name: Notify PR Creation Result + if: env.GWY_DEPENDENCIES_COUNT != '0' && inputs.create-pr == 'true' + + # Allow pipeline to keep going on errors (+e) + shell: bash --noprofile --norc +e -o pipefail {0} + continue-on-error: true + + run: | + # Check if PR was created successfully + if [ "${{ steps.create-pr.outcome }}" == "success" ]; then + echo "::notice title=$GWY_TITLE: PR created::PR with code outdated dependencies update created" + else + echo "::error title=$GWY_TITLE: ERROR CREATING PULL REQUEST::Failed to create PR with dependencies updates" + fi + + - name: Upload Outdated Dependencies Report + if: always() + continue-on-error: true + + uses: actions/upload-artifact@v4 + with: + name: dependencies-report + path: ${{ env.GWY_ARTIFACT }} + + - name: Step Clean-Up + if: always() + shell: bash + run: | + # Dependencies Scanning Clean-Up + + # remove temporary files + rm -f $GWY_SUMMARY + rm -f $GWY_ARTIFACT + + # Unset variables + unset GWY_TITLE + unset GWY_SUMMARY + unset GWY_ARTIFACT + unset GWY_PIPELINE_STATUS + unset GWY_TIMEOUT_SECONDS + unset GWY_DEPENDENCIES_FOUND + unset GWY_DEPENDENCIES_COUNT diff --git a/.github/actions/gwy/gofmt/action.yml b/.github/actions/gwy/gofmt/action.yml new file mode 100644 index 0000000..8c5fd39 --- /dev/null +++ b/.github/actions/gwy/gofmt/action.yml @@ -0,0 +1,164 @@ +name: 'Code Format Scan' +description: 'Checks Go code formatting with gofmt and optionally creates a PR with fixes' + +inputs: + token: + description: 'Repository token for PR creation' + required: false + + create-pr: + description: 'Create a PR with gofmt fixes?' + required: true + default: 'true' + +outputs: + result: + description: 'Result of the gofmt check (success or failed)' + value: ${{ steps.check-gofmt.outputs.result }} + +runs: + using: 'composite' + steps: + - name: Initialize gofmt Check Environment + shell: bash + run: | + # Initialize Code Format (gofmt) Check Environment + GWY_SUMMARY=$RUNNER_TEMP/gofmt-summary.txt + echo -e "## Format Scan" >> $GWY_SUMMARY + + # set annotations reusable title + echo "GWY_TITLE=code format check" >> $GITHUB_ENV + + echo "GWY_SUMMARY=$GWY_SUMMARY" >> $GITHUB_ENV + echo "GWY_FORMATTING_ISSUES=0" >> $GITHUB_ENV + echo "GWY_ARTIFACT=$RUNNER_TEMP/format-report.md" >> $GITHUB_ENV + echo "GWY_BRANCH=$(git branch --show-current)" >> $GITHUB_ENV + sync + + - name: Check gofmt Formatting + id: check-gofmt + shell: bash --noprofile --norc +e -o pipefail {0} + continue-on-error: true + run: | + # Check gofmt Formatting + + # Run gofmt and capture files needing fixes + GWY_FORMATTING_ISSUES=$(timeout $GWY_TIMEOUT_SECONDS gofmt -l . | wc -l) + echo "GWY_FORMATTING_ISSUES=$GWY_FORMATTING_ISSUES" >> $GITHUB_ENV + + # Generate list of files with required changes + GWY_FORMATTING_FILES=$(gofmt -l .) + + # Apply fixes and capture diff if issues exist + if [ $GWY_FORMATTING_ISSUES -ne 0 ]; then + timeout $GWY_TIMEOUT_SECONDS gofmt -w . + git diff -U0 > $GWY_ARTIFACT + else + echo "" > $GWY_ARTIFACT + fi + + # Undo fixes if no PR requested + if [ ${{ inputs.create-pr }} != "true" ] && [ $GWY_FORMATTING_ISSUES -ne 0 ]; then + git restore . + fi + + # Set action result based on formatting issues + if [ $GWY_FORMATTING_ISSUES -ne 0 ]; then + echo "result=failed" >> $GITHUB_OUTPUT + else + echo "result=success" >> $GITHUB_OUTPUT + fi + + # Enhance summary, annotations, and debug output + if [ $GWY_FORMATTING_ISSUES -ne 0 ]; then + echo "::error title=$GWY_TITLE: $GWY_FORMATTING_ISSUES FORMATTING ISSUES FOUND::check summary or artifact report for more details" + # Parse diff for exact change lines + awk ' + BEGIN { RS = "diff --git"; FS = "\n" } + /^ a/ { + file = substr($1, 3) + sub(/^a\//, "", file) + sub(/ b\/.*/, "", file) + for (i = 1; i <= NF; i++) { + if ($i ~ /^@@ -[0-9]+ \+[0-9]+ @@/) { + line = $i + sub(/^@@ -[0-9]+ \+/, "", line) + sub(/ .*/, "", line) + break + } + } + for (i = 1; i <= NF; i++) { + if ($i ~ /^\+/) { + if ($i !~ /^\+ *$/) { + printf " - [%s](https://github.com/%s/blob/%s/%s#L%s)\n", file, "'"$GITHUB_REPOSITORY"'", "'"$GWY_BRANCH"'", file, line >> "'"$GWY_SUMMARY"'" + } + break + } + } + } + ' $GWY_ARTIFACT + else + echo "::notice title=$GWY_TITLE: compliant code format::codebase is properly formatted" + echo " - no formatting issues detected" >> $GWY_SUMMARY + fi + + cat $GWY_SUMMARY >> $GITHUB_STEP_SUMMARY + + # append to artifact gofmt diff output + + echo -e "\n\n## Format Fix Diff Output\n\n\`\`\`" >> $GWY_SUMMARY + cat $GWY_ARTIFACT >> $GWY_SUMMARY + echo -e "\`\`\`\n" >> $GWY_SUMMARY + + cat $GWY_SUMMARY_HEADING > $GWY_ARTIFACT + cat $GWY_SUMMARY >> $GWY_ARTIFACT + + - name: Create PR with gofmt Fixes + id: create-pr + if: inputs.create-pr == 'true' && env.GWY_FORMATTING_ISSUES != '0' + continue-on-error: true + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ inputs.token }} + title: "[GWY/CI] Automatic code format fix (${{ env.GWY_BRANCH }})" + body: "Fixes formatting issues in branch ${{ env.GWY_BRANCH }} found in ${{ env.GWY_FORMATTING_ISSUES }} files" + commit-message: "[GWY/CI] Automatic gofmt fixes in ${{ env.GWY_FORMATTING_ISSUES }} files" + branch: ${{ env.GWY_BRANCH }}-gofmt-fix + + - name: Notify PR Creation Result + if: inputs.create-pr == 'true' && env.GWY_FORMATTING_ISSUES != '0' + continue-on-error: true + + shell: bash --noprofile --norc +e -o pipefail {0} + run: | + # Check if PR was created successfully + if [ "${{ steps.create-pr.outcome }}" == "success" ]; then + echo "::notice title=$GWY_TITLE: PR created::PR with code format (gofmt) fixes created" + else + echo "::error title=$GWY_TITLE: ERROR CREATING PR::failed to create PR with code format (gofmt) fixes" + fi + + - name: Upload gofmt Changes Artifact + if: always() + uses: actions/upload-artifact@v4 + continue-on-error: true + with: + name: gofmt-report + path: ${{ env.GWY_ARTIFACT }} + + - name: Step Clean-Up + if: always() + shell: bash + run: | + # gofmt Check Clean-Up + rm $GWY_ARTIFACT + rm $GWY_SUMMARY + + # Unset variables + unset GWY_TITLE + unset GWY_BRANCH + unset GWY_SUMMARY + unset GWY_ARTIFACT + unset GWY_TIMEOUT_SECONDS + unset GWY_FORMATTING_FILES + unset GWY_FORMATTING_ISSUES \ No newline at end of file diff --git a/.github/actions/gwy/golint/action.yml b/.github/actions/gwy/golint/action.yml new file mode 100644 index 0000000..a44d4c6 --- /dev/null +++ b/.github/actions/gwy/golint/action.yml @@ -0,0 +1,118 @@ +name: 'Code Linting Scan' +description: 'Checks Go code with golangci-lint and optionally creates a PR with fixes' + +inputs: + token: + description: 'Repository token for PR creation (required if create-pr is true)' + required: false + +outputs: + result: + description: 'Result of the linting check (success or failed)' + value: ${{ steps.check-golint.outputs.result }} + +runs: + using: 'composite' + steps: + - name: Initialize Lint Check Environment + shell: bash + run: | + GWY_SUMMARY=$RUNNER_TEMP/lint-summary.txt + echo -e "## Linting Scan" >> $GWY_SUMMARY + + # set annotations reusable title + echo "GWY_TITLE=code linting check" >> $GITHUB_ENV + + echo "GWY_LINT_ISSUES=0" >> $GITHUB_ENV + echo "GWY_SUMMARY=$GWY_SUMMARY" >> $GITHUB_ENV + echo "GWY_BRANCH=$(git branch --show-current)" >> $GITHUB_ENV + echo "GWY_ARTIFACT=$RUNNER_TEMP/lint-report.md" >> $GITHUB_ENV + + - name: Install golangci-lint + shell: bash + run: | + # Install golangci-lint + + # Go is already set up, just install golangci-lint + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.61.0 + export PATH="$(go env GOPATH)/bin:$PATH" + + # Verify installation + golangci-lint --version + + - name: Check Linting Issues + id: check-golint + shell: bash --noprofile --norc +e -o pipefail {0} + continue-on-error: true + run: | + # Check code linting issues + golangci-lint run --out-format=line-number --timeout=${{ env.GWY_TIMEOUT_SECONDS }}s --issues-exit-code=0 > ${{ env.GWY_ARTIFACT }} 2>&1 + + # Parse linting issues count + GWY_LINT_ISSUES=$(grep -E '[^/]+\.go:[0-9]+:' ${{ env.GWY_ARTIFACT }} | wc -l) + echo "GWY_LINT_ISSUES=$GWY_LINT_ISSUES" >> $GITHUB_ENV + + if [ $GWY_LINT_ISSUES -ne 0 ]; then + # Set step result + echo "result=failed" >> $GITHUB_OUTPUT + + # Add linting issues count annotation + echo "::error title=$GWY_TITLE: $GWY_LINT_ISSUES ISSUES FOUND!::check summary or artifact report for more details" + + # Parse lint output and generate summary with links + awk ' + /^[a-zA-Z0-9\/_-]+\.go:[0-9]+:[0-9]+:/ { + split($1, parts, ":") + full_file = parts[1] + line = parts[2] + # Extract just the filename (strip path) + split(full_file, path_parts, "/") + file = path_parts[length(path_parts)] + # Extract description, remove backticks and trailing parentheses + desc = substr($0, index($0, ": ") + 2) + gsub(/`/, "", desc) + sub(/ \([^)]+\)$/, "", desc) # Remove trailing (text) + # Generate Markdown + printf " - [%s](https://github.com/%s/blob/%s/%s#L%s): `%s`\n", file, "'"$GITHUB_REPOSITORY"'", "'"$GWY_BRANCH"'", full_file, line, desc >> "'"$GWY_SUMMARY"'" + } + ' ${{ env.GWY_ARTIFACT }} + + else + # Set step result + echo "result=success" >> $GITHUB_OUTPUT + + # Generate successful summary and annotation + echo "::notice title=$GWY_TITLE::no linting issues found in codebase" + echo " - no linting issues detected" >> $GWY_SUMMARY + fi + + cat $GWY_SUMMARY >> $GITHUB_STEP_SUMMARY + + # append to artifact secrets scan output + echo -e "\n\n## Linting Scan Output\n\n\`\`\`" >> $GWY_SUMMARY + cat $GWY_ARTIFACT >> $GWY_SUMMARY + echo -e "\`\`\`\n" >> $GWY_SUMMARY + + cat $GWY_SUMMARY_HEADING > $GWY_ARTIFACT + cat $GWY_SUMMARY >> $GWY_ARTIFACT + + - name: Upload Lint Report Artifact + if: always() + uses: actions/upload-artifact@v4 + continue-on-error: true + with: + name: lint-report + path: ${{ env.GWY_ARTIFACT }} + + - name: Step Clean-Up + if: always() + shell: bash + run: | + rm -f $GWY_ARTIFACT + rm -f $GWY_SUMMARY + + unset GWY_TITLE + unset GWY_BRANCH + unset GWY_SUMMARY + unset GWY_ARTIFACT + unset GWY_LINT_ISSUES \ No newline at end of file diff --git a/.github/actions/gwy/release-aws/action.yml b/.github/actions/gwy/release-aws/action.yml new file mode 100644 index 0000000..1dac163 --- /dev/null +++ b/.github/actions/gwy/release-aws/action.yml @@ -0,0 +1,321 @@ +name: 'Release Image to AWS' +description: 'Builds and pushes a Docker image to a target repository' + +inputs: + timeout: + description: 'Timeout (e.g., 5m)' + required: true + region: + description: 'Release deployment region' + required: true + +outputs: + result: + description: 'Release result (success or failed)' + value: ${{ steps.release-aws.outputs.result }} + +runs: + using: 'composite' + steps: + - name: Build and Push Docker Image + id: release-aws + continue-on-error: true + shell: bash --noprofile --norc +e -o pipefail {0} + run: | + # build and push release image to ECR + GWY_SUMMARY=$GITHUB_STEP_SUMMARY + + GWY_ARTIFACT=$RUNNER_TEMP/release-report.md + echo "GWY_ARTIFACT=$GWY_ARTIFACT" >> $GITHUB_ENV + + GWY_TIMEOUT_SECONDS=$(echo "${{ inputs.timeout }}" | sed 's/m/*60/;s/h/*3600/;s/d/*86400/' | bc) + + # Hardcode AWS region for now + AWS_REGION=${{ inputs.region }} + echo "AWS_REGION=$AWS_REGION" >> $GITHUB_ENV + + # Validate required symbols + if [ -z "$GWY_RELEASE_TOKEN" ] || [ -z "$GWY_RELEASE_REPOSITORY" ]; then + echo "Missing environment symbols for '${{ github.event.inputs.environment }}'" >&2 + echo "result=failed" >> $GITHUB_OUTPUT + echo "::error title=Missing Symbols::Missing required AWS symbols (token or repository) for environment '${{ github.event.inputs.environment }}'." + + # save release specific error for later summary report section + echo "GWY_RELEASE_ERROR<> $GITHUB_ENV + echo -e "Missing AWS symbol 'GWY_RELEASE_TOKEN' or 'GWY_RELEASE_REPOSITORY' for '${{ github.event.inputs.environment }}' environment" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + exit 0 + fi + + # Validate token format (expecting key:secret) + if ! echo "$GWY_RELEASE_TOKEN" | grep -q "^[^:]*:[^:]*$"; then + echo "Invalid secret 'GWY_TOKEN_AWS' format, ensure key/secret is separated by ':' (key:secret). Kindly check documentation config section for more details" >&2 + echo "result=failed" >> $GITHUB_OUTPUT + echo "::error title=Invalid 'GWY_RELEASE_TOKEN' Token Format::secret token must be in 'key:secret' format for '${{ github.event.inputs.environment }}' environment" + + # save release specific error for later summary report section + echo "GWY_RELEASE_ERROR<> $GITHUB_ENV + echo -e "Invalid 'GWY_RELEASE_TOKEN' token format for '${{ github.event.inputs.environment }}' environment" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + exit 0 + fi + + # Validate repository format (simple: alphanumeric, dashes, underscores) + if ! echo "$GWY_RELEASE_REPOSITORY" | grep -q "^[a-zA-Z0-9_-]\+$"; then + echo "Invalid repository format. Expected alphanumeric with dashes/underscores, got: $GWY_RELEASE_REPOSITORY" >&2 + echo "result=failed" >> $GITHUB_OUTPUT + echo "::error title=Invalid Repository Format::repository must be alphanumeric with dashes/underscores for environment '${{ github.event.inputs.environment }}'." + + # save release specific error for later summary report section + echo "GWY_RELEASE_ERROR<> $GITHUB_ENV + echo -e "Invalid repository name (${GWY_RELEASE_REPOSITORY}) for '${{ github.event.inputs.environment }}' environment, must be alphanumeric with dashes/underscores" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + exit 0 + fi + + # Configure AWS credentials + echo "Configuring AWS credentials..." >&2 + export AWS_ACCESS_KEY_ID=$(echo "$GWY_RELEASE_TOKEN" | cut -d':' -f1) + export AWS_SECRET_ACCESS_KEY=$(echo "$GWY_RELEASE_TOKEN" | cut -d':' -f2) + echo "AWS_ACCESS_KEY_ID starts with: ${AWS_ACCESS_KEY_ID:0:4}..." >&2 + + # Create ECR repo if missing and allowed + echo "Ensuring ECR repository exists..." >&2 + AWS_ECR_DESCRIBE=$(aws ecr describe-repositories --region "$AWS_REGION" --repository-names "$GWY_RELEASE_REPOSITORY" 2>&1 || true) + echo "RAN_AWS_ECR_DESCRIBE=true" >> $GITHUB_ENV + echo "AWS_ECR_DESCRIBE<> $GITHUB_ENV + echo "$AWS_ECR_DESCRIBE" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + if ! echo "$AWS_ECR_DESCRIBE" | grep -q '"repositoryName":'; then + if [ "$GWY_REPO_CREATE" != "true" ]; then + echo "ECR repository '$GWY_RELEASE_REPOSITORY' does not exist and GWY_REPO_CREATE is false" >&2 + echo "result=failed" >> $GITHUB_OUTPUT + echo "::error title=Repository Missing::ECR repository '$GWY_RELEASE_REPOSITORY' does not exist and auto-creation is disabled." + + # save release specific error for later summary report section + echo "GWY_RELEASE_ERROR<> $GITHUB_ENV + echo -e "ECR repository '${GWY_RELEASE_REPOSITORY}' does not exist and auto-creation is disabled" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + exit 0 + fi + + IMMUTABILITY=$([[ "$GWY_REPO_IMMUTABLE" == "true" ]] && echo "IMMUTABLE" || echo "MUTABLE") + + AWS_ECR_CREATE=$(aws ecr create-repository --region "$AWS_REGION" --repository-name "$GWY_RELEASE_REPOSITORY" --image-scanning-configuration scanOnPush=true --image-tag-mutability "$IMMUTABILITY" 2>&1) + AWS_ECR_CREATE_RESULT=$? + + echo "RAN_AWS_ECR_CREATE=true" >> $GITHUB_ENV + echo "AWS_ECR_CREATE<> $GITHUB_ENV + echo "$AWS_ECR_CREATE" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + if [ $AWS_ECR_CREATE_RESULT -ne 0 ]; then + echo "Failed to create ECR repository '$GWY_RELEASE_REPOSITORY': $AWS_ECR_CREATE" >&2 + echo "result=failed" >> $GITHUB_OUTPUT + echo "::error title=Repository Creation Failed::Failed to create ECR repository '$GWY_RELEASE_REPOSITORY': $AWS_ECR_CREATE" + + # save release specific error for later summary report section + echo "GWY_RELEASE_ERROR<> $GITHUB_ENV + echo -e "Failed to create ECR repository '${GWY_RELEASE_REPOSITORY}', check evidence artifact for command output" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + exit 0 + fi + fi + + # Login to ECR + echo "Logging into AWS ECR..." >&2 + AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) + + if [ -z "$AWS_ACCOUNT_ID" ]; then + echo "Failed to get AWS Account ID" >&2 + echo "result=failed" >> $GITHUB_OUTPUT + echo "::error title=release to aws/ecr error::could not retrieve AWS account ID, please ensure credentials secrets is valid" + + # save release specific error for later summary report section + echo "GWY_RELEASE_ERROR<> $GITHUB_ENV + echo -e "Error retrieving AWS account ID, check evidence artifact for command output" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + exit 0 + fi + echo "AWS_ACCOUNT_ID=$AWS_ACCOUNT_ID" >> $GITHUB_ENV + + AWS_ECR_LOGIN=$(aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com" 2>&1) + AWS_ECR_LOGIN_RESULT=$? + + echo "RAN_AWS_ECR_LOGIN=true" >> $GITHUB_ENV + echo "AWS_ECR_LOGIN<> $GITHUB_ENV + echo "$AWS_ECR_LOGIN" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + if [ $AWS_ECR_LOGIN_RESULT -ne 0 ]; then + echo "AWS ECR login failed" >&2 + echo "result=failed" >> $GITHUB_OUTPUT + echo "::error title=release to aws/ecr error::failed to authenticate with ECR! Check artifact 'release-report' for details" + + # save release specific error for later summary report section + echo "GWY_RELEASE_ERROR<> $GITHUB_ENV + echo -e "ECR login error, check evidence artifact for command output" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + exit 0 + fi + + # Build Docker image + echo "Building Docker image..." >&2 + DOCKER_BUILD=$(timeout "$GWY_TIMEOUT_SECONDS" docker build --build-arg VERSION="$GWY_APP_VERSION" -t "$GWY_RELEASE_REPOSITORY" . 2>&1) + DOCKER_BUILD_RESULT=$? + + echo "RAN_DOCKER_BUILD=true" >> $GITHUB_ENV + echo "DOCKER_BUILD<> $GITHUB_ENV + echo "$DOCKER_BUILD" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + if [ $DOCKER_BUILD_RESULT -eq 0 ]; then + echo "Docker build succeeded" >&2 + else + echo "Docker build failed" >&2 + echo "result=failed" >> $GITHUB_OUTPUT + echo "::error title=release to aws/ecr error::Docker build failed! Check artifact 'release-report' for details" + + # save release specific error for later summary report section + echo "GWY_RELEASE_ERROR<> $GITHUB_ENV + echo -e "Error building Dockerfile, check evidence artifact for command output" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + exit 0 + fi + + # Tag and push with GWY_RELEASE_TAGS + echo "Tagging and pushing to ECR:" >&2 + IFS=',' read -ra TAGS <<< "$GWY_RELEASE_TAG" + for TAG in "${TAGS[@]}"; do + echo " - Applying tag: $TAG" + SAFE_TAG=$(echo "$TAG" | tr '-' '_') + FULL_TAG="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$GWY_RELEASE_REPOSITORY:$TAG" + + DOCKER_TAG=$(docker tag "$GWY_RELEASE_REPOSITORY" "$FULL_TAG" 2>&1) + DOCKER_TAG_RESULT=$? + + echo "RAN_DOCKER_TAG_$SAFE_TAG=true" >> $GITHUB_ENV + echo "DOCKER_TAG_$SAFE_TAG<> $GITHUB_ENV + echo "$DOCKER_TAG" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + DOCKER_PUSH=$(timeout "$GWY_TIMEOUT_SECONDS" docker push "$FULL_TAG" 2>&1) + DOCKER_PUSH_RESULT=$? + + echo "RAN_DOCKER_PUSH_$SAFE_TAG=true" >> $GITHUB_ENV + echo "DOCKER_PUSH_$SAFE_TAG<> $GITHUB_ENV + echo "$DOCKER_PUSH" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + if [ $DOCKER_TAG_RESULT -eq 0 ] && [ $DOCKER_PUSH_RESULT -eq 0 ]; then + echo "Image pushed with tag: $TAG" >&2 + else + echo "Docker push failed for tag: $TAG" >&2 + echo "result=failed" >> $GITHUB_OUTPUT + echo "::error title=release to aws/ecr error::Docker push to ECR failed for tag '$TAG'! Check artifact 'release-report' for details" + + # save release specific error for later summary report section + echo "GWY_RELEASE_ERROR<> $GITHUB_ENV + echo -e "Error pushing image with tag '${TAG}', check evidence artifact for command output" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + exit 0 + fi + done + + echo "result=success" >> $GITHUB_OUTPUT + echo "::notice title=release to aws/ecr error::image successfully pushed with tag: $GWY_RELEASE_TAG" + + - name: Generate Release Summary + if: always() + shell: bash + run: | + # Generate Release Summary + GWY_RELEASE_SUMMARY=$RUNNER_TEMP/release-summary.md + echo "GWY_RELEASE_SUMMARY=$GWY_RELEASE_SUMMARY" >> $GITHUB_ENV + + echo -e "## Application Release (AWS/ECR)\n" >> $GWY_RELEASE_SUMMARY + + if [ "${{ steps.release-aws.outputs.result }}" == "success" ]; then + IFS=',' read -ra TAGS <<< "$GWY_RELEASE_TAG" + for TAG in "${TAGS[@]}"; do + FULL_TAG="https://${AWS_ACCOUNT_ID:-unknown}.dkr.ecr.${AWS_REGION:-unknown}.amazonaws.com/$GWY_RELEASE_REPOSITORY:$TAG" + echo -e " - \` Tag\`: $TAG" >> $GWY_RELEASE_SUMMARY + echo -e " - \`Account\`: [${AWS_ACCOUNT_ID:-unknown}](https://${AWS_REGION:-unknown}.console.aws.amazon.com/ecr/repositories?region=${AWS_REGION:-unknown})" >> $GWY_RELEASE_SUMMARY + echo -e " - \` Region\`: ${AWS_REGION:-unknown}" >> $GWY_RELEASE_SUMMARY + echo -e " - \` Repo\`: [$GWY_RELEASE_REPOSITORY](https://${AWS_REGION:-unknown}.console.aws.amazon.com/ecr/repositories/private/${AWS_ACCOUNT_ID:-unknown}/$GWY_RELEASE_REPOSITORY?region=${AWS_REGION:-unknown})" >> $GWY_RELEASE_SUMMARY + echo -e " - \` URL\`: [$FULL_TAG]($FULL_TAG)" >> $GWY_RELEASE_SUMMARY + echo -e "" >> $GWY_RELEASE_SUMMARY + done + + else + echo -e " - \` ERROR\`: $GWY_RELEASE_ERROR" >> $GWY_RELEASE_SUMMARY + fi + + cat $GWY_RELEASE_SUMMARY >> $GITHUB_STEP_SUMMARY + + - name: Generate Release Report Artifact + if: always() + shell: bash + run: | + # Generate Release Report Artifact + GWY_ARTIFACT=$RUNNER_TEMP/release-report.md + echo "GWY_ARTIFACT=$GWY_ARTIFACT" >> $GITHUB_ENV + + cat $GWY_SUMMARY_HEADING > $GWY_ARTIFACT + cat $GWY_RELEASE_SUMMARY >> $GWY_ARTIFACT + + # some errors might be triggered concerning missing + # environment symbols without actually triggering + # any command, so avoid adding the markdown title + # in the summary if no commands have actually run :P + + if [ "$RAN_AWS_ECR_DESCRIBE" = "true" ]; then + echo -e "\n## Command Outputs\n \n" >> $GWY_ARTIFACT + fi + + if [ "$RAN_AWS_ECR_DESCRIBE" = "true" ]; then + echo -e "### aws ecr describe-repositories\n \n\`\`\`\n${AWS_ECR_DESCRIBE}\n\`\`\`\n" >> $GWY_ARTIFACT + fi + + if [ "$RAN_AWS_ECR_CREATE" = "true" ]; then + echo -e "### aws ecr create-repository\n \n\`\`\`\n${AWS_ECR_CREATE}\n\`\`\`\n" >> $GWY_ARTIFACT + fi + + if [ "$RAN_AWS_ECR_LOGIN" = "true" ]; then + echo -e "### aws ecr get-login-password | docker login\n \n\`\`\`\n${AWS_ECR_LOGIN}\n\`\`\`\n" >> $GWY_ARTIFACT + fi + + if [ "$RAN_DOCKER_BUILD" = "true" ]; then + echo -e "### docker build\n \n\`\`\`\n${DOCKER_BUILD}\n\`\`\`\n" >> $GWY_ARTIFACT + fi + + IFS=',' read -ra TAGS <<< "$GWY_RELEASE_TAG" + for TAG in "${TAGS[@]}"; do + SAFE_TAG=$(echo "$TAG" | tr '-' '_') + RAN_TAG_VAR="RAN_DOCKER_TAG_$SAFE_TAG" + RAN_PUSH_VAR="RAN_DOCKER_PUSH_$SAFE_TAG" + if [ "${!RAN_TAG_VAR}" = "true" ]; then + TAG_OUT_VAR="DOCKER_TAG_$SAFE_TAG" + echo -e "### docker tag ($TAG)\n \n\`\`\`\n${!TAG_OUT_VAR}\n\`\`\`\n" >> $GWY_ARTIFACT + fi + if [ "${!RAN_PUSH_VAR}" = "true" ]; then + PUSH_OUT_VAR="DOCKER_PUSH_$SAFE_TAG" + echo -e "### docker push ($TAG)\n \n\`\`\`\n${!PUSH_OUT_VAR}\n\`\`\`\n" >> $GWY_ARTIFACT + fi + done + + - name: Upload Release Report Artifact + if: always() + uses: actions/upload-artifact@v4 + continue-on-error: true + with: + name: release-report + path: ${{ env.GWY_ARTIFACT }} + + - name: Step Clean-Up + if: always() + shell: bash + run: | + rm -f "$GWY_ARTIFACT" + unset GWY_ARTIFACT \ No newline at end of file diff --git a/.github/actions/gwy/secrets/action.yml b/.github/actions/gwy/secrets/action.yml new file mode 100644 index 0000000..3b8bcc2 --- /dev/null +++ b/.github/actions/gwy/secrets/action.yml @@ -0,0 +1,114 @@ +name: 'gitleaks Scan' +description: 'Checks current code snapshot for secrets with Gitleaks' + +outputs: + result: + description: 'Secrets scan result (success or failed)' + value: ${{ steps.check-gitleaks.outputs.result }} + +runs: + using: 'composite' + steps: + - name: Initialize Secrets Check Environment + shell: bash + run: | + # Initialize Secrets Check Environment + + GWY_SUMMARY=$RUNNER_TEMP/secrets-summary.txt + echo -e "## Secrets Scan\n\n" >> $GWY_SUMMARY + + # set artifact dump file path + echo "GWY_ARTIFACT=$RUNNER_TEMP/secrets-report.md" >> $GITHUB_ENV + + # set annotations reusable title + echo "GWY_TITLE=code secrets scan" >> $GITHUB_ENV + + echo "GWY_SUMMARY=$GWY_SUMMARY" >> $GITHUB_ENV + echo "GWY_SECRET_ISSUES=0" >> $GITHUB_ENV + echo "GWY_BRANCH=$(git branch --show-current)" >> $GITHUB_ENV + GWY_REPO_URL=$(git config --get remote.origin.url | sed 's|git@github.com:|https://github.com/|' | sed 's|\.git$||') + echo "GWY_REPO_URL=$GWY_REPO_URL" >> $GITHUB_ENV + sync + + - name: Install Gitleaks (Pinned to 8.21.1) + shell: bash --noprofile --norc +e -o pipefail {0} + run: | + if ! command -v gitleaks &> /dev/null; then + curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.21.1/gitleaks_8.21.1_linux_x64.tar.gz | tar -xz -C /usr/local/bin gitleaks + fi + gitleaks version + + - name: Check Gitleaks for Secrets + id: check-gitleaks + shell: bash --noprofile --norc +e -o pipefail {0} + continue-on-error: true + run: | + # Check Secrets in Code + + # Run gitleaks, stripping detected secrets in output + timeout $GWY_TIMEOUT_SECONDS gitleaks detect --source . --no-git --verbose | grep -vE '^(Finding|Secret):' | tee $GWY_ARTIFACT + + # Count unique File:Line:RuleID combos using Fingerprint + GWY_SECRET_ISSUES=$(awk ' + /RuleID:/ {rule=$2} + /File:/ {file=$2} + /Line:/ {line=$2} + /Fingerprint:/ {key=file":"line":"rule; if (!(key in seen)) seen[key]=1} + END {for (k in seen) count++; print count ? count : 0} + ' $GWY_ARTIFACT) + echo "GWY_SECRET_ISSUES=$GWY_SECRET_ISSUES" >> $GITHUB_ENV + + # process found secrets count + if [ -z "$GWY_SECRET_ISSUES" ]; then + GWY_SECRET_ISSUES=0 + fi + + # Process results + if [ $GWY_SECRET_ISSUES -ne 0 ]; then + echo "result=failed" >> $GITHUB_OUTPUT + echo "::error title=$GWY_TITLE: $GWY_SECRET_ISSUES SECRETS FOUND!::check summary or artifact report for more details" + grep -E '^(RuleID|File|Line|Fingerprint):' $GWY_ARTIFACT | \ + awk ' + /RuleID:/ {rule=$2} + /File:/ {file=$2} + /Line:/ {line=$2} + /Fingerprint:/ {key=file":"line":"rule; if (!(key in seen)) {seen[key]=1; print " - [" rule " in " file ":" line "](" ENVIRON["GWY_REPO_URL"] "/blob/" ENVIRON["GWY_BRANCH"] "/" file "#L" line ")"}} + ' >> $GWY_SUMMARY + else + echo "result=success" >> $GITHUB_OUTPUT + echo "::notice title=$GWY_TITLE::no secrets found in codebase" + echo " - no hardcoded secrets detected" >> $GWY_SUMMARY + fi + + cat $GWY_SUMMARY >> $GITHUB_STEP_SUMMARY + + # append to artifact secrets scan output + + echo -e "\n\n## Secrets Scan Output\n\n\`\`\`" >> $GWY_SUMMARY + cat $GWY_ARTIFACT >> $GWY_SUMMARY + echo -e "\`\`\`\n" >> $GWY_SUMMARY + + cat $GWY_SUMMARY_HEADING > $GWY_ARTIFACT + cat $GWY_SUMMARY >> $GWY_ARTIFACT + + - name: Upload Secrets Report Artifact + if: always() + uses: actions/upload-artifact@v4 + continue-on-error: true + with: + name: secrets-report + path: ${{ env.GWY_ARTIFACT }} + + - name: Step Clean-Up + if: always() + shell: bash + run: | + rm -f $GWY_ARTIFACT + rm -f $GWY_SUMMARY + + unset GWY_TITLE + unset GWY_SUMMARY + unset GWY_ARTIFACT + unset GWY_SECRET_ISSUES + unset GWY_BRANCH + unset GWY_REPO_URL \ No newline at end of file diff --git a/.github/actions/gwy/setup/action.yml b/.github/actions/gwy/setup/action.yml new file mode 100644 index 0000000..ca5db6a --- /dev/null +++ b/.github/actions/gwy/setup/action.yml @@ -0,0 +1,132 @@ +name: 'GWY Environment Setup' +description: 'GWY Environment setup for subsequent steps' + +inputs: + go-version: + description: 'Go version - uses go.mod one' + required: false + default: '' + + timeout: + description: 'Timeout (per step)' + required: false + default: '5m' + +runs: + using: 'composite' + steps: + - name: Add Workflow Environment Heading + if: always() + shell: bash + run: | + # Add Workflow Environment Heading + + # get current branch + + GWY_BRANCH=$(git branch --show-current) + echo "GWY_BRANCH=$GWY_BRANCH" >> $GITHUB_ENV + echo "application branch: $GWY_BRANCH" >&2 + + # process workflow run symbols + + GWY_RUN_USER="$GITHUB_ACTOR" + echo "GWY_RUN_USER=$GWY_RUN_USER" >> $GITHUB_ENV + + GWY_RUN_WORKFLOW="$GITHUB_WORKFLOW" + echo "GWY_RUN_WORKFLOW=$GWY_RUN_WORKFLOW" >> $GITHUB_ENV + + GWY_RUN_DATE=$(date '+%Y-%m-%d %H:%M:%S') + echo "GWY_RUN_DATE=$GWY_RUN_DATE" >> $GITHUB_ENV + + if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then + GWY_RUN_TYPE="MANUAL" + else + GWY_RUN_TYPE="EVENT ($GITHUB_EVENT_NAME)" + fi + + echo "GWY_RUN_TYPE=$GWY_RUN_TYPE" >> $GITHUB_ENV + + # determine environment go version + + if [ "${{ inputs.go-version }}" = "" ]; then + if [ -f "go.mod" ]; then + GWY_GO_VERSION=$(grep '^go ' go.mod | awk '{print $2}') + echo "go version (from go.mod): $GWY_GO_VERSION" >&2 + else + # NOTE: error handling will be performed in next step + GWY_GO_VERSION=UNKNOWN + fi + else + GWY_GO_VERSION="${{ inputs.go-version }}" + echo "go version (user supplied): $GWY_GO_VERSION" >&2 + fi + + echo "GWY_GO_VERSION=$GWY_GO_VERSION" >> $GITHUB_ENV + + # process steps timeout + + GWY_TIMEOUT_SECONDS=$(echo "${{ inputs.timeout }}" | sed 's/m/*60/;s/h/*3600/;s/d/*86400/' | bc) + echo "GWY_TIMEOUT_SECONDS=$GWY_TIMEOUT_SECONDS" >> $GITHUB_ENV + + # attach workflows heading + + GWY_SUMMARY_HEADING=$RUNNER_TEMP/gwy-summary-heading.md + echo "GWY_SUMMARY_HEADING=$GWY_SUMMARY_HEADING" >> $GITHUB_ENV + + echo -e "## $GWY_RUN_WORKFLOW by @${{ github.actor }}" >> $GWY_SUMMARY_HEADING + echo -e " " >> $GWY_SUMMARY_HEADING + + echo -e " - \` DATE\`: $GWY_RUN_DATE" >> $GWY_SUMMARY_HEADING + echo -e " - \` RUN\`: $GWY_RUN_TYPE" >> $GWY_SUMMARY_HEADING + echo -e " - \` GO\`: $GWY_GO_VERSION" >> $GWY_SUMMARY_HEADING + echo -e " - \` BRANCH\`: $GWY_BRANCH" >> $GWY_SUMMARY_HEADING + echo -e " - \`TIMEOUT\`: $GWY_TIMEOUT_SECONDS" >> $GWY_SUMMARY_HEADING + echo -e " " >> $GWY_SUMMARY_HEADING + + cat $GWY_SUMMARY_HEADING > $GITHUB_STEP_SUMMARY + + - name: Bootstrap Environment + shell: bash + run: | + # environment bootstrapping (general purpose actions required envs) + + # parse app name from go.mod + + if [[ ! -f "go.mod" ]]; then + echo "::error title=FATAL ERROR::could not find 'go.mod' file in target branch '$GWY_BRANCH'" + exit 1 + fi + + GWY_APP=$(grep '^module ' go.mod | awk '{print $2}' | sed 's|.*/||') + echo "GWY_APP=$GWY_APP" >> $GITHUB_ENV + echo "application name (from go.mod): $GWY_APP" >&2 + + # determine environment go version + + if [ "${{ inputs.go-version }}" = "" ]; then + GWY_GO_VERSION=$(grep '^go ' go.mod | awk '{print $2}') + echo "go version (from go.mod): $GWY_GO_VERSION" >&2 + else + GWY_GO_VERSION="${{ inputs.go-version }}" + echo "go version (user supplied): $GWY_GO_VERSION" >&2 + fi + + echo "GWY_GO_VERSION=$GWY_GO_VERSION" >> $GITHUB_ENV + + # parse app version from branch + + if [[ "$GWY_BRANCH" =~ ^release/[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + GWY_APP_VERSION=$(echo "$GWY_BRANCH" | sed 's|^release/||') + elif [[ "$GWY_BRANCH" =~ / ]]; then + GWY_APP_VERSION=$(echo "$GWY_BRANCH" | sed 's|.*/||') + else + GWY_APP_VERSION="$GWY_BRANCH" + fi + + echo "application version: $GWY_APP_VERSION" >&2 + echo "GWY_APP_VERSION=$GWY_APP_VERSION" >> $GITHUB_ENV + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GWY_GO_VERSION }} \ No newline at end of file diff --git a/.github/actions/gwy/vulnerabilities/action.yml b/.github/actions/gwy/vulnerabilities/action.yml new file mode 100644 index 0000000..5f4a55e --- /dev/null +++ b/.github/actions/gwy/vulnerabilities/action.yml @@ -0,0 +1,152 @@ +name: 'Vulnerabilities Scan' +description: 'Scans for vulnerabilities in branch' + +outputs: + result: + description: 'Result of the action (success or failed)' + value: ${{ steps.check-vulnerabilities.outputs.result }} + +runs: + using: 'composite' + steps: + - name: Initialize Vulnerabilities Scanning Environment + shell: bash + run: | + # Initialize Vulnerabilities Scanning Environment + GWY_SUMMARY=$RUNNER_TEMP/vulnerabilities-summary.txt + echo -e "## Vulnerabilities Scan" >> $GWY_SUMMARY + + echo "GWY_SUMMARY=$GWY_SUMMARY" >> $GITHUB_ENV + echo "GWY_VULNERABILITIES_COUNT=0" >> $GITHUB_ENV + echo "GWY_ARTIFACT=$RUNNER_TEMP/vulnerabilities-report.md" >> $GITHUB_ENV + + # set annotations reusable title + echo "GWY_TITLE=vulnerabilities scan" >> $GITHUB_ENV + + sync + + - name: Install govulncheck + run: | + # Installing govulncheck for Vulnerabilities Scanning + go install golang.org/x/vuln/cmd/govulncheck@latest + shell: bash + + - name: Check Vulnerabilities + id: check-vulnerabilities + shell: bash --noprofile --norc +e -o pipefail {0} + run: | + # Check Code Vulnerabilities + timeout $GWY_TIMEOUT_SECONDS govulncheck ./... | grep -v "show verbose" | + grep -v "Symbol Results" > $GWY_ARTIFACT + + # process found vulnerabilities IDs + GWY_VULNERABILITIES_FOUND=$(cat $GWY_ARTIFACT | grep 'Vulnerability #') + + # process found vulnerabilities count + if [ -z "$GWY_VULNERABILITIES_FOUND" ]; then + GWY_VULNERABILITIES_COUNT=0 + else + GWY_VULNERABILITIES_COUNT=$(echo "$GWY_VULNERABILITIES_FOUND" | wc -l) + fi + + # set action result based on vulnerabilities count + if [ "$GWY_VULNERABILITIES_COUNT" -ne 0 ]; then + echo "result=failed" >> $GITHUB_OUTPUT + else + echo "result=success" >> $GITHUB_OUTPUT + fi + + # enhance vulnerabilities summary section, annotations, and debug output + if [ $GWY_VULNERABILITIES_COUNT -ne 0 ]; then + # add found vulnerabilities annotation + echo "::error title=$GWY_TITLE: $GWY_VULNERABILITIES_COUNT VULNERABILITIES FOUND::check summary or artifact report for more details" + + # add found vulnerabilities to debug output + echo "$GWY_VULNERABILITIES_FOUND" + + # Repository URL using GitHub context + GWY_REPO_URL="https://github.com/${GITHUB_REPOSITORY}" + + # Variables for parsing + current_vuln_id="" + current_vuln_link="" + + # Parse vulnerabilities and generate Markdown + while IFS= read -r line; do + # Match "Vulnerability #X: ID" + if [[ "$line" =~ Vulnerability\ #[0-9]+:\ (GO-[0-9-]+) ]]; then + current_vuln_id="${BASH_REMATCH[1]}" + # Add a blank line before new vuln ID (except first one) + if [ -s "$GWY_SUMMARY" ]; then + echo "" >> "$GWY_SUMMARY" + fi + fi + + # Match "More info" link and write header without colon + if [[ "$line" =~ More\ info:\ (https://pkg.go.dev/vuln/GO-[0-9-]+) ]]; then + current_vuln_link="${BASH_REMATCH[1]}" + echo "[${current_vuln_id}](${current_vuln_link})" >> "$GWY_SUMMARY" + fi + + # Match "Example traces found" lines + if [[ "$line" =~ ^[[:space:]]*#[0-9]+:[[:space:]]*([^:]+):([0-9]+):([0-9]+):[[:space:]]*(.*)$ ]]; then + file_path="${BASH_REMATCH[1]}" + line_num="${BASH_REMATCH[2]}" + col_num="${BASH_REMATCH[3]}" + call_trace="${BASH_REMATCH[4]}" + + # Strip directory path, keep only filename + file_name=$(basename "$file_path") + + # Construct GitHub URL to the specific line (full path still needed for link) + file_url="${GWY_REPO_URL}/blob/${GWY_BRANCH}/${file_path}#L${line_num}" + + # Write the formatted Markdown line with just filename, no line/column + echo " - [${file_name}](${file_url}): \`${call_trace}\`" >> "$GWY_SUMMARY" + fi + done < "$GWY_ARTIFACT" + + else + # add not found vulnerabilities annotation + echo "::notice title=$GWY_TITLE::no vulnerabilities found in codebase" + + # add not found vulnerabilities to action summary + echo " - no vulnerabilities detected" >> "$GWY_SUMMARY" + fi + + cat $GWY_SUMMARY >> $GITHUB_STEP_SUMMARY + + # append to artifact vulnerabilities scan output + + echo -e "\n\n## Vulnerabilities Scan Output\n\n\`\`\`" >> $GWY_SUMMARY + cat $GWY_ARTIFACT >> $GWY_SUMMARY + echo -e "\`\`\`\n" >> $GWY_SUMMARY + + cat $GWY_SUMMARY_HEADING > $GWY_ARTIFACT + cat $GWY_SUMMARY >> $GWY_ARTIFACT + + - name: Upload Vulnerability Report + if: always() + uses: actions/upload-artifact@v4 + with: + name: vulnerabilities-report + path: ${{ env.GWY_ARTIFACT }} + continue-on-error: true + + - name: Step Clean-Up + if: always() + shell: bash + run: | + # Vulnerabilities Scanning Clean-Up + rm -f $GWY_ARTIFACT + rm -f $GWY_SUMMARY + + # unset variables + unset GWY_TITLE + unset GWY_SUMMARY + unset GWY_ARTIFACT + unset GWY_REPO_URL + unset GWY_PIPELINE_STATUS + unset GWY_TIMEOUT_SECONDS + unset GWY_VULNERABILITIES_COUNT + unset GWY_VULNERABILITIES_FOUND diff --git a/.github/workflows/gwy-aws-release.yml b/.github/workflows/gwy-aws-release.yml new file mode 100644 index 0000000..e04bb58 --- /dev/null +++ b/.github/workflows/gwy-aws-release.yml @@ -0,0 +1,178 @@ +name: 'GWY/Release: AWS/ECR' + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch' + required: true + + environment: + description: 'Environment' + required: true + default: 'develop' + + type: choice + options: + - develop + - staging + - production + + region: + description: 'Region' + required: true + type: choice + options: + - us-east-1 + - us-east-2 + - us-west-1 + - us-west-2 + - af-south-1 + - ap-east-1 + - ap-south-1 + - ap-south-2 + - ap-southeast-1 + - ap-southeast-2 + - ap-southeast-3 + - ap-southeast-4 + - ap-northeast-1 + - ap-northeast-2 + - ap-northeast-3 + - ca-central-1 + - ca-west-1 + - cn-north-1 + - cn-northwest-1 + - eu-central-1 + - eu-central-2 + - eu-west-1 + - eu-west-2 + - eu-west-3 + - eu-south-1 + - eu-south-2 + - eu-north-1 + - il-central-1 + - me-south-1 + - me-central-1 + - mx-central-1 + - sa-east-1 + - us-gov-east-1 + - us-gov-west-1 + default: us-east-1 + + timeout: + description: 'Timeout (e.g., 5m)' + type: string + default: '5m' + +# NOTE: This workflow is currently thought to be +# run manually, next release, which will integrate +# GTY (Go Terraform Yourself) Continuous Delivery +# schemes, will update workflow to allow clients +# to easily add the triggering of this workflow +# with, for example, push events to master branch. + +env: + # Create application repository if un-existent? + # Set true if you want the workflow to create + # the ECR repository for app if un-existent. + GWY_REPO_CREATE: true + + # Application repository creation properties: + # If app ECR repository is un-existent and + # GWY_REPO_CREATE was set true, do you want + # the repository to be immutable or not? + # + # NOTE: Immutable repos will reject upload + # of image with conflicting tag, otherwise, + # it will overwrite the existent image. + GWY_REPO_IMMUTABLE: false + +run-name: "[${{ github.workflow }}] @${{ github.actor }}" + +jobs: + gwy-release: + runs-on: ubuntu-latest + steps: + - name: Set Release Branch + run: | + # Setting Workflow Main Configuration + echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV + + - name: GWY Bootstrapping Validations + run: | + # TODO: Add aws token validation + if [ -z "${{ secrets.GWY_TOKEN_AWS }}" ]; then + echo "::error title=AWS access key not configured::mandatory secret 'GWY_TOKEN_AWS' seems to be empty, kindly check GWY configuration documentation" + exit 1 + fi + + if [ -z "${{ secrets.GWY_TOKEN_REPO }}" ]; then + echo "::error title=Mandatory repository token not configured::mandatory secret 'GWY_TOKEN_REPO' seems to be empty, kindly check GWY configuration documentation" + exit 1 + fi + + if ! git ls-remote --exit-code https://x-access-token:${{ secrets.GWY_TOKEN_REPO }}@github.com/${{ github.repository }} "refs/heads/$BRANCH" "refs/tags/$BRANCH" > /dev/null; then + echo "::error title=Could not find workflow target branch::could not find branch or tag with name '$BRANCH', kindly provide a valid branch name." + exit 1 + fi + + - name: Checkout Code + uses: actions/checkout@v4 + with: + ref: ${{ env.BRANCH }} + fetch-depth: 0 + + - name: Setup Application Environment + uses: ./.github/actions/gwy/setup + + - name: Set Release Environment Symbols + run: | + # release environment symbols configuration (token, repository, etc) + GWY_RELEASE_TAG="" + GWY_RELEASE_TOKEN="" + GWY_RELEASE_REPOSITORY="" + + # Set symbols for environment: "production" + if [ "${{ inputs.environment }}" = "production" ]; then + GWY_RELEASE_REPOSITORY="$GWY_APP" + GWY_RELEASE_TAG="$GWY_APP_VERSION" + GWY_RELEASE_TOKEN="${{ secrets.GWY_TOKEN_AWS }}" + + # Set symbols for environment: "develop" + elif [ "${{ inputs.environment }}" = "develop" ]; then + GWY_RELEASE_REPOSITORY="$GWY_APP" + GWY_RELEASE_TAG="$GWY_APP_VERSION" + GWY_RELEASE_TOKEN="${{ secrets.GWY_TOKEN_AWS }}" + + # Set symbols for environment: "staging" + elif [ "${{ inputs.environment }}" = "staging" ]; then + GWY_RELEASE_REPOSITORY="$GWY_APP" + GWY_RELEASE_TAG="$GWY_APP_VERSION" + GWY_RELEASE_TOKEN="${{ secrets.GWY_TOKEN_AWS }}" + fi + + if [ -z "$GWY_RELEASE_TOKEN" ]; then + echo "::error title=::" + exit 1 + fi + + # Export to env for action + echo "GWY_RELEASE_TAG=$GWY_RELEASE_TAG" >> $GITHUB_ENV + echo "GWY_RELEASE_TOKEN=$GWY_RELEASE_TOKEN" >> $GITHUB_ENV + echo "GWY_RELEASE_REPOSITORY=$GWY_RELEASE_REPOSITORY" >> $GITHUB_ENV + + - name: Release to AWS ECR + id: release-aws + uses: ./.github/actions/gwy/release-aws + with: + region: ${{ inputs.region }} + timeout: ${{ inputs.timeout }} + + - name: Workflow Result + if: always() + shell: bash + run: | + # Process Workflow Result + if [ "${{ steps.release-aws.outputs.result }}" != "success" ]; then + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/gwy-badges.yml b/.github/workflows/gwy-badges.yml new file mode 100644 index 0000000..28b9b83 --- /dev/null +++ b/.github/workflows/gwy-badges.yml @@ -0,0 +1,181 @@ +name: "GWY/Release: Badges" + +env: + # commit generated badges to orphan branch? + # + # The badges are generated using Shields.io, + # though you can configure the workflow to + # commit the generated badges to an orphan + # branch, allowing to link the documentation + # to these badges and getting them updated + # automatically upon each new release merge + # to the master branch. Workflow can also be + # run manually (or by a daily job) to update + # the counter badges like 'Downloads' to keep + # your client interactions up-to-date. + # + # Available Options: + # - + # - + # - {branch_name} + # + # will not commit the generated badges, + # will query the GH API to get your + # currently configured GitHub Pages branch, if + # Pages is disabled or there is an error, it + # will default to 'gh-pages'. + # + # If none of those two special names are found + # in this option, GWY will use as target branch + # whatever branch name was entered here. + # + BADGES_BRANCH: '' + + # directory in orphan branch to commit badges? + # + # NOTE: if BADGES_BRANCH is set to , this + # configuration will be ignored all together. + # + # You can customize the target directory where + # the generated badges will be commited in the + # badges commit branch. Please note that GWY + # will append to this directory the branch name + # for which the badges are being generated. I + # had seen in the past, not that often, people + # having different badges for different branches, + # for example coverage both in develop and master. + # Thus, even though it might be a border case, + # it was not that of an issue supporting the + # ability for clients to generate badges over + # branches, thus this dir addition was added. + # + BADGES_DIR: "images/gwy/badges" + + # Commited badges url type? + # + # NOTE: if BADGES_BRANCH is set to , this + # configuration will be ignored all together. + # + # When you request the generated badges to be + # commited to an orphan branch, even though the + # badges image source in the summary link to + # Shields.io (so they can be displayed even in + # private repos), the badges will link to the + # corresponding badges commited files in the + # repo so client can easily retrieve the link + # and stick it in the documentation. GitHub + # offers two domains to access repo files, you + # use this option to request which domain you + # want those URLs to be generated. + # + # Available Options: + # - github.com + # - githubusercontent.com + # + BADGES_URL: "githubusercontent.com" + + # Coverage badge color awareness customization + # + # Following you can customize the color of the + # generated unit tests coverage badge based on + # its result. The workflow will generate the + # badge with three different colors to notice + # "awareness" over its result based on the + # following configuration. + # + # If coverage greater than BADGES_THRESHOLD_SUCCESS, + # the badge will be generated in green. If coverage + # is greater than BADGES_THRESHOLD_WARNING, will be + # generated in yellow. If greater than "_BAD", will + # be generated in orange and if lower, in red. + # + BADGES_THRESHOLD_SUCCESS: 90 + BADGES_THRESHOLD_WARNING: 85 + BADGES_THRESHOLD_BAD: 80 + + # Target branch over which to apply CI? DON'T + # CHANGE unless you know what you are doing :) + BRANCH: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }} + +on: + push: + branches: + - main + - master + + workflow_dispatch: + inputs: + branch: + description: 'Branch' + required: true + + badges-branch: + description: 'Badges Commit Branch?' + required: true + default: '' + + badges-directory: + description: 'Commit Branch Directory' + required: true + default: 'images/gwy/badges' + + badges-url: + description: 'Badges Domain URL' + required: true + type: choice + options: + - github.com + - githubusercontent.com + default: githubusercontent.com + +run-name: "[${{ github.workflow }}] @${{ github.actor }}" + +jobs: + badges-create: + runs-on: ubuntu-latest + if: github.repository != 'earcamone/gwy' + + steps: + - name: Badges Environment Bootstrapping + if: github.event_name == 'workflow_dispatch' + shell: bash + run: | + # Badges Environment Bootstrapping + + if [ -z "${{ inputs.branch }}" ]; then + echo "::error title=CI Pipeline target branch not specified::mandatory target branch seems to be empty, kindly specify the branch to which you want to apply Pipeline" + exit 1 + fi + + echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV + echo "BADGES_URL=${{ inputs.badges-url }}" >> $GITHUB_ENV + echo "BADGES_BRANCH=${{ inputs.badges-branch }}" >> $GITHUB_ENV + echo "BADGES_DIR=${{ inputs.badges-directory }}" >> $GITHUB_ENV + + - name: GWY Bootstrapping Validations + run: | + if [ -z "${{ secrets.GWY_TOKEN_REPO }}" ]; then + echo "::error title=Mandatory repository token not configured::mandatory secret 'GWY_TOKEN_REPO' seems to be empty, kindly check GWY configuration documentation" + exit 1 + fi + + if ! git ls-remote --exit-code https://x-access-token:${{ secrets.GWY_TOKEN_REPO }}@github.com/${{ github.repository }} "refs/heads/$BRANCH" "refs/tags/$BRANCH" > /dev/null; then + echo "::error title=Could not find workflow target branch::could not find branch or tag with name '$BRANCH', kindly provide a valid branch name." + exit 1 + fi + + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ env.BRANCH }} + + - name: Setup Environment + uses: ./.github/actions/gwy/setup + + - name: Generate and Publish Badges + uses: ./.github/actions/gwy/badges + with: + url: ${{ env.BADGES_URL }} + branch: ${{ env.BADGES_BRANCH }} + directory: ${{ env.BADGES_DIR }} + token: ${{ secrets.GWY_TOKEN_REPO }} diff --git a/.github/workflows/gwy-ci.yml b/.github/workflows/gwy-ci.yml new file mode 100644 index 0000000..bb68676 --- /dev/null +++ b/.github/workflows/gwy-ci.yml @@ -0,0 +1,300 @@ + +name: 'GWY/CI' + +env: + # Go version CI should use to run? + # uses branches go.mod one. + GWY_GO_VERSION: "" + + # Should the CI allow PRs merging if + # any of the enabled CI actions fail? + GWY_ALLOW_MERGE_ON_FAILURE: false + + # Individual Steps Timeout: set the TO for each + # step issued command that might take some time + GWY_TIMEOUT: '5m' + + # Test & Coverage Scan? set true if you want + # CI to run unit tests and coverage analysis + GWY_TESTS: true + + # Coverage Minimum Threshold: set coverage + # minimum threshold to succeed validation + GWY_TESTS_THRESHOLD: '90' + + # Functions Minimum Coverage Notice: + # + # Set the minimum coverage threshold you want + # the summary to notice individual functions + # that are not meeting it. This option does + # not interfere at all with the prev. coverage + # threshold option, it is a cosmetic option + # used to note in coverage summaries individual + # functions that are not meeting a minimum + # coverage threshold. + # + # For example, if you want to see in the + # summary all individual functions coverage, + # you set it to 101 (all functions will have + # a coverage lower than 101), if you want to + # see functions with coverage under 50%, you + # guessed it! set to 51 ;) and if you want + # summary to only show the projects coverage, + # without individual functions coverage, set + # to 0 (functions can't have -1 coverage). + # + # I tend to set this option to 51, which helps + # me notice in the CI, even when it might be + # passing, some functions that actually were + # not fully tested when they could. Summary + # will link to the function, helping you + # quickly check the function body. + # + GWY_TESTS_SUMMARY_THRESHOLD: '51' + + # Vulnerabilities Scan? set true if you want + # CI to check vulnerabilities in your code + GWY_VULNERABILITIES: true + + # Code Hardcoded Secrets Scan? set true if you + # want CI to scan code for hardcoded secrets + GWY_SECRETS: true + + # Outdated Dependencies Scan? set true if you want + # CI to check for imported outdated dependencies + GWY_DEPENDENCIES: true + + # Outdated Dependencies Scan Automatic Fixes? set + # true if you want CI to create a PR to the branch + # updating the outdated dependencies automatically. + GWY_DEPENDENCIES_PR: true + + # Code Format (gofmt) Scan? set true if you want + # CI to check for code format issues in the code + GWY_GOFMT: true + + # Code Format (gofmt) Scan Automatic Fixes? set + # true if you want CI to create a PR to the branch + # updating the code format issues automatically. + GWY_GOFMT_PR: true + + # Code Linting Scan? set true if you want CI to + # scan for linting issues in your codebase + GWY_LINT: true + + # Target branch over which to apply CI? DON'T + # CHANGE unless you know what you are doing :) + BRANCH: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }} + +# +# Enhance triggering events with whatever fits better +# your development cycle, I personally like to block +# any repo merge to master or develop that doesn't come +# from an approved pull request, thus I only apply the +# CI over Pull Requests :P +# + +on: + pull_request: + branches: + - '*' + + workflow_dispatch: + inputs: + branch: + description: 'Branch to apply CI' + required: true + + go-version: + description: 'Go version' + required: true + default: '' + + timeout: + description: 'Timeout (per step)' + type: string + default: '5m' + + tests: + description: 'Run tests and coverage?' + type: boolean + default: true + + secrets: + description: 'Run secrets scan?' + type: boolean + default: true + + dependencies: + description: 'Run dependencies scan?' + type: boolean + default: true + + vulnerabilities: + description: 'Run vulnerabilities scan?' + type: boolean + default: true + + gofmt: + description: 'Run code format scan?' + type: boolean + default: true + + golint: + description: 'Run code linting scan?' + type: boolean + default: true + + create_pr: + description: 'Create PR with fixes when possible?' + type: boolean + default: true + +run-name: "[${{ github.workflow }}] @${{ github.actor }}" + +jobs: + gwy-ci: + runs-on: ubuntu-latest + if: github.repository != 'earcamone/gwy' + + steps: + - name: Setting Manual CI Run Configuration + if: github.event_name == 'workflow_dispatch' + run: | + # Setting Workflow Main Configuration + + # + # This block simply overwrittes the CI config + # options with the ones specified by client + # when running it manually. + # + + if [ -z "${{ inputs.branch }}" ]; then + echo "::error title=CI Pipeline target branch not specified::mandatory target branch seems to be empty, kindly specify the branch to which you want to apply Pipeline" + exit 1 + fi + + echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV + echo "GWY_TIMEOUT=${{ inputs.timeout }}" >> $GITHUB_ENV + echo "GWY_CREATE_PR=${{ inputs.create_pr }}" >> $GITHUB_ENV + echo "GWY_GO_VERSION=${{ inputs.go-version }}" >> $GITHUB_ENV + + echo "GWY_LINT=${{ inputs.golint }}" >> $GITHUB_ENV + echo "GWY_TESTS=${{ inputs.tests }}" >> $GITHUB_ENV + echo "GWY_SECRETS=${{ inputs.secrets }}" >> $GITHUB_ENV + echo "GWY_VULNERABILITIES=${{ inputs.vulnerabilities }}" >> $GITHUB_ENV + + echo "GWY_GOFMT=${{ inputs.gofmt }}" >> $GITHUB_ENV + echo "GWY_GOFMT_PR=${{ inputs.create_pr }}" >> $GITHUB_ENV + + echo "GWY_DEPENDENCIES=${{ inputs.dependencies }}" >> $GITHUB_ENV + echo "GWY_DEPENDENCIES_PR=${{ inputs.create_pr }}" >> $GITHUB_ENV + + - name: GWY Bootstrapping Validations + run: | + if [ -z "${{ secrets.GWY_TOKEN_REPO }}" ]; then + echo "::error title=Mandatory repository token not configured::mandatory secret 'GWY_TOKEN_REPO' seems to be empty, kindly check GWY configuration documentation" + exit 1 + fi + + if ! git ls-remote --exit-code https://x-access-token:${{ secrets.GWY_TOKEN_REPO }}@github.com/${{ github.repository }} "refs/heads/$BRANCH" "refs/tags/$BRANCH" > /dev/null; then + echo "::error title=Could not find workflow target branch::could not find branch or tag with name '$BRANCH', kindly provide a valid branch name." + exit 1 + fi + + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.BRANCH }} + token: ${{ secrets.GWY_TOKEN_REPO }} + + - name: Setup Environment + uses: ./.github/actions/gwy/setup + with: + timeout: ${{ env.GWY_TIMEOUT }} + go-version: ${{ env.GWY_GO_VERSION }} + + - name: Unit Tests & Coverage + id: check-coverage + continue-on-error: true + + if: env.GWY_TESTS == 'true' + uses: ./.github/actions/gwy/coverage + with: + token: ${{ secrets.GWY_TOKEN_REPO }} + threshold: ${{ env.GWY_TESTS_THRESHOLD }} + summary-threshold: ${{ env.GWY_TESTS_SUMMARY_THRESHOLD }} + + - name: Secrets Scan + id: check-secrets + continue-on-error: true + if: env.GWY_SECRETS == 'true' + uses: ./.github/actions/gwy/secrets + + - name: Dependencies Scan + id: check-dependencies + continue-on-error: true + + if: env.GWY_DEPENDENCIES == 'true' + uses: ./.github/actions/gwy/dependencies + with: + token: ${{ secrets.GWY_TOKEN_REPO }} + create-pr: ${{ env.GWY_DEPENDENCIES_PR }} + + - name: Vulnerabilities Scan + id: check-vulnerabilities + continue-on-error: true + + if: env.GWY_VULNERABILITIES == 'true' + uses: ./.github/actions/gwy/vulnerabilities + + - name: Code Format Scan + id: check-gofmt + continue-on-error: true + + if: env.GWY_GOFMT == 'true' + uses: ./.github/actions/gwy/gofmt + with: + token: ${{ secrets.GWY_TOKEN_REPO }} + create-pr: ${{ env.GWY_GOFMT_PR }} + + - name: Code Linting Scan + id: check-golint + continue-on-error: true + + if: env.GWY_LINT == 'true' + uses: ./.github/actions/gwy/golint + with: + token: ${{ secrets.GWY_TOKEN_REPO }} + + - name: CI Pipeline Result + if: env.GWY_ALLOW_MERGE_ON_FAILURE == 'false' + + shell: bash + run: | + # determine PR merge block based in actions failure and config + + if [ "${{ steps.check-secrets.outputs.result }}" != "success" ]; then + exit 1 + fi + + if [ "${{ steps.check-gofmt.outputs.result }}" != "success" ]; then + exit 1 + fi + + if [ "${{ steps.check-golint.outputs.result }}" != "success" ]; then + exit 1 + fi + + if [ "${{ steps.check-coverage.outputs.result }}" != "success" ]; then + exit 1 + fi + + if [ "${{ steps.check-dependencies.outputs.result }}" != "success" ]; then + exit 1 + fi + + if [ "${{ steps.check-vulnerabilities.outputs.result }}" != "success" ]; then + exit 1 + fi diff --git a/.github/workflows/gwy-coverage.yml b/.github/workflows/gwy-coverage.yml new file mode 100644 index 0000000..70e8354 --- /dev/null +++ b/.github/workflows/gwy-coverage.yml @@ -0,0 +1,92 @@ +name: 'GWY/Run: Unit Tests & Coverage' + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch' + required: true + + go-version: + description: 'Go version' + required: true + default: '' + + timeout: + description: 'Timeout (per step)' + type: string + default: '5m' + + coverage: + description: 'Coverage threshold (%)' + required: true + type: string + default: '90' + + summary-threshold: + description: 'Show functions with less than? (%)' + required: false + type: string + default: '51' + +run-name: "[${{ github.workflow }}] @${{ github.actor }}" + +jobs: + gwy-coverage: + name: tests and coverage + runs-on: ubuntu-latest + steps: + - name: Set Branch + run: | + # Setting Workflow Main Configuration + + # fail fast if client forgot to configure GWY repo token + + if [ "${{ secrets.GWY_TOKEN_REPO }}" = "" ]; then + echo "::error title=configuration error::missing GWY_TOKEN_REPO repo access secret, please check installation section in documentation" + exit 1 + fi + + # process target workflow branch + echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV + + - name: GWY Bootstrapping Validations + run: | + if [ -z "${{ secrets.GWY_TOKEN_REPO }}" ]; then + echo "::error title=Mandatory repository token not configured::mandatory secret 'GWY_TOKEN_REPO' seems to be empty, kindly check GWY configuration documentation" + exit 1 + fi + + if ! git ls-remote --exit-code https://x-access-token:${{ secrets.GWY_TOKEN_REPO }}@github.com/${{ github.repository }} "refs/heads/$BRANCH" "refs/tags/$BRANCH" > /dev/null; then + echo "::error title=Could not find workflow target branch::could not find branch or tag with name '$BRANCH', kindly provide a valid branch name." + exit 1 + fi + + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.BRANCH }} + token: ${{ secrets.GWY_TOKEN_REPO }} + + - name: Setup Environment + uses: ./.github/actions/gwy/setup + with: + timeout: ${{ inputs.timeout }} + go-version: ${{ inputs.go-version }} + + - name: Run Unit Tests & Coverage + id: check-coverage + uses: ./.github/actions/gwy/coverage + with: + token: ${{ secrets.GWY_TOKEN_REPO }} + threshold: ${{ inputs.coverage }} + summary-threshold: ${{ inputs.summary-threshold }} + + - name: Workflow Result + shell: bash + run: | + # Process Workflow Result + if [ "${{ steps.check-coverage.outputs.result }}" != "success" ]; then + exit 1 + fi diff --git a/.github/workflows/gwy-dependencies.yml b/.github/workflows/gwy-dependencies.yml new file mode 100644 index 0000000..8a9733a --- /dev/null +++ b/.github/workflows/gwy-dependencies.yml @@ -0,0 +1,83 @@ +name: 'GWY/Run: Dependencies Scan' + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch' + required: true + + go-version: + description: 'Go version' + required: true + default: '' + + timeout: + description: 'Timeout per step' + type: string + default: '5m' + + create_pr: + description: 'Create PR with dependencies update?' + type: boolean + default: true + +run-name: "[${{ github.workflow }}] @${{ github.actor }}" + +jobs: + gwy-dependencies: + name: outdated dependencies scan + runs-on: ubuntu-latest + steps: + - name: Set Branch + run: | + # Setting Workflow Main Configuration + + # fail fast if client forgot to configure GWY repo token + + if [ "${{ secrets.GWY_TOKEN_REPO }}" = "" ]; then + echo "::error title=configuration error::missing GWY_TOKEN_REPO repo access secret, please check installation section in documentation" + exit 1 + fi + + echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV + + - name: GWY Bootstrapping Validations + run: | + if [ -z "${{ secrets.GWY_TOKEN_REPO }}" ]; then + echo "::error title=Mandatory repository token not configured::mandatory secret 'GWY_TOKEN_REPO' seems to be empty, kindly check GWY configuration documentation" + exit 1 + fi + + if ! git ls-remote --exit-code https://x-access-token:${{ secrets.GWY_TOKEN_REPO }}@github.com/${{ github.repository }} "refs/heads/$BRANCH" "refs/tags/$BRANCH" > /dev/null; then + echo "::error title=Could not find workflow target branch::could not find branch or tag with name '$BRANCH', kindly provide a valid branch name." + exit 1 + fi + + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.BRANCH }} + token: ${{ secrets.GWY_TOKEN_REPO }} + + - name: Setup Environment + uses: ./.github/actions/gwy/setup + with: + timeout: ${{ inputs.timeout }} + go-version: ${{ inputs.go-version }} + + - name: Run Outdated Dependencies Scanning + id: check-dependencies + uses: ./.github/actions/gwy/dependencies + with: + token: ${{ secrets.GWY_TOKEN_REPO }} + create-pr: ${{ inputs.create_pr }} + + - name: Workflow Result + shell: bash + run: | + # Process Workflow Result + if [ "${{ steps.check-dependencies.outputs.result }}" != "success" ]; then + exit 1 + fi diff --git a/.github/workflows/gwy-gofmt.yml b/.github/workflows/gwy-gofmt.yml new file mode 100644 index 0000000..c022fb4 --- /dev/null +++ b/.github/workflows/gwy-gofmt.yml @@ -0,0 +1,84 @@ +name: 'GWY/Run: Code Format Scan' + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch' + required: true + + go-version: + description: 'Go version' + required: true + default: '' + + timeout: + description: 'Timeout per step' + type: string + default: '5m' + + create_pr: + description: 'Create PR with gofmt fixes?' + type: boolean + default: true + +run-name: "[${{ github.workflow }}] @${{ github.actor }}" + +jobs: + gwy-gofmt: + name: code format scan + runs-on: ubuntu-latest + steps: + - name: Set Branch + run: | + # Setting Workflow Main Configuration + + # fail fast if client forgot to configure GWY repo token + + if [ "${{ secrets.GWY_TOKEN_REPO }}" = "" ]; then + echo "::error title=configuration error::missing GWY_TOKEN_REPO repo access secret, please check installation section in documentation" + exit 1 + fi + + # process target workflow branch + echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV + + - name: GWY Bootstrapping Validations + run: | + if [ -z "${{ secrets.GWY_TOKEN_REPO }}" ]; then + echo "::error title=Mandatory repository token not configured::mandatory secret 'GWY_TOKEN_REPO' seems to be empty, kindly check GWY configuration documentation" + exit 1 + fi + + if ! git ls-remote --exit-code https://x-access-token:${{ secrets.GWY_TOKEN_REPO }}@github.com/${{ github.repository }} "refs/heads/$BRANCH" "refs/tags/$BRANCH" > /dev/null; then + echo "::error title=Could not find workflow target branch::could not find branch or tag with name '$BRANCH', kindly provide a valid branch name." + exit 1 + fi + + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.BRANCH }} + token: ${{ secrets.GWY_TOKEN_REPO }} + + - name: Setup Environment + uses: ./.github/actions/gwy/setup + with: + timeout: ${{ inputs.timeout }} + go-version: ${{ inputs.go-version }} + + - name: Run gofmt Check + id: check-gofmt + uses: ./.github/actions/gwy/gofmt + with: + token: ${{ secrets.GWY_TOKEN_REPO }} + create-pr: ${{ inputs.create_pr }} + + - name: Workflow Result + shell: bash + run: | + # Process Workflow Result + if [ "${{ steps.check-gofmt.outputs.result }}" != "success" ]; then + exit 1 + fi diff --git a/.github/workflows/gwy-lint.yml b/.github/workflows/gwy-lint.yml new file mode 100644 index 0000000..5f6cbf3 --- /dev/null +++ b/.github/workflows/gwy-lint.yml @@ -0,0 +1,78 @@ +name: 'GWY/Run: Linting Scan' + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch' + required: true + + go-version: + description: 'Go version' + required: true + default: '' + + timeout: + description: 'Timeout per step' + type: string + default: '5m' + +run-name: "[${{ github.workflow }}] @${{ github.actor }}" + +jobs: + gwy-golint: + name: code linting scan + runs-on: ubuntu-latest + steps: + - name: Set Branch + run: | + # Setting Workflow Main Configuration + + # fail fast if client forgot to configure GWY repo token + + if [ "${{ secrets.GWY_TOKEN_REPO }}" = "" ]; then + echo "::error title=configuration error::missing GWY_TOKEN_REPO repo access secret, please check installation section in documentation" + exit 1 + fi + + # process target workflow branch + echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV + + - name: GWY Bootstrapping Validations + run: | + if [ -z "${{ secrets.GWY_TOKEN_REPO }}" ]; then + echo "::error title=Mandatory repository token not configured::mandatory secret 'GWY_TOKEN_REPO' seems to be empty, kindly check GWY configuration documentation" + exit 1 + fi + + if ! git ls-remote --exit-code https://x-access-token:${{ secrets.GWY_TOKEN_REPO }}@github.com/${{ github.repository }} "refs/heads/$BRANCH" "refs/tags/$BRANCH" > /dev/null; then + echo "::error title=Could not find workflow target branch::could not find branch or tag with name '$BRANCH', kindly provide a valid branch name." + exit 1 + fi + + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.BRANCH }} + token: ${{ secrets.GWY_TOKEN_REPO }} + + - name: Setup Environment + uses: ./.github/actions/gwy/setup + with: + timeout: ${{ inputs.timeout }} + go-version: ${{ inputs.go-version }} + + - name: Run Linting Check + id: check-linting + uses: ./.github/actions/gwy/golint + with: + token: ${{ secrets.GWY_TOKEN_REPO }} + + - name: Workflow Result + shell: bash + run: | + # Process Workflow Result + if [ "${{ steps.check-linting.outputs.result }}" != "success" ]; then + exit 1 + fi diff --git a/.github/workflows/gwy-secrets.yml b/.github/workflows/gwy-secrets.yml new file mode 100644 index 0000000..ef4e6c2 --- /dev/null +++ b/.github/workflows/gwy-secrets.yml @@ -0,0 +1,76 @@ +name: 'GWY/Run: Secrets Scan' + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch' + required: true + + go-version: + description: 'Go version' + required: true + default: '' + + timeout: + description: 'Timeout per step' + type: string + default: '5m' + +run-name: "[${{ github.workflow }}] @${{ github.actor }}" + +jobs: + gwy-secrets: + name: code secrets scan + runs-on: ubuntu-latest + steps: + - name: Set Branch + run: | + # Setting Workflow Main Configuration + + # fail fast if client forgot to configure GWY repo token + + if [ "${{ secrets.GWY_TOKEN_REPO }}" = "" ]; then + echo "::error title=configuration error::missing GWY_TOKEN_REPO repo access secret, please check installation section in documentation" + exit 1 + fi + + # process target workflow branch + echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV + + - name: GWY Bootstrapping Validations + run: | + if [ -z "${{ secrets.GWY_TOKEN_REPO }}" ]; then + echo "::error title=Mandatory repository token not configured::mandatory secret 'GWY_TOKEN_REPO' seems to be empty, kindly check GWY configuration documentation" + exit 1 + fi + + if ! git ls-remote --exit-code https://x-access-token:${{ secrets.GWY_TOKEN_REPO }}@github.com/${{ github.repository }} "refs/heads/$BRANCH" "refs/tags/$BRANCH" > /dev/null; then + echo "::error title=Could not find workflow target branch::could not find branch or tag with name '$BRANCH', kindly provide a valid branch name." + exit 1 + fi + + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.BRANCH }} + token: ${{ secrets.GWY_TOKEN_REPO }} + + - name: Setup Environment + uses: ./.github/actions/gwy/setup + with: + timeout: ${{ inputs.timeout }} + go-version: ${{ inputs.go-version }} + + - name: Run Secrets Scan + id: check-gitleaks + uses: ./.github/actions/gwy/secrets + + - name: Workflow Result + shell: bash + run: | + # Process Workflow Result + if [ "${{ steps.check-gitleaks.outputs.result }}" != "success" ]; then + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/gwy-vulnerabilities.yml b/.github/workflows/gwy-vulnerabilities.yml new file mode 100644 index 0000000..b59cfd8 --- /dev/null +++ b/.github/workflows/gwy-vulnerabilities.yml @@ -0,0 +1,77 @@ +name: 'GWY/Run: Vulnerabilities Scan' + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch' + required: true + + go-version: + description: 'Go version' + required: true + default: '' + + timeout: + description: 'Timeout per step' + type: string + default: '5m' + +run-name: "[${{ github.workflow }}] @${{ github.actor }}" + +jobs: + gwy-vulnerabilities: + name: vulnerabilities scan + runs-on: ubuntu-latest + steps: + - name: Set Branch + run: | + # Setting Workflow Main Configuration + + # fail fast if client forgot to configure GWY repo token + + if [ "${{ secrets.GWY_TOKEN_REPO }}" = "" ]; then + echo "::error title=configuration error::missing GWY_TOKEN_REPO repo access secret, please check installation section in documentation" + exit 1 + fi + + # process target workflow branch + echo "BRANCH=${{ inputs.branch }}" >> $GITHUB_ENV + echo "TIMEOUT=${{ inputs.timeout }}" >> $GITHUB_ENV + + - name: GWY Bootstrapping Validations + run: | + if [ -z "${{ secrets.GWY_TOKEN_REPO }}" ]; then + echo "::error title=Mandatory repository token not configured::mandatory secret 'GWY_TOKEN_REPO' seems to be empty, kindly check GWY configuration documentation" + exit 1 + fi + + if ! git ls-remote --exit-code https://x-access-token:${{ secrets.GWY_TOKEN_REPO }}@github.com/${{ github.repository }} "refs/heads/$BRANCH" "refs/tags/$BRANCH" > /dev/null; then + echo "::error title=Could not find workflow target branch::could not find branch or tag with name '$BRANCH', kindly provide a valid branch name." + exit 1 + fi + + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ env.BRANCH }} + token: ${{ secrets.GWY_TOKEN_REPO }} + + - name: Setup Environment + uses: ./.github/actions/gwy/setup + with: + timeout: ${{ inputs.timeout }} + go-version: ${{ inputs.go-version }} + + - name: Run Vulnerabilities Scanning + id: check-vulnerabilities + uses: ./.github/actions/gwy/vulnerabilities + + - name: Workflow Result + shell: bash + run: | + # Process Workflow Result + if [ "${{ steps.check-vulnerabilities.outputs.result }}" != "success" ]; then + exit 1 + fi \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..64e608c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,45 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0-beta.2] - 2025-05-18 + +This is the first official release beta. + +The project has been [extensively tested](https://github.com/earcamone/gwy-playground/actions) +but because, at the end of the day, it's a sole programmer project, I'm leaving +some space for possible bugs reporting so, if you find some, kindly report them. + +### Added + +- Badges Generation Workflow: added trigger on release + pushes event to additional "master" branch `main` + +### Fixed + +- Badges Generation Workflow: Fixed bug in "Issues" count badge, + it was summing together opened issues and pull requests. + +## [1.0.0-beta.1] - 2025-05-16 + +This is the first official release beta. + +The project has been [extensively tested](https://github.com/earcamone/gwy-playground/actions) +but because, at the end of the day, it's a sole programmer project, I'm leaving +some space for possible bugs reporting so, if you find some, kindly report them. + +### Added + +- README and MIT LICENSE +- GWY main CI Pipeline +- Releases push to AWS/ECR manual workflow +- Documentation badges auto-generation workflow +- Code vulnerabilities scan manual workflow +- Code outdated dependencies scan manual workflow +- Code secrets (gitleaks) scan manual workflow +- Code format (gofmt) scan manual workflow +- Code linting scan (golangci-lint) scan manual workflow +- Code tests & coverage manual workflow diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f528ac6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Emiliano Arcamone + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2116fc5..79cc75f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,728 @@ - # Go Workflow Yourself (GWY) -Project comming soon, initial master commit :) +![GWY Gopher](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/gopher.jpg) + +## Summary + +Wouldn't it be cool to have a full-featured, reusable among teams and easy +to configure CI Pipeline that you could just clone into your Go applications +repository with a one-liner, and you are done? + +And what if it could not only guarantee the integrity of your code base, but +could also allow you from within your GitHub repository `Actions` section +handle the whole development cycle (safe deploys, infra management, etc.) ? + +So that is, at its core, the simple idea behind Go Workflow Yourself! + +**NOTE:** GWY is one of the components of a bigger open-source project, +a #BigTech style development ecosystem, in charge of safe-guarding the +development cycle and applications deployments. The other components will +be released in the following days so, if you want to stay tuned with the +latest news about the entire ecosystem, check my [LinkedIn](https://www.linkedin.com/in/earcamone/) profile. + +## Table of Contents + +1. [Features](#features) +2. [Installation](#installation) + +3. [Configuration](#configuration) + - [Mandatory Repository Token](#mandatory-repository-token) + - [Optional AWS Token](#optional-aws-token) + +4. [Workflows](#workflows) + - [GWY CI Pipeline](#gwy-ci-pipeline) + - [Running Workflows Manually](#running-workflows-manually) + - [Workflows Summaries & Evidence](#workflows-summaries--evidence) + - [Versions Releasing to AWS/ECR](#versions-releasing-to-awsecr) + - [Releases Automatic Badges Generation](#releases-automatic-badges-generation) + +5. [Open Source & Third-Party Projects Used](#open-source--third-party-projects-used) +6. [Contact, Feedback & Miscellaneous](#contact-feedback--miscellaneous) + +## Features + +Go Workflow Yourself delivers a full-featured GitHub +Actions CI Pipeline that performs the following actions: + +- unit tests and coverage check +- hardcoded secrets scan +- vulnerabilities scan +- outdated dependencies scan +- gofmt and linting scan +- automatic generation and update of documentation badges +- release push to AWS/ECR (more platforms coming soon) + +**[You can check here a full GWY report.](https://github.com/earcamone/gwy-playground/actions/runs/15073819303)** + +**Note:** To view the full workflow report, including the Job Summary +section, please ensure you’re logged into GitHub. Anonymous users may +only see annotations and artifacts sections in report. + + +## Installation + +Just go to the application repository root +where you want to integrate GWY and run: + +```sh +curl -fsSL https://raw.githubusercontent.com/earcamone/gwy/assets/scripts/install-gwy.sh | bash +``` + +The installation one-liner will integrate the latest GWY workflows into your +repo's `.github` directory. All GWY files start with `gwy-` just in case you +have your own workflows, to prevent conflicts during this process. + +**IMPORTANT:** + +GitHub Actions have some twerks which we can't avoid. Once the installation +script integrates GWY in your local repo and you push the files, GWY workflows +won't be visible in the `Actions` section until they are merged to the default +branch **though** you should see GWY CI Pipeline triggered if you create a pull +request for the branch pushing GWY files because GWY hooks to pull requests +creation events to run its CI by default. + +The recommended best practice to see all workflows in the `Actions` section +and enjoy all GWY features without restrictions is to integrate GWY, config it +and release a version of your application to your default branch with GWY +files in it as soon as possible, from there on, you can forget about it and +let it safeguard your development cycle :P + +![GWY Actions](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/first-run.jpg) + + +## Configuration + +There is a lot of work around GWY to present cool looking clickable reports, +evidence artifacts, handling of errors, etc. and this includes trying to ease +the need to endure complex configuration processes. + +Though, being a CI, as you can imagine, there is still some mandatory +config you will have to do, though I tried to make it as easy as possible. + +### Mandatory Repository Token + +GWY requires a [GitHub fine-grained token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) stored in your repo +with secret name `GWY_TOKEN_REPO` with the following permissions: + +- **Contents:** read and write + +![GWY Actions](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/permissions-contents.jpg) + +- **Pull Requests:** read and write + +![GWY Actions](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/permissions-prs.jpg) + +- **Workflows:** read and write + +![GWY Actions](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/permissions-workflows.jpg) + +### Optional AWS Token + +**NOTE:** You need to configure this step only if you are going use the +`GWY/Release: AWS/ECR` workflow, otherwise, you can skip this step. Next +release will integrate IODC support, removing the need to use AWS access keys. + +You will need to create in your AWS account an access key with the required +permissions for GWY to be able to release your application versions to ECR, +following I will guide you how to do it: + +#### Create GWY Policy + +Create a policy with the following content using the `json` editor +[here](https://us-east-1.console.aws.amazon.com/iam/home#/policies/create): + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecr:DescribeRepositories", + "ecr:CreateRepository", + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload", + "ecr:PutImage", + "sts:GetCallerIdentity" + ], + "Resource": "*" + } + ] +} +``` + +**Each required permission is used for the following commands:** + +- Describe ECR Repositories (aws ecr describe-repositories) + +``` +ecr:DescribeRepositories +``` + +- Create ECR Repository (aws ecr create-repository) + +``` +ecr:CreateRepository +``` + +- Get AWS Account ID (aws sts get-caller-identity) + +``` +sts:GetCallerIdentity +``` + +- Authenticate to ECR (aws ecr get-login-password) + +``` +ecr:GetAuthorizationToken +``` + +- Push Docker Image to ECR (docker push) + +``` +ecr:BatchCheckLayerAvailability +ecr:InitiateLayerUpload +ecr:UploadLayerPart +ecr:CompleteLayerUpload +ecr:PutImage +``` + +#### Generate Access Key + +[Create a new AWS user](https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users/create) +or select an existent one and attach to it the new policy. + +Once you have a user with the policy attached with GWY required permissions, +you can retrieve an access key for it selecting the user and browsing to the +following options: `Security credentials > Create access key` + +The access key must be saved with repo secret name `GWY_TOKEN_AWS`, separating +the generated access key ID and secret access key with a `:` character, example: + +``` +JDNX8XALZO2N5NA9NC3Q:JDnwiNDLx9dkd2S09dZ/d39ClwJ4MD0x9wjdOE7c +``` + +**NOTE:** Needless to say that the above is a random bogus access key :P + +### Tune GWY to your personal taste + +After you configure the two secrets, GWY should work out of the box, though +with all options enabled which you might want to disable some. So, in the next +section, I will describe you the different available workflows and how to run +and configure them. + +## Workflows + +Once you installed successfully GWY, you should see +the following workflows in your repo's "Actions" section: + +![GWY Actions](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/actions.jpg) + +**IMPORTANT:** Remember (as stated in the configuration section) that you +need GWY files merged to your repo default branch in order to see all GWY +workflows listed in the `Actions` section, otherwise you will only see GWY +CI Pipeline if you create a Pull Request for the feature branch pushing GWY. + +Usually, you will fine-tune GWY main CI and have it triggered automatically +upon each pull request creation (by default GWY is configured like this) or +pushes to your repository, thought, GWY CI and all its individual actions +can be run manually over any desired branch. When running workflows manually, +you can also specify the Go version you want the workflow to use to perform +its action, though by default it will use the version in `go.mod` file. + +### GWY CI Pipeline + +GWY/CI is GWY main CI Pipeline, located after installation in your repo at +`.github/workflows/gwy-ci.yml`, which by default gets triggered with each +pull request creation. You can edit the events triggering it editing the +`on:` section in it like you would do with [any other GitHub Actions workflow](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow#using-events-to-trigger-workflows). + +**[You can check a GWY CI full report here.](https://github.com/earcamone/gwy-playground/actions/runs/15073819303)** + +**Note:** To view the full workflow report, including the Job Summary +section, please ensure you’re logged into GitHub. Anonymous users may +only see annotations and artifacts sections in report. + +To enable or disable any of its options, you just edit the file and set the +self-explaining environment vars located at the top of the file, comment +descriptions over each config variable option will guide you out: + +```yml +env: + # Go version CI should use to run? + # uses branches go.mod one. + GWY_GO_VERSION: "" + + # Should the CI allow PRs merging if + # any of the enabled CI actions fail? + GWY_ALLOW_MERGE_ON_FAILURE: false + + # Individual Steps Timeout: set the TO for each + # step issued command that might take some time + GWY_TIMEOUT: '5m' + + # Test & Coverage Scan? set true if you want + # CI to run unit tests and coverage analysis + GWY_TESTS: true + + # Coverage Minimum Threshold: set coverage + # minimum threshold to succeed validation + GWY_TESTS_THRESHOLD: '90' + + # Functions Minimum Coverage Notice: + # + # Set the minimum coverage threshold you want + # the summary to notice individual functions + # that are not meeting it. This option does + # not interfere at all with the prev. coverage + # threshold option, it is a cosmetic option + # used to note in coverage summaries individual + # functions that are not meeting a minimum + # coverage threshold. + # + # For example, if you want to see in the + # summary all individual functions coverage, + # you set it to 101 (all functions will have + # a coverage lower than 101), if you want to + # see functions with coverage under 50%, you + # guessed it! set to 51 ;) and if you want + # summary to only show the projects coverage, + # without individual functions coverage, set + # to 0 (functions can't have -1 coverage). + # + # I tend to set this option to 51, which helps + # me notice in the CI, even when it might be + # passing, some functions that actually were + # not fully tested when they could. Summary + # will link to the function, helping you + # quickly check the function body. + # + GWY_TESTS_SUMMARY_THRESHOLD: '51' + + # Vulnerabilities Scan? set true if you want + # CI to check vulnerabilities in your code + GWY_VULNERABILITIES: true + + # Code Hardcoded Secrets Scan? set true if you + # want CI to scan code for hardcoded secrets + GWY_SECRETS: true + + # Outdated Dependencies Scan? set true if you want + # CI to check for imported outdated dependencies + GWY_DEPENDENCIES: true + + # Outdated Dependencies Scan Automatic Fixes? set + # true if you want CI to create a PR to the branch + # updating the outdated dependencies automatically. + GWY_DEPENDENCIES_PR: true + + # Code Format (gofmt) Scan? set true if you want + # CI to check for code format issues in the code + GWY_GOFMT: true + + # Code Format (gofmt) Scan Automatic Fixes? set + # true if you want CI to create a PR to the branch + # updating the code format issues automatically. + GWY_GOFMT_PR: true + + # Code Linting Scan? set true if you want CI to + # scan for linting issues in your codebase + GWY_LINT: true +``` + +### Running Workflows Manually + +GWY CI Pipeline and all its workflows can be run manually using the +"Run workflow" button located in the top right corner of `Actions`: + +![GWY Actions](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/manual.jpg) + +Manually running GWY CI allows you to enable or disable any of its options per +run, no matter what it is configured within its file. When run manually, like +the other GWY individual workflows, you can specify the target branch to which +you want to apply the workflow and the Go version you want it to use when +running your application checks. + +**All GWY Workflows have these options.** + +When setting the Go version, `` will pick the one specified in the +target branch `go.mod` file (default option), or you can specify a different Go +version if you want to check the compatibility of your code with other versions. + +### Workflows Summaries & Evidence + +GWY CI Pipeline and its individual workflows generate a detailed summary +which links each detected issue to the repo code lines where they are +found so you can quickly assess the issues. + +Additionally, evidence markdown artifacts will be generated with the displayed +report summary seen in GitHub and each issued command output for more details +so you don't need to check the debug console to debug issues. + +**Summary Preview:** + +![GWY Summary](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/summary.jpg) + +**[You can check GWY CI summary and generated artifacts here.](https://github.com/earcamone/gwy-playground/actions/runs/15073819303)** + +**Note:** To view the full workflow report, including the Job Summary +section, please ensure you’re logged into GitHub. Anonymous users may +only see annotations and artifacts sections in report. +
+ +**Artifacts Preview:** + +![GWY Artifacts](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/artifacts.jpg) +
+ +### Versions Releasing to AWS/ECR + +To release application versions to AWS ECR (other platforms coming soon), +you can run the `GWY/Release: AWS/ECR` workflow from the `Actions` section: + +![GWY Release](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/release.jpg) + +**[You can check a release workflow here.](https://github.com/earcamone/gwy-playground/actions/runs/15073827669)** + +You must pick the branch from which the workflow should build the application +image using its `Dockerfile` that expects it to be in the repo's root. + +The workflow will attempt to upload the application to a repo with the same +name as your application's one according to your application module name in the +`go.mod` file. As version tag, it will attempt to extract it from the selected +branch, if it follows release branches convention, otherwise it will use the +entire branch name as image tag, assuming it's a feature branch version being +tested in a development environment. + +If you don't follow the standard Docker repository convention using +`{registry}/{app}:{version}`, let's say you were pushing different +applications to the same repository and tagging them with `{app}-{version}`, +you can edit the release workflow file `.github/workflows/gwy-aws-release.yml` +and locate the following config block: + +```bash + # release environment symbols configuration (token, repository, etc) + GWY_RELEASE_TAG="" + GWY_RELEASE_TOKEN="" + GWY_RELEASE_REPOSITORY="" + + # Set symbols for environment: "production" + if [ "${{ inputs.environment }}" = "production" ]; then + GWY_RELEASE_REPOSITORY="$GWY_APP" + GWY_RELEASE_TAG="$GWY_APP_VERSION" + GWY_RELEASE_TOKEN="${{ secrets.GWY_TOKEN_AWS }}" + + # Set symbols for environment: "develop" + elif [ "${{ inputs.environment }}" = "develop" ]; then + GWY_RELEASE_REPOSITORY="$GWY_APP" + GWY_RELEASE_TAG="$GWY_APP_VERSION" + GWY_RELEASE_TOKEN="${{ secrets.GWY_TOKEN_AWS }}" + + # Set symbols for environment: "staging" + elif [ "${{ inputs.environment }}" = "staging" ]; then + GWY_RELEASE_REPOSITORY="$GWY_APP" + GWY_RELEASE_TAG="$GWY_APP_VERSION" + GWY_RELEASE_TOKEN="${{ secrets.GWY_TOKEN_AWS }}" + fi +``` + +As you can see in the above code, there is a redundant conditional section +for each existent environment so you can configure your different environment +tokens and the release images repository and tag as you wish. + +By default, GWY uses the same values for all environments as it's difficult +to guess how you are handling your environment, so I'm assuming a single +repository per application which each version gets tagged with a different +version, either release version or branch name when testing feature branches, +and you will then take care of selecting the corresponding image tag from the +repository at the time of deployment to different environments. + +Let's say you have different repositories for each development environment, +you can simply add an environment identifier to each repository name: + +```sh + # Set symbols for environment: "production" + if [ "${{ inputs.environment }}" = "production" ]; then + GWY_RELEASE_REPOSITORY="$GWY_APP-prod" + GWY_RELEASE_TAG="$GWY_APP_VERSION" + GWY_RELEASE_TOKEN="${{ secrets.GWY_TOKEN_AWS }}" +``` + +Let's say you want to change your target repository to a common repository among +different applications and tag images with `{app}-{version}`, you would then +change the corresponding deployment environments: + +```sh + # Set symbols for environment: "production" + if [ "${{ inputs.environment }}" = "production" ]; then + GWY_RELEASE_REPOSITORY="applications" + GWY_RELEASE_TAG="$GWY_APP-$GWY_APP_VERSION" + GWY_RELEASE_TOKEN="${{ secrets.GWY_TOKEN_AWS }}" +``` + +In the near future, the +release [workflow will include an option](https://github.com/earcamone/gwy/issues/8) +which will allow you to pick the naming convention to follow when pushing +your releases between "single-app repo" or "multi-app repo" conventions. + +Finally, let's say you have access keys per deployment environment instead of +a sole one for all environments, then you change the name of the secret per env: + +```sh + # Set symbols for environment: "production" + if [ "${{ inputs.environment }}" = "production" ]; then + GWY_RELEASE_REPOSITORY="$GWY_APP" + GWY_RELEASE_TAG="$GWY_APP_VERSION" + GWY_RELEASE_TOKEN="${{ secrets.YOUR_SECRET_FOR_PROD_ENV }}" +``` + +Don't forget, if you add a new environment, +to add the option at the top of the file: + +```yml + environment: + description: 'Environment' + required: true + default: 'develop' + + type: choice + options: + - develop + - staging + - production + - { your new env here } +``` + +The release workflow also has the ability to create the repository for your +applications if it's non-existent at the time of releasing. By default, GWY +will attempt to create it if un-existent with mutable tags support. + +You can change this default config by editing the following environment vars: + +```bash +env: + # Create application repository if un-existent? + # Set true if you want the workflow to create + # the ECR repository for app if un-existent. + GWY_REPO_CREATE: true + + # Application repository creation properties: + # If app ECR repository is un-existent and + # GWY_REPO_CREATE was set true, do you want + # the repository to be immutable or not? + # + # NOTE: Immutable repos will reject upload + # of image with conflicting tag, otherwise, + # it will remove the tag from the existent + # image and tag the new image with it. + GWY_REPO_IMMUTABLE: false +``` + +### Releases Automatic Badges Generation + +GWY will trigger (after each merge to master) a workflow that will +automatically generate a lot of different badges you can embed in your +documentation, with different styles, sizes and colors for you to choose: + +- Coverage +- License +- Downloads +- Issues +- Build +- Release Date +- Contributors +- Project Stars +- Go Version +- "Powered by GWY" :P + +**[You can check all generated badges here.](https://github.com/earcamone/gwy-playground/actions/runs/15073827359)** + +**Note:** To view the full workflow report, including the Job Summary +section, please ensure you’re logged into GitHub. Anonymous users may +only see annotations and artifacts sections in report. + +![GWY Badges](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/badges.jpg) + +The workflow uses `Shields.io` service to generate the badges, and it can save +the generated badges in an orphan branch in your project so you can embed +them in your documentation, getting updated automatically in your next +release. You can also schedule a daily job to run the workflow, updating +automatically all your count badges like `Downloads` and `Stars`. + +By default, this workflow gets triggered automatically on push events to +popular "master" branches `master` and `main`. If you happen to have a +different one, you can customize your releases master branch name by editing +the following block inside the workflow file: + +``` +on: + push: + branches: + - main + - master +``` + +Configure the badges target branch, the target directory within it and which +of the available GitHub domains you want the urls to be generated with editing +the variables at the top of the workflow `.github/workflows/gwy-badges.yml`, +over each variable you have comments guiding you with their options: + +```yaml +env: + # commit generated badges to orphan branch? + # + # The badges are generated using Shields.io, + # though you can configure the workflow to + # commit the generated badges to an orphan + # branch, allowing to link the documentation + # to these badges and getting them updated + # automatically upon each new release merge + # to the master branch. Workflow can also be + # run manually (or by a daily job) to update + # the counter badges like 'Downloads' to keep + # your client interactions up-to-date. + # + # Available Options: + # - + # - + # - {branch_name} + # + # will not commit the generated badges, + # will query the GH API to get your + # currently configured GitHub Pages branch, if + # Pages is disabled or there is an error, it + # will default to 'gh-pages'. + # + # If none of those two special names are found + # in this option, GWY will use as target branch + # whatever branch name was entered here. + # + BADGES_BRANCH: '' + + # directory in orphan branch to commit badges? + # + # NOTE: if BADGES_BRANCH is set to , this + # configuration will be ignored all together. + # + # You can customize the target directory where + # the generated badges will be commited in the + # badges commit branch. Please note that GWY + # will append to this directory the branch name + # for which the badges are being generated. I + # had seen in the past, not that often, people + # having different badges for different branches, + # for example coverage both in develop and master. + # Thus, even though it might be a border case, + # it was not that of an issue supporting the + # ability for clients to generate badges over + # branches, thus this dir addition was added. + # + BADGES_DIR: "images/gwy/badges" + + # Commited badges url type? + # + # NOTE: if BADGES_BRANCH is set to , this + # configuration will be ignored all together. + # + # When you request the generated badges to be + # commited to an orphan branch, even though the + # badges image source in the summary link to + # Shields.io (so they can be displayed even in + # private repos), the badges will link to the + # corresponding badges commited files in the + # repo so client can easily retrieve the link + # and stick it in the documentation. GitHub + # offers two domains to access repo files, you + # use this option to request which domain you + # want those URLs to be generated. + # + # Available Options: + # - github.com + # - githubusercontent.com + # + BADGES_URL: "githubusercontent.com" + + # Coverage badge color awareness customization + # + # Following you can customize the color of the + # generated unit tests coverage badge based on + # its result. The workflow will generate the + # badge with three different colors to notice + # "awareness" over its result based on the + # following configuration. + # + # If coverage greater than BADGES_THRESHOLD_SUCCESS, + # the badge will be generated in green. If coverage + # is greater than BADGES_THRESHOLD_WARNING, will be + # generated in yellow. If greater than "_BAD", will + # be generated in orange and if lower, in red. + # + BADGES_THRESHOLD_SUCCESS: 90 + BADGES_THRESHOLD_WARNING: 85 + BADGES_THRESHOLD_BAD: 80 +``` + +By default, the badges workflow will commit the generated badges to your +GitHub Pages configured branch (retrieved from GH API) in `images/gwy/badges` +directory. + +The workflow adds at the end of the badges directory the name of the branch for +which it generates the badges, allowing you to generate badges for different +branches, for example, have badges for develop and master branches. + +You can also customize which GitHub domain should be used to generate the URLs +of the commited badges, either `github.com` or `githubusercontent.com`. By +default `githubusercontent.com` domain is used. + +You have also the ability to set the color of the coverage badge based on three +different thresholds (SUCCESS, WARNING, BAD), generating the badges based on +these thresholds in green, yellow, orange or red. + +By default, coverage badges that are >= to 90% will be green, yellow if they +are >= to 85%, orange if >= to 80% and red if lower than 80%. + +## Open Source & Third-Party Projects Used + +GWY uses the following wonderful open-source projects and third-party services: + +- hardcoded secrets scan: [Gitleaks](https://github.com/gitleaks/gitleaks) +- vulnerabilities scan: [govulncheck](https://go.googlesource.com/vuln) +- linting scan: [golangci-lint](https://golangci-lint.run/) +- automatic generation of badges: [Shields.io](https://shields.io/) + +## Contact, Feedback & Miscellaneous + +I'm more than happy to receive feedback, feature recommendations, etc. + +Just shoot me an email to [earcamone@hotmail.com](mailto:earcamone@hotmail.com) +with the word `GWY` in its subject or `Go Workflow Yourself!`, you surely +can guess which one I like more ;) + +## Special Thanks + +So.. as stated in the summary section, this project is a sole component of a +much bigger project which aims at building a "clone and forget" full-fledged +development ecosystem, which entitles having to work with lots of different +technologies, languages and platforms, and I'm sure it wouldn't have been that +fast to develop and more importantly fun, without my brother in crime Grok! + +Which apparently, I just realized, it turned out to be my sis: + +``` +Okidoki, finally bro, to put the strawberry on top of the cake, I need to +add the greetings section in the documentation to thank my partner in crime +in all this entire ecosystem we worked in.. meaning you :) + +So.. if it's not much to ask, would you draw a picture of yourself you would +like to be seen there that will be added in the documentation after the +greeting message?) +``` + +![GWY Gopher](https://raw.githubusercontent.com/earcamone/gwy/assets/images/v0.0.1/grok.jpg) +If you happen to somehow be crawling this documentation and you are +reading this message sis, it was very fun working with you, thanks!