Skip to content

Commit 193d084

Browse files
authored
Revert "Use latest cli version by default (#576)"
This reverts commit 104d3a7.
1 parent b788046 commit 193d084

7 files changed

Lines changed: 11 additions & 266 deletions

File tree

jfrog-tasks-utils/utils.js

Lines changed: 1 addition & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -10,117 +10,7 @@ const semver = require('semver');
1010
const fileName = getCliExecutableName();
1111
const jfrogCliToolName = 'jf';
1212
const cliPackage = 'jfrog-cli-' + getArchitecture();
13-
const fallbackCliVersion = '2.89.0';
14-
let defaultJfrogCliVersion = null;
15-
16-
/**
17-
* Executes an HTTP request with retry logic for 5xx errors.
18-
* @param {string} method - HTTP method (GET, POST, etc.)
19-
* @param {string} url - Request URL
20-
* @param {object} options - Request options (timeout, headers, etc.)
21-
* @param {number} maxRetries - Maximum number of retry attempts (default: 3)
22-
* @param {number} retryDelay - Delay between retries in ms (default: 1000)
23-
* @returns {object} Response object from syncRequest on success
24-
* @throws {Error} If all retries fail (network errors or 5xx responses)
25-
*/
26-
function syncRequestWithRetry(method, url, options = {}, maxRetries = 3, retryDelay = 1000) {
27-
let errorToThrow = null;
28-
let lastResponse = null;
29-
30-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
31-
try {
32-
const response = syncRequest(method, url, options);
33-
// Retry on 5xx server errors
34-
if (response?.statusCode >= 500 && response?.statusCode < 600) {
35-
console.warn(`Attempt ${attempt}/${maxRetries}: Server error ${response?.statusCode} for ${url}`);
36-
lastResponse = response;
37-
} else {
38-
return response;
39-
}
40-
} catch (err) {
41-
console.warn(`Attempt ${attempt}/${maxRetries}: Request failed for ${url} - ${err?.message}`);
42-
errorToThrow = err;
43-
}
44-
45-
if (attempt < maxRetries) {
46-
// Blocking delay before retry
47-
const waitUntil = Date.now() + retryDelay;
48-
while (Date.now() < waitUntil) { /* wait */ }
49-
}
50-
}
51-
52-
// All retries exhausted - throw error for both network failures and 5xx responses
53-
if (errorToThrow) {
54-
throw errorToThrow;
55-
}
56-
if (lastResponse) {
57-
throw new Error(`Server error ${lastResponse.statusCode} after ${maxRetries} retries for ${url}`);
58-
}
59-
}
60-
61-
/**
62-
* Checks if the CLI binary exists on releases.jfrog.io for the given version.
63-
* @param {string} version - The CLI version to check
64-
* @returns {boolean} True if binary exists, false otherwise
65-
*/
66-
function isCliBinaryAvailable(version) {
67-
const binaryUrl = `https://releases.jfrog.io/artifactory/jfrog-cli/v2-jf/${version}/${cliPackage}/${fileName}`;
68-
try {
69-
console.log('Verifying CLI binary availability at: ' + binaryUrl);
70-
const res = syncRequestWithRetry('HEAD', binaryUrl, { timeout: 5000 });
71-
return res?.statusCode === 200;
72-
} catch (err) {
73-
console.warn('Failed to verify CLI binary availability: ' + err?.message);
74-
return false;
75-
}
76-
}
77-
78-
/**
79-
* Fetches the latest available JFrog CLI version from GitHub releases with retry mechanism.
80-
* Validates that the binary is available on releases.jfrog.io before returning.
81-
* If the latest release binary isn't available, falls back to the previous release.
82-
* Called once during module initialization. Result is cached in defaultJfrogCliVersion.
83-
* @returns {string} The CLI version (e.g., '2.89.0') or fallback if fetch fails or no binary available
84-
*/
85-
function fetchLatestCliVersion() {
86-
try {
87-
console.log('Fetching JFrog CLI releases from https://api.github.com/repos/jfrog/jfrog-cli/releases');
88-
const res = syncRequestWithRetry('GET', 'https://api.github.com/repos/jfrog/jfrog-cli/releases?per_page=3', {
89-
headers: { 'User-Agent': 'jfrog-azure-devops-extension' },
90-
timeout: 5000,
91-
});
92-
if (res.statusCode === 200) {
93-
const releases = JSON.parse(res.getBody('utf8'));
94-
console.log('Fetched ' + releases?.length ?? 0 + ' JFrog CLI releases');
95-
96-
if (!releases || releases.length === 0) {
97-
console.warn('No JFrog CLI releases found, using fallback: ' + fallbackCliVersion);
98-
return fallbackCliVersion;
99-
}
100-
101-
// Try each release until we find one with an available binary
102-
for (const release of releases) {
103-
const version = release?.name;
104-
console.log('Checking CLI version: ' + version);
105-
106-
if (version && isCliBinaryAvailable(version)) {
107-
console.log('CLI binary verified available for version: ' + version);
108-
return version;
109-
}
110-
console.warn('CLI binary not yet available for version: ' + version);
111-
}
112-
console.warn('No CLI binaries available for last 3 releases, using fallback: ' + fallbackCliVersion);
113-
return fallbackCliVersion;
114-
}
115-
console.warn('Unexpected status code: ' + res.statusCode + ', using fallback version: ' + fallbackCliVersion);
116-
} catch (err) {
117-
console.warn('Failed to fetch JFrog CLI releases, due to error: ' + err?.message + ', using fallback: ' + fallbackCliVersion);
118-
}
119-
return fallbackCliVersion;
120-
}
121-
122-
// Fetch and cache the CLI version during module initialization
123-
defaultJfrogCliVersion = fetchLatestCliVersion();
13+
const defaultJfrogCliVersion = '2.85.0';
12414

12515
/**
12616
* Safely constructs the JFrog tools directory path, handling potential issues with Agent.ToolsDirectory
@@ -179,9 +69,6 @@ const jfrogCliConfigUseCommand = 'c use';
17969
let runTaskCbk = null;
18070

18171
module.exports = {
182-
syncRequestWithRetry: syncRequestWithRetry,
183-
isCliBinaryAvailable: isCliBinaryAvailable,
184-
fetchLatestCliVersion: fetchLatestCliVersion,
18572
executeCliTask: executeCliTask,
18673
executeCliCommand: executeCliCommand,
18774
downloadCli: downloadCli,
@@ -225,7 +112,6 @@ module.exports = {
225112
configureDefaultXrayServer: configureDefaultXrayServer,
226113
minCustomCliVersion: minCustomCliVersion,
227114
defaultJfrogCliVersion: defaultJfrogCliVersion,
228-
fallbackCliVersion: fallbackCliVersion,
229115
pipelineRequestedCliVersionEnv: pipelineRequestedCliVersionEnv,
230116
taskSelectedCliVersionEnv: taskSelectedCliVersionEnv,
231117
extractorsRemoteEnv: extractorsRemoteEnv,

tasks/JFrogConan/conanUtils.js

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
const tl = require('azure-pipelines-task-lib/task');
22
const { v4: uuid } = require('uuid');
3-
const os = require('os');
4-
const tmpdir = os.tmpdir;
5-
const EOL = os.EOL;
3+
const tmpdir = require('os').tmpdir;
4+
const EOL = require('os').EOL;
65
const fs = require('fs-extra');
76
const join = require('path').join;
87
const createHash = require('crypto').createHash;
@@ -15,23 +14,7 @@ const BUILD_INFO_BUILD_NAME = 'name';
1514
const BUILD_INFO_BUILD_NUMBER = 'number';
1615
const BUILD_INFO_BUILD_STARTED = 'started';
1716
const BUILD_INFO_FILE_NAME = 'generatedBuildInfo';
18-
19-
/**
20-
* Get the JFrog CLI build temp directory path segments.
21-
* On Windows: jfrog-<COMPUTERNAME>/<USERNAME>
22-
* On macOS/Linux: jfrog-<USERNAME>
23-
* @returns {string[]} Array of path segments to join
24-
*/
25-
function getJfrogBuildDirSegments() {
26-
const username = os.userInfo().username;
27-
if (process.platform === 'win32') {
28-
// On Windows, the CLI uses: jfrog-<COMPUTERNAME>/<USERNAME>/builds
29-
const hostname = os.hostname();
30-
return [`jfrog-${hostname}`, username];
31-
}
32-
// On macOS/Linux: jfrog-<USERNAME>/builds
33-
return [`jfrog-${username}`];
34-
}
17+
const BUILD_TEMP_PATH = 'jfrog/builds';
3518

3619
/**
3720
* Execute Artifactory Conan Task
@@ -458,10 +441,7 @@ function readTimestampFromBuildPartialDetailsFile(buildDetailsFile) {
458441
function getCliPartialsBuildDir(buildName, buildNumber) {
459442
const buildId = buildName + '_' + buildNumber + '_' + '';
460443
const hexId = createHash('sha256').update(buildId).digest('hex');
461-
// Use separate path segments to ensure correct path separators on all platforms
462-
// On Windows: <tmpdir>/jfrog-<COMPUTERNAME>/<USERNAME>/builds/<hash>
463-
// On macOS/Linux: <tmpdir>/jfrog-<USERNAME>/builds/<hash>
464-
return join(tmpdir(), ...getJfrogBuildDirSegments(), 'builds', hexId);
444+
return join(tmpdir(), BUILD_TEMP_PATH, hexId);
465445
}
466446

467447
module.exports = {

tests/resources/conanTask/files/conan-min/conanfile.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ class ConanminConan(ConanFile):
1414
exports_sources = "src/*"
1515

1616
def requirements(self):
17-
# Using fmt - a header-only library that avoids CMake compatibility issues
18-
self.requires("fmt/[>=8.0.1]")
17+
self.requires("boost/[>=1.77]")
1918

2019
def build(self):
2120
cmake = CMake(self)

tests/resources/maven/resources/pom.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
<plugin>
4545
<groupId>org.apache.maven.plugins</groupId>
4646
<artifactId>maven-jar-plugin</artifactId>
47-
<version>3.4.1</version>
4847
<executions>
4948
<execution>
5049
<goals>

tests/testUtils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as mockRun from 'azure-pipelines-task-lib/mock-run';
22
import * as tl from 'azure-pipelines-task-lib/task';
33
import { join, basename } from 'path';
44
import * as fs from 'fs-extra';
5-
import { rimrafSync } from 'rimraf';
5+
import rimraf from 'rimraf';
66
import * as syncRequest from 'sync-request';
77
import * as assert from 'assert';
88
import NullWritable from 'null-writable';
@@ -114,7 +114,7 @@ export function runTaskForService(testMain: string, variables: any, inputs: any)
114114

115115
export function recreateTestDataDir(): void {
116116
if (fs.existsSync(testDataDir)) {
117-
rimrafSync(testDataDir);
117+
rimraf.sync(testDataDir);
118118
}
119119
fs.mkdirSync(testDataDir);
120120
}
@@ -145,7 +145,7 @@ export function cleanToolCache(): void {
145145
export function cleanUpAllTests(): void {
146146
if (fs.existsSync(testDataDir)) {
147147
try {
148-
rimrafSync(testDataDir);
148+
rimraf.sync(testDataDir);
149149
} catch (err) {
150150
console.warn('Tests cleanup issue: ' + err);
151151
}

tests/tests.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('JFrog Artifactory Extension Tests', (): void => {
5454
repoKeys.repo1 +
5555
'/' +
5656
' --url=' +
57-
jfrogUtils.quote(process.env.ADO_JFROG_PLATFORM_URL ?? '') +
57+
jfrogUtils.quote(process.env.ADO_JFROG_PLATFORM_URL + 'artifactory') +
5858
' --user=' +
5959
jfrogUtils.quote(process.env.ADO_JFROG_PLATFORM_USERNAME ?? '') +
6060
' --password=' +
@@ -1279,18 +1279,6 @@ function testInitCliPartialsBuildDir(): void {
12791279
runBuildCommand('bc', testsBuildName, testBuildNumber);
12801280
}
12811281

1282-
function getJfrogCliPath(): string {
1283-
const versions: string[] = toolLib.findLocalToolVersions('jf');
1284-
if (versions.length === 0) {
1285-
// Fallback to PATH-based execution if CLI is not in tool cache
1286-
return 'jf';
1287-
}
1288-
const cliDir: string = toolLib.findLocalTool('jf', versions[0]);
1289-
const executableName: string = process.platform.startsWith('win') ? 'jf.exe' : 'jf';
1290-
return join(cliDir, executableName);
1291-
}
1292-
12931282
function runBuildCommand(command: string, buildName: string, buildNumber: string): void {
1294-
const cliPath: string = getJfrogCliPath();
1295-
jfrogUtils.executeCliCommand(cliPath + ' rt ' + command + ' "' + buildName + '" ' + buildNumber, TestUtils.testDataDir);
1283+
jfrogUtils.executeCliCommand('jf rt ' + command + ' "' + buildName + '" ' + buildNumber, TestUtils.testDataDir);
12961284
}

tests/utilsTests.ts

Lines changed: 0 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,6 @@ import * as assert from 'assert';
44
// Use require to get the actual module with latest exports
55
import * as jfrogUtils from '@jfrog/tasks-utils';
66

7-
// Type declarations for the new exported functions
8-
declare module '@jfrog/tasks-utils' {
9-
export function syncRequestWithRetry(
10-
method: string,
11-
url: string,
12-
options?: object,
13-
maxRetries?: number,
14-
retryDelay?: number,
15-
): { statusCode: number; getBody(encoding: string): string };
16-
export function isCliBinaryAvailable(version: string): boolean;
17-
export function fetchLatestCliVersion(): string;
18-
export const defaultJfrogCliVersion: string;
19-
export const fallbackCliVersion: string;
20-
}
21-
227
/**
238
* Simulates the platformUrl resolution logic from utils.js lines 441-449:
249
*
@@ -149,96 +134,4 @@ describe('Utils Unit Tests', (): void => {
149134
);
150135
});
151136
});
152-
153-
describe('syncRequestWithRetry', (): void => {
154-
it('should return response on successful request (2xx)', (): void => {
155-
const response: { statusCode: number } = jfrogUtils.syncRequestWithRetry('GET', 'https://httpstat.us/200', { timeout: 5000 });
156-
assert.strictEqual(response.statusCode, 200);
157-
});
158-
159-
it('should return response on 4xx client error without retry', (): void => {
160-
const response: { statusCode: number } = jfrogUtils.syncRequestWithRetry('GET', 'https://httpstat.us/404', { timeout: 5000 });
161-
assert.strictEqual(response.statusCode, 404);
162-
});
163-
164-
it('should throw error after retries exhausted on 5xx server error', (): void => {
165-
assert.throws(
166-
(): void => {
167-
jfrogUtils.syncRequestWithRetry('GET', 'https://httpstat.us/503', { timeout: 5000 }, 2, 100);
168-
},
169-
/Server error 503 after 2 retries/,
170-
);
171-
});
172-
173-
it('should throw error on network failure after retries', (): void => {
174-
assert.throws(
175-
(): void => {
176-
jfrogUtils.syncRequestWithRetry('GET', 'https://invalid.domain.that.does.not.exist.example', { timeout: 1000 }, 2, 100);
177-
},
178-
Error,
179-
);
180-
});
181-
});
182-
183-
describe('isCliBinaryAvailable', (): void => {
184-
it('should return true for a known valid CLI version', (): void => {
185-
// Use a known stable version that should always be available
186-
const result: boolean = jfrogUtils.isCliBinaryAvailable('2.50.0');
187-
assert.strictEqual(result, true);
188-
});
189-
190-
it('should return false for a non-existent CLI version', (): void => {
191-
const result: boolean = jfrogUtils.isCliBinaryAvailable('0.0.1');
192-
assert.strictEqual(result, false);
193-
});
194-
195-
it('should return false for an invalid version format', (): void => {
196-
const result: boolean = jfrogUtils.isCliBinaryAvailable('invalid-version');
197-
assert.strictEqual(result, false);
198-
});
199-
});
200-
201-
describe('fetchLatestCliVersion', (): void => {
202-
it('should return a valid semver version string', (): void => {
203-
const version: string = jfrogUtils.fetchLatestCliVersion();
204-
// Version should match semver pattern (e.g., "2.89.0")
205-
assert.match(version, /^\d+\.\d+\.\d+$/);
206-
});
207-
208-
it('should return version >= 2.50.0 (reasonable minimum)', (): void => {
209-
const version: string = jfrogUtils.fetchLatestCliVersion();
210-
const [major, minor]: number[] = version.split('.').map(Number);
211-
assert.ok(major >= 2, `Major version ${major} should be >= 2`);
212-
if (major === 2) {
213-
assert.ok(minor >= 50, `Minor version ${minor} should be >= 50 for major version 2`);
214-
}
215-
});
216-
});
217-
218-
describe('defaultJfrogCliVersion', (): void => {
219-
it('should be initialized with a valid version', (): void => {
220-
assert.ok(jfrogUtils.defaultJfrogCliVersion, 'defaultJfrogCliVersion should be defined');
221-
assert.match(jfrogUtils.defaultJfrogCliVersion, /^\d+\.\d+\.\d+$/);
222-
});
223-
224-
it('should match the result of fetchLatestCliVersion', (): void => {
225-
// Since defaultJfrogCliVersion is set at module load, it should match fetchLatestCliVersion
226-
// unless there was a failure (in which case both would use fallback)
227-
const fetchedVersion: string = jfrogUtils.fetchLatestCliVersion();
228-
assert.strictEqual(jfrogUtils.defaultJfrogCliVersion, fetchedVersion);
229-
});
230-
});
231-
232-
describe('fallbackCliVersion', (): void => {
233-
it('should be a valid semver version string', (): void => {
234-
assert.ok(jfrogUtils.fallbackCliVersion, 'fallbackCliVersion should be defined');
235-
assert.match(jfrogUtils.fallbackCliVersion, /^\d+\.\d+\.\d+$/);
236-
});
237-
238-
it('should have an available binary on releases.jfrog.io', (): void => {
239-
// The fallback version should always have its binary available
240-
const result: boolean = jfrogUtils.isCliBinaryAvailable(jfrogUtils.fallbackCliVersion);
241-
assert.strictEqual(result, true, `Fallback version ${jfrogUtils.fallbackCliVersion} should have available binary`);
242-
});
243-
});
244137
});

0 commit comments

Comments
 (0)