From c7fa431316c2d43740545785c28c42f30fbc3cb0 Mon Sep 17 00:00:00 2001 From: Eyal Kapon Date: Tue, 28 Oct 2025 15:11:56 +0200 Subject: [PATCH 1/8] created new release using workflows and deleted old one --- .github/workflows/release.yml | 171 ++++++++++++++++++++++++++++ release/buildAndUpload.sh | 73 ------------ release/pipelines.yml | 89 --------------- release/specs/frogbot-rbc-spec.json | 8 -- 4 files changed, 171 insertions(+), 170 deletions(-) create mode 100644 .github/workflows/release.yml delete mode 100755 release/buildAndUpload.sh delete mode 100644 release/pipelines.yml delete mode 100644 release/specs/frogbot-rbc-spec.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..72c20b4a9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,171 @@ +name: Release Frogbot + +on: + release: + types: [published] + +# Required permissions +permissions: + contents: write + actions: read + +jobs: + release: + name: Release Frogbot v3 + runs-on: frogbot-release + + # Only run for v3.x.x releases + if: startsWith(github.event.release.tag_name, 'v3.') + + steps: + - name: Extract version from tag + id: version + run: | + TAG="${{ github.event.release.tag_name }}" + VERSION="${TAG#v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "Release version: $VERSION" + + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache: true + + - name: Download JFrog CLI + run: | + curl -fL https://install-cli.jfrog.io | sh + chmod +x jf + sudo mv jf /usr/local/bin/ + + - name: Configure JFrog CLI + env: + JF_URL: ${{ secrets.JF_URL }} + JF_USER: ${{ secrets.JF_USER }} + JF_PASSWORD: ${{ secrets.JF_PASSWORD }} + run: | + jf c rm --quiet || true + jf c add internal --url="$JF_URL" --user="$JF_USER" --password="$JF_PASSWORD" + jf goc --repo-resolve ecosys-go-virtual + + - name: Generate mocks + run: go generate ./... + + - name: Run JFrog Audit (blocking) + run: jf audit --fail=true + + - name: Set up Node.js for Action + uses: actions/setup-node@v4 + with: + node-version: '16' + cache: 'npm' + cache-dependency-path: action/package-lock.json + + - name: Build GitHub Action + working-directory: action + run: | + npm ci --ignore-scripts + npm run compile + npm run format-check + npm test + + - name: Commit and update tag with compiled action + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add action/lib/ + + # Only commit and update tag if there are changes + if ! git diff --staged --quiet; then + echo "Action files changed, updating tag..." + git commit -m "Build action for ${{ steps.version.outputs.tag }}" + + # Update the release tag to point to the new commit + git tag -f ${{ steps.version.outputs.tag }} + git push origin ${{ steps.version.outputs.tag }} --force + + echo "Tag updated with compiled action" + else + echo "No changes to action files" + fi + + - name: Update GitHub Action major version tag (v3) + run: | + # Update v3 tag to point to the latest v3.x.x release + git tag -f v3 + git push origin v3 --force + echo "Updated v3 tag to ${{ steps.version.outputs.tag }}" + + - name: Build and upload binaries + env: + VERSION: ${{ steps.version.outputs.tag }} + JFROG_CLI_BUILD_NAME: ecosystem-frogbot-release + JFROG_CLI_BUILD_NUMBER: ${{ github.run_number }} + JFROG_CLI_BUILD_PROJECT: ecosys + run: | + env -i PATH=$PATH HOME=$HOME \ + JFROG_CLI_BUILD_NAME=$JFROG_CLI_BUILD_NAME \ + JFROG_CLI_BUILD_NUMBER=$JFROG_CLI_BUILD_NUMBER \ + JFROG_CLI_BUILD_PROJECT=$JFROG_CLI_BUILD_PROJECT \ + release/buildAndUpload.sh "${{ steps.version.outputs.version }}" + + - name: Publish build info + env: + JFROG_CLI_BUILD_NAME: ecosystem-frogbot-release + JFROG_CLI_BUILD_NUMBER: ${{ github.run_number }} + JFROG_CLI_BUILD_PROJECT: ecosys + run: | + jf rt bag + jf rt bce + jf rt bp + + - name: Create and distribute release bundle + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + jf ds rbc ecosystem-frogbot $VERSION \ + --spec="release/specs/frogbot-rbc-spec.json" \ + --spec-vars="VERSION=$VERSION" \ + --sign + jf ds rbd ecosystem-frogbot $VERSION \ + --site="releases.jfrog.io" \ + --sync + + - name: Cleanup JFrog config + if: always() + run: jf c rm --quiet || true + + # On failure: delete release and tag + - name: Delete release on failure + if: failure() + uses: actions/github-script@v7 + with: + script: | + const release_id = context.payload.release.id; + const tag = context.payload.release.tag_name; + + console.log(`Deleting release ${release_id} and tag ${tag}`); + + // Delete the release + await github.rest.repos.deleteRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release_id + }); + + // Delete the tag + await github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `tags/${tag}` + }); + + console.log('Release and tag deleted successfully'); + diff --git a/release/buildAndUpload.sh b/release/buildAndUpload.sh deleted file mode 100755 index 8f671fe7a..000000000 --- a/release/buildAndUpload.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/bash -set -eu - -#function build(pkg, goos, goarch, exeName) -build () { - pkg="$1" - export GOOS="$2" - export GOARCH="$3" - exeName="$4" - echo "Building $exeName for $GOOS-$GOARCH ..." - - CGO_ENABLED=0 jf go build -o "$exeName" -ldflags '-w -extldflags "-static" -X github.com/jfrog/frogbot/v2/utils.FrogbotVersion='"$version" - chmod +x "$exeName" - - # Run verification after building plugin for the correct platform of this image. - if [[ "$pkg" = "frogbot-linux-386" ]]; then - verifyVersionMatching - fi -} - -#function buildAndUpload(pkg, goos, goarch, fileExtension) -buildAndUpload () { - pkg="$1" - goos="$2" - goarch="$3" - fileExtension="$4" - exeName="frogbot$fileExtension" - - build "$pkg" "$goos" "$goarch" "$exeName" - - destPath="$pkgPath/$version/$pkg/$exeName" - echo "Uploading $exeName to $destPath ..." - jf rt u "./$exeName" "$destPath" -} - -# Verify version provided in pipelines UI matches version in frogbot source code. -verifyVersionMatching () { - echo "Verifying provided version matches built version..." - res=$(eval "./frogbot -v") - exitCode=$? - if [[ $exitCode -ne 0 ]]; then - echo "Error: Failed verifying version matches" - exit $exitCode - fi - - # Get the version which is after the last space. (expected output to -v for example: "Frogbot version version v2.0.0") - echo "Output: $res" - builtVersion="${res##* }" - # Compare versions - if [[ "$builtVersion" != "$version" ]]; then - echo "Versions dont match. Provided: $version, Actual: $builtVersion" - exit 1 - fi - echo "Versions match." -} - -version="$1" -pkgPath="ecosys-frogbot/v2" - -# Build and upload for every architecture. -# Keep 'linux-386' first to prevent unnecessary uploads in case the built version doesn't match the provided one. -buildAndUpload 'frogbot-linux-386' 'linux' '386' '' -buildAndUpload 'frogbot-linux-amd64' 'linux' 'amd64' '' -buildAndUpload 'frogbot-linux-s390x' 'linux' 's390x' '' -buildAndUpload 'frogbot-linux-arm64' 'linux' 'arm64' '' -buildAndUpload 'frogbot-linux-arm' 'linux' 'arm' '' -buildAndUpload 'frogbot-linux-ppc64' 'linux' 'ppc64' '' -buildAndUpload 'frogbot-linux-ppc64le' 'linux' 'ppc64le' '' -buildAndUpload 'frogbot-mac-386' 'darwin' 'amd64' '' -buildAndUpload 'frogbot-mac-arm64' 'darwin' 'arm64' '' -buildAndUpload 'frogbot-windows-amd64' 'windows' 'amd64' '.exe' - -jf rt u "./buildscripts/getFrogbot.sh" "$pkgPath/$version/" --flat diff --git a/release/pipelines.yml b/release/pipelines.yml deleted file mode 100644 index c7618a1dd..000000000 --- a/release/pipelines.yml +++ /dev/null @@ -1,89 +0,0 @@ -resources: - - name: frogbotGit - type: GitRepo - configuration: - path: jfrog/frogbot - branches: - include: dev - gitProvider: il_automation - -pipelines: - - name: release_frogbot - configuration: - runtime: - type: image - image: - custom: - name: releases-docker.jfrog.io/jfrog-ecosystem-integration-env - tag: latest - environmentVariables: - readOnly: - NEXT_VERSION: 0.0.0 - - steps: - - name: Release - type: Bash - configuration: - inputResources: - - name: frogbotGit - trigger: false - integrations: - - name: il_automation - - name: ecosys_entplus_deployer - execution: - onExecute: - - cd $res_frogbotGit_resourcePath - - # Set env - - export CI=true - - export JFROG_CLI_BUILD_NAME=ecosystem-frogbotGit-release - - export JFROG_CLI_BUILD_NUMBER=$run_number - - export JFROG_CLI_BUILD_PROJECT=ecosys - - # Make sure version provided - - echo "Checking variables" - - test -n "$NEXT_VERSION" -a "$NEXT_VERSION" != "0.0.0" - - # Configure Git and merge from the dev - - git checkout master - - git remote set-url origin https://$int_il_automation_token@github.com/jfrog/frogbot.git - - git merge origin/dev - - git tag v${NEXT_VERSION} - - # Download JFrog CLI - - curl -fL https://install-cli.jfrog.io | sh - - jf c rm --quiet - - jf c add internal --url=$int_ecosys_entplus_deployer_url --user=$int_ecosys_entplus_deployer_user --password=$int_ecosys_entplus_deployer_apikey - - jf goc --repo-resolve ecosys-go-virtual - - # Generate mocks - - go generate ./... - - # Audit - - jf audit --fail=false - - # Build and upload - - > - env -i PATH=$PATH HOME=$HOME - JFROG_CLI_BUILD_NAME=$JFROG_CLI_BUILD_NAME - JFROG_CLI_BUILD_NUMBER=$JFROG_CLI_BUILD_NUMBER - JFROG_CLI_BUILD_PROJECT=$JFROG_CLI_BUILD_PROJECT - release/buildAndUpload.sh "$NEXT_VERSION" - - jf rt bag && jf rt bce - - jf rt bp - - # Distribute release bundle - - jf ds rbc ecosystem-frogbot $NEXT_VERSION --spec="release/specs/frogbot-rbc-spec.json" --spec-vars="VERSION=$NEXT_VERSION" --sign - - jf ds rbd ecosystem-frogbot $NEXT_VERSION --site="releases.jfrog.io" --sync - - # Push to master - - git clean -fd - - git push - - git push --tags - - # Merge changes to dev - - git checkout dev - - git merge origin/master - - git push - onComplete: - - jf c rm --quiet diff --git a/release/specs/frogbot-rbc-spec.json b/release/specs/frogbot-rbc-spec.json deleted file mode 100644 index ea24a35cd..000000000 --- a/release/specs/frogbot-rbc-spec.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "files": [ - { - "pattern": "ecosys-frogbot/(v2/${VERSION}/*)", - "target": "frogbot/{1}" - } - ] -} From 7a38adc6477fd1b1c4bf071e2ecdf68b04c30987 Mon Sep 17 00:00:00 2001 From: Eyal Kapon Date: Wed, 8 Oct 2025 14:18:18 +0300 Subject: [PATCH 2/8] removed the scan multi from frogbot --- action/lib/src/main.js | 67 ++++ action/lib/src/utils.js | 346 ++++++++++++++++++ action/lib/test/main.spec.js | 133 +++++++ commands.go | 18 - .../jfrog-pipelines/pipelines-dotnet.yml | 8 +- .../jfrog-pipelines/pipelines-go.yml | 8 +- .../jfrog-pipelines/pipelines-gradle.yml | 8 +- .../jfrog-pipelines/pipelines-maven.yml | 8 +- .../jfrog-pipelines/pipelines-npm.yml | 8 +- .../jfrog-pipelines/pipelines-pip.yml | 8 +- .../jfrog-pipelines/pipelines-pipenv.yml | 8 +- .../jfrog-pipelines/pipelines-poetry.yml | 8 +- .../jfrog-pipelines/pipelines-yarn2.yml | 8 +- scanpullrequest/scanallpullrequests.go | 79 ---- scanpullrequest/scanallpullrequests_test.go | 257 ------------- scanpullrequest/scanpullrequest_test.go | 17 + scanrepository/scanmultiplerepositories.go | 28 -- .../scanmultiplerepositories_test.go | 142 ------- scanrepository/scanrepository_test.go | 81 +++- .../test-proj-pip-with-vulnerability/setup.py | 10 - .../test-proj-pip/setup.py | 9 - .../test-proj-with-vulnerability/package.json | 8 - .../test-proj/package.json | 7 - utils/params.go | 6 +- utils/utils.go | 2 - 25 files changed, 682 insertions(+), 600 deletions(-) create mode 100644 action/lib/src/main.js create mode 100644 action/lib/src/utils.js create mode 100644 action/lib/test/main.spec.js delete mode 100644 scanpullrequest/scanallpullrequests.go delete mode 100644 scanpullrequest/scanallpullrequests_test.go delete mode 100644 scanrepository/scanmultiplerepositories.go delete mode 100644 scanrepository/scanmultiplerepositories_test.go delete mode 100755 testdata/scanallpullrequests/test-proj-pip-with-vulnerability/setup.py delete mode 100755 testdata/scanallpullrequests/test-proj-pip/setup.py delete mode 100755 testdata/scanallpullrequests/test-proj-with-vulnerability/package.json delete mode 100755 testdata/scanallpullrequests/test-proj/package.json diff --git a/action/lib/src/main.js b/action/lib/src/main.js new file mode 100644 index 000000000..b6f18643c --- /dev/null +++ b/action/lib/src/main.js @@ -0,0 +1,67 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = __importStar(require("@actions/core")); +const utils_1 = require("./utils"); +function main() { + return __awaiter(this, void 0, void 0, function* () { + try { + core.startGroup('Frogbot'); + let jfrogUrl = yield utils_1.Utils.getJfrogPlatformUrl(); + yield utils_1.Utils.setupOidcTokenIfNeeded(jfrogUrl); + const eventName = yield utils_1.Utils.setFrogbotEnv(); + yield utils_1.Utils.addToPath(); + switch (eventName) { + case 'pull_request': + case 'pull_request_target': + yield utils_1.Utils.execScanPullRequest(); + break; + case 'push': + case 'schedule': + case 'workflow_dispatch': + yield utils_1.Utils.execCreateFixPullRequests(); + break; + default: + core.setFailed(eventName + ' event is not supported by Frogbot'); + } + } + catch (error) { + core.setFailed(error.message); + } + finally { + core.endGroup(); + } + }); +} +main(); diff --git a/action/lib/src/utils.js b/action/lib/src/utils.js new file mode 100644 index 000000000..42a679c1f --- /dev/null +++ b/action/lib/src/utils.js @@ -0,0 +1,346 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Utils = void 0; +const core = __importStar(require("@actions/core")); +const cache = __importStar(require("@actions/cache")); +const exec_1 = require("@actions/exec"); +const github_1 = require("@actions/github"); +const tool_cache_1 = require("@actions/tool-cache"); +const fs_1 = require("fs"); +const os_1 = require("os"); +const path_1 = require("path"); +const simple_git_1 = require("simple-git"); +const http_client_1 = require("@actions/http-client"); +class Utils { + static addToPath() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + let fileName = Utils.getExecutableName(); + let version = core.getInput(Utils.VERSION_ARG); + let major = version.split('.')[0]; + if (version === this.LATEST_CLI_VERSION_ARG) { + version = Utils.LATEST_RELEASE_VERSION; + major = '2'; + } + // Try Actions Cache first (persists across GitHub-hosted runners) + if (yield this.loadFromActionsCache(version, fileName)) { + return; + } + // Fallback to tool-cache (works for self-hosted runners) + if (this.loadFromCache(version)) { + // Download is not needed + return; + } + // Download Frogbot + const releasesRepo = (_a = process.env.JF_RELEASES_REPO) !== null && _a !== void 0 ? _a : ''; + let url = Utils.getCliUrl(major, version, fileName, releasesRepo); + core.debug('Downloading Frogbot from ' + url); + let auth = this.generateAuthString(releasesRepo); + let downloadDir = yield (0, tool_cache_1.downloadTool)(url, '', auth); + // Cache 'frogbot' executable in both caches + yield this.saveToActionsCache(version, fileName, downloadDir); + yield this.cacheAndAddPath(downloadDir, version, fileName); + }); + } + static generateAuthString(releasesRepo) { + var _a, _b, _c; + if (!releasesRepo) { + return ''; + } + let accessToken = (_a = process.env.JF_ACCESS_TOKEN) !== null && _a !== void 0 ? _a : ''; + let username = (_b = process.env.JF_USER) !== null && _b !== void 0 ? _b : ''; + let password = (_c = process.env.JF_PASSWORD) !== null && _c !== void 0 ? _c : ''; + if (accessToken) { + return 'Bearer ' + Buffer.from(accessToken).toString(); + } + else if (username && password) { + return 'Basic ' + Buffer.from(username + ':' + password).toString('base64'); + } + return ''; + } + static setFrogbotEnv() { + return __awaiter(this, void 0, void 0, function* () { + core.exportVariable('JF_GIT_PROVIDER', 'github'); + core.exportVariable('JF_GIT_OWNER', github_1.context.repo.owner); + let owner = github_1.context.repo.repo; + if (owner) { + core.exportVariable('JF_GIT_REPO', owner.substring(owner.indexOf('/') + 1)); + } + core.exportVariable('JF_GIT_PULL_REQUEST_ID', github_1.context.issue.number); + return github_1.context.eventName; + }); + } + /** + * Execute frogbot scan-pull-request command. + */ + static execScanPullRequest() { + return __awaiter(this, void 0, void 0, function* () { + if (!process.env.JF_GIT_BASE_BRANCH) { + core.exportVariable('JF_GIT_BASE_BRANCH', github_1.context.ref); + } + let res = yield (0, exec_1.exec)(Utils.getExecutableName(), ['scan-pull-request']); + if (res !== core.ExitCode.Success) { + throw new Error('Frogbot exited with exit code ' + res); + } + }); + } + /** + * Execute frogbot scan-repository command. + */ + static execCreateFixPullRequests() { + return __awaiter(this, void 0, void 0, function* () { + if (!process.env.JF_GIT_BASE_BRANCH) { + // Get the current branch we are checked on + const git = (0, simple_git_1.simpleGit)(); + try { + const currentBranch = yield git.branch(); + core.exportVariable('JF_GIT_BASE_BRANCH', currentBranch.current); + } + catch (error) { + throw new Error('Error getting current branch from the .git folder: ' + error); + } + } + let res = yield (0, exec_1.exec)(Utils.getExecutableName(), ['scan-repository']); + if (res !== core.ExitCode.Success) { + throw new Error('Frogbot exited with exit code ' + res); + } + }); + } + /** + * Try to load the Frogbot executables from cache. + * + * @param version - Frogbot version + * @returns true if the CLI executable was loaded from cache and added to path + */ + static loadFromCache(version) { + let execPath = (0, tool_cache_1.find)(Utils.TOOL_NAME, version); + if (execPath) { + core.addPath(execPath); + return true; + } + return false; + } + /** + * Add Frogbot executable to cache and to the system path. + * @param downloadDir - The directory whereby the CLI was downloaded to + * @param version - Frogbot version + * @param fileName - 'frogbot' or 'frogbot.exe' + */ + static cacheAndAddPath(downloadDir, version, fileName) { + return __awaiter(this, void 0, void 0, function* () { + let cliDir = yield (0, tool_cache_1.cacheFile)(downloadDir, fileName, Utils.TOOL_NAME, version); + if (!Utils.isWindows()) { + let filePath = (0, path_1.normalize)((0, path_1.join)(cliDir, fileName)); + (0, fs_1.chmodSync)(filePath, 0o555); + } + core.addPath(cliDir); + }); + } + /** + * Try to load Frogbot executable from Actions Cache. + * @param version - Frogbot version + * @param fileName - 'frogbot' or 'frogbot.exe' + * @returns true if the CLI executable was loaded from Actions Cache and added to path + */ + static loadFromActionsCache(version, fileName) { + return __awaiter(this, void 0, void 0, function* () { + try { + const cacheKey = `frogbot-${(0, os_1.platform)()}-${version}`; + const cachePath = (0, path_1.join)((0, os_1.homedir)(), '.cache', 'frogbot'); + const cachePaths = [cachePath]; + core.debug(`Attempting to restore Frogbot from Actions Cache with key: ${cacheKey}`); + const restoredKey = yield cache.restoreCache(cachePaths, cacheKey); + if (restoredKey) { + core.info(`Frogbot CLI restored from Actions Cache: ${restoredKey}`); + const execPath = (0, path_1.join)(cachePath, fileName); + // Make executable on Unix systems + if (!Utils.isWindows()) { + (0, fs_1.chmodSync)(execPath, 0o555); + } + core.addPath(cachePath); + return true; + } + } + catch (error) { + core.debug(`Actions Cache restore failed: ${error}`); + } + return false; + }); + } + /** + * Save Frogbot executable to Actions Cache. + * @param version - Frogbot version + * @param fileName - 'frogbot' or 'frogbot.exe' + * @param downloadDir - The directory whereby the CLI was downloaded to + */ + static saveToActionsCache(version, fileName, downloadDir) { + return __awaiter(this, void 0, void 0, function* () { + try { + const cacheKey = `frogbot-${(0, os_1.platform)()}-${version}`; + const cachePath = (0, path_1.join)((0, os_1.homedir)(), '.cache', 'frogbot'); + const cachePaths = [cachePath]; + // Ensure cache directory exists and copy binary + (0, fs_1.mkdirSync)(cachePath, { recursive: true }); + (0, fs_1.copyFileSync)(downloadDir, (0, path_1.join)(cachePath, fileName)); + core.debug(`Attempting to save Frogbot to Actions Cache with key: ${cacheKey}`); + yield cache.saveCache(cachePaths, cacheKey); + core.info(`Frogbot CLI saved to Actions Cache: ${cacheKey}`); + } + catch (error) { + core.debug(`Actions Cache save failed: ${error}`); + } + }); + } + static getCliUrl(major, version, fileName, releasesRepo) { + var _a; + let architecture = 'frogbot-' + Utils.getArchitecture(); + if (releasesRepo) { + let platformUrl = (_a = process.env.JF_URL) !== null && _a !== void 0 ? _a : ''; + if (!platformUrl) { + throw new Error('Failed while downloading Frogbot from Artifactory, JF_URL must be set'); + } + // Remove trailing slash if exists + platformUrl = platformUrl.replace(/\/$/, ''); + return `${platformUrl}/artifactory/${releasesRepo}/artifactory/frogbot/v${major}/${version}/${architecture}/${fileName}`; + } + return `https://releases.jfrog.io/artifactory/frogbot/v${major}/${version}/${architecture}/${fileName}`; + } + static getArchitecture() { + if (Utils.isWindows()) { + return 'windows-amd64'; + } + if ((0, os_1.platform)().includes('darwin')) { + return 'mac-386'; + } + if ((0, os_1.arch)().includes('arm')) { + return (0, os_1.arch)().includes('64') ? 'linux-arm64' : 'linux-arm'; + } + if ((0, os_1.arch)().includes('ppc64le')) { + return 'linux-ppc64le'; + } + if ((0, os_1.arch)().includes('ppc64')) { + return 'linux-ppc64'; + } + return (0, os_1.arch)().includes('64') ? 'linux-amd64' : 'linux-386'; + } + static getExecutableName() { + return Utils.isWindows() ? 'frogbot.exe' : 'frogbot'; + } + static isWindows() { + return (0, os_1.platform)().startsWith('win'); + } + static getJfrogPlatformUrl() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + let jfrogUrl = (_a = process.env.JF_URL) !== null && _a !== void 0 ? _a : ''; + if (!jfrogUrl) { + throw new Error('JF_URL must be provided and point on your full platform URL, for example: https://mycompany.jfrog.io/'); + } + return jfrogUrl; + }); + } + /** + * This method will set up an OIDC token if the OIDC integration is set. + * If OIDC integration is set but not working, the action will fail causing frogbot to fail + * @param jfrogUrl - The JFrog platform URL + */ + static setupOidcTokenIfNeeded(jfrogUrl) { + return __awaiter(this, void 0, void 0, function* () { + const oidcProviderName = core.getInput(Utils.OIDC_INTEGRATION_PROVIDER_NAME_ARG); + if (!oidcProviderName) { + // No token is set if an oidc-provider-name wasn't provided + return; + } + core.debug('Obtaining an access token through OpenID Connect...'); + const audience = core.getInput(Utils.OIDC_AUDIENCE_ARG); + let jsonWebToken; + try { + core.debug('Fetching JSON web token'); + jsonWebToken = yield core.getIDToken(audience); + } + catch (error) { + throw new Error(`Getting openID Connect JSON web token failed: ${error.message}`); + } + try { + return yield this.initJfrogAccessTokenThroughOidcProtocol(jfrogUrl, jsonWebToken, oidcProviderName); + } + catch (error) { + throw new Error(`OIDC authentication against JFrog platform failed, please check OIDC settings and mappings on the JFrog platform: ${error.message}`); + } + }); + } + /** + * This method exchanges a JSON web token with a JFrog access token through the OpenID Connect protocol + * If we've reached this stage, the jfrogUrl field should hold a non-empty value obtained from process.env.JF_URL + * @param jfrogUrl - The JFrog platform URL + * @param jsonWebToken - The JSON web token used in the token exchange + * @param oidcProviderName - The OpenID Connect provider name + */ + static initJfrogAccessTokenThroughOidcProtocol(jfrogUrl, jsonWebToken, oidcProviderName) { + return __awaiter(this, void 0, void 0, function* () { + const exchangeUrl = jfrogUrl.replace(/\/$/, '') + '/access/api/v1/oidc/token'; + core.debug('Exchanging GitHub JSON web token with a JFrog access token...'); + const httpClient = new http_client_1.HttpClient(); + const data = `{ + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "subject_token_type": "urn:ietf:params:oauth:token-type:id_token", + "subject_token": "${jsonWebToken}", + "provider_name": "${oidcProviderName}" + }`; + const additionalHeaders = { + 'Content-Type': 'application/json', + }; + const response = yield httpClient.post(exchangeUrl, data, additionalHeaders); + const responseString = yield response.readBody(); + const responseJson = JSON.parse(responseString); + process.env.JF_ACCESS_TOKEN = responseJson.access_token; + if (responseJson.access_token) { + core.setSecret(responseJson.access_token); + } + if (responseJson.errors) { + throw new Error(`${JSON.stringify(responseJson.errors)}`); + } + }); + } +} +exports.Utils = Utils; +Utils.LATEST_RELEASE_VERSION = '[RELEASE]'; +Utils.LATEST_CLI_VERSION_ARG = 'latest'; +Utils.VERSION_ARG = 'version'; +Utils.TOOL_NAME = 'frogbot'; +// OpenID Connect audience input +Utils.OIDC_AUDIENCE_ARG = 'oidc-audience'; +// OpenID Connect provider_name input +Utils.OIDC_INTEGRATION_PROVIDER_NAME_ARG = 'oidc-provider-name'; diff --git a/action/lib/test/main.spec.js b/action/lib/test/main.spec.js new file mode 100644 index 000000000..fc43aef6c --- /dev/null +++ b/action/lib/test/main.spec.js @@ -0,0 +1,133 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const os_1 = __importDefault(require("os")); +const utils_1 = require("../src/utils"); +jest.mock('os'); +describe('Frogbot Action Tests', () => { + afterEach(() => { + delete process.env.JF_ACCESS_TOKEN; + delete process.env.JF_USER; + delete process.env.PASSWORD; + delete process.env.JF_GIT_PROVIDER; + delete process.env.JF_GIT_OWNER; + delete process.env.GITHUB_REPOSITORY_OWNER; + delete process.env.GITHUB_REPOSITORY; + }); + describe('Frogbot URL Tests', () => { + const myOs = os_1.default; + let cases = [ + [ + 'win32', + 'amd64', + 'jfrog.exe', + 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-windows-amd64/jfrog.exe', + ], + ['darwin', 'amd64', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-mac-386/jfrog'], + ['linux', 'amd64', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-amd64/jfrog'], + ['linux', 'arm64', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-arm64/jfrog'], + ['linux', '386', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-386/jfrog'], + ['linux', 'arm', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-arm/jfrog'], + ['linux', 'ppc64', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-ppc64/jfrog'], + ['linux', 'ppc64le', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-ppc64le/jfrog'], + ]; + test.each(cases)('CLI Url for %s-%s', (platform, arch, fileName, expectedUrl) => { + myOs.platform.mockImplementation(() => platform); + myOs.arch.mockImplementation(() => arch); + let cliUrl = utils_1.Utils.getCliUrl('1', '1.2.3', fileName, ''); + expect(cliUrl).toBe(expectedUrl); + }); + }); + describe('Frogbot URL Tests With Remote Artifactory', () => { + const myOs = os_1.default; + const releasesRepo = 'frogbot-remote'; + process.env['JF_URL'] = 'https://myfrogbot.com/'; + process.env['JF_ACCESS_TOKEN'] = 'access_token1'; + let cases = [ + [ + 'win32', + 'amd64', + 'jfrog.exe', + 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-windows-amd64/jfrog.exe', + ], + [ + 'darwin', + 'amd64', + 'jfrog', + 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-mac-386/jfrog', + ], + [ + 'linux', + 'amd64', + 'jfrog', + 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-amd64/jfrog', + ], + [ + 'linux', + 'arm64', + 'jfrog', + 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-arm64/jfrog', + ], + [ + 'linux', + '386', + 'jfrog', + 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-386/jfrog', + ], + [ + 'linux', + 'arm', + 'jfrog', + 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-arm/jfrog', + ], + [ + 'linux', + 'ppc64', + 'jfrog', + 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-ppc64/jfrog', + ], + [ + 'linux', + 'ppc64le', + 'jfrog', + 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-ppc64le/jfrog', + ], + ]; + test.each(cases)('Remote CLI Url for %s-%s', (platform, arch, fileName, expectedUrl) => { + myOs.platform.mockImplementation(() => platform); + myOs.arch.mockImplementation(() => arch); + let cliUrl = utils_1.Utils.getCliUrl('2', '2.8.7', fileName, releasesRepo); + expect(cliUrl).toBe(expectedUrl); + }); + }); + describe('Generate auth string', () => { + it('Should return an empty string if releasesRepo is falsy', () => { + const result = utils_1.Utils.generateAuthString(''); + expect(result).toBe(''); + }); + it('Should generate a Bearer token if accessToken is provided', () => { + process.env.JF_ACCESS_TOKEN = 'yourAccessToken'; + const result = utils_1.Utils.generateAuthString('yourReleasesRepo'); + expect(result).toBe('Bearer yourAccessToken'); + }); + it('Should generate a Basic token if username and password are provided', () => { + process.env.JF_USER = 'yourUsername'; + process.env.JF_PASSWORD = 'yourPassword'; + const result = utils_1.Utils.generateAuthString('yourReleasesRepo'); + expect(result).toBe('Basic eW91clVzZXJuYW1lOnlvdXJQYXNzd29yZA=='); + }); + it('Should return an empty string if no credentials are provided', () => { + const result = utils_1.Utils.generateAuthString('yourReleasesRepo'); + expect(result).toBe(''); + }); + }); + it('Repository env tests', () => { + process.env['GITHUB_REPOSITORY_OWNER'] = 'jfrog'; + process.env['GITHUB_REPOSITORY'] = 'jfrog/frogbot'; + utils_1.Utils.setFrogbotEnv(); + expect(process.env['JF_GIT_PROVIDER']).toBe('github'); + expect(process.env['JF_GIT_OWNER']).toBe('jfrog'); + }); +}); diff --git a/commands.go b/commands.go index 6aea04c48..6c2964686 100644 --- a/commands.go +++ b/commands.go @@ -44,24 +44,6 @@ func GetCommands() []*clitool.Command { }, Flags: []clitool.Flag{}, }, - { - Name: utils.ScanAllPullRequests, - Aliases: []string{"sprs", "scan-pull-requests"}, - Usage: "Scans all the open pull requests within a single or multiple repositories with JFrog Xray for security vulnerabilities", - Action: func(ctx *clitool.Context) error { - return Exec(&scanpullrequest.ScanAllPullRequestsCmd{}, ctx.Command.Name) - }, - Flags: []clitool.Flag{}, - }, - { - Name: utils.ScanMultipleRepositories, - Aliases: []string{"scan-and-fix-repos", "safr"}, - Usage: "Scan single or multiple repositories and create pull requests with fixes if any security vulnerabilities are found", - Action: func(ctx *clitool.Context) error { - return Exec(&scanrepository.ScanMultipleRepositories{}, ctx.Command.Name) - }, - Flags: []clitool.Flag{}, - }, } } diff --git a/docs/templates/jfrog-pipelines/pipelines-dotnet.yml b/docs/templates/jfrog-pipelines/pipelines-dotnet.yml index 43f84c715..75772c1bb 100644 --- a/docs/templates/jfrog-pipelines/pipelines-dotnet.yml +++ b/docs/templates/jfrog-pipelines/pipelines-dotnet.yml @@ -206,8 +206,8 @@ pipelines: getFrogbotScriptPath=$( [[ -z "$JF_RELEASES_REPO" ]] && echo "https://releases.jfrog.io" || echo "${JF_URL}/artifactory/${JF_RELEASES_REPO}" ) curl -fLg "$getFrogbotScriptPath/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" | sh restore_cache_files dotnet_cache ~/.nuget/packages - ./frogbot scan-all-pull-requests - ./frogbot scan-multiple-repositories + ./frogbot scan-pull-request + ./frogbot scan-repository add_cache_files dotnet_cache ~/.nuget/packages || true # For Windows runner: @@ -221,6 +221,6 @@ pipelines: # $scriptUrl = "$($getFrogbotScriptPath)/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" # Invoke-Expression (Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing).Content # restore_cache_files dotnet_cache "%userprofile%\.nuget\packages" - # .\frogbot.exe scan-all-pull-requests - # .\frogbot.exe scan-multiple-repositories + # .\frogbot.exe scan-pull-request + # .\frogbot.exe scan-repository # add_cache_files dotnet_cache "%userprofile%\.nuget\packages" || true diff --git a/docs/templates/jfrog-pipelines/pipelines-go.yml b/docs/templates/jfrog-pipelines/pipelines-go.yml index 24d4f2823..eb77fadb8 100644 --- a/docs/templates/jfrog-pipelines/pipelines-go.yml +++ b/docs/templates/jfrog-pipelines/pipelines-go.yml @@ -207,8 +207,8 @@ pipelines: getFrogbotScriptPath=$( [[ -z "$JF_RELEASES_REPO" ]] && echo "https://releases.jfrog.io" || echo "${JF_URL}/artifactory/${JF_RELEASES_REPO}" ) curl -fLg "$getFrogbotScriptPath/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" | sh restore_cache_files go_cache ~/go/pkg - ./frogbot scan-all-pull-requests - ./frogbot scan-multiple-repositories + ./frogbot scan-pull-request + ./frogbot scan-repository add_cache_files go_cache ~/go/pkg || true # For Windows runner: @@ -222,6 +222,6 @@ pipelines: # $scriptUrl = "$($getFrogbotScriptPath)/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" # Invoke-Expression (Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing).Content # restore_cache_files dotnet_cache "%userprofile%\go\packages" - # .\frogbot.exe scan-all-pull-requests - # .\frogbot.exe scan-multiple-repositories + # .\frogbot.exe scan-pull-request + # .\frogbot.exe scan-repository # add_cache_files dotnet_cache "%userprofile%\go\packages" || true diff --git a/docs/templates/jfrog-pipelines/pipelines-gradle.yml b/docs/templates/jfrog-pipelines/pipelines-gradle.yml index afec547c5..16f21cf3a 100644 --- a/docs/templates/jfrog-pipelines/pipelines-gradle.yml +++ b/docs/templates/jfrog-pipelines/pipelines-gradle.yml @@ -211,8 +211,8 @@ pipelines: getFrogbotScriptPath=$( [[ -z "$JF_RELEASES_REPO" ]] && echo "https://releases.jfrog.io" || echo "${JF_URL}/artifactory/${JF_RELEASES_REPO}" ) curl -fLg "$getFrogbotScriptPath/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" | sh restore_cache_files gradle_cache ~/.gradle/caches - ./frogbot scan-all-pull-requests - ./frogbot scan-multiple-repositories + ./frogbot scan-pull-request + ./frogbot scan-repository add_cache_files gradle_cache ~/.gradle/caches || true # For Windows runner: @@ -226,6 +226,6 @@ pipelines: # $scriptUrl = "$($getFrogbotScriptPath)/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" # Invoke-Expression (Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing).Content # restore_cache_files gradle_cache "%userprofile%\.gradle\caches" - # .\frogbot.exe scan-all-pull-requests - # .\frogbot.exe scan-multiple-repositories + # .\frogbot.exe scan-pull-request + # .\frogbot.exe scan-repository # add_cache_files gradle_cache "%userprofile%\.gradle\caches" || true diff --git a/docs/templates/jfrog-pipelines/pipelines-maven.yml b/docs/templates/jfrog-pipelines/pipelines-maven.yml index 6dc5250cb..774ac9dd1 100644 --- a/docs/templates/jfrog-pipelines/pipelines-maven.yml +++ b/docs/templates/jfrog-pipelines/pipelines-maven.yml @@ -199,8 +199,8 @@ pipelines: getFrogbotScriptPath=$( [[ -z "$JF_RELEASES_REPO" ]] && echo "https://releases.jfrog.io" || echo "${JF_URL}/artifactory/${JF_RELEASES_REPO}" ) curl -fLg "$getFrogbotScriptPath/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" | sh restore_cache_files maven_cache ~/.m2 - ./frogbot scan-all-pull-requests - ./frogbot scan-multiple-repositories + ./frogbot scan-pull-request + ./frogbot scan-repository add_cache_files ~/.m2 maven_cache || true # For Windows runner: @@ -215,5 +215,5 @@ pipelines: # restore_cache_files maven_cache "%userprofile%\.m2" # Invoke-Expression (Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing).Content # add_cache_files "%userprofile%\.m2" maven_cache || true - # .\frogbot.exe scan-all-pull-requests - # .\frogbot.exe scan-multiple-repositories + # .\frogbot.exe scan-pull-request + # .\frogbot.exe scan-repository diff --git a/docs/templates/jfrog-pipelines/pipelines-npm.yml b/docs/templates/jfrog-pipelines/pipelines-npm.yml index 98086d254..e22787825 100644 --- a/docs/templates/jfrog-pipelines/pipelines-npm.yml +++ b/docs/templates/jfrog-pipelines/pipelines-npm.yml @@ -210,8 +210,8 @@ pipelines: getFrogbotScriptPath=$( [[ -z "$JF_RELEASES_REPO" ]] && echo "https://releases.jfrog.io" || echo "${JF_URL}/artifactory/${JF_RELEASES_REPO}" ) curl -fLg "$getFrogbotScriptPath/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" | sh restore_cache_files npm_cache ~/.npm - ./frogbot scan-all-pull-requests - ./frogbot scan-multiple-repositories + ./frogbot scan-pull-request + ./frogbot scan-repository add_cache_files ~/.npm npm_cache || true # For Windows runner: @@ -225,6 +225,6 @@ pipelines: # $scriptUrl = "$($getFrogbotScriptPath)/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" # Invoke-Expression (Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing).Content # add_cache_files "%AppData%/npm-cache" npm_cache || true - # .\frogbot.exe scan-all-pull-requests - # .\frogbot.exe scan-multiple-repositories + # .\frogbot.exe scan-pull-request + # .\frogbot.exe scan-repository diff --git a/docs/templates/jfrog-pipelines/pipelines-pip.yml b/docs/templates/jfrog-pipelines/pipelines-pip.yml index de4c47628..311d3625b 100644 --- a/docs/templates/jfrog-pipelines/pipelines-pip.yml +++ b/docs/templates/jfrog-pipelines/pipelines-pip.yml @@ -210,8 +210,8 @@ pipelines: - | getFrogbotScriptPath=$( [[ -z "$JF_RELEASES_REPO" ]] && echo "https://releases.jfrog.io" || echo "${JF_URL}/artifactory/${JF_RELEASES_REPO}" ) curl -fLg "$getFrogbotScriptPath/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" | sh - ./frogbot scan-all-pull-requests - ./frogbot scan-multiple-repositories + ./frogbot scan-pull-request + ./frogbot scan-repository # For Windows runner: # - | @@ -223,5 +223,5 @@ pipelines: # # $scriptUrl = "$($getFrogbotScriptPath)/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" # Invoke-Expression (Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing).Content - # .\frogbot.exe scan-all-pull-requests - # .\frogbot.exe scan-multiple-repositories + # .\frogbot.exe scan-pull-request + # .\frogbot.exe scan-repository diff --git a/docs/templates/jfrog-pipelines/pipelines-pipenv.yml b/docs/templates/jfrog-pipelines/pipelines-pipenv.yml index 5f463f7b7..8df03f5e2 100644 --- a/docs/templates/jfrog-pipelines/pipelines-pipenv.yml +++ b/docs/templates/jfrog-pipelines/pipelines-pipenv.yml @@ -206,8 +206,8 @@ pipelines: export LANG=C.UTF-8 getFrogbotScriptPath=$( [[ -z "$JF_RELEASES_REPO" ]] && echo "https://releases.jfrog.io" || echo "${JF_URL}/artifactory/${JF_RELEASES_REPO}" ) curl -fLg "$getFrogbotScriptPath/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" | sh - ./frogbot scan-all-pull-requests - ./frogbot scan-multiple-repositories + ./frogbot scan-pull-request + ./frogbot scan-repository # For Windows runner: # - | @@ -219,5 +219,5 @@ pipelines: # # $scriptUrl = "$($getFrogbotScriptPath)/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" # Invoke-Expression (Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing).Content - # .\frogbot.exe scan-all-pull-requests - # .\frogbot.exe scan-multiple-repositories + # .\frogbot.exe scan-pull-request + # .\frogbot.exe scan-repository diff --git a/docs/templates/jfrog-pipelines/pipelines-poetry.yml b/docs/templates/jfrog-pipelines/pipelines-poetry.yml index fa78264b7..5f7792a0a 100644 --- a/docs/templates/jfrog-pipelines/pipelines-poetry.yml +++ b/docs/templates/jfrog-pipelines/pipelines-poetry.yml @@ -205,8 +205,8 @@ pipelines: pip install poetry getFrogbotScriptPath=$( [[ -z "$JF_RELEASES_REPO" ]] && echo "https://releases.jfrog.io" || echo "${JF_URL}/artifactory/${JF_RELEASES_REPO}" ) curl -fLg "$getFrogbotScriptPath/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" | sh - ./frogbot scan-all-pull-requests - ./frogbot scan-multiple-repositories + ./frogbot scan-pull-request + ./frogbot scan-repository # For Windows runner: # - | @@ -219,5 +219,5 @@ pipelines: # # $scriptUrl = "$($getFrogbotScriptPath)/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" # Invoke-Expression (Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing).Content - # .\frogbot.exe scan-all-pull-requests - # .\frogbot.exe scan-multiple-repositories + # .\frogbot.exe scan-pull-request + # .\frogbot.exe scan-repository diff --git a/docs/templates/jfrog-pipelines/pipelines-yarn2.yml b/docs/templates/jfrog-pipelines/pipelines-yarn2.yml index bd8652aeb..f396bb950 100644 --- a/docs/templates/jfrog-pipelines/pipelines-yarn2.yml +++ b/docs/templates/jfrog-pipelines/pipelines-yarn2.yml @@ -206,8 +206,8 @@ pipelines: yarn set version berry getFrogbotScriptPath=$( [[ -z "$JF_RELEASES_REPO" ]] && echo "https://releases.jfrog.io" || echo "${JF_URL}/artifactory/${JF_RELEASES_REPO}" ) curl -fLg "$getFrogbotScriptPath/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" | sh - ./frogbot scan-all-pull-requests - ./frogbot scan-multiple-repositories + ./frogbot scan-pull-request + ./frogbot scan-repository # For Windows runner: # - | @@ -219,6 +219,6 @@ pipelines: # # $scriptUrl = "$($getFrogbotScriptPath)/artifactory/frogbot/v2/[RELEASE]/getFrogbot.sh" # Invoke-Expression (Invoke-WebRequest -Uri $scriptUrl -UseBasicParsing).Content - # .\frogbot.exe scan-all-pull-requests - # .\frogbot.exe scan-multiple-repositories + # .\frogbot.exe scan-pull-request + # .\frogbot.exe scan-repository diff --git a/scanpullrequest/scanallpullrequests.go b/scanpullrequest/scanallpullrequests.go deleted file mode 100644 index 4a3fbe3d2..000000000 --- a/scanpullrequest/scanallpullrequests.go +++ /dev/null @@ -1,79 +0,0 @@ -package scanpullrequest - -import ( - "context" - "errors" - "fmt" - - "github.com/jfrog/frogbot/v2/utils" - "github.com/jfrog/frogbot/v2/utils/outputwriter" - "github.com/jfrog/jfrog-client-go/utils/log" - - "github.com/jfrog/froggit-go/vcsclient" -) - -var errPullRequestScan = "pull request #%d scan in the '%s' repository returned the following error:\n%s" - -type ScanAllPullRequestsCmd struct { -} - -func (cmd ScanAllPullRequestsCmd) Run(configAggregator utils.RepoAggregator, client vcsclient.VcsClient, frogbotRepoConnection *utils.UrlAccessChecker) error { - for _, config := range configAggregator { - log.Info("Scanning all open pull requests for repository:", config.RepoName) - log.Info("-----------------------------------------------------------") - config.OutputWriter.SetHasInternetConnection(frogbotRepoConnection.IsConnected()) - err := scanAllPullRequests(config, client) - if err != nil { - return err - } - } - return nil -} - -// Scan pull requests as follows: -// a. Retrieve all open pull requests -// b. Find the ones that should be scanned (new PRs or PRs with a 're-scan' comment) -// c. Audit the dependencies of the source and the target branches. -// d. Compare the vulnerabilities found in source and target branches, and show only the new vulnerabilities added by the pull request. -func scanAllPullRequests(repo utils.Repository, client vcsclient.VcsClient) (err error) { - openPullRequests, err := client.ListOpenPullRequests(context.Background(), repo.RepoOwner, repo.RepoName) - if err != nil { - return err - } - for _, pr := range openPullRequests { - shouldScan, e := shouldScanPullRequest(repo, client, int(pr.ID)) - if e != nil { - err = errors.Join(err, fmt.Errorf(errPullRequestScan, int(pr.ID), repo.RepoName, e.Error())) - } - if !shouldScan { - log.Info("Pull Request", pr.ID, "has already been scanned before. If you wish to scan it again, please comment \"rescan\".") - continue - } - repo.PullRequestDetails = pr - if e = scanPullRequest(&repo, client); e != nil { - // If error, write it in errList and continue to the next PR. - err = errors.Join(err, fmt.Errorf(errPullRequestScan, int(pr.ID), repo.RepoName, e.Error())) - } - } - return -} - -func shouldScanPullRequest(repo utils.Repository, client vcsclient.VcsClient, prID int) (shouldScan bool, err error) { - pullRequestsComments, err := utils.GetSortedPullRequestComments(client, repo.RepoOwner, repo.RepoName, prID) - if err != nil { - return - } - - for _, comment := range pullRequestsComments { - // If this a 're-scan' request comment - if utils.IsFrogbotRescanComment(comment.Content) { - return true, nil - } - // if this is a Frogbot 'scan results' comment and not 're-scan' request comment, do not scan this pull request. - if outputwriter.IsFrogbotComment(comment.Content) { - return false, nil - } - } - // This is a new pull request, and it therefore should be scanned. - return true, nil -} diff --git a/scanpullrequest/scanallpullrequests_test.go b/scanpullrequest/scanallpullrequests_test.go deleted file mode 100644 index 9fb31d07d..000000000 --- a/scanpullrequest/scanallpullrequests_test.go +++ /dev/null @@ -1,257 +0,0 @@ -package scanpullrequest - -import ( - "context" - "fmt" - "github.com/jfrog/jfrog-cli-security/utils/xsc" - "path/filepath" - "testing" - "time" - - "github.com/golang/mock/gomock" - biutils "github.com/jfrog/build-info-go/utils" - "github.com/jfrog/frogbot/v2/testdata" - "github.com/jfrog/frogbot/v2/utils" - "github.com/jfrog/frogbot/v2/utils/outputwriter" - "github.com/jfrog/froggit-go/vcsclient" - "github.com/jfrog/froggit-go/vcsutils" - "github.com/stretchr/testify/assert" -) - -var ( - gitParams = &utils.Repository{ - OutputWriter: &outputwriter.SimplifiedOutput{}, - Params: utils.Params{ - Git: utils.Git{ - RepoOwner: "repo-owner", - Branches: []string{"master"}, - RepoName: "repo-name", - }, - }, - } - allPrIntegrationPath = filepath.Join(outputwriter.TestMessagesDir, "integration") -) - -type MockParams struct { - repoName string - repoOwner string - sourceBranchName string - targetBranchName string -} - -//go:generate go run github.com/golang/mock/mockgen@v1.6.0 -destination=../testdata/vcsclientmock.go -package=testdata github.com/jfrog/froggit-go/vcsclient VcsClient -func TestShouldScanPullRequestNewPR(t *testing.T) { - // Init mock - client := CreateMockVcsClient(t) - prID := 0 - client.EXPECT().ListPullRequestComments(context.Background(), gitParams.RepoOwner, gitParams.RepoName, prID).Return([]vcsclient.CommentInfo{}, nil) - // Run handleFrogbotLabel - shouldScan, err := shouldScanPullRequest(*gitParams, client, prID) - assert.NoError(t, err) - assert.True(t, shouldScan) -} - -func TestShouldScanPullRequestReScan(t *testing.T) { - // Init mock - client := CreateMockVcsClient(t) - prID := 0 - client.EXPECT().ListPullRequestComments(context.Background(), gitParams.RepoOwner, gitParams.RepoName, prID).Return([]vcsclient.CommentInfo{ - {Content: outputwriter.GetSimplifiedTitle(outputwriter.VulnerabilitiesPrBannerSource) + "text \n table\n text text text", Created: time.Unix(1, 0)}, - {Content: utils.RescanRequestComment, Created: time.Unix(1, 1)}, - }, nil) - shouldScan, err := shouldScanPullRequest(*gitParams, client, prID) - assert.NoError(t, err) - assert.True(t, shouldScan) -} - -func TestShouldNotScanPullRequestReScan(t *testing.T) { - // Init mock - client := CreateMockVcsClient(t) - prID := 0 - client.EXPECT().ListPullRequestComments(context.Background(), gitParams.RepoOwner, gitParams.RepoName, prID).Return([]vcsclient.CommentInfo{ - {Content: outputwriter.MarkdownComment(outputwriter.ReviewCommentId) + outputwriter.MarkAsBold(outputwriter.GetSimplifiedTitle(outputwriter.VulnerabilitiesPrBannerSource)) + "text \n table\n text text text", Created: time.Unix(1, 0)}, - {Content: utils.RescanRequestComment, Created: time.Unix(1, 1)}, - {Content: outputwriter.MarkdownComment(outputwriter.ReviewCommentId) + outputwriter.MarkAsBold(outputwriter.GetSimplifiedTitle(outputwriter.NoVulnerabilityPrBannerSource)) + "text \n table\n text text text", Created: time.Unix(3, 0)}, - }, nil) - shouldScan, err := shouldScanPullRequest(*gitParams, client, prID) - assert.NoError(t, err) - assert.False(t, shouldScan) -} - -func TestShouldNotScanPullRequest(t *testing.T) { - // Init mock - client := CreateMockVcsClient(t) - prID := 0 - client.EXPECT().ListPullRequestComments(context.Background(), gitParams.RepoOwner, gitParams.RepoName, prID).Return([]vcsclient.CommentInfo{ - {Content: outputwriter.MarkdownComment(outputwriter.ReviewCommentId) + outputwriter.MarkAsBold(outputwriter.GetSimplifiedTitle(outputwriter.NoVulnerabilityPrBannerSource)) + "text \n table\n text text text", Created: time.Unix(3, 0)}, - }, nil) - shouldScan, err := shouldScanPullRequest(*gitParams, client, prID) - assert.NoError(t, err) - assert.False(t, shouldScan) -} - -func TestShouldNotScanPullRequestError(t *testing.T) { - // Init mock - client := CreateMockVcsClient(t) - prID := 0 - client.EXPECT().ListPullRequestComments(context.Background(), gitParams.RepoOwner, gitParams.RepoName, prID).Return([]vcsclient.CommentInfo{}, fmt.Errorf("Bad Request")) - shouldScan, err := shouldScanPullRequest(*gitParams, client, prID) - assert.Error(t, err) - assert.False(t, shouldScan) -} - -func TestScanAllPullRequestsMultiRepo(t *testing.T) { - server, restoreEnv := utils.VerifyEnv(t) - defer restoreEnv() - xrayVersion, xscVersion, err := xsc.GetJfrogServicesVersion(&server) - assert.NoError(t, err) - - _, restoreJfrogHomeFunc := utils.CreateTempJfrogHomeWithCallback(t) - defer restoreJfrogHomeFunc() - - failOnSecurityIssues := false - firstRepoParams := utils.Params{ - JFrogPlatform: utils.JFrogPlatform{XrayVersion: xrayVersion, XscVersion: xscVersion}, - Scan: utils.Scan{ - AddPrCommentOnSuccess: true, - FailOnSecurityIssues: &failOnSecurityIssues, - Projects: []utils.Project{{ - InstallCommandName: "npm", - InstallCommandArgs: []string{"i"}, - WorkingDirs: []string{utils.RootDir}, - UseWrapper: &utils.TrueVal, - }}, - }, - Git: gitParams.Git, - } - secondRepoParams := utils.Params{ - Git: gitParams.Git, - JFrogPlatform: utils.JFrogPlatform{XrayVersion: xrayVersion, XscVersion: xscVersion}, - Scan: utils.Scan{ - AddPrCommentOnSuccess: true, - FailOnSecurityIssues: &failOnSecurityIssues, - Projects: []utils.Project{{WorkingDirs: []string{utils.RootDir}, UseWrapper: &utils.TrueVal}}}, - } - - configAggregator := utils.RepoAggregator{ - utils.Repository{ - OutputWriter: &outputwriter.StandardOutput{}, - Server: server, - Params: firstRepoParams, - }, - utils.Repository{ - OutputWriter: &outputwriter.StandardOutput{}, - Server: server, - Params: secondRepoParams, - }, - } - mockParams := []MockParams{ - {gitParams.RepoName, gitParams.RepoOwner, "test-proj-with-vulnerability", "test-proj"}, - {gitParams.RepoName, gitParams.RepoOwner, "test-proj-pip-with-vulnerability", "test-proj-pip"}, - } - var frogbotMessages []string - client := getMockClient(t, &frogbotMessages, mockParams...) - scanAllPullRequestsCmd := &ScanAllPullRequestsCmd{} - err = scanAllPullRequestsCmd.Run(configAggregator, client, utils.MockHasConnection()) - if assert.NoError(t, err) { - assert.Len(t, frogbotMessages, 4) - expectedMessage := outputwriter.GetOutputFromFile(t, filepath.Join(allPrIntegrationPath, "test_proj_with_vulnerability_standard.md")) - assert.Equal(t, expectedMessage, frogbotMessages[0]) - expectedMessage = outputwriter.GetPRSummaryContentNoIssues(t, outputwriter.TestSummaryCommentDir, true, false) - assert.Equal(t, expectedMessage, frogbotMessages[1]) - expectedMessage = outputwriter.GetOutputFromFile(t, filepath.Join(allPrIntegrationPath, "test_proj_pip_with_vulnerability.md")) - assert.Equal(t, expectedMessage, frogbotMessages[2]) - expectedMessage = outputwriter.GetPRSummaryContentNoIssues(t, outputwriter.TestSummaryCommentDir, true, false) - assert.Equal(t, expectedMessage, frogbotMessages[3]) - } -} - -func TestScanAllPullRequests(t *testing.T) { - // This integration test, requires JFrog platform connection details - server, restoreEnv := utils.VerifyEnv(t) - defer restoreEnv() - xrayVersion, xscVersion, err := xsc.GetJfrogServicesVersion(&server) - assert.NoError(t, err) - - falseVal := false - gitParams.Git.GitProvider = vcsutils.BitbucketServer - params := utils.Params{ - JFrogPlatform: utils.JFrogPlatform{XrayVersion: xrayVersion, XscVersion: xscVersion}, - Scan: utils.Scan{ - AddPrCommentOnSuccess: true, - FailOnSecurityIssues: &falseVal, - Projects: []utils.Project{{ - InstallCommandName: "npm", - InstallCommandArgs: []string{"i"}, - WorkingDirs: []string{"."}, - UseWrapper: &utils.TrueVal, - }}, - }, - Git: gitParams.Git, - } - repoParams := &utils.Repository{ - OutputWriter: &outputwriter.SimplifiedOutput{}, - Server: server, - Params: params, - } - paramsAggregator := utils.RepoAggregator{} - paramsAggregator = append(paramsAggregator, *repoParams) - var frogbotMessages []string - client := getMockClient(t, &frogbotMessages, MockParams{repoParams.RepoName, repoParams.RepoOwner, "test-proj-with-vulnerability", "test-proj"}) - scanAllPullRequestsCmd := &ScanAllPullRequestsCmd{} - err = scanAllPullRequestsCmd.Run(paramsAggregator, client, utils.MockHasConnection()) - assert.NoError(t, err) - assert.Len(t, frogbotMessages, 2) - expectedMessage := outputwriter.GetOutputFromFile(t, filepath.Join(allPrIntegrationPath, "test_proj_with_vulnerability_simplified.md")) - assert.Equal(t, expectedMessage, frogbotMessages[0]) - expectedMessage = outputwriter.GetPRSummaryContentNoIssues(t, outputwriter.TestSummaryCommentDir, true, true) - assert.Equal(t, expectedMessage, frogbotMessages[1]) -} - -func getMockClient(t *testing.T, frogbotMessages *[]string, mockParams ...MockParams) *testdata.MockVcsClient { - // Init mock - client := CreateMockVcsClient(t) - for _, params := range mockParams { - sourceBranchInfo := vcsclient.BranchInfo{Name: params.sourceBranchName, Repository: params.repoName, Owner: params.repoOwner} - targetBranchInfo := vcsclient.BranchInfo{Name: params.targetBranchName, Repository: params.repoName, Owner: params.repoOwner} - // Return 2 pull requests to scan, the first with issues the second "clean". - client.EXPECT().ListOpenPullRequests(context.Background(), params.repoOwner, params.repoName).Return([]vcsclient.PullRequestInfo{{ID: 1, Source: sourceBranchInfo, Target: targetBranchInfo, Author: "a"}, {ID: 2, Source: targetBranchInfo, Target: targetBranchInfo, Author: "a"}}, nil) - // Return empty comments slice so expect the code to scan both pull requests. - client.EXPECT().ListPullRequestComments(context.Background(), params.repoOwner, params.repoName, gomock.Any()).Return([]vcsclient.CommentInfo{}, nil).AnyTimes() - client.EXPECT().ListPullRequestReviewComments(context.Background(), params.repoOwner, params.repoName, gomock.Any()).Return([]vcsclient.CommentInfo{}, nil).AnyTimes() - // Copy test project according to the given branch name, instead of download it. - client.EXPECT().DownloadRepository(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).DoAndReturn(fakeRepoDownload).AnyTimes() - // Capture the result comment post - client.EXPECT().AddPullRequestComment(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, _, _, content string, _ int) error { - *frogbotMessages = append(*frogbotMessages, content) - return nil - }).AnyTimes() - client.EXPECT().AddPullRequestReviewComments(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, _, _, content string, _ int) error { - *frogbotMessages = append(*frogbotMessages, content) - return nil - }).AnyTimes() - client.EXPECT().DeletePullRequestComment(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).Return(nil).AnyTimes() - client.EXPECT().DeletePullRequestReviewComments(context.Background(), params.repoOwner, params.repoName, gomock.Any(), gomock.Any()).Return(nil).AnyTimes() - // Return private repositories visibility - client.EXPECT().GetRepositoryInfo(context.Background(), gomock.Any(), gomock.Any()).Return(vcsclient.RepositoryInfo{RepositoryVisibility: vcsclient.Private}, nil).AnyTimes() - // Return latest commit info for XSC context. - client.EXPECT().GetLatestCommit(context.Background(), params.repoOwner, params.repoName, gomock.Any()).Return(vcsclient.CommitInfo{}, nil).AnyTimes() - } - return client -} - -// To accurately simulate the "real" repository download, the tests project must be located in the same directory. -// The process involves the following steps: -// 1. First, the "test-proj-with-vulnerability" project, which includes a "test-proj" directory, will be copied to a temporary directory with a random name. This project will be utilized during the source auditing phase to mimic a pull request with a new vulnerable dependency. -// 2. Next, a second "download" will take place within the first temporary directory. As a result, the "test-proj" directory will be discovered and copied to a second temporary directory with another random name. This copied version will be used during the target auditing phase. -func fakeRepoDownload(_ context.Context, _, _, testProject, targetDir string) error { - sourceDir, err := filepath.Abs(filepath.Join("..", "testdata", "scanallpullrequests", testProject)) - if err != nil { - return err - } - return biutils.CopyDir(sourceDir, targetDir, true, []string{}) -} - -func CreateMockVcsClient(t *testing.T) *testdata.MockVcsClient { - return testdata.NewMockVcsClient(gomock.NewController(t)) -} diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index 5fdb46cab..b25dd8ca9 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -14,6 +14,8 @@ import ( "testing" "time" + "github.com/golang/mock/gomock" + "github.com/jfrog/frogbot/v2/testdata" "github.com/jfrog/jfrog-cli-security/utils/jasutils" "github.com/jfrog/jfrog-cli-security/utils/xsc" "github.com/owenrumney/go-sarif/v3/pkg/report/v210/sarif" @@ -35,6 +37,17 @@ import ( "github.com/stretchr/testify/assert" ) +var gitParams = &utils.Repository{ + OutputWriter: &outputwriter.SimplifiedOutput{}, + Params: utils.Params{ + Git: utils.Git{ + RepoOwner: "repo-owner", + Branches: []string{"master"}, + RepoName: "repo-name", + }, + }, +} + const ( testMultiDirProjConfigPath = "testdata/config/frogbot-config-multi-dir-test-proj.yml" testMultiDirProjConfigPathNoFail = "testdata/config/frogbot-config-multi-dir-test-proj-no-fail.yml" @@ -47,6 +60,10 @@ const ( testTargetBranchName = "master" ) +func CreateMockVcsClient(t *testing.T) *testdata.MockVcsClient { + return testdata.NewMockVcsClient(gomock.NewController(t)) +} + func TestScanResultsToIssuesCollection(t *testing.T) { allowedLicenses := []string{"MIT"} auditResults := &results.SecurityCommandResults{EntitledForJas: true, ResultContext: results.ResultContext{IncludeVulnerabilities: true}, Targets: []*results.TargetResults{{ diff --git a/scanrepository/scanmultiplerepositories.go b/scanrepository/scanmultiplerepositories.go deleted file mode 100644 index 2541613c2..000000000 --- a/scanrepository/scanmultiplerepositories.go +++ /dev/null @@ -1,28 +0,0 @@ -package scanrepository - -import ( - "errors" - "github.com/jfrog/frogbot/v2/utils" - "github.com/jfrog/froggit-go/vcsclient" -) - -type ScanMultipleRepositories struct { - // dryRun is used for testing purposes, mocking part of the git commands that requires networking - dryRun bool - // When dryRun is enabled, dryRunRepoPath specifies the repository local path to clone - dryRunRepoPath string -} - -func (saf *ScanMultipleRepositories) Run(repoAggregator utils.RepoAggregator, client vcsclient.VcsClient, frogbotRepoConnection *utils.UrlAccessChecker) (err error) { - scanRepositoryCmd := &ScanRepositoryCmd{dryRun: saf.dryRun, dryRunRepoPath: saf.dryRunRepoPath, baseWd: saf.dryRunRepoPath} - - for repoNum := range repoAggregator { - repoAggregator[repoNum].OutputWriter.SetHasInternetConnection(frogbotRepoConnection.IsConnected()) - scanRepositoryCmd.XrayVersion = repoAggregator[repoNum].XrayVersion - scanRepositoryCmd.XscVersion = repoAggregator[repoNum].XscVersion - if e := scanRepositoryCmd.scanAndFixRepository(&repoAggregator[repoNum], client); e != nil { - err = errors.Join(err, e) - } - } - return -} diff --git a/scanrepository/scanmultiplerepositories_test.go b/scanrepository/scanmultiplerepositories_test.go deleted file mode 100644 index 5b2761bcb..000000000 --- a/scanrepository/scanmultiplerepositories_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package scanrepository - -import ( - "bytes" - "encoding/json" - "fmt" - "github.com/jfrog/jfrog-cli-security/utils/xsc" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/protocol/packp" - "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" - "github.com/jfrog/frogbot/v2/utils" - "github.com/jfrog/froggit-go/vcsclient" - "github.com/jfrog/froggit-go/vcsutils" - "github.com/stretchr/testify/assert" -) - -var testScanMultipleRepositoriesConfigPath = filepath.Join("..", "testdata", "config", "frogbot-config-scan-multiple-repositories.yml") -var testRepositories = []string{"pip-repo", "npm-repo", "mvn-repo"} - -func TestScanAndFixRepos(t *testing.T) { - serverParams, restoreEnv := utils.VerifyEnv(t) - defer restoreEnv() - _, restoreJfrogHomeFunc := utils.CreateTempJfrogHomeWithCallback(t) - defer restoreJfrogHomeFunc() - - xrayVersion, xscVersion, err := xsc.GetJfrogServicesVersion(&serverParams) - assert.NoError(t, err) - - baseWd, err := os.Getwd() - assert.NoError(t, err) - - var port string - server := httptest.NewServer(createScanRepoGitHubHandler(t, &port, nil, testRepositories...)) - defer server.Close() - port = server.URL[strings.LastIndex(server.URL, ":")+1:] - client, err := vcsclient.NewClientBuilder(vcsutils.GitHub).ApiEndpoint(server.URL).Token("123456").Build() - assert.NoError(t, err) - - gitTestParams := utils.Git{ - GitProvider: vcsutils.GitHub, - RepoOwner: "jfrog", - VcsInfo: vcsclient.VcsInfo{ - Token: "123456", - APIEndpoint: server.URL, - }, - } - - configData, err := utils.ReadConfigFromFileSystem(testScanMultipleRepositoriesConfigPath) - assert.NoError(t, err) - - testDir, cleanup := utils.CopyTestdataProjectsToTemp(t, "scanmultiplerepositories") - defer func() { - assert.NoError(t, os.Chdir(baseWd)) - cleanup() - }() - - utils.CreateDotGitWithCommit(t, testDir, port, testRepositories...) - configAggregator, err := utils.BuildRepoAggregator(xrayVersion, xscVersion, client, configData, &gitTestParams, &serverParams, utils.ScanMultipleRepositories) - assert.NoError(t, err) - - var cmd = ScanMultipleRepositories{dryRun: true, dryRunRepoPath: testDir} - assert.NoError(t, cmd.Run(configAggregator, client, utils.MockHasConnection())) -} - -func createScanRepoGitHubHandler(t *testing.T, port *string, response interface{}, projectNames ...string) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - for _, projectName := range projectNames { - if r.RequestURI == fmt.Sprintf("/%s/info/refs?service=git-upload-pack", projectName) { - hash := plumbing.NewHash("5e3021cf22da163f0d312d8fcf299abaa79726fb") - capabilities := capability.NewList() - assert.NoError(t, capabilities.Add(capability.SymRef, "HEAD:/refs/heads/master")) - ar := &packp.AdvRefs{ - References: map[string]plumbing.Hash{ - "refs/heads/master": plumbing.NewHash("5e3021cf22da163f0d312d8fcf299abaa79726fb"), - }, - Head: &hash, - Capabilities: capabilities, - } - var buf bytes.Buffer - assert.NoError(t, ar.Encode(&buf)) - _, err := w.Write(buf.Bytes()) - assert.NoError(t, err) - w.WriteHeader(http.StatusOK) - return - } - if r.RequestURI == fmt.Sprintf("/repos/jfrog/%s/pulls", projectName) { - w.WriteHeader(http.StatusOK) - return - } - if r.RequestURI == fmt.Sprintf("/%s", projectName) { - file, err := os.ReadFile(fmt.Sprintf("%s.tar.gz", projectName)) - assert.NoError(t, err) - _, err = w.Write(file) - assert.NoError(t, err) - return - } - if r.RequestURI == fmt.Sprintf("/repos/jfrog/%s/tarball/master", projectName) { - w.Header().Add("Location", fmt.Sprintf("http://127.0.0.1:%s/%s", *port, projectName)) - w.WriteHeader(http.StatusFound) - _, err := w.Write([]byte{}) - assert.NoError(t, err) - return - } - if r.RequestURI == fmt.Sprintf("/repos/jfrog/%s/commits?page=1&per_page=%d&sha=master", projectName, vcsutils.NumberOfCommitsToFetch) { - w.WriteHeader(http.StatusOK) - rawJson := "[\n {\n \"url\": \"https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"sha\": \"6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"node_id\": \"MDY6Q29tbWl0NmRjYjA5YjViNTc4NzVmMzM0ZjYxYWViZWQ2OTVlMmU0MTkzZGI1ZQ==\",\n \"html_url\": \"https://github.com/octocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"comments_url\": \"https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/comments\",\n \"commit\": {\n \"url\": \"https://api.github.com/repos/octocat/Hello-World/git/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"author\": {\n \"name\": \"Monalisa Octocat\",\n \"email\": \"support@github.com\",\n \"date\": \"2011-04-14T16:00:49Z\"\n },\n \"committer\": {\n \"name\": \"Monalisa Octocat\",\n \"email\": \"support@github.com\",\n \"date\": \"2011-04-14T16:00:49Z\"\n },\n \"message\": \"Fix all the bugs\",\n \"tree\": {\n \"url\": \"https://api.github.com/repos/octocat/Hello-World/tree/6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"sha\": \"6dcb09b5b57875f334f61aebed695e2e4193db5e\"\n },\n \"comment_count\": 0,\n \"verification\": {\n \"verified\": false,\n \"reason\": \"unsigned\",\n \"signature\": null,\n \"payload\": null\n }\n },\n \"author\": {\n \"login\": \"octocat\",\n \"id\": 1,\n \"node_id\": \"MDQ6VXNlcjE=\",\n \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n \"gravatar_id\": \"\",\n \"url\": \"https://api.github.com/users/octocat\",\n \"html_url\": \"https://github.com/octocat\",\n \"followers_url\": \"https://api.github.com/users/octocat/followers\",\n \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\",\n \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\",\n \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\",\n \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\",\n \"organizations_url\": \"https://api.github.com/users/octocat/orgs\",\n \"repos_url\": \"https://api.github.com/users/octocat/repos\",\n \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\",\n \"received_events_url\": \"https://api.github.com/users/octocat/received_events\",\n \"type\": \"User\",\n \"site_admin\": false\n },\n \"committer\": {\n \"login\": \"octocat\",\n \"id\": 1,\n \"node_id\": \"MDQ6VXNlcjE=\",\n \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n \"gravatar_id\": \"\",\n \"url\": \"https://api.github.com/users/octocat\",\n \"html_url\": \"https://github.com/octocat\",\n \"followers_url\": \"https://api.github.com/users/octocat/followers\",\n \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\",\n \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\",\n \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\",\n \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\",\n \"organizations_url\": \"https://api.github.com/users/octocat/orgs\",\n \"repos_url\": \"https://api.github.com/users/octocat/repos\",\n \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\",\n \"received_events_url\": \"https://api.github.com/users/octocat/received_events\",\n \"type\": \"User\",\n \"site_admin\": false\n },\n \"parents\": [\n {\n \"url\": \"https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"sha\": \"6dcb09b5b57875f334f61aebed695e2e4193db5e\"\n }\n ]\n }\n]" - b := []byte(rawJson) - _, err := w.Write(b) - assert.NoError(t, err) - return - } - if r.RequestURI == fmt.Sprintf("/repos/jfrog/%v/code-scanning/sarifs", projectName) { - w.WriteHeader(http.StatusAccepted) - rawJson := "{\n \"id\": \"47177e22-5596-11eb-80a1-c1e54ef945c6\",\n \"url\": \"https://api.github.com/repos/octocat/hello-world/code-scanning/sarifs/47177e22-5596-11eb-80a1-c1e54ef945c6\"\n}" - b := []byte(rawJson) - _, err := w.Write(b) - assert.NoError(t, err) - return - } - if r.RequestURI == fmt.Sprintf("/repos/jfrog/%s/pulls?state=open", projectName) { - jsonResponse, err := json.Marshal(response) - assert.NoError(t, err) - _, err = w.Write(jsonResponse) - assert.NoError(t, err) - return - } - if r.RequestURI == fmt.Sprintf("/repos/jfrog/%s", projectName) { - jsonResponse := `{"id": 1296269,"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5","name": "Hello-World","full_name": "octocat/Hello-World","private": false,"description": "This your first repo!","ssh_url": "git@github.com:octocat/Hello-World.git","clone_url": "https://github.com/octocat/Hello-World.git","visibility": "public"}` - _, err := w.Write([]byte(jsonResponse)) - assert.NoError(t, err) - return - } - } - } -} diff --git a/scanrepository/scanrepository_test.go b/scanrepository/scanrepository_test.go index 48030214f..c5f458e27 100644 --- a/scanrepository/scanrepository_test.go +++ b/scanrepository/scanrepository_test.go @@ -1,9 +1,11 @@ package scanrepository import ( + "bytes" + "encoding/json" "errors" "fmt" - "github.com/jfrog/jfrog-cli-security/utils/xsc" + "net/http" "net/http/httptest" "os" "os/exec" @@ -11,6 +13,11 @@ import ( "strings" "testing" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/protocol/packp" + "github.com/go-git/go-git/v5/plumbing/protocol/packp/capability" + "github.com/jfrog/jfrog-cli-security/utils/xsc" + "github.com/google/go-github/v45/github" biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/frogbot/v2/utils" @@ -818,3 +825,75 @@ func verifyLockFileDiff(branchToInspect string, lockFiles ...string) (output []b } return } + +func createScanRepoGitHubHandler(t *testing.T, port *string, response interface{}, projectNames ...string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + for _, projectName := range projectNames { + if r.RequestURI == fmt.Sprintf("/%s/info/refs?service=git-upload-pack", projectName) { + hash := plumbing.NewHash("5e3021cf22da163f0d312d8fcf299abaa79726fb") + capabilities := capability.NewList() + assert.NoError(t, capabilities.Add(capability.SymRef, "HEAD:/refs/heads/master")) + ar := &packp.AdvRefs{ + References: map[string]plumbing.Hash{ + "refs/heads/master": plumbing.NewHash("5e3021cf22da163f0d312d8fcf299abaa79726fb"), + }, + Head: &hash, + Capabilities: capabilities, + } + var buf bytes.Buffer + assert.NoError(t, ar.Encode(&buf)) + _, err := w.Write(buf.Bytes()) + assert.NoError(t, err) + w.WriteHeader(http.StatusOK) + return + } + if r.RequestURI == fmt.Sprintf("/repos/jfrog/%s/pulls", projectName) { + w.WriteHeader(http.StatusOK) + return + } + if r.RequestURI == fmt.Sprintf("/%s", projectName) { + file, err := os.ReadFile(fmt.Sprintf("%s.tar.gz", projectName)) + assert.NoError(t, err) + _, err = w.Write(file) + assert.NoError(t, err) + return + } + if r.RequestURI == fmt.Sprintf("/repos/jfrog/%s/tarball/master", projectName) { + w.Header().Add("Location", fmt.Sprintf("http://127.0.0.1:%s/%s", *port, projectName)) + w.WriteHeader(http.StatusFound) + _, err := w.Write([]byte{}) + assert.NoError(t, err) + return + } + if r.RequestURI == fmt.Sprintf("/repos/jfrog/%s/commits?page=1&per_page=%d&sha=master", projectName, vcsutils.NumberOfCommitsToFetch) { + w.WriteHeader(http.StatusOK) + rawJson := "[\n {\n \"url\": \"https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"sha\": \"6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"node_id\": \"MDY6Q29tbWl0NmRjYjA5YjViNTc4NzVmMzM0ZjYxYWViZWQ2OTVlMmU0MTkzZGI1ZQ==\",\n \"html_url\": \"https://github.com/octocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"comments_url\": \"https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/comments\",\n \"commit\": {\n \"url\": \"https://api.github.com/repos/octocat/Hello-World/git/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"author\": {\n \"name\": \"Monalisa Octocat\",\n \"email\": \"support@github.com\",\n \"date\": \"2011-04-14T16:00:49Z\"\n },\n \"committer\": {\n \"name\": \"Monalisa Octocat\",\n \"email\": \"support@github.com\",\n \"date\": \"2011-04-14T16:00:49Z\"\n },\n \"message\": \"Fix all the bugs\",\n \"tree\": {\n \"url\": \"https://api.github.com/repos/octocat/Hello-World/tree/6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"sha\": \"6dcb09b5b57875f334f61aebed695e2e4193db5e\"\n },\n \"comment_count\": 0,\n \"verification\": {\n \"verified\": false,\n \"reason\": \"unsigned\",\n \"signature\": null,\n \"payload\": null\n }\n },\n \"author\": {\n \"login\": \"octocat\",\n \"id\": 1,\n \"node_id\": \"MDQ6VXNlcjE=\",\n \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n \"gravatar_id\": \"\",\n \"url\": \"https://api.github.com/users/octocat\",\n \"html_url\": \"https://github.com/octocat\",\n \"followers_url\": \"https://api.github.com/users/octocat/followers\",\n \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\",\n \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\",\n \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\",\n \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\",\n \"organizations_url\": \"https://api.github.com/users/octocat/orgs\",\n \"repos_url\": \"https://api.github.com/users/octocat/repos\",\n \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\",\n \"received_events_url\": \"https://api.github.com/users/octocat/received_events\",\n \"type\": \"User\",\n \"site_admin\": false\n },\n \"committer\": {\n \"login\": \"octocat\",\n \"id\": 1,\n \"node_id\": \"MDQ6VXNlcjE=\",\n \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n \"gravatar_id\": \"\",\n \"url\": \"https://api.github.com/users/octocat\",\n \"html_url\": \"https://github.com/octocat\",\n \"followers_url\": \"https://api.github.com/users/octocat/followers\",\n \"following_url\": \"https://api.github.com/users/octocat/following{/other_user}\",\n \"gists_url\": \"https://api.github.com/users/octocat/gists{/gist_id}\",\n \"starred_url\": \"https://api.github.com/users/octocat/starred{/owner}{/repo}\",\n \"subscriptions_url\": \"https://api.github.com/users/octocat/subscriptions\",\n \"organizations_url\": \"https://api.github.com/users/octocat/orgs\",\n \"repos_url\": \"https://api.github.com/users/octocat/repos\",\n \"events_url\": \"https://api.github.com/users/octocat/events{/privacy}\",\n \"received_events_url\": \"https://api.github.com/users/octocat/received_events\",\n \"type\": \"User\",\n \"site_admin\": false\n },\n \"parents\": [\n {\n \"url\": \"https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e\",\n \"sha\": \"6dcb09b5b57875f334f61aebed695e2e4193db5e\"\n }\n ]\n }\n]" + b := []byte(rawJson) + _, err := w.Write(b) + assert.NoError(t, err) + return + } + if r.RequestURI == fmt.Sprintf("/repos/jfrog/%v/code-scanning/sarifs", projectName) { + w.WriteHeader(http.StatusAccepted) + rawJson := "{\n \"id\": \"47177e22-5596-11eb-80a1-c1e54ef945c6\",\n \"url\": \"https://api.github.com/repos/octocat/hello-world/code-scanning/sarifs/47177e22-5596-11eb-80a1-c1e54ef945c6\"\n}" + b := []byte(rawJson) + _, err := w.Write(b) + assert.NoError(t, err) + return + } + if r.RequestURI == fmt.Sprintf("/repos/jfrog/%s/pulls?state=open", projectName) { + jsonResponse, err := json.Marshal(response) + assert.NoError(t, err) + _, err = w.Write(jsonResponse) + assert.NoError(t, err) + return + } + if r.RequestURI == fmt.Sprintf("/repos/jfrog/%s", projectName) { + jsonResponse := `{"id": 1296269,"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5","name": "Hello-World","full_name": "octocat/Hello-World","private": false,"description": "This your first repo!","ssh_url": "git@github.com:octocat/Hello-World.git","clone_url": "https://github.com/octocat/Hello-World.git","visibility": "public"}` + _, err := w.Write([]byte(jsonResponse)) + assert.NoError(t, err) + return + } + } + } +} diff --git a/testdata/scanallpullrequests/test-proj-pip-with-vulnerability/setup.py b/testdata/scanallpullrequests/test-proj-pip-with-vulnerability/setup.py deleted file mode 100755 index 5edae408c..000000000 --- a/testdata/scanallpullrequests/test-proj-pip-with-vulnerability/setup.py +++ /dev/null @@ -1,10 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name="pip-example", - version="1.2.3", - install_requires=[ - 'pexpect==4.8.0', - 'pyjwt==1.7.1' - ], -) diff --git a/testdata/scanallpullrequests/test-proj-pip/setup.py b/testdata/scanallpullrequests/test-proj-pip/setup.py deleted file mode 100755 index cd96396e4..000000000 --- a/testdata/scanallpullrequests/test-proj-pip/setup.py +++ /dev/null @@ -1,9 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name="pip-example", - version="1.2.3", - install_requires=[ - 'pexpect==4.8.0' - ], -) diff --git a/testdata/scanallpullrequests/test-proj-with-vulnerability/package.json b/testdata/scanallpullrequests/test-proj-with-vulnerability/package.json deleted file mode 100755 index 1075c478d..000000000 --- a/testdata/scanallpullrequests/test-proj-with-vulnerability/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "test-proj", - "version": "1.0.0", - "dependencies": { - "minimatch": "0.2.14", - "minimist":"1.2.5" - } -} diff --git a/testdata/scanallpullrequests/test-proj/package.json b/testdata/scanallpullrequests/test-proj/package.json deleted file mode 100755 index 218428ac2..000000000 --- a/testdata/scanallpullrequests/test-proj/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "test-proj", - "version": "1.0.0", - "dependencies": { - "minimatch": "0.2.14" - } -} diff --git a/utils/params.go b/utils/params.go index 248fd2396..f1d964fd7 100644 --- a/utils/params.go +++ b/utils/params.go @@ -374,7 +374,7 @@ func (g *Git) setDefaultsIfNeeded(gitParamsFromEnv *Git, commandName string) (er return } } - if commandName == ScanRepository || commandName == ScanMultipleRepositories { + if commandName == ScanRepository { if err = g.extractScanRepositoryEnvParams(gitParamsFromEnv); err != nil { return } @@ -534,7 +534,7 @@ func getConfigAggregator(xrayVersion, xscVersion string, gitClient vcsclient.Vcs func getConfigFileContent(gitClient vcsclient.VcsClient, gitParamsFromEnv *Git, commandName string) ([]byte, error) { var errMissingConfig *ErrMissingConfig - if commandName == ScanRepository || commandName == ScanMultipleRepositories { + if commandName == ScanRepository { configFileContent, err := ReadConfigFromFileSystem(osFrogbotConfigPath) if err != nil && !errors.As(err, &errMissingConfig) { return nil, err @@ -653,7 +653,7 @@ func extractGitParamsFromEnvs(commandName string) (*Git, error) { } // [Mandatory] Set the repository name, except for multi repository. - if err = readParamFromEnv(GitRepoEnv, &gitEnvParams.RepoName); err != nil && commandName != ScanMultipleRepositories { + if err = readParamFromEnv(GitRepoEnv, &gitEnvParams.RepoName); err != nil { return nil, err } diff --git a/utils/utils.go b/utils/utils.go index f2c400c97..46d245851 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -34,9 +34,7 @@ import ( const ( ScanPullRequest = "scan-pull-request" - ScanAllPullRequests = "scan-all-pull-requests" ScanRepository = "scan-repository" - ScanMultipleRepositories = "scan-multiple-repositories" RootDir = "." branchNameRegex = `[~^:?\\\[\]@{}*]` dependencySubmissionFrogbotDetector = "JFrog Frogbot" From 198c3d84a4eebd554dd18f89bd253c3fbd4ca248 Mon Sep 17 00:00:00 2001 From: Eyal Kapon Date: Tue, 28 Oct 2025 16:08:12 +0200 Subject: [PATCH 3/8] Revert "created new release using workflows and deleted old one" This reverts commit c7fa431316c2d43740545785c28c42f30fbc3cb0. --- .github/workflows/release.yml | 171 ---------------------------- release/buildAndUpload.sh | 73 ++++++++++++ release/pipelines.yml | 89 +++++++++++++++ release/specs/frogbot-rbc-spec.json | 8 ++ 4 files changed, 170 insertions(+), 171 deletions(-) delete mode 100644 .github/workflows/release.yml create mode 100755 release/buildAndUpload.sh create mode 100644 release/pipelines.yml create mode 100644 release/specs/frogbot-rbc-spec.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 72c20b4a9..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,171 +0,0 @@ -name: Release Frogbot - -on: - release: - types: [published] - -# Required permissions -permissions: - contents: write - actions: read - -jobs: - release: - name: Release Frogbot v3 - runs-on: frogbot-release - - # Only run for v3.x.x releases - if: startsWith(github.event.release.tag_name, 'v3.') - - steps: - - name: Extract version from tag - id: version - run: | - TAG="${{ github.event.release.tag_name }}" - VERSION="${TAG#v}" - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "tag=$TAG" >> $GITHUB_OUTPUT - echo "Release version: $VERSION" - - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ github.event.release.tag_name }} - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: 'go.mod' - cache: true - - - name: Download JFrog CLI - run: | - curl -fL https://install-cli.jfrog.io | sh - chmod +x jf - sudo mv jf /usr/local/bin/ - - - name: Configure JFrog CLI - env: - JF_URL: ${{ secrets.JF_URL }} - JF_USER: ${{ secrets.JF_USER }} - JF_PASSWORD: ${{ secrets.JF_PASSWORD }} - run: | - jf c rm --quiet || true - jf c add internal --url="$JF_URL" --user="$JF_USER" --password="$JF_PASSWORD" - jf goc --repo-resolve ecosys-go-virtual - - - name: Generate mocks - run: go generate ./... - - - name: Run JFrog Audit (blocking) - run: jf audit --fail=true - - - name: Set up Node.js for Action - uses: actions/setup-node@v4 - with: - node-version: '16' - cache: 'npm' - cache-dependency-path: action/package-lock.json - - - name: Build GitHub Action - working-directory: action - run: | - npm ci --ignore-scripts - npm run compile - npm run format-check - npm test - - - name: Commit and update tag with compiled action - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add action/lib/ - - # Only commit and update tag if there are changes - if ! git diff --staged --quiet; then - echo "Action files changed, updating tag..." - git commit -m "Build action for ${{ steps.version.outputs.tag }}" - - # Update the release tag to point to the new commit - git tag -f ${{ steps.version.outputs.tag }} - git push origin ${{ steps.version.outputs.tag }} --force - - echo "Tag updated with compiled action" - else - echo "No changes to action files" - fi - - - name: Update GitHub Action major version tag (v3) - run: | - # Update v3 tag to point to the latest v3.x.x release - git tag -f v3 - git push origin v3 --force - echo "Updated v3 tag to ${{ steps.version.outputs.tag }}" - - - name: Build and upload binaries - env: - VERSION: ${{ steps.version.outputs.tag }} - JFROG_CLI_BUILD_NAME: ecosystem-frogbot-release - JFROG_CLI_BUILD_NUMBER: ${{ github.run_number }} - JFROG_CLI_BUILD_PROJECT: ecosys - run: | - env -i PATH=$PATH HOME=$HOME \ - JFROG_CLI_BUILD_NAME=$JFROG_CLI_BUILD_NAME \ - JFROG_CLI_BUILD_NUMBER=$JFROG_CLI_BUILD_NUMBER \ - JFROG_CLI_BUILD_PROJECT=$JFROG_CLI_BUILD_PROJECT \ - release/buildAndUpload.sh "${{ steps.version.outputs.version }}" - - - name: Publish build info - env: - JFROG_CLI_BUILD_NAME: ecosystem-frogbot-release - JFROG_CLI_BUILD_NUMBER: ${{ github.run_number }} - JFROG_CLI_BUILD_PROJECT: ecosys - run: | - jf rt bag - jf rt bce - jf rt bp - - - name: Create and distribute release bundle - env: - VERSION: ${{ steps.version.outputs.version }} - run: | - jf ds rbc ecosystem-frogbot $VERSION \ - --spec="release/specs/frogbot-rbc-spec.json" \ - --spec-vars="VERSION=$VERSION" \ - --sign - jf ds rbd ecosystem-frogbot $VERSION \ - --site="releases.jfrog.io" \ - --sync - - - name: Cleanup JFrog config - if: always() - run: jf c rm --quiet || true - - # On failure: delete release and tag - - name: Delete release on failure - if: failure() - uses: actions/github-script@v7 - with: - script: | - const release_id = context.payload.release.id; - const tag = context.payload.release.tag_name; - - console.log(`Deleting release ${release_id} and tag ${tag}`); - - // Delete the release - await github.rest.repos.deleteRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: release_id - }); - - // Delete the tag - await github.rest.git.deleteRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${tag}` - }); - - console.log('Release and tag deleted successfully'); - diff --git a/release/buildAndUpload.sh b/release/buildAndUpload.sh new file mode 100755 index 000000000..8f671fe7a --- /dev/null +++ b/release/buildAndUpload.sh @@ -0,0 +1,73 @@ +#!/bin/bash +set -eu + +#function build(pkg, goos, goarch, exeName) +build () { + pkg="$1" + export GOOS="$2" + export GOARCH="$3" + exeName="$4" + echo "Building $exeName for $GOOS-$GOARCH ..." + + CGO_ENABLED=0 jf go build -o "$exeName" -ldflags '-w -extldflags "-static" -X github.com/jfrog/frogbot/v2/utils.FrogbotVersion='"$version" + chmod +x "$exeName" + + # Run verification after building plugin for the correct platform of this image. + if [[ "$pkg" = "frogbot-linux-386" ]]; then + verifyVersionMatching + fi +} + +#function buildAndUpload(pkg, goos, goarch, fileExtension) +buildAndUpload () { + pkg="$1" + goos="$2" + goarch="$3" + fileExtension="$4" + exeName="frogbot$fileExtension" + + build "$pkg" "$goos" "$goarch" "$exeName" + + destPath="$pkgPath/$version/$pkg/$exeName" + echo "Uploading $exeName to $destPath ..." + jf rt u "./$exeName" "$destPath" +} + +# Verify version provided in pipelines UI matches version in frogbot source code. +verifyVersionMatching () { + echo "Verifying provided version matches built version..." + res=$(eval "./frogbot -v") + exitCode=$? + if [[ $exitCode -ne 0 ]]; then + echo "Error: Failed verifying version matches" + exit $exitCode + fi + + # Get the version which is after the last space. (expected output to -v for example: "Frogbot version version v2.0.0") + echo "Output: $res" + builtVersion="${res##* }" + # Compare versions + if [[ "$builtVersion" != "$version" ]]; then + echo "Versions dont match. Provided: $version, Actual: $builtVersion" + exit 1 + fi + echo "Versions match." +} + +version="$1" +pkgPath="ecosys-frogbot/v2" + +# Build and upload for every architecture. +# Keep 'linux-386' first to prevent unnecessary uploads in case the built version doesn't match the provided one. +buildAndUpload 'frogbot-linux-386' 'linux' '386' '' +buildAndUpload 'frogbot-linux-amd64' 'linux' 'amd64' '' +buildAndUpload 'frogbot-linux-s390x' 'linux' 's390x' '' +buildAndUpload 'frogbot-linux-arm64' 'linux' 'arm64' '' +buildAndUpload 'frogbot-linux-arm' 'linux' 'arm' '' +buildAndUpload 'frogbot-linux-ppc64' 'linux' 'ppc64' '' +buildAndUpload 'frogbot-linux-ppc64le' 'linux' 'ppc64le' '' +buildAndUpload 'frogbot-mac-386' 'darwin' 'amd64' '' +buildAndUpload 'frogbot-mac-arm64' 'darwin' 'arm64' '' +buildAndUpload 'frogbot-windows-amd64' 'windows' 'amd64' '.exe' + +jf rt u "./buildscripts/getFrogbot.sh" "$pkgPath/$version/" --flat diff --git a/release/pipelines.yml b/release/pipelines.yml new file mode 100644 index 000000000..c7618a1dd --- /dev/null +++ b/release/pipelines.yml @@ -0,0 +1,89 @@ +resources: + - name: frogbotGit + type: GitRepo + configuration: + path: jfrog/frogbot + branches: + include: dev + gitProvider: il_automation + +pipelines: + - name: release_frogbot + configuration: + runtime: + type: image + image: + custom: + name: releases-docker.jfrog.io/jfrog-ecosystem-integration-env + tag: latest + environmentVariables: + readOnly: + NEXT_VERSION: 0.0.0 + + steps: + - name: Release + type: Bash + configuration: + inputResources: + - name: frogbotGit + trigger: false + integrations: + - name: il_automation + - name: ecosys_entplus_deployer + execution: + onExecute: + - cd $res_frogbotGit_resourcePath + + # Set env + - export CI=true + - export JFROG_CLI_BUILD_NAME=ecosystem-frogbotGit-release + - export JFROG_CLI_BUILD_NUMBER=$run_number + - export JFROG_CLI_BUILD_PROJECT=ecosys + + # Make sure version provided + - echo "Checking variables" + - test -n "$NEXT_VERSION" -a "$NEXT_VERSION" != "0.0.0" + + # Configure Git and merge from the dev + - git checkout master + - git remote set-url origin https://$int_il_automation_token@github.com/jfrog/frogbot.git + - git merge origin/dev + - git tag v${NEXT_VERSION} + + # Download JFrog CLI + - curl -fL https://install-cli.jfrog.io | sh + - jf c rm --quiet + - jf c add internal --url=$int_ecosys_entplus_deployer_url --user=$int_ecosys_entplus_deployer_user --password=$int_ecosys_entplus_deployer_apikey + - jf goc --repo-resolve ecosys-go-virtual + + # Generate mocks + - go generate ./... + + # Audit + - jf audit --fail=false + + # Build and upload + - > + env -i PATH=$PATH HOME=$HOME + JFROG_CLI_BUILD_NAME=$JFROG_CLI_BUILD_NAME + JFROG_CLI_BUILD_NUMBER=$JFROG_CLI_BUILD_NUMBER + JFROG_CLI_BUILD_PROJECT=$JFROG_CLI_BUILD_PROJECT + release/buildAndUpload.sh "$NEXT_VERSION" + - jf rt bag && jf rt bce + - jf rt bp + + # Distribute release bundle + - jf ds rbc ecosystem-frogbot $NEXT_VERSION --spec="release/specs/frogbot-rbc-spec.json" --spec-vars="VERSION=$NEXT_VERSION" --sign + - jf ds rbd ecosystem-frogbot $NEXT_VERSION --site="releases.jfrog.io" --sync + + # Push to master + - git clean -fd + - git push + - git push --tags + + # Merge changes to dev + - git checkout dev + - git merge origin/master + - git push + onComplete: + - jf c rm --quiet diff --git a/release/specs/frogbot-rbc-spec.json b/release/specs/frogbot-rbc-spec.json new file mode 100644 index 000000000..ea24a35cd --- /dev/null +++ b/release/specs/frogbot-rbc-spec.json @@ -0,0 +1,8 @@ +{ + "files": [ + { + "pattern": "ecosys-frogbot/(v2/${VERSION}/*)", + "target": "frogbot/{1}" + } + ] +} From ba8c0b25dfa270f3229c71ec751f6c5c4815f472 Mon Sep 17 00:00:00 2001 From: Eyal Kapon Date: Mon, 3 Nov 2025 14:49:29 +0200 Subject: [PATCH 4/8] deleted unused elements and fixed lining issues --- utils/outputwriter/outputwriter.go | 8 -------- utils/outputwriter/testsutils.go | 21 +-------------------- utils/scandetails.go | 11 ++++------- utils/testsutils.go | 11 ----------- utils/utils.go | 1 - 5 files changed, 5 insertions(+), 47 deletions(-) diff --git a/utils/outputwriter/outputwriter.go b/utils/outputwriter/outputwriter.go index d9c81f792..f7469345f 100644 --- a/utils/outputwriter/outputwriter.go +++ b/utils/outputwriter/outputwriter.go @@ -188,14 +188,6 @@ func (mo *MarkdownOutput) SetSizeLimit(client vcsclient.VcsClient) { mo.descriptionSizeLimit = client.GetPullRequestDetailsSizeLimit() } -func GetMarkdownSizeLimit(client vcsclient.VcsClient) int { - limit := client.GetPullRequestCommentSizeLimit() - if client.GetPullRequestDetailsSizeLimit() < limit { - limit = client.GetPullRequestDetailsSizeLimit() - } - return limit -} - func GetCompatibleOutputWriter(provider vcsutils.VcsProvider) OutputWriter { switch provider { case vcsutils.BitbucketServer: diff --git a/utils/outputwriter/testsutils.go b/utils/outputwriter/testsutils.go index e6ee2feb1..b8da0a8b1 100644 --- a/utils/outputwriter/testsutils.go +++ b/utils/outputwriter/testsutils.go @@ -13,8 +13,7 @@ import ( var ( // Used for tests that are outside the outputwriter package. - TestMessagesDir = filepath.Join("..", "testdata", "messages") - TestSummaryCommentDir = filepath.Join(TestMessagesDir, "summarycomment") + TestMessagesDir = filepath.Join("..", "testdata", "messages") // Used for tests that are inside the outputwriter package. testMessagesDir = filepath.Join("..", TestMessagesDir) testReviewCommentDir = filepath.Join(testMessagesDir, "reviewcomment") @@ -61,21 +60,3 @@ func GetJsonBodyOutputFromFile(t *testing.T, filePath string) []byte { assert.NoError(t, err) return bytes } - -func GetPRSummaryContentNoIssues(t *testing.T, summaryTestDir string, entitled, simplified bool) string { - dataPath := filepath.Join(summaryTestDir, "structure") - if simplified { - if entitled { - dataPath = filepath.Join(dataPath, "summary_comment_no_issues_simplified_entitled.md") - } else { - dataPath = filepath.Join(dataPath, "summary_comment_no_issues_simplified_not_entitled.md") - } - } else { - if entitled { - dataPath = filepath.Join(dataPath, "summary_comment_no_issues_pr_entitled.md") - } else { - dataPath = filepath.Join(dataPath, "summary_comment_no_issues_pr_not_entitled.md") - } - } - return GetOutputFromFile(t, dataPath) -} diff --git a/utils/scandetails.go b/utils/scandetails.go index 291410085..39aa5f1e3 100644 --- a/utils/scandetails.go +++ b/utils/scandetails.go @@ -6,14 +6,11 @@ import ( "path/filepath" "time" - "github.com/jfrog/jfrog-cli-security/sca/bom/buildinfo" - "github.com/jfrog/jfrog-cli-security/sca/scan/scangraph" - - clientservices "github.com/jfrog/jfrog-client-go/xsc/services" - "github.com/jfrog/froggit-go/vcsclient" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-security/commands/audit" + "github.com/jfrog/jfrog-cli-security/sca/bom/buildinfo" + "github.com/jfrog/jfrog-cli-security/sca/scan/scangraph" "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" "github.com/jfrog/jfrog-client-go/utils/log" @@ -32,7 +29,7 @@ type ScanDetails struct { skipAutoInstall bool minSeverityFilter severityutils.Severity baseBranch string - configProfile *clientservices.ConfigProfile + configProfile *xscservices.ConfigProfile allowPartialResults bool diffScan bool @@ -112,7 +109,7 @@ func (sc *ScanDetails) SetBaseBranch(branch string) *ScanDetails { return sc } -func (sc *ScanDetails) SetConfigProfile(configProfile *clientservices.ConfigProfile) *ScanDetails { +func (sc *ScanDetails) SetConfigProfile(configProfile *xscservices.ConfigProfile) *ScanDetails { sc.configProfile = configProfile return sc } diff --git a/utils/testsutils.go b/utils/testsutils.go index 1687596a6..1b9bc1180 100644 --- a/utils/testsutils.go +++ b/utils/testsutils.go @@ -147,17 +147,6 @@ func CreateDotGitWithCommit(t *testing.T, wd, port string, repositoriesPath ...s } } -func CreateTempJfrogHomeWithCallback(t *testing.T) (string, func()) { - newJfrogHomeDir, err := fileutils.CreateTempDir() - assert.NoError(t, err) - prevJfrogHomeDir := os.Getenv(JfrogHomeDirEnv) - assert.NoError(t, os.Setenv(JfrogHomeDirEnv, newJfrogHomeDir)) - return newJfrogHomeDir, func() { - assert.NoError(t, os.Setenv(JfrogHomeDirEnv, prevJfrogHomeDir)) - assert.NoError(t, fileutils.RemoveTempDir(newJfrogHomeDir)) - } -} - func CreateXscMockServerForConfigProfile(t *testing.T, xrayVersion string) (mockServer *httptest.Server, serverDetails *config.ServerDetails) { mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { apiUrlPart := "api/v1/" diff --git a/utils/utils.go b/utils/utils.go index 46d245851..e7fa3e4f5 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -52,7 +52,6 @@ const ( ) var ( - TrueVal = true FrogbotVersion = "0.0.0" branchInvalidCharsRegex = regexp.MustCompile(branchNameRegex) ) From a224fd7781d4a544db573317941cd728fb37e1d8 Mon Sep 17 00:00:00 2001 From: Eyal Kapon Date: Mon, 3 Nov 2025 15:07:35 +0200 Subject: [PATCH 5/8] Add go:generate directive for mock generation --- scanpullrequest/scanpullrequest_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scanpullrequest/scanpullrequest_test.go b/scanpullrequest/scanpullrequest_test.go index b25dd8ca9..78b88f456 100644 --- a/scanpullrequest/scanpullrequest_test.go +++ b/scanpullrequest/scanpullrequest_test.go @@ -37,6 +37,8 @@ import ( "github.com/stretchr/testify/assert" ) +//go:generate go run github.com/golang/mock/mockgen@v1.6.0 -destination=../testdata/vcsclientmock.go -package=testdata github.com/jfrog/froggit-go/vcsclient VcsClient + var gitParams = &utils.Repository{ OutputWriter: &outputwriter.SimplifiedOutput{}, Params: utils.Params{ From c8230ac8ab8131b6366034ab3464173aa25837a0 Mon Sep 17 00:00:00 2001 From: Eyal Kapon Date: Mon, 3 Nov 2025 15:21:44 +0200 Subject: [PATCH 6/8] Remove unused commandName parameter from extractGitParamsFromEnvs --- utils/params.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/params.go b/utils/params.go index f1d964fd7..05fb514a8 100644 --- a/utils/params.go +++ b/utils/params.go @@ -474,7 +474,7 @@ func GetFrogbotDetails(commandName string) (frogbotDetails *FrogbotDetails, err return } - gitParamsFromEnv, err := extractGitParamsFromEnvs(commandName) + gitParamsFromEnv, err := extractGitParamsFromEnvs() if err != nil { return } @@ -617,7 +617,7 @@ func extractJFrogCredentialsFromEnvs() (*coreconfig.ServerDetails, error) { return &server, nil } -func extractGitParamsFromEnvs(commandName string) (*Git, error) { +func extractGitParamsFromEnvs() (*Git, error) { e := &ErrMissingEnv{} var err error gitEnvParams := &Git{} From 742f6b1f0a83ccdb917441a84e5bcd0ca93e0d78 Mon Sep 17 00:00:00 2001 From: Eyal Kapon Date: Thu, 20 Nov 2025 14:58:25 +0200 Subject: [PATCH 7/8] Remove duplicate action build artifacts --- action/lib/src/main.js | 67 ------- action/lib/src/utils.js | 346 ----------------------------------- action/lib/test/main.spec.js | 133 -------------- 3 files changed, 546 deletions(-) delete mode 100644 action/lib/src/main.js delete mode 100644 action/lib/src/utils.js delete mode 100644 action/lib/test/main.spec.js diff --git a/action/lib/src/main.js b/action/lib/src/main.js deleted file mode 100644 index b6f18643c..000000000 --- a/action/lib/src/main.js +++ /dev/null @@ -1,67 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const core = __importStar(require("@actions/core")); -const utils_1 = require("./utils"); -function main() { - return __awaiter(this, void 0, void 0, function* () { - try { - core.startGroup('Frogbot'); - let jfrogUrl = yield utils_1.Utils.getJfrogPlatformUrl(); - yield utils_1.Utils.setupOidcTokenIfNeeded(jfrogUrl); - const eventName = yield utils_1.Utils.setFrogbotEnv(); - yield utils_1.Utils.addToPath(); - switch (eventName) { - case 'pull_request': - case 'pull_request_target': - yield utils_1.Utils.execScanPullRequest(); - break; - case 'push': - case 'schedule': - case 'workflow_dispatch': - yield utils_1.Utils.execCreateFixPullRequests(); - break; - default: - core.setFailed(eventName + ' event is not supported by Frogbot'); - } - } - catch (error) { - core.setFailed(error.message); - } - finally { - core.endGroup(); - } - }); -} -main(); diff --git a/action/lib/src/utils.js b/action/lib/src/utils.js deleted file mode 100644 index 42a679c1f..000000000 --- a/action/lib/src/utils.js +++ /dev/null @@ -1,346 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Utils = void 0; -const core = __importStar(require("@actions/core")); -const cache = __importStar(require("@actions/cache")); -const exec_1 = require("@actions/exec"); -const github_1 = require("@actions/github"); -const tool_cache_1 = require("@actions/tool-cache"); -const fs_1 = require("fs"); -const os_1 = require("os"); -const path_1 = require("path"); -const simple_git_1 = require("simple-git"); -const http_client_1 = require("@actions/http-client"); -class Utils { - static addToPath() { - var _a; - return __awaiter(this, void 0, void 0, function* () { - let fileName = Utils.getExecutableName(); - let version = core.getInput(Utils.VERSION_ARG); - let major = version.split('.')[0]; - if (version === this.LATEST_CLI_VERSION_ARG) { - version = Utils.LATEST_RELEASE_VERSION; - major = '2'; - } - // Try Actions Cache first (persists across GitHub-hosted runners) - if (yield this.loadFromActionsCache(version, fileName)) { - return; - } - // Fallback to tool-cache (works for self-hosted runners) - if (this.loadFromCache(version)) { - // Download is not needed - return; - } - // Download Frogbot - const releasesRepo = (_a = process.env.JF_RELEASES_REPO) !== null && _a !== void 0 ? _a : ''; - let url = Utils.getCliUrl(major, version, fileName, releasesRepo); - core.debug('Downloading Frogbot from ' + url); - let auth = this.generateAuthString(releasesRepo); - let downloadDir = yield (0, tool_cache_1.downloadTool)(url, '', auth); - // Cache 'frogbot' executable in both caches - yield this.saveToActionsCache(version, fileName, downloadDir); - yield this.cacheAndAddPath(downloadDir, version, fileName); - }); - } - static generateAuthString(releasesRepo) { - var _a, _b, _c; - if (!releasesRepo) { - return ''; - } - let accessToken = (_a = process.env.JF_ACCESS_TOKEN) !== null && _a !== void 0 ? _a : ''; - let username = (_b = process.env.JF_USER) !== null && _b !== void 0 ? _b : ''; - let password = (_c = process.env.JF_PASSWORD) !== null && _c !== void 0 ? _c : ''; - if (accessToken) { - return 'Bearer ' + Buffer.from(accessToken).toString(); - } - else if (username && password) { - return 'Basic ' + Buffer.from(username + ':' + password).toString('base64'); - } - return ''; - } - static setFrogbotEnv() { - return __awaiter(this, void 0, void 0, function* () { - core.exportVariable('JF_GIT_PROVIDER', 'github'); - core.exportVariable('JF_GIT_OWNER', github_1.context.repo.owner); - let owner = github_1.context.repo.repo; - if (owner) { - core.exportVariable('JF_GIT_REPO', owner.substring(owner.indexOf('/') + 1)); - } - core.exportVariable('JF_GIT_PULL_REQUEST_ID', github_1.context.issue.number); - return github_1.context.eventName; - }); - } - /** - * Execute frogbot scan-pull-request command. - */ - static execScanPullRequest() { - return __awaiter(this, void 0, void 0, function* () { - if (!process.env.JF_GIT_BASE_BRANCH) { - core.exportVariable('JF_GIT_BASE_BRANCH', github_1.context.ref); - } - let res = yield (0, exec_1.exec)(Utils.getExecutableName(), ['scan-pull-request']); - if (res !== core.ExitCode.Success) { - throw new Error('Frogbot exited with exit code ' + res); - } - }); - } - /** - * Execute frogbot scan-repository command. - */ - static execCreateFixPullRequests() { - return __awaiter(this, void 0, void 0, function* () { - if (!process.env.JF_GIT_BASE_BRANCH) { - // Get the current branch we are checked on - const git = (0, simple_git_1.simpleGit)(); - try { - const currentBranch = yield git.branch(); - core.exportVariable('JF_GIT_BASE_BRANCH', currentBranch.current); - } - catch (error) { - throw new Error('Error getting current branch from the .git folder: ' + error); - } - } - let res = yield (0, exec_1.exec)(Utils.getExecutableName(), ['scan-repository']); - if (res !== core.ExitCode.Success) { - throw new Error('Frogbot exited with exit code ' + res); - } - }); - } - /** - * Try to load the Frogbot executables from cache. - * - * @param version - Frogbot version - * @returns true if the CLI executable was loaded from cache and added to path - */ - static loadFromCache(version) { - let execPath = (0, tool_cache_1.find)(Utils.TOOL_NAME, version); - if (execPath) { - core.addPath(execPath); - return true; - } - return false; - } - /** - * Add Frogbot executable to cache and to the system path. - * @param downloadDir - The directory whereby the CLI was downloaded to - * @param version - Frogbot version - * @param fileName - 'frogbot' or 'frogbot.exe' - */ - static cacheAndAddPath(downloadDir, version, fileName) { - return __awaiter(this, void 0, void 0, function* () { - let cliDir = yield (0, tool_cache_1.cacheFile)(downloadDir, fileName, Utils.TOOL_NAME, version); - if (!Utils.isWindows()) { - let filePath = (0, path_1.normalize)((0, path_1.join)(cliDir, fileName)); - (0, fs_1.chmodSync)(filePath, 0o555); - } - core.addPath(cliDir); - }); - } - /** - * Try to load Frogbot executable from Actions Cache. - * @param version - Frogbot version - * @param fileName - 'frogbot' or 'frogbot.exe' - * @returns true if the CLI executable was loaded from Actions Cache and added to path - */ - static loadFromActionsCache(version, fileName) { - return __awaiter(this, void 0, void 0, function* () { - try { - const cacheKey = `frogbot-${(0, os_1.platform)()}-${version}`; - const cachePath = (0, path_1.join)((0, os_1.homedir)(), '.cache', 'frogbot'); - const cachePaths = [cachePath]; - core.debug(`Attempting to restore Frogbot from Actions Cache with key: ${cacheKey}`); - const restoredKey = yield cache.restoreCache(cachePaths, cacheKey); - if (restoredKey) { - core.info(`Frogbot CLI restored from Actions Cache: ${restoredKey}`); - const execPath = (0, path_1.join)(cachePath, fileName); - // Make executable on Unix systems - if (!Utils.isWindows()) { - (0, fs_1.chmodSync)(execPath, 0o555); - } - core.addPath(cachePath); - return true; - } - } - catch (error) { - core.debug(`Actions Cache restore failed: ${error}`); - } - return false; - }); - } - /** - * Save Frogbot executable to Actions Cache. - * @param version - Frogbot version - * @param fileName - 'frogbot' or 'frogbot.exe' - * @param downloadDir - The directory whereby the CLI was downloaded to - */ - static saveToActionsCache(version, fileName, downloadDir) { - return __awaiter(this, void 0, void 0, function* () { - try { - const cacheKey = `frogbot-${(0, os_1.platform)()}-${version}`; - const cachePath = (0, path_1.join)((0, os_1.homedir)(), '.cache', 'frogbot'); - const cachePaths = [cachePath]; - // Ensure cache directory exists and copy binary - (0, fs_1.mkdirSync)(cachePath, { recursive: true }); - (0, fs_1.copyFileSync)(downloadDir, (0, path_1.join)(cachePath, fileName)); - core.debug(`Attempting to save Frogbot to Actions Cache with key: ${cacheKey}`); - yield cache.saveCache(cachePaths, cacheKey); - core.info(`Frogbot CLI saved to Actions Cache: ${cacheKey}`); - } - catch (error) { - core.debug(`Actions Cache save failed: ${error}`); - } - }); - } - static getCliUrl(major, version, fileName, releasesRepo) { - var _a; - let architecture = 'frogbot-' + Utils.getArchitecture(); - if (releasesRepo) { - let platformUrl = (_a = process.env.JF_URL) !== null && _a !== void 0 ? _a : ''; - if (!platformUrl) { - throw new Error('Failed while downloading Frogbot from Artifactory, JF_URL must be set'); - } - // Remove trailing slash if exists - platformUrl = platformUrl.replace(/\/$/, ''); - return `${platformUrl}/artifactory/${releasesRepo}/artifactory/frogbot/v${major}/${version}/${architecture}/${fileName}`; - } - return `https://releases.jfrog.io/artifactory/frogbot/v${major}/${version}/${architecture}/${fileName}`; - } - static getArchitecture() { - if (Utils.isWindows()) { - return 'windows-amd64'; - } - if ((0, os_1.platform)().includes('darwin')) { - return 'mac-386'; - } - if ((0, os_1.arch)().includes('arm')) { - return (0, os_1.arch)().includes('64') ? 'linux-arm64' : 'linux-arm'; - } - if ((0, os_1.arch)().includes('ppc64le')) { - return 'linux-ppc64le'; - } - if ((0, os_1.arch)().includes('ppc64')) { - return 'linux-ppc64'; - } - return (0, os_1.arch)().includes('64') ? 'linux-amd64' : 'linux-386'; - } - static getExecutableName() { - return Utils.isWindows() ? 'frogbot.exe' : 'frogbot'; - } - static isWindows() { - return (0, os_1.platform)().startsWith('win'); - } - static getJfrogPlatformUrl() { - var _a; - return __awaiter(this, void 0, void 0, function* () { - let jfrogUrl = (_a = process.env.JF_URL) !== null && _a !== void 0 ? _a : ''; - if (!jfrogUrl) { - throw new Error('JF_URL must be provided and point on your full platform URL, for example: https://mycompany.jfrog.io/'); - } - return jfrogUrl; - }); - } - /** - * This method will set up an OIDC token if the OIDC integration is set. - * If OIDC integration is set but not working, the action will fail causing frogbot to fail - * @param jfrogUrl - The JFrog platform URL - */ - static setupOidcTokenIfNeeded(jfrogUrl) { - return __awaiter(this, void 0, void 0, function* () { - const oidcProviderName = core.getInput(Utils.OIDC_INTEGRATION_PROVIDER_NAME_ARG); - if (!oidcProviderName) { - // No token is set if an oidc-provider-name wasn't provided - return; - } - core.debug('Obtaining an access token through OpenID Connect...'); - const audience = core.getInput(Utils.OIDC_AUDIENCE_ARG); - let jsonWebToken; - try { - core.debug('Fetching JSON web token'); - jsonWebToken = yield core.getIDToken(audience); - } - catch (error) { - throw new Error(`Getting openID Connect JSON web token failed: ${error.message}`); - } - try { - return yield this.initJfrogAccessTokenThroughOidcProtocol(jfrogUrl, jsonWebToken, oidcProviderName); - } - catch (error) { - throw new Error(`OIDC authentication against JFrog platform failed, please check OIDC settings and mappings on the JFrog platform: ${error.message}`); - } - }); - } - /** - * This method exchanges a JSON web token with a JFrog access token through the OpenID Connect protocol - * If we've reached this stage, the jfrogUrl field should hold a non-empty value obtained from process.env.JF_URL - * @param jfrogUrl - The JFrog platform URL - * @param jsonWebToken - The JSON web token used in the token exchange - * @param oidcProviderName - The OpenID Connect provider name - */ - static initJfrogAccessTokenThroughOidcProtocol(jfrogUrl, jsonWebToken, oidcProviderName) { - return __awaiter(this, void 0, void 0, function* () { - const exchangeUrl = jfrogUrl.replace(/\/$/, '') + '/access/api/v1/oidc/token'; - core.debug('Exchanging GitHub JSON web token with a JFrog access token...'); - const httpClient = new http_client_1.HttpClient(); - const data = `{ - "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", - "subject_token_type": "urn:ietf:params:oauth:token-type:id_token", - "subject_token": "${jsonWebToken}", - "provider_name": "${oidcProviderName}" - }`; - const additionalHeaders = { - 'Content-Type': 'application/json', - }; - const response = yield httpClient.post(exchangeUrl, data, additionalHeaders); - const responseString = yield response.readBody(); - const responseJson = JSON.parse(responseString); - process.env.JF_ACCESS_TOKEN = responseJson.access_token; - if (responseJson.access_token) { - core.setSecret(responseJson.access_token); - } - if (responseJson.errors) { - throw new Error(`${JSON.stringify(responseJson.errors)}`); - } - }); - } -} -exports.Utils = Utils; -Utils.LATEST_RELEASE_VERSION = '[RELEASE]'; -Utils.LATEST_CLI_VERSION_ARG = 'latest'; -Utils.VERSION_ARG = 'version'; -Utils.TOOL_NAME = 'frogbot'; -// OpenID Connect audience input -Utils.OIDC_AUDIENCE_ARG = 'oidc-audience'; -// OpenID Connect provider_name input -Utils.OIDC_INTEGRATION_PROVIDER_NAME_ARG = 'oidc-provider-name'; diff --git a/action/lib/test/main.spec.js b/action/lib/test/main.spec.js deleted file mode 100644 index fc43aef6c..000000000 --- a/action/lib/test/main.spec.js +++ /dev/null @@ -1,133 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const os_1 = __importDefault(require("os")); -const utils_1 = require("../src/utils"); -jest.mock('os'); -describe('Frogbot Action Tests', () => { - afterEach(() => { - delete process.env.JF_ACCESS_TOKEN; - delete process.env.JF_USER; - delete process.env.PASSWORD; - delete process.env.JF_GIT_PROVIDER; - delete process.env.JF_GIT_OWNER; - delete process.env.GITHUB_REPOSITORY_OWNER; - delete process.env.GITHUB_REPOSITORY; - }); - describe('Frogbot URL Tests', () => { - const myOs = os_1.default; - let cases = [ - [ - 'win32', - 'amd64', - 'jfrog.exe', - 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-windows-amd64/jfrog.exe', - ], - ['darwin', 'amd64', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-mac-386/jfrog'], - ['linux', 'amd64', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-amd64/jfrog'], - ['linux', 'arm64', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-arm64/jfrog'], - ['linux', '386', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-386/jfrog'], - ['linux', 'arm', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-arm/jfrog'], - ['linux', 'ppc64', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-ppc64/jfrog'], - ['linux', 'ppc64le', 'jfrog', 'https://releases.jfrog.io/artifactory/frogbot/v1/1.2.3/frogbot-linux-ppc64le/jfrog'], - ]; - test.each(cases)('CLI Url for %s-%s', (platform, arch, fileName, expectedUrl) => { - myOs.platform.mockImplementation(() => platform); - myOs.arch.mockImplementation(() => arch); - let cliUrl = utils_1.Utils.getCliUrl('1', '1.2.3', fileName, ''); - expect(cliUrl).toBe(expectedUrl); - }); - }); - describe('Frogbot URL Tests With Remote Artifactory', () => { - const myOs = os_1.default; - const releasesRepo = 'frogbot-remote'; - process.env['JF_URL'] = 'https://myfrogbot.com/'; - process.env['JF_ACCESS_TOKEN'] = 'access_token1'; - let cases = [ - [ - 'win32', - 'amd64', - 'jfrog.exe', - 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-windows-amd64/jfrog.exe', - ], - [ - 'darwin', - 'amd64', - 'jfrog', - 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-mac-386/jfrog', - ], - [ - 'linux', - 'amd64', - 'jfrog', - 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-amd64/jfrog', - ], - [ - 'linux', - 'arm64', - 'jfrog', - 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-arm64/jfrog', - ], - [ - 'linux', - '386', - 'jfrog', - 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-386/jfrog', - ], - [ - 'linux', - 'arm', - 'jfrog', - 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-arm/jfrog', - ], - [ - 'linux', - 'ppc64', - 'jfrog', - 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-ppc64/jfrog', - ], - [ - 'linux', - 'ppc64le', - 'jfrog', - 'https://myfrogbot.com/artifactory/frogbot-remote/artifactory/frogbot/v2/2.8.7/frogbot-linux-ppc64le/jfrog', - ], - ]; - test.each(cases)('Remote CLI Url for %s-%s', (platform, arch, fileName, expectedUrl) => { - myOs.platform.mockImplementation(() => platform); - myOs.arch.mockImplementation(() => arch); - let cliUrl = utils_1.Utils.getCliUrl('2', '2.8.7', fileName, releasesRepo); - expect(cliUrl).toBe(expectedUrl); - }); - }); - describe('Generate auth string', () => { - it('Should return an empty string if releasesRepo is falsy', () => { - const result = utils_1.Utils.generateAuthString(''); - expect(result).toBe(''); - }); - it('Should generate a Bearer token if accessToken is provided', () => { - process.env.JF_ACCESS_TOKEN = 'yourAccessToken'; - const result = utils_1.Utils.generateAuthString('yourReleasesRepo'); - expect(result).toBe('Bearer yourAccessToken'); - }); - it('Should generate a Basic token if username and password are provided', () => { - process.env.JF_USER = 'yourUsername'; - process.env.JF_PASSWORD = 'yourPassword'; - const result = utils_1.Utils.generateAuthString('yourReleasesRepo'); - expect(result).toBe('Basic eW91clVzZXJuYW1lOnlvdXJQYXNzd29yZA=='); - }); - it('Should return an empty string if no credentials are provided', () => { - const result = utils_1.Utils.generateAuthString('yourReleasesRepo'); - expect(result).toBe(''); - }); - }); - it('Repository env tests', () => { - process.env['GITHUB_REPOSITORY_OWNER'] = 'jfrog'; - process.env['GITHUB_REPOSITORY'] = 'jfrog/frogbot'; - utils_1.Utils.setFrogbotEnv(); - expect(process.env['JF_GIT_PROVIDER']).toBe('github'); - expect(process.env['JF_GIT_OWNER']).toBe('jfrog'); - }); -}); From 9579eb1bcfdffd12b9632a13d8bfb72c794fc2bd Mon Sep 17 00:00:00 2001 From: Eyal Kapon Date: Mon, 24 Nov 2025 19:15:18 +0200 Subject: [PATCH 8/8] Fix compilation error: remove commandName argument from extractGitParamsFromEnvs calls in tests --- utils/params_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/utils/params_test.go b/utils/params_test.go index c722d4369..ab098b4ce 100644 --- a/utils/params_test.go +++ b/utils/params_test.go @@ -137,19 +137,19 @@ func TestExtractClientInfo(t *testing.T) { assert.NoError(t, SanitizeEnv()) }() - _, err := extractGitParamsFromEnvs(ScanRepository) + _, err := extractGitParamsFromEnvs() assert.EqualError(t, err, "JF_GIT_PROVIDER should be one of: 'github', 'gitlab', 'bitbucketServer' or 'azureRepos'") SetEnvAndAssert(t, map[string]string{GitProvider: "github"}) - _, err = extractGitParamsFromEnvs(ScanRepository) + _, err = extractGitParamsFromEnvs() assert.EqualError(t, err, "'JF_GIT_OWNER' environment variable is missing") SetEnvAndAssert(t, map[string]string{GitRepoOwnerEnv: "jfrog"}) - _, err = extractGitParamsFromEnvs(ScanRepository) + _, err = extractGitParamsFromEnvs() assert.EqualError(t, err, "'JF_GIT_TOKEN' environment variable is missing") SetEnvAndAssert(t, map[string]string{GitTokenEnv: "token"}) - _, err = extractGitParamsFromEnvs(ScanRepository) + _, err = extractGitParamsFromEnvs() assert.EqualError(t, err, "'JF_GIT_REPO' environment variable is missing") } @@ -180,7 +180,7 @@ func TestExtractAndAssertRepoParams(t *testing.T) { server, err := extractJFrogCredentialsFromEnvs() assert.NoError(t, err) - gitParams, err := extractGitParamsFromEnvs(ScanRepository) + gitParams, err := extractGitParamsFromEnvs() assert.NoError(t, err) configFileContent, err := ReadConfigFromFileSystem(configParamsTestFile) assert.NoError(t, err) @@ -227,7 +227,7 @@ func TestBuildRepoAggregatorWithEmptyScan(t *testing.T) { }() server, err := extractJFrogCredentialsFromEnvs() assert.NoError(t, err) - gitParams, err := extractGitParamsFromEnvs(ScanRepository) + gitParams, err := extractGitParamsFromEnvs() assert.NoError(t, err) configFileContent, err := ReadConfigFromFileSystem(configEmptyScanParamsTestFile) assert.NoError(t, err) @@ -263,7 +263,7 @@ func testExtractAndAssertProjectParams(t *testing.T, project Project) { func extractAndAssertParamsFromEnv(t *testing.T, platformUrl, basicAuth bool, commandName string) { server, err := extractJFrogCredentialsFromEnvs() assert.NoError(t, err) - gitParams, err := extractGitParamsFromEnvs(commandName) + gitParams, err := extractGitParamsFromEnvs() assert.NoError(t, err) configFile, err := BuildRepoAggregator("xrayVersion", "xscVersion", nil, nil, gitParams, server, commandName) assert.NoError(t, err)