diff --git a/.github/workflows/auto-build-publish.yml b/.github/workflows/auto-build-publish.yml index 8a19842b4..3484456f0 100644 --- a/.github/workflows/auto-build-publish.yml +++ b/.github/workflows/auto-build-publish.yml @@ -26,20 +26,35 @@ jobs: cli-version: [ "latest", "2.66.0" ] runs-on: ${{ matrix.os }}-latest steps: + - name: Skip macOS - JGC-413 + if: matrix.os == 'macos' + run: | + echo "::warning::JGC-413 - Skip until artifactory bootstrap in osx is fixed" + exit 0 + - name: Checkout Repository + if: matrix.os != 'macos' uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} + # This repo is a Node action and has no go.mod. install-go-with-cache@main + # reads go.mod when go-version is unset (#28), which fails Setup Go immediately. + # local-rt-setup (used by install-local-artifactory) requires Go 1.24+. - name: Setup Go with cache + if: matrix.os != 'macos' uses: jfrog/.github/actions/install-go-with-cache@main + with: + go-version: "1.24" - name: Install local Artifactory + if: matrix.os != 'macos' uses: jfrog/.github/actions/install-local-artifactory@main with: RTLIC: ${{ secrets.RTLIC }} - name: Post Step to Test the Auto Build-Publish post step + if: matrix.os != 'macos' uses: gacts/run-and-post-run@v1 with: post: | @@ -57,6 +72,7 @@ jobs: fi - name: Setup JFrog CLI + if: matrix.os != 'macos' id: setup-jfrog-cli uses: ./ with: @@ -67,6 +83,7 @@ jobs: JF_PASSWORD: password - name: Create NPM Remote Repository JSON + if: matrix.os != 'macos' uses: jsdaniell/create-json@v1.2.3 with: name: "npm-remote-template.json" @@ -78,10 +95,12 @@ jobs: }' - name: Configure Artifactory NPM Remote Repository + if: matrix.os != 'macos' run: jf rt repo-create npm-remote-template.json shell: bash - name: Add npm modules to local build-info + if: matrix.os != 'macos' run: | jf npm-config --repo-resolve npm-remote jf npm install \ No newline at end of file diff --git a/.github/workflows/frogbot-scan-repository.yml b/.github/workflows/frogbot-scan-repository.yml index 19936936a..5294a1176 100644 --- a/.github/workflows/frogbot-scan-repository.yml +++ b/.github/workflows/frogbot-scan-repository.yml @@ -19,6 +19,8 @@ jobs: steps: - name: Setup Go with cache uses: jfrog/.github/actions/install-go-with-cache@main + with: + go-version: "1.24" - uses: jfrog/frogbot@v2 env: diff --git a/README.md b/README.md index cfbe42b42..64be2c7c6 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,8 @@ It is also possible to set the latest JFrog CLI version by adding the _version_ version: latest ``` +Do not combine `version: latest` with `download-repository`. See [Downloading JFrog CLI from Artifactory](#downloading-jfrog-cli-from-artifactory). + | Important: Only JFrog CLI versions 1.46.4 or above are supported. | |-------------------------------------------------------------------| @@ -417,10 +419,17 @@ In this example, each job builds and publishes a different service from the mono If your agent has no Internet access, you can configure the workflow to download JFrog CLI from a [remote repository](https://www.jfrog.com/confluence/display/JFROG/Remote+Repositories) in your JFrog Artifactory, which is configured to proxy the official download URL. +> [!NOTE] +> With `download-repository`, prefer a concrete `version: X.Y.Z` over `latest`. +> +> `latest` is not resolved to a version number. It becomes the literal path segment `[RELEASE]` in the download URL (`v2/[RELEASE]/jfrog-cli-.../jfrog`), and a generic repository serves that path like any other. If the repository has **Store Artifacts Locally** enabled (the default), the binary returned for that path is cached under it, so later runs can keep receiving that same binary instead of a newer CLI. A concrete version avoids this, because every version has its own immutable path. +> +> Jobs that can reach the internet and want the newest CLI: omit `download-repository`. + Here's how you do this: 1. Create a remote repository in Artifactory. Name the repository jfrog-cli-remote and set its URL to https://releases.jfrog.io/artifactory/jfrog-cli/ -2. Set _download-repository_ input to jfrog-cli-remote: +2. Set _download-repository_ input to jfrog-cli-remote and pin `version`: ```yml - uses: jfrog/setup-jfrog-cli@v4 @@ -430,6 +439,7 @@ Here's how you do this: JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }} with: + version: X.Y.Z download-repository: jfrog-cli-remote ``` diff --git a/action.yml b/action.yml index d1a779ea2..5e17f6127 100644 --- a/action.yml +++ b/action.yml @@ -3,11 +3,11 @@ description: "Install and configure JFrog CLI." author: "JFrog" inputs: version: - description: "JFrog CLI Version" + description: "JFrog CLI Version. A concrete X.Y.Z is recommended when download-repository points to a remote repository that stores artifacts locally, since latest is requested as the v2/[RELEASE] path and can be served from cache." default: "2.91.0" required: false download-repository: - description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access." + description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access. When the repository stores artifacts locally, prefer a concrete version over latest: latest is requested as the literal v2/[RELEASE] path, and the binary cached under that path can keep being served on later runs." required: false oidc-provider-name: description: "Provider Name's value that was set in OpenId Connect integration in the JFrog platform." diff --git a/lib/utils.js b/lib/utils.js index 3e3f302ca..0cb235467 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -82,6 +82,7 @@ class Utils { let version = core.getInput(Utils.CLI_VERSION_ARG); let cliRemote = core.getInput(Utils.CLI_REMOTE_ARG); const isLatestVer = version === Utils.LATEST_CLI_VERSION; + Utils.logIfLatestDownloadedFromRemote(version, cliRemote); if (!isLatestVer && (0, semver_1.lt)(version, this.MIN_CLI_VERSION)) { throw new Error('Requested to download JFrog CLI version ' + version + ' but must be at least ' + this.MIN_CLI_VERSION); } @@ -168,6 +169,16 @@ class Utils { } return `${artifactoryUrl}/${downloadDetails.repository}/v${major}/${version}/${architecture}/${fileName}`; } + /** + * Log when latest is resolved through an Artifactory repository. + * [RELEASE] is part of the artifact path, so a remote repository caches it like any other file + * and keeps serving the first version it resolved. Pin a concrete version instead. + */ + static logIfLatestDownloadedFromRemote(version, cliRemote) { + if (cliRemote && version === Utils.LATEST_CLI_VERSION) { + core.info(Utils.LATEST_FROM_REMOTE_INFO); + } + } // Get Config Tokens created on your local machine using JFrog CLI. // The Tokens configured with JF_ENV_ environment variables. static getConfigTokens() { @@ -470,7 +481,7 @@ class Utils { } /** * If enable-package-alias is true and GITHUB_PATH is set, runs `jf package-alias install` - * and appends the alias bin directory to GITHUB_PATH so subsequent steps intercept mvn, npm, go, etc. + * and adds the alias bin directory to PATH via core.addPath so subsequent steps intercept mvn, npm, go, etc. * On failure (e.g. older CLI without package-alias), logs a warning and does not fail the job. */ static setupPackageAliasIfRequested() { @@ -513,8 +524,8 @@ class Utils { return; } const aliasBinDir = Utils.getPackageAliasBinDir(); - (0, fs_1.appendFileSync)(githubPath, aliasBinDir + '\n'); - core.info('Package aliases installed and "' + aliasBinDir + '" appended to GITHUB_PATH.'); + core.addPath(aliasBinDir); + core.info('Package aliases installed and "' + aliasBinDir + '" added to PATH.'); }); } } @@ -534,6 +545,8 @@ Utils.MIN_CLI_VERSION = '1.46.4'; Utils.LATEST_CLI_VERSION = 'latest'; // The value in the download URL to set to get the latest version Utils.LATEST_RELEASE_VERSION = '[RELEASE]'; +// Logged when version=latest is downloaded through a remote repository, which caches the literal [RELEASE] path +Utils.LATEST_FROM_REMOTE_INFO = 'download-repository is set with version=latest, so the CLI is requested from the literal path v2/[RELEASE] instead of a version number. If that repository stores artifacts locally, the binary cached under this path can keep being served on later runs. Use a concrete version to download from a per-version path, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. Utils.LATEST_SEMVER = '100.100.100'; // The default server id name for separate env config diff --git a/src/utils.ts b/src/utils.ts index 8ff668407..edbdb9e8a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,6 +30,9 @@ export class Utils { public static readonly LATEST_CLI_VERSION: string = 'latest'; // The value in the download URL to set to get the latest version private static readonly LATEST_RELEASE_VERSION: string = '[RELEASE]'; + // Logged when version=latest is downloaded through a remote repository, which caches the literal [RELEASE] path + public static readonly LATEST_FROM_REMOTE_INFO: string = + 'download-repository is set with version=latest, so the CLI is requested from the literal path v2/[RELEASE] instead of a version number. If that repository stores artifacts locally, the binary cached under this path can keep being served on later runs. Use a concrete version to download from a per-version path, or omit download-repository if the runner can reach releases.jfrog.io.'; // Placeholder CLI version to use to keep 'latest' in cache. public static readonly LATEST_SEMVER: string = '100.100.100'; // The default server id name for separate env config @@ -108,6 +111,7 @@ export class Utils { let version: string = core.getInput(Utils.CLI_VERSION_ARG); let cliRemote: string = core.getInput(Utils.CLI_REMOTE_ARG); const isLatestVer: boolean = version === Utils.LATEST_CLI_VERSION; + Utils.logIfLatestDownloadedFromRemote(version, cliRemote); if (!isLatestVer && lt(version, this.MIN_CLI_VERSION)) { throw new Error('Requested to download JFrog CLI version ' + version + ' but must be at least ' + this.MIN_CLI_VERSION); @@ -199,6 +203,17 @@ export class Utils { return `${artifactoryUrl}/${downloadDetails.repository}/v${major}/${version}/${architecture}/${fileName}`; } + /** + * Log when latest is resolved through an Artifactory repository. + * [RELEASE] is part of the artifact path, so a remote repository caches it like any other file + * and keeps serving the first version it resolved. Pin a concrete version instead. + */ + public static logIfLatestDownloadedFromRemote(version: string, cliRemote: string): void { + if (cliRemote && version === Utils.LATEST_CLI_VERSION) { + core.info(Utils.LATEST_FROM_REMOTE_INFO); + } + } + // Get Config Tokens created on your local machine using JFrog CLI. // The Tokens configured with JF_ENV_ environment variables. public static getConfigTokens(): Set { diff --git a/test/main.spec.ts b/test/main.spec.ts index 19258b5a4..58c511ea4 100644 --- a/test/main.spec.ts +++ b/test/main.spec.ts @@ -259,6 +259,27 @@ describe('JFrog CLI V2 URL Tests', () => { }); }); +describe('logIfLatestDownloadedFromRemote', () => { + beforeEach(() => { + (core.info as jest.Mock).mockClear(); + }); + + test('Logs info when latest is downloaded from an Artifactory repository', () => { + Utils.logIfLatestDownloadedFromRemote(Utils.LATEST_CLI_VERSION, 'jfrog-cli-remote'); + expect(core.info).toHaveBeenCalledWith(Utils.LATEST_FROM_REMOTE_INFO); + }); + + test('Does not log when version is pinned', () => { + Utils.logIfLatestDownloadedFromRemote('2.91.0', 'jfrog-cli-remote'); + expect(core.info).not.toHaveBeenCalled(); + }); + + test('Does not log when download-repository is unset', () => { + Utils.logIfLatestDownloadedFromRemote(Utils.LATEST_CLI_VERSION, ''); + expect(core.info).not.toHaveBeenCalled(); + }); +}); + test('Extract download details Tests', () => { for (let config of [V1_CONFIG, V2_CONFIG]) { process.env.JF_ENV_LOCAL = config;