Skip to content

Commit 4725e4a

Browse files
SierraNLclaude
andcommitted
feat(JFrogCliV2): allow persisting and reusing JFrog CLI config across steps
Adds opt-in inputs so downstream pipeline steps (e.g. dotnet restore) can reuse the CLI config this task creates instead of each step configuring its own connection: - registerInPath: adds jf's directory to PATH for later steps - keepConfig: skips cleanup so the config survives after this task - configurationName: reuse an existing named config instead of creating one - enablePackageAlias / packageAliasTools: installs JFrog CLI's package-alias ("Ghost Frog") shims (CLI >= 2.93.0) so native build tool calls route through jf for the rest of the job jfrogPlatformConnection is now optional (required only when configurationName isn't set), and the resolved config name is exposed via the JFROG_CLI_CONFIG_NAME variable for teardown tasks to reference. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent bdd1501 commit 4725e4a

3 files changed

Lines changed: 104 additions & 12 deletions

File tree

jfrog-tasks-utils/utils.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ const minCustomCliVersion = '2.10.0';
144144
const minSupportedStdinSecretCliVersion = '2.36.0';
145145
const minSupportedServerIdEnvCliVersion = '2.37.0';
146146
const minSupportedOidcCliVersion = '2.75.0';
147+
const minSupportedPackageAliasCliVersion = '2.93.0';
147148
const pluginVersion = '2.14.1';
148149
const buildAgent = 'jfrog-azure-devops-extension';
149150

@@ -229,6 +230,7 @@ module.exports = {
229230
configureDefaultDistributionServer: configureDefaultDistributionServer,
230231
configureDefaultXrayServer: configureDefaultXrayServer,
231232
minCustomCliVersion: minCustomCliVersion,
233+
minSupportedPackageAliasCliVersion: minSupportedPackageAliasCliVersion,
232234
defaultJfrogCliVersion: defaultJfrogCliVersion,
233235
fallbackCliVersion: fallbackCliVersion,
234236
pipelineRequestedCliVersionEnv: pipelineRequestedCliVersionEnv,
@@ -512,7 +514,11 @@ function forwardProxyToEnv() {
512514
const converted = [];
513515
for (const h of hosts) {
514516
if (/[*+?[\]()^${}|\\][^.]/.test(h) || h.includes('*')) {
515-
tl.warning('Skipping Agent.ProxyBypassList entry "' + h + '": regex patterns with wildcards or special characters are not supported for NO_PROXY conversion. Use a plain hostname in your .proxybypass file instead.');
517+
tl.warning(
518+
'Skipping Agent.ProxyBypassList entry "' +
519+
h +
520+
'": regex patterns with wildcards or special characters are not supported for NO_PROXY conversion. Use a plain hostname in your .proxybypass file instead.',
521+
);
516522
continue;
517523
}
518524
converted.push(h.replace(/\\\./g, '.'));

tasks/JFrogCliV2/jfrogCliRun.js

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const tl = require('azure-pipelines-task-lib/task');
22
const utils = require('@jfrog/tasks-utils/utils.js');
33
const fs = require('fs');
4+
const path = require('path');
45

56
let serverId;
67
RunJfrogCliCommand(RunTaskCbk);
@@ -46,8 +47,27 @@ async function RunTaskCbk(cliPath) {
4647
process.env.JFROG_CLI_BUILD_NAME = tl.getVariable('Build.DefinitionName');
4748
process.env.JFROG_CLI_BUILD_NUMBER = tl.getVariable('Build.BuildNumber');
4849

49-
serverId = utils.assembleUniqueServerId('jfrog_cli_cmd');
50-
await utils.configureDefaultJfrogServer(serverId, cliPath, requiredWorkDir);
50+
let configurationName = tl.getInput('configurationName', false);
51+
if (configurationName) {
52+
// Reuse an existing JFrog CLI configuration instead of creating a new one.
53+
serverId = configurationName;
54+
} else {
55+
if (!tl.getInput('jfrogPlatformConnection', false)) {
56+
tl.setResult(tl.TaskResult.Failed, "Either 'JFrog Platform service connection' or 'Configuration Name' must be provided.");
57+
return;
58+
}
59+
serverId = utils.assembleUniqueServerId('jfrog_cli_cmd');
60+
await utils.configureDefaultJfrogServer(serverId, cliPath, requiredWorkDir);
61+
}
62+
tl.setVariable('JFROG_CLI_CONFIG_NAME', serverId, false, true);
63+
64+
if (tl.getBoolInput('registerInPath')) {
65+
tl.prependPath(path.dirname(cliPath));
66+
}
67+
68+
if (tl.getBoolInput('enablePackageAlias')) {
69+
setUpPackageAlias(cliPath, requiredWorkDir);
70+
}
5171

5272
let cliCommandsList = tl.getInput('command', true).split('\n');
5373
try {
@@ -58,7 +78,9 @@ async function RunTaskCbk(cliPath) {
5878
tl.TaskResult.Failed,
5979
"Unexpected JFrog CLI command prefix. Expecting the command to start with 'jf '. The command received is: " + cliCommand,
6080
);
61-
utils.taskDefaultCleanup(cliPath, requiredWorkDir, [serverId]);
81+
if (!tl.getBoolInput('keepConfig')) {
82+
utils.taskDefaultCleanup(cliPath, requiredWorkDir, [serverId]);
83+
}
6284
return;
6385
}
6486
// Remove 'jf' and space from the beginning of the command string, so we can use the CLI's path
@@ -77,7 +99,33 @@ async function RunTaskCbk(cliPath) {
7799
} catch (executionException) {
78100
tl.setResult(tl.TaskResult.Failed, executionException);
79101
} finally {
80-
utils.taskDefaultCleanup(cliPath, requiredWorkDir, [serverId]);
102+
if (!tl.getBoolInput('keepConfig')) {
103+
utils.taskDefaultCleanup(cliPath, requiredWorkDir, [serverId]);
104+
}
81105
}
82106
tl.setResult(tl.TaskResult.Succeeded, 'Command Succeeded.', cliPath);
83107
}
108+
109+
/**
110+
* Installs JFrog CLI's package-alias ('Ghost Frog') shims and registers them on PATH,
111+
* so native build tool invocations for the rest of the pipeline job route through 'jf'.
112+
*/
113+
function setUpPackageAlias(cliPath, requiredWorkDir) {
114+
let cliVersion = tl.getVariable(utils.taskSelectedCliVersionEnv);
115+
if (utils.compareVersions(cliVersion, utils.minSupportedPackageAliasCliVersion) < 0) {
116+
console.warn(
117+
`Package Alias is not supported by JFrog CLI ${cliVersion}. Minimum required version is ${utils.minSupportedPackageAliasCliVersion}. Skipping.`,
118+
);
119+
return;
120+
}
121+
let packageAliasCommand = utils.cliJoin(cliPath, 'package-alias', 'install');
122+
let packageAliasTools = tl.getInput('packageAliasTools', false);
123+
if (packageAliasTools) {
124+
packageAliasCommand = utils.cliJoin(packageAliasCommand, '--packages=' + utils.quote(packageAliasTools));
125+
}
126+
utils.executeCliCommand(packageAliasCommand, requiredWorkDir);
127+
128+
tl.prependPath(path.join(utils.getJfrogFolderPath(), 'package-alias', 'bin'));
129+
process.env.JFROG_CLI_GHOST_FROG = 'true';
130+
tl.setVariable('JFROG_CLI_GHOST_FROG', 'true');
131+
}

tasks/JFrogCliV2/task.json

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66
"author": "JFrog",
77
"category": "Tool",
88
"helpMarkDown": "[More Information](https://github.com/jfrog/jfrog-azure-devops-extension#Executing-JFrog-CLI-Commands)",
9-
"visibility": [
10-
"Build",
11-
"Release"
12-
],
9+
"visibility": ["Build", "Release"],
1310
"version": {
1411
"Major": "1",
1512
"Minor": "14",
@@ -24,8 +21,8 @@
2421
"type": "connectedService:jfrogPlatformService",
2522
"label": "JFrog Platform service connection",
2623
"defaultValue": "",
27-
"required": true,
28-
"helpMarkDown": "JFrog Platform service connection to use in the command."
24+
"required": false,
25+
"helpMarkDown": "JFrog Platform service connection to use in the command. Required unless 'Configuration Name' is provided to reuse an existing JFrog CLI configuration."
2926
},
3027
{
3128
"name": "useCustomVersion",
@@ -63,6 +60,47 @@
6360
"defaultValue": "",
6461
"required": false,
6562
"helpMarkDown": "The working directory where the command will run. When empty, the value of '$(System.DefaultWorkingDirectory)' is used."
63+
},
64+
{
65+
"name": "configurationName",
66+
"type": "string",
67+
"label": "Configuration Name",
68+
"defaultValue": "",
69+
"required": false,
70+
"helpMarkDown": "Reuse an existing JFrog CLI configuration by name instead of creating a new one from 'JFrog Platform service connection'. The named configuration must already exist in the JFrog CLI's config store (for example, created by a previous run of this task with 'Keep Configuration' enabled)."
71+
},
72+
{
73+
"name": "registerInPath",
74+
"type": "boolean",
75+
"label": "Add JFrog CLI to PATH",
76+
"defaultValue": "false",
77+
"required": false,
78+
"helpMarkDown": "Add the JFrog CLI executable's directory to the agent's PATH, so subsequent steps in the pipeline job can call 'jf' directly."
79+
},
80+
{
81+
"name": "keepConfig",
82+
"type": "boolean",
83+
"label": "Keep Configuration",
84+
"defaultValue": "false",
85+
"required": false,
86+
"helpMarkDown": "Skip removing the JFrog CLI configuration when this task completes, so it can be reused by later steps via 'Configuration Name'. Use the 'JFROG_CLI_CONFIG_NAME' variable set by this task to reference it, and run a teardown task with 'Configuration Name' set and 'Keep Configuration' disabled to remove it at the end of the pipeline."
87+
},
88+
{
89+
"name": "enablePackageAlias",
90+
"type": "boolean",
91+
"label": "Enable Package Alias (Ghost Frog)",
92+
"defaultValue": "false",
93+
"required": false,
94+
"helpMarkDown": "Install JFrog CLI's package-alias shims (requires JFrog CLI 2.93.0 or later), which transparently route native build tool commands (npm, mvn, gradle, dotnet, nuget, pip, go, docker, etc.) through 'jf' for the rest of the pipeline job. Requires 'Add JFrog CLI to PATH' to be enabled."
95+
},
96+
{
97+
"name": "packageAliasTools",
98+
"type": "string",
99+
"label": "Package Alias Tools",
100+
"defaultValue": "",
101+
"required": false,
102+
"visibleRule": "enablePackageAlias = true",
103+
"helpMarkDown": "Comma-separated list of tools to enable package aliasing for (e.g. 'npm,dotnet'). Leave empty to enable all supported tools."
66104
}
67105
],
68106
"execution": {
@@ -75,4 +113,4 @@
75113
"workingDirectory": "$(currentDirectory)"
76114
}
77115
}
78-
}
116+
}

0 commit comments

Comments
 (0)