Skip to content

Commit

Permalink
feat: download helm binary based on os and platform architecture (#128)
Browse files Browse the repository at this point in the history
Signed-off-by: Lenin Mehedy <[email protected]>
  • Loading branch information
leninmehedy authored Mar 7, 2024
1 parent d890974 commit e64e9a2
Show file tree
Hide file tree
Showing 21 changed files with 493 additions and 129 deletions.
4 changes: 4 additions & 0 deletions src/core/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,7 @@ export const KEY_TYPE_GOSSIP = 'gossip'
export const KEY_TYPE_TLS = 'tls'
export const SIGNING_KEY_PREFIX = 's'
export const AGREEMENT_KEY_PREFIX = 'a'

export const OS_WINDOWS = 'windows'
export const OS_DARWIN = 'darwin'
export const OS_LINUX = 'linux'
85 changes: 0 additions & 85 deletions src/core/dependency_manager.mjs

This file was deleted.

62 changes: 62 additions & 0 deletions src/core/dependency_managers/dependency_manager.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import os from 'os'
import { FullstackTestingError, MissingArgumentError } from '../errors.mjs'
import { ShellRunner } from '../shell_runner.mjs'

export class DependencyManager extends ShellRunner {
constructor (logger, depManagerMap) {
super(logger)
if (!depManagerMap) throw new MissingArgumentError('A map of dependency managers are required')
this.depManagerMap = depManagerMap
}

/**
* Check if the required dependency is installed or not
* @param dep is the name of the program
* @param shouldInstall Whether or not install the dependency if not installed
* @returns {Promise<boolean>}
*/
async checkDependency (dep, shouldInstall = true) {
this.logger.debug(`Checking for dependency: ${dep}`)

let status = false
const manager = this.depManagerMap.get(dep)
if (manager) {
status = await manager.checkVersion(shouldInstall)
}

if (!status) {
throw new FullstackTestingError(`Dependency '${dep}' is not found`)
}

this.logger.debug(`Dependency '${dep}' is found`)
return true
}

taskCheckDependencies (deps = []) {
const subTasks = []
deps.forEach(dep => {
subTasks.push({
title: `Check dependency: ${dep} [OS: ${os.platform()}, Release: ${os.release()}, Arch: ${os.arch()}]`,
task: () => this.checkDependency(dep)
})
})

return subTasks
}
}
126 changes: 126 additions & 0 deletions src/core/dependency_managers/helm_dependency_manager.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import fs from 'fs'
import os from 'os'
import path from 'path'
import * as util from 'util'
import { MissingArgumentError } from '../errors.mjs'
import * as helpers from '../helpers.mjs'
import { constants, Templates } from '../index.mjs'
import * as version from '../../../version.mjs'
import { ShellRunner } from '../shell_runner.mjs'

// constants required by HelmDependencyManager
const HELM_RELEASE_BASE_URL = 'https://get.helm.sh'
const HELM_ARTIFACT_TEMPLATE = 'helm-%s-%s-%s.%s'
const HELM_ARTIFACT_EXT = new Map()
.set(constants.OS_DARWIN, 'tar.gz')
.set(constants.OS_LINUX, 'tar.gz')
.set(constants.OS_WINDOWS, 'zip')

/**
* Helm dependency manager installs or uninstalls helm client at SOLO_HOME_DIR/bin directory
*/
export class HelmDependencyManager extends ShellRunner {
constructor (
downloader,
zippy,
logger,
installationDir = path.join(constants.SOLO_HOME_DIR, 'bin'),
osPlatform = os.platform(),
osArch = os.arch(),
helmVersion = version.HELM_VERSION
) {
super(logger)

if (!downloader) throw new MissingArgumentError('An instance of core/PackageDownloader is required')
if (!zippy) throw new MissingArgumentError('An instance of core/Zippy is required')
if (!installationDir) throw new MissingArgumentError('installation directory is required')

this.downloader = downloader
this.zippy = zippy
this.installationDir = installationDir
this.osPlatform = osPlatform
this.osArch = ['x64', 'x86-64'].includes(osArch) ? 'amd64' : osArch
this.helmVersion = helmVersion
this.helmPath = Templates.installationPath(constants.HELM, this.installationDir, this.osPlatform, this.osArch)

const fileExt = HELM_ARTIFACT_EXT.get(this.osPlatform)
this.artifactName = util.format(HELM_ARTIFACT_TEMPLATE, this.helmVersion, this.osPlatform, this.osArch, fileExt)
this.helmURL = `${HELM_RELEASE_BASE_URL}/${this.artifactName}`
}

getHelmPath () {
return this.helmPath
}

isInstalled () {
return fs.existsSync(this.helmPath)
}

/**
* Uninstall helm from solo bin folder
* @return {Promise<void>}
*/
async uninstall () {
if (this.isInstalled()) {
fs.rmSync(this.helmPath)
}
}

async install () {
const tmpDir = helpers.getTmpDir()
const extractedDir = path.join(tmpDir, 'extracted')
const tmpFile = path.join(tmpDir, this.artifactName)
let helmSrc = path.join(extractedDir, `${this.osPlatform}-${this.osArch}`, constants.HELM)

await this.downloader.fetchFile(this.helmURL, tmpFile)
if (this.osPlatform === constants.OS_WINDOWS) {
await this.zippy.unzip(tmpFile, extractedDir)
// append .exe for windows
helmSrc = path.join(extractedDir, `${this.osPlatform}-${this.osArch}`, `${constants.HELM}.exe`)
} else {
await this.zippy.untar(tmpFile, extractedDir)
}

if (!fs.existsSync(this.installationDir)) {
fs.mkdirSync(this.installationDir)
}

// install new helm
await this.uninstall()
this.helmPath = Templates.installationPath(constants.HELM, this.installationDir, this.osPlatform)
fs.cpSync(helmSrc, this.helmPath)

return this.isInstalled()
}

async checkVersion (shouldInstall = true) {
if (!this.isInstalled()) {
if (shouldInstall) {
await this.install()
} else {
return false
}
}

const output = await this.run(`${this.helmPath} version --short`)
const parts = output[0].split('+')
this.logger.debug(`Found ${constants.HELM}:${parts[0]}`)
return helpers.compareVersion(version.HELM_VERSION, parts[0]) >= 0
}
}
20 changes: 20 additions & 0 deletions src/core/dependency_managers/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { DependencyManager } from './dependency_manager.mjs'
import { HelmDependencyManager } from './helm_dependency_manager.mjs'

export { HelmDependencyManager, DependencyManager }
14 changes: 13 additions & 1 deletion src/core/helm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
* limitations under the License.
*
*/
import { constants } from './index.mjs'
import { ShellRunner } from './shell_runner.mjs'
import { Templates } from './templates.mjs'

const helmPath = Templates.installationPath(constants.HELM)

export class Helm extends ShellRunner {
/**
Expand All @@ -24,7 +28,7 @@ export class Helm extends ShellRunner {
* @returns {string}
*/
prepareCommand (action, ...args) {
let cmd = `helm ${action}`
let cmd = `${helmPath} ${action}`
args.forEach(arg => { cmd += ` ${arg}` })
return cmd
}
Expand Down Expand Up @@ -84,4 +88,12 @@ export class Helm extends ShellRunner {
async repo (subCommand, ...args) {
return this.run(this.prepareCommand('repo', subCommand, ...args))
}

/**
* Get helm version
* @return {Promise<Array>}
*/
async version (args = ['--short']) {
return this.run(this.prepareCommand('version', ...args))
}
}
5 changes: 5 additions & 0 deletions src/core/helpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*
*/
import fs from 'fs'
import os from 'os'
import path from 'path'
import { FullstackTestingError } from './errors.mjs'
import * as paths from 'path'
import { fileURLToPath } from 'url'
Expand Down Expand Up @@ -132,3 +134,6 @@ export function getRootImageRepository (releaseTag) {

return 'hashgraph/full-stack-testing/ubi8-init-java21'
}
export function getTmpDir () {
return fs.mkdtempSync(path.join(os.tmpdir(), 'solo-'))
}
2 changes: 0 additions & 2 deletions src/core/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { Zippy } from './zippy.mjs'
import { Templates } from './templates.mjs'
import { ChartManager } from './chart_manager.mjs'
import { ConfigManager } from './config_manager.mjs'
import { DependencyManager } from './dependency_manager.mjs'
import { KeyManager } from './key_manager.mjs'

// Expose components from the core module
Expand All @@ -39,6 +38,5 @@ export {
Templates,
ChartManager,
ConfigManager,
DependencyManager,
KeyManager
}
Loading

0 comments on commit e64e9a2

Please sign in to comment.