diff --git a/README.md b/README.md
index 1a1f4693b..9a8747a87 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@
- [Setting JFrog CLI Version](#setting-jfrog-cli-version)
- [Setting the JFrog Project Key](#setting-the-jfrog-project-key)
- [Downloading JFrog CLI from Artifactory](#downloading-jfrog-cli-from-artifactory)
+ - [Custom Server ID and Multi-Configuration](#custom-server-id-and-multi-configuration)
- [JFrog Job Summary](#jfrog-job-summary)
- [Code Scanning Alerts](#code-scanning-alerts)
- [Example Projects](#example-projects)
@@ -301,6 +302,26 @@ Here's how you do this:
```
+
+ Custom Server ID and Multi-Configuration
+
+### Custom Server ID and Multi-Configuration
+
+The action configures JFrog CLI with a default server ID, which is unique for each run of a workflow.
+
+You may override the default server ID by providing a custom server ID:
+
+ ```yml
+ - uses: jfrog/setup-jfrog-cli@v4
+ with:
+ custom-server-id: my-server
+ ```
+
+You may also use multiple configurations in the same workflow by providing a custom server ID for each configuration.
+
+Alternating between configurations can be done by providing the `--server-id` option to JFrog CLI commands or by setting a default server using `jf c use `.
+
+
## JFrog Job Summary
Workflows using this GitHub action will output a summary of some of the key commands that were performed using JFrog CLI.
diff --git a/action.yml b/action.yml
index 1a9a88d9e..393cc15b3 100644
--- a/action.yml
+++ b/action.yml
@@ -23,6 +23,9 @@ inputs:
description: "By default, if the workflow completes with collected build-info that has not been published using the jf rt build-publish command, the build-info will be automatically published to Artifactory. Set this to true to disable the automatic publication of build-info at the end of the workflow."
default: "false"
required: false
+ custom-server-id:
+ description: "Custom JFrog CLI configuration server ID to use instead of the default one generated by the action."
+ required: false
outputs:
oidc-token:
description: "JFrog OIDC token generated by the Setup JFrog CLI when setting oidc-provider-name."
diff --git a/lib/cleanup.js b/lib/cleanup.js
index 06492779d..9d11dc0df 100644
--- a/lib/cleanup.js
+++ b/lib/cleanup.js
@@ -36,8 +36,7 @@ const core = __importStar(require("@actions/core"));
const utils_1 = require("./utils");
function cleanup() {
return __awaiter(this, void 0, void 0, function* () {
- if (!utils_1.Utils.loadFromCache(core.getInput(utils_1.Utils.CLI_VERSION_ARG))) {
- core.warning('Could not find JFrog CLI executable. Skipping cleanup.');
+ if (yield shouldSkipCleanup()) {
return;
}
// Run post tasks related to Build Info (auto build publish, job summary)
@@ -198,4 +197,19 @@ function generateJobSummary() {
}
});
}
+function shouldSkipCleanup() {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (!utils_1.Utils.loadFromCache(core.getInput(utils_1.Utils.CLI_VERSION_ARG))) {
+ core.warning('Could not find JFrog CLI executable. Skipping cleanup.');
+ return true;
+ }
+ // Skip cleanup if no servers are configured (already removed)
+ const servers = process.env[utils_1.Utils.JFROG_CLI_SERVER_IDS_ENV_VAR];
+ if (!servers) {
+ core.debug('No servers are configured. Skipping cleanup.');
+ return true;
+ }
+ return false;
+ });
+}
cleanup();
diff --git a/lib/utils.js b/lib/utils.js
index 0fe8d85e0..efd3ab424 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -311,7 +311,7 @@ class Utils {
let password = jfrogCredentials.password;
let accessToken = jfrogCredentials.accessToken;
if (url) {
- let configCmd = [Utils.SETUP_JFROG_CLI_SERVER_ID, '--url', url, '--interactive=false', '--overwrite=true'];
+ let configCmd = [Utils.getServerIdForConfig(), '--url', url, '--interactive=false', '--overwrite=true'];
if (accessToken) {
configCmd.push('--access-token', accessToken);
}
@@ -321,6 +321,36 @@ class Utils {
return configCmd;
}
}
+ /**
+ * Get server ID for JFrog CLI configuration. Save the server ID in the servers env var if it doesn't already exist.
+ */
+ static getServerIdForConfig() {
+ let serverId = Utils.getCustomOrDefaultServerId();
+ // Add new serverId to the servers env var if it doesn't already exist.
+ if (Utils.getConfiguredJFrogServers().includes(serverId)) {
+ return serverId;
+ }
+ const currentValue = process.env[Utils.JFROG_CLI_SERVER_IDS_ENV_VAR];
+ const newVal = currentValue ? `${currentValue};${serverId}` : serverId;
+ core.exportVariable(Utils.JFROG_CLI_SERVER_IDS_ENV_VAR, newVal);
+ return serverId;
+ }
+ /**
+ * Return custom server ID if provided, or default server ID otherwise.
+ */
+ static getCustomOrDefaultServerId() {
+ let customServerId = core.getInput(Utils.CUSTOM_SERVER_ID);
+ if (customServerId) {
+ return customServerId;
+ }
+ return Utils.getRunDefaultServerId();
+ }
+ /**
+ * Return a server ID that is unique for this workflow run based on the GitHub repository and run ID.
+ */
+ static getRunDefaultServerId() {
+ return [Utils.SETUP_JFROG_CLI_SERVER_ID_PREFIX, process.env.GITHUB_REPOSITORY, process.env.GITHUB_RUN_ID].join('-');
+ }
static setCliEnv() {
Utils.exportVariableIfNotSet('JFROG_CLI_ENV_EXCLUDE', '*password*;*secret*;*key*;*token*;*auth*;JF_ARTIFACTORY_*;JF_ENV_*;JF_URL;JF_USER;JF_PASSWORD;JF_ACCESS_TOKEN');
Utils.exportVariableIfNotSet('JFROG_CLI_OFFER_CONFIG', 'false');
@@ -372,11 +402,28 @@ class Utils {
}
});
}
+ /**
+ * Removed configured JFrog CLI servers that are saved in the servers env var, and unset the env var.
+ */
static removeJFrogServers() {
return __awaiter(this, void 0, void 0, function* () {
- yield Utils.runCli(['c', 'rm', '--quiet']);
+ for (const serverId of Utils.getConfiguredJFrogServers()) {
+ core.debug(`Removing server ID: '${serverId}'...`);
+ yield Utils.runCli(['c', 'rm', serverId, '--quiet']);
+ }
+ core.exportVariable(Utils.JFROG_CLI_SERVER_IDS_ENV_VAR, '');
});
}
+ /**
+ * Split and return the configured JFrog CLI servers that are saved in the servers env var.
+ */
+ static getConfiguredJFrogServers() {
+ const serversValue = process.env[Utils.JFROG_CLI_SERVER_IDS_ENV_VAR];
+ if (!serversValue) {
+ return [];
+ }
+ return serversValue.split(';');
+ }
static getArchitecture() {
if (Utils.isWindows()) {
return 'windows-amd64';
@@ -722,8 +769,10 @@ Utils.LATEST_CLI_VERSION = 'latest';
Utils.LATEST_RELEASE_VERSION = '[RELEASE]';
// 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
-Utils.SETUP_JFROG_CLI_SERVER_ID = 'setup-jfrog-cli-server';
+// The prefix for the default server id name for JFrog CLI config
+Utils.SETUP_JFROG_CLI_SERVER_ID_PREFIX = 'setup-jfrog-cli-server';
+// Environment variable to hold all configured server IDs, separated by ';'
+Utils.JFROG_CLI_SERVER_IDS_ENV_VAR = 'SETUP_JFROG_CLI_SERVER_IDS';
// Directory name which holds markdown files for the Workflow summary
Utils.JOB_SUMMARY_DIR_NAME = 'jfrog-command-summary';
// Directory name which holds security command summary files
@@ -749,6 +798,8 @@ Utils.OIDC_INTEGRATION_PROVIDER_NAME = 'oidc-provider-name';
Utils.JOB_SUMMARY_DISABLE = 'disable-job-summary';
// Disable auto build info publish feature flag
Utils.AUTO_BUILD_PUBLISH_DISABLE = 'disable-auto-build-publish';
+// Custom server ID input
+Utils.CUSTOM_SERVER_ID = 'custom-server-id';
// URL for the markdown header image
// This is hosted statically because its usage is outside the context of the JFrog setup action.
// It cannot be linked to the repository, as GitHub serves the image from a CDN,
diff --git a/src/cleanup.ts b/src/cleanup.ts
index 9c1ac8f5a..281e7f4d3 100644
--- a/src/cleanup.ts
+++ b/src/cleanup.ts
@@ -2,10 +2,10 @@ import * as core from '@actions/core';
import { Utils } from './utils';
async function cleanup() {
- if (!Utils.loadFromCache(core.getInput(Utils.CLI_VERSION_ARG))) {
- core.warning('Could not find JFrog CLI executable. Skipping cleanup.');
+ if (await shouldSkipCleanup()) {
return;
}
+
// Run post tasks related to Build Info (auto build publish, job summary)
await buildInfoPostTasks();
@@ -158,4 +158,19 @@ async function generateJobSummary() {
}
}
+async function shouldSkipCleanup(): Promise {
+ if (!Utils.loadFromCache(core.getInput(Utils.CLI_VERSION_ARG))) {
+ core.warning('Could not find JFrog CLI executable. Skipping cleanup.');
+ return true;
+ }
+
+ // Skip cleanup if no servers are configured (already removed)
+ const servers: string | undefined = process.env[Utils.JFROG_CLI_SERVER_IDS_ENV_VAR];
+ if (!servers) {
+ core.debug('No servers are configured. Skipping cleanup.');
+ return true;
+ }
+ return false;
+}
+
cleanup();
diff --git a/src/utils.ts b/src/utils.ts
index e4e8a4425..f9a54b77c 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -33,8 +33,10 @@ export class Utils {
private static readonly LATEST_RELEASE_VERSION: string = '[RELEASE]';
// 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
- public static readonly SETUP_JFROG_CLI_SERVER_ID: string = 'setup-jfrog-cli-server';
+ // The prefix for the default server id name for JFrog CLI config
+ public static readonly SETUP_JFROG_CLI_SERVER_ID_PREFIX: string = 'setup-jfrog-cli-server';
+ // Environment variable to hold all configured server IDs, separated by ';'
+ public static readonly JFROG_CLI_SERVER_IDS_ENV_VAR: string = 'SETUP_JFROG_CLI_SERVER_IDS';
// Directory name which holds markdown files for the Workflow summary
private static readonly JOB_SUMMARY_DIR_NAME: string = 'jfrog-command-summary';
// Directory name which holds security command summary files
@@ -61,6 +63,8 @@ export class Utils {
public static readonly JOB_SUMMARY_DISABLE: string = 'disable-job-summary';
// Disable auto build info publish feature flag
public static readonly AUTO_BUILD_PUBLISH_DISABLE: string = 'disable-auto-build-publish';
+ // Custom server ID input
+ private static readonly CUSTOM_SERVER_ID: string = 'custom-server-id';
// URL for the markdown header image
// This is hosted statically because its usage is outside the context of the JFrog setup action.
// It cannot be linked to the repository, as GitHub serves the image from a CDN,
@@ -352,7 +356,7 @@ export class Utils {
let accessToken: string | undefined = jfrogCredentials.accessToken;
if (url) {
- let configCmd: string[] = [Utils.SETUP_JFROG_CLI_SERVER_ID, '--url', url, '--interactive=false', '--overwrite=true'];
+ let configCmd: string[] = [Utils.getServerIdForConfig(), '--url', url, '--interactive=false', '--overwrite=true'];
if (accessToken) {
configCmd.push('--access-token', accessToken);
} else if (user && password) {
@@ -362,6 +366,40 @@ export class Utils {
}
}
+ /**
+ * Get server ID for JFrog CLI configuration. Save the server ID in the servers env var if it doesn't already exist.
+ */
+ private static getServerIdForConfig(): string {
+ let serverId: string = Utils.getCustomOrDefaultServerId();
+
+ // Add new serverId to the servers env var if it doesn't already exist.
+ if (Utils.getConfiguredJFrogServers().includes(serverId)) {
+ return serverId;
+ }
+ const currentValue: string | undefined = process.env[Utils.JFROG_CLI_SERVER_IDS_ENV_VAR];
+ const newVal: string = currentValue ? `${currentValue};${serverId}` : serverId;
+ core.exportVariable(Utils.JFROG_CLI_SERVER_IDS_ENV_VAR, newVal);
+ return serverId;
+ }
+
+ /**
+ * Return custom server ID if provided, or default server ID otherwise.
+ */
+ private static getCustomOrDefaultServerId(): string {
+ let customServerId: string = core.getInput(Utils.CUSTOM_SERVER_ID);
+ if (customServerId) {
+ return customServerId;
+ }
+ return Utils.getRunDefaultServerId();
+ }
+
+ /**
+ * Return a server ID that is unique for this workflow run based on the GitHub repository and run ID.
+ */
+ static getRunDefaultServerId(): string {
+ return [Utils.SETUP_JFROG_CLI_SERVER_ID_PREFIX, process.env.GITHUB_REPOSITORY, process.env.GITHUB_RUN_ID].join('-');
+ }
+
public static setCliEnv() {
Utils.exportVariableIfNotSet(
'JFROG_CLI_ENV_EXCLUDE',
@@ -424,8 +462,26 @@ export class Utils {
}
}
+ /**
+ * Removed configured JFrog CLI servers that are saved in the servers env var, and unset the env var.
+ */
public static async removeJFrogServers() {
- await Utils.runCli(['c', 'rm', '--quiet']);
+ for (const serverId of Utils.getConfiguredJFrogServers()) {
+ core.debug(`Removing server ID: '${serverId}'...`);
+ await Utils.runCli(['c', 'rm', serverId, '--quiet']);
+ }
+ core.exportVariable(Utils.JFROG_CLI_SERVER_IDS_ENV_VAR, '');
+ }
+
+ /**
+ * Split and return the configured JFrog CLI servers that are saved in the servers env var.
+ */
+ public static getConfiguredJFrogServers(): string[] {
+ const serversValue: string | undefined = process.env[Utils.JFROG_CLI_SERVER_IDS_ENV_VAR];
+ if (!serversValue) {
+ return [];
+ }
+ return serversValue.split(';');
}
public static getArchitecture() {
diff --git a/test/main.spec.ts b/test/main.spec.ts
index 98ae7c634..10a63b2e4 100644
--- a/test/main.spec.ts
+++ b/test/main.spec.ts
@@ -111,7 +111,7 @@ describe('Collect JFrog Credentials from env vars exceptions', () => {
});
});
-test('Get separate env config', async () => {
+function testConfigCommand(expectedServerId: string) {
// No url
let configCommand: string[] | undefined = Utils.getSeparateEnvConfigArgs({} as JfrogCredentials);
expect(configCommand).toBe(undefined);
@@ -121,14 +121,14 @@ test('Get separate env config', async () => {
// No credentials
configCommand = Utils.getSeparateEnvConfigArgs(jfrogCredentials);
- expect(configCommand).toStrictEqual([Utils.SETUP_JFROG_CLI_SERVER_ID, '--url', DEFAULT_CLI_URL, '--interactive=false', '--overwrite=true']);
+ expect(configCommand).toStrictEqual([expectedServerId, '--url', DEFAULT_CLI_URL, '--interactive=false', '--overwrite=true']);
// Basic authentication
jfrogCredentials.username = 'user';
jfrogCredentials.password = 'password';
configCommand = Utils.getSeparateEnvConfigArgs(jfrogCredentials);
expect(configCommand).toStrictEqual([
- Utils.SETUP_JFROG_CLI_SERVER_ID,
+ expectedServerId,
'--url',
DEFAULT_CLI_URL,
'--interactive=false',
@@ -145,7 +145,7 @@ test('Get separate env config', async () => {
jfrogCredentials.accessToken = 'accessToken';
configCommand = Utils.getSeparateEnvConfigArgs(jfrogCredentials);
expect(configCommand).toStrictEqual([
- Utils.SETUP_JFROG_CLI_SERVER_ID,
+ expectedServerId,
'--url',
DEFAULT_CLI_URL,
'--interactive=false',
@@ -153,6 +153,42 @@ test('Get separate env config', async () => {
'--access-token',
'accessToken',
]);
+}
+
+describe('JFrog CLI Configuration', () => {
+ beforeAll(() => {
+ process.env.GITHUB_REPOSITORY = 'owner/repo';
+ process.env.GITHUB_RUN_ID = '1';
+ });
+
+ afterAll(() => {
+ ['GITHUB_REPOSITORY', 'GITHUB_RUN_ID', Utils.JFROG_CLI_SERVER_IDS_ENV_VAR].forEach((envKey) => {
+ delete process.env[envKey];
+ });
+ });
+ const myCore: jest.Mocked = core as any;
+
+ test('Get separate env config', async () => {
+ myCore.exportVariable = jest.fn().mockImplementation((name: string, val: string) => {
+ process.env[name] = val;
+ });
+
+ // Before setting a custom server ID, expect the default server ID to be used.
+ testConfigCommand(Utils.getRunDefaultServerId());
+
+ // Expect the custom server ID to be used.
+ let customServerId: string = 'custom-server-id';
+ jest.spyOn(core, 'getInput').mockReturnValue(customServerId);
+ testConfigCommand(customServerId);
+
+ // Expect the servers env var to include both servers.
+ const servers: string[] = Utils.getConfiguredJFrogServers();
+ expect(servers).toStrictEqual([Utils.getRunDefaultServerId(), customServerId]);
+ });
+
+ test('Get default server ID', async () => {
+ expect(Utils.getRunDefaultServerId()).toStrictEqual('setup-jfrog-cli-server-owner/repo-1');
+ });
});
describe('JFrog CLI V1 URL Tests', () => {