From 2e3aafc5d0f0abe24dfefe753a3396b5aaa7c893 Mon Sep 17 00:00:00 2001 From: Lenin Mehedy Date: Tue, 13 Feb 2024 14:20:50 +1100 Subject: [PATCH 1/4] fix: do version check for dependencies Signed-off-by: Lenin Mehedy Signed-off-by: Jeromy Cannon --- solo/src/core/dependency_manager.mjs | 26 +++++++++-------- solo/src/core/helpers.mjs | 42 ++++++++++++++++++++++------ solo/src/core/platform_installer.mjs | 2 +- solo/test/unit/core/helpers.test.mjs | 39 +++++++++++++++++++++----- 4 files changed, 80 insertions(+), 29 deletions(-) diff --git a/solo/src/core/dependency_manager.mjs b/solo/src/core/dependency_manager.mjs index 834a0aabf..43019a936 100644 --- a/solo/src/core/dependency_manager.mjs +++ b/solo/src/core/dependency_manager.mjs @@ -15,10 +15,15 @@ * */ import { FullstackTestingError } from './errors.mjs' +import { constants } from './index.mjs' import * as core from './index.mjs' +import * as helpers from './helpers.mjs' import { ShellRunner } from './shell_runner.mjs' export class DependencyManager extends ShellRunner { + static depVersions = new Map() + .set(constants.HELM, 'v3.14.0') + constructor (logger) { super(logger) @@ -27,23 +32,20 @@ export class DependencyManager extends ShellRunner { .set(core.constants.HELM, () => this.checkHelm()) } - async runCheck (cmdString) { - try { - await this.run(cmdString) - } catch (e) { - this.logger.error(e) - return false - } - - return true - } - /** * Check if 'helm' CLI program is installed or not * @returns {Promise} */ async checkHelm () { - return this.runCheck(`${core.constants.HELM} version`) + try { + const output = await this.run(`${core.constants.HELM} version --short`) + const parts = output[0].split('+') + return helpers.compareVersion(DependencyManager.depVersions.get(constants.HELM), parts[0]) >= 0 + } catch (e) { + this.logger.error(`failed to check helm dependency:${e.message}`, e) + } + + return false } /** diff --git a/solo/src/core/helpers.mjs b/solo/src/core/helpers.mjs index 78c5ec480..497ec4af5 100644 --- a/solo/src/core/helpers.mjs +++ b/solo/src/core/helpers.mjs @@ -67,19 +67,19 @@ export function packageVersion () { } /** - * Split the release version into its major, minor and patch number - * @param releaseTag platform release version + * Split semantic version into its major, minor and patch number + * @param semver release version * @return {{patch: number, major: number, minor: number}} */ -export function parseReleaseTag (releaseTag) { - if (!releaseTag || releaseTag[0] !== 'v') { - throw new FullstackTestingError(`invalid release tag. Expected 'v..', found '${releaseTag}'`) +export function parseSemver (semver) { + if (!semver || semver[0] !== 'v') { + throw new FullstackTestingError(`invalid version. Expected 'v..', found '${semver}'`) } - releaseTag = releaseTag.replace('v', '') // remove first 'v' - const parts = releaseTag.split('-')[0].split('.') // just take the major.minor.patch part of the version + const version = semver.replace('v', '') // remove first 'v' + const parts = version.split('-')[0].split('.') // just take the major.minor.patch part of the version if (parts.length < 3) { - throw new FullstackTestingError('releaseTag must have the format MAJOR.MINOR.PATCH') + throw new FullstackTestingError(`version '${semver}' must have the format MAJOR.MINOR.PATCH`) } return { @@ -89,13 +89,37 @@ export function parseReleaseTag (releaseTag) { } } +/** + * Compare two version + * + * It returns 1, 0, -1 depending on the following three cases: + * - candidate > target: 1 + * - candidate == target: 0 + * - candidate < target: -1 + * + * @param target target version + * @param candidate candidate version + * @return {number} + */ +export function compareVersion (target, candidate) { + if (target === candidate) return 0 + + const v1 = parseSemver(target) + const v2 = parseSemver(candidate) + if (v2.major >= v1.major && v2.minor >= v1.minor && v2.patch >= v1.patch) { + return 1 + } + + return -1 +} + /** * Return the required root image for a platform version * @param releaseTag platform version * @return {string} */ export function getRootImageRepository (releaseTag) { - const releaseVersion = parseReleaseTag(releaseTag) + const releaseVersion = parseSemver(releaseTag) if (releaseVersion.minor < 46) { return 'hashgraph/full-stack-testing/ubi8-init-java17' } diff --git a/solo/src/core/platform_installer.mjs b/solo/src/core/platform_installer.mjs index faa0485ef..6460c5524 100644 --- a/solo/src/core/platform_installer.mjs +++ b/solo/src/core/platform_installer.mjs @@ -316,7 +316,7 @@ export class PlatformInstaller { const appName = constants.HEDERA_APP_NAME const nodeStakeAmount = constants.HEDERA_NODE_DEFAULT_STAKE_AMOUNT - const releaseVersion = helpers.parseReleaseTag(releaseTag) + const releaseVersion = helpers.parseSemver(releaseTag) try { const configLines = [] diff --git a/solo/test/unit/core/helpers.test.mjs b/solo/test/unit/core/helpers.test.mjs index 076b29a17..e6051585a 100644 --- a/solo/test/unit/core/helpers.test.mjs +++ b/solo/test/unit/core/helpers.test.mjs @@ -23,22 +23,47 @@ describe('Helpers', () => { ['v0.42.5', { major: 0, minor: 42, patch: 5 }], ['v0.42.5-alpha.0', { major: 0, minor: 42, patch: 5 }] ])('should parse release tag into major, minor and patch numbers', (input, expected) => { - const result = helpers.parseReleaseTag(input) + const result = helpers.parseSemver(input) expect(result).toEqual(expected) }) it.each([ - ['', new FullstackTestingError('invalid release tag. Expected \'v..\', found \'\'')], - ['0.42.5', new FullstackTestingError('invalid release tag. Expected \'v..\', found \'0.42.5\'')], - ['v0.42', new FullstackTestingError('releaseTag must have the format MAJOR.MINOR.PATCH')], - ['v0.42', new FullstackTestingError('releaseTag must have the format MAJOR.MINOR.PATCH')], - ['v0.NEW', new FullstackTestingError('releaseTag must have the format MAJOR.MINOR.PATCH')] + ['', new FullstackTestingError('invalid version. Expected \'v..\', found \'\'')], + ['0.42.5', new FullstackTestingError('invalid version. Expected \'v..\', found \'0.42.5\'')], + ['v0.42', new FullstackTestingError("version 'v0.42' must have the format MAJOR.MINOR.PATCH")], + ['v0.NEW', new FullstackTestingError("version 'v0.NEW' must have the format MAJOR.MINOR.PATCH")] ])('should throw error in parsing release tag', (input, expectedError) => { expect.assertions(1) try { - helpers.parseReleaseTag(input) // Error(new FullstackTestingError('releaseTag must have the format MAJOR.MINOR.PATCH')) + helpers.parseSemver(input) // Error(new FullstackTestingError('releaseTag must have the format MAJOR.MINOR.PATCH')) } catch (e) { expect(e).toEqual(expectedError) } }) + + describe('compareVersion', () => { + it('should succeed with same version', () => { + expect(helpers.compareVersion('v3.14.0', 'v3.14.0')).toBe(0) + }) + + it('should succeed with patch higher than target', () => { + expect(helpers.compareVersion('v3.14.0', 'v3.14.1')).toBe(1) + }) + + it('should succeed with minor version higher than target', () => { + expect(helpers.compareVersion('v3.14.0', 'v3.15.0')).toBe(1) + }) + + it('should succeed with major version higher than target', () => { + expect(helpers.compareVersion('v3.14.0', 'v4.14.0')).toBe(1) + }) + + it('should fail with major version lower than target', () => { + expect(helpers.compareVersion('v3.14.0', 'v2.14.0')).toBe(-1) + }) + + it('should fail with minor version lower than target', () => { + expect(helpers.compareVersion('v3.14.0', 'v3.11.0')).toBe(-1) + }) + }) }) From d5e86bcc3bf0159ba2e440cac51dc96e2d79e930 Mon Sep 17 00:00:00 2001 From: Lenin Mehedy Date: Tue, 13 Feb 2024 14:42:59 +1100 Subject: [PATCH 2/4] fix: lower the helm dependency version Signed-off-by: Lenin Mehedy Signed-off-by: Jeromy Cannon --- solo/README.md | 6 +++--- solo/src/core/dependency_manager.mjs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/solo/README.md b/solo/README.md index 22aeffcaa..395797f4a 100644 --- a/solo/README.md +++ b/solo/README.md @@ -4,9 +4,9 @@ An opinionated CLI tool to deploy and manage private Hedera Networks. ## Requirements -* Node(^18.19.0) (*lts/hydrogen*) -* Helm(^3.14.0) -* Kubectl(^1.28.2) +* Node(^v18.19.0) (*lts/hydrogen*) +* Helm(^v3.12.3) +* Kubectl(^v1.28.2) ## Setup diff --git a/solo/src/core/dependency_manager.mjs b/solo/src/core/dependency_manager.mjs index 43019a936..787a37a29 100644 --- a/solo/src/core/dependency_manager.mjs +++ b/solo/src/core/dependency_manager.mjs @@ -22,7 +22,7 @@ import { ShellRunner } from './shell_runner.mjs' export class DependencyManager extends ShellRunner { static depVersions = new Map() - .set(constants.HELM, 'v3.14.0') + .set(constants.HELM, 'v3.12.3') constructor (logger) { super(logger) From 8dee121ab819b5f163c80647a487a58c2a638f0e Mon Sep 17 00:00:00 2001 From: Lenin Mehedy Date: Tue, 13 Feb 2024 14:45:29 +1100 Subject: [PATCH 3/4] fix: update error message with dependency version Signed-off-by: Lenin Mehedy Signed-off-by: Jeromy Cannon --- solo/src/core/dependency_manager.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/solo/src/core/dependency_manager.mjs b/solo/src/core/dependency_manager.mjs index 787a37a29..4e387f367 100644 --- a/solo/src/core/dependency_manager.mjs +++ b/solo/src/core/dependency_manager.mjs @@ -63,8 +63,9 @@ export class DependencyManager extends ShellRunner { } if (!status) { - this.logger.warn(`Dependency ${dep} is not found`) - throw new FullstackTestingError(`${dep} is not found`) + + this.logger.warn(`Dependency ${dep}:${DependencyManager.depVersions.get(dep)} is not found`) + throw new FullstackTestingError(`${dep}:^${DependencyManager.depVersions.get(dep)} is not found`) } this.logger.debug(`Dependency ${dep} is found`) From bcda7bc19caf13956956fff809ae3ef7de862f7e Mon Sep 17 00:00:00 2001 From: Lenin Mehedy Date: Tue, 13 Feb 2024 14:53:33 +1100 Subject: [PATCH 4/4] fix: fix version comparison Signed-off-by: Lenin Mehedy Signed-off-by: Jeromy Cannon --- solo/src/core/dependency_manager.mjs | 3 +-- solo/src/core/helpers.mjs | 12 +++++++++--- solo/test/unit/core/dependency_manager.test.mjs | 2 +- solo/test/unit/core/helpers.test.mjs | 4 ++++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/solo/src/core/dependency_manager.mjs b/solo/src/core/dependency_manager.mjs index 4e387f367..325ac43e5 100644 --- a/solo/src/core/dependency_manager.mjs +++ b/solo/src/core/dependency_manager.mjs @@ -40,6 +40,7 @@ export class DependencyManager extends ShellRunner { try { const output = await this.run(`${core.constants.HELM} version --short`) const parts = output[0].split('+') + this.logger.debug(`Found dependency ${constants.HELM}:${parts[0]}`) return helpers.compareVersion(DependencyManager.depVersions.get(constants.HELM), parts[0]) >= 0 } catch (e) { this.logger.error(`failed to check helm dependency:${e.message}`, e) @@ -63,8 +64,6 @@ export class DependencyManager extends ShellRunner { } if (!status) { - - this.logger.warn(`Dependency ${dep}:${DependencyManager.depVersions.get(dep)} is not found`) throw new FullstackTestingError(`${dep}:^${DependencyManager.depVersions.get(dep)} is not found`) } diff --git a/solo/src/core/helpers.mjs b/solo/src/core/helpers.mjs index 497ec4af5..3921aaac3 100644 --- a/solo/src/core/helpers.mjs +++ b/solo/src/core/helpers.mjs @@ -102,11 +102,17 @@ export function parseSemver (semver) { * @return {number} */ export function compareVersion (target, candidate) { - if (target === candidate) return 0 - const v1 = parseSemver(target) const v2 = parseSemver(candidate) - if (v2.major >= v1.major && v2.minor >= v1.minor && v2.patch >= v1.patch) { + + if (v2.major === v1.major && v2.minor === v1.minor && v2.patch === v1.patch) { + return 0 + } + + if ((v2.major > v1.major) || + (v2.major >= v1.major && v2.minor > v1.minor) || + (v2.major >= v1.major && v2.minor >= v1.minor && v2.patch >= v1.patch) + ) { return 1 } diff --git a/solo/test/unit/core/dependency_manager.test.mjs b/solo/test/unit/core/dependency_manager.test.mjs index 62699d3d1..1c35f6a3c 100644 --- a/solo/test/unit/core/dependency_manager.test.mjs +++ b/solo/test/unit/core/dependency_manager.test.mjs @@ -31,7 +31,7 @@ describe('DependencyManager', () => { describe('checkDependency', () => { it('should fail during invalid dependency check', async () => { - await expect(depManager.checkDependency('INVALID_PROGRAM')).rejects.toThrowError(new FullstackTestingError('INVALID_PROGRAM is not found')) + await expect(depManager.checkDependency('INVALID_PROGRAM')).rejects.toThrowError(new FullstackTestingError('INVALID_PROGRAM:^undefined is not found')) }) it('should succeed during kubectl dependency check', async () => { await expect(depManager.checkDependency(constants.HELM)).resolves.toBe(true) diff --git a/solo/test/unit/core/helpers.test.mjs b/solo/test/unit/core/helpers.test.mjs index e6051585a..46c558a83 100644 --- a/solo/test/unit/core/helpers.test.mjs +++ b/solo/test/unit/core/helpers.test.mjs @@ -65,5 +65,9 @@ describe('Helpers', () => { it('should fail with minor version lower than target', () => { expect(helpers.compareVersion('v3.14.0', 'v3.11.0')).toBe(-1) }) + + it('should succeed with a later version', () => { + expect(helpers.compareVersion('v3.12.3', 'v3.14.0')).toBe(1) + }) }) })