Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: do version check for dependencies #755

Merged
merged 4 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions solo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
30 changes: 16 additions & 14 deletions solo/src/core/dependency_manager.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.12.3')

constructor (logger) {
super(logger)

Expand All @@ -27,23 +32,21 @@ 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<boolean>}
*/
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('+')
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)
}

return false
}

/**
Expand All @@ -61,8 +64,7 @@ export class DependencyManager extends ShellRunner {
}

if (!status) {
this.logger.warn(`Dependency ${dep} is not found`)
throw new FullstackTestingError(`${dep} is not found`)
throw new FullstackTestingError(`${dep}:^${DependencyManager.depVersions.get(dep)} is not found`)
}

this.logger.debug(`Dependency ${dep} is found`)
Expand Down
48 changes: 39 additions & 9 deletions solo/src/core/helpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MAJOR>.<MINOR>.<PATCH>', found '${releaseTag}'`)
export function parseSemver (semver) {
if (!semver || semver[0] !== 'v') {
throw new FullstackTestingError(`invalid version. Expected 'v<MAJOR>.<MINOR>.<PATCH>', 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 {
Expand All @@ -89,13 +89,43 @@ 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) {
const v1 = parseSemver(target)
const v2 = parseSemver(candidate)

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
}

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'
}
Expand Down
2 changes: 1 addition & 1 deletion solo/src/core/platform_installer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
2 changes: 1 addition & 1 deletion solo/test/unit/core/dependency_manager.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
43 changes: 36 additions & 7 deletions solo/test/unit/core/helpers.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,51 @@ 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<MAJOR>.<MINOR>.<PATCH>\', found \'\'')],
['0.42.5', new FullstackTestingError('invalid release tag. Expected \'v<MAJOR>.<MINOR>.<PATCH>\', 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<MAJOR>.<MINOR>.<PATCH>\', found \'\'')],
['0.42.5', new FullstackTestingError('invalid version. Expected \'v<MAJOR>.<MINOR>.<PATCH>\', 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)
})

it('should succeed with a later version', () => {
expect(helpers.compareVersion('v3.12.3', 'v3.14.0')).toBe(1)
})
})
})
Loading