diff --git a/src/alizer/alizerWrapper.ts b/src/alizer/alizerWrapper.ts new file mode 100644 index 000000000..a20772a10 --- /dev/null +++ b/src/alizer/alizerWrapper.ts @@ -0,0 +1,35 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +import { CommandText } from '../base/command'; +import { CliChannel } from '../cli'; +import { Uri } from 'vscode'; +import { CliExitData } from '../util/childProcessUtil'; +import { AlizerAnalyzeResponse } from './types'; + +/** + * A wrapper around the `alizer` CLI tool. + * + * This class is a stateless singleton. + * This makes it easier to stub its methods than if it were a bunch of directly exported functions. + */ +export class Alizer { + private static INSTANCE = new Alizer(); + + static get Instance() { + return Alizer.INSTANCE; + } + + public async alizerAnalyze(currentFolderPath: Uri): Promise { + const cliData: CliExitData = await CliChannel.getInstance().executeTool( + new CommandText('alizer', `devfile ${currentFolderPath.fsPath}`), undefined, false + ); + if (cliData.error || cliData.stderr.length > 0) { + return undefined; + } + const parse = JSON.parse(cliData.stdout) as AlizerAnalyzeResponse[]; + return parse[0]; + } +} diff --git a/src/alizer/types.ts b/src/alizer/types.ts new file mode 100644 index 000000000..2002cfd37 --- /dev/null +++ b/src/alizer/types.ts @@ -0,0 +1,18 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ + +export interface Version { + SchemaVersion: number; + Default: boolean; + Version: string; +} + +export interface AlizerAnalyzeResponse { + Name: string; + Language: string; + ProjectType: string; + Tags: string[]; + Versions: Version[]; +} diff --git a/src/downloadUtil/downloadBinaries.ts b/src/downloadUtil/downloadBinaries.ts index ba602cec9..a6d738993 100644 --- a/src/downloadUtil/downloadBinaries.ts +++ b/src/downloadUtil/downloadBinaries.ts @@ -57,10 +57,12 @@ export async function downloadFileAndCreateSha256( console.log(`${current}%`), ); const currentSHA256 = await hasha.fromFile(currentFile, { algorithm: 'sha256' }); - if (currentSHA256 === platform.sha256sum) { - console.log(`Download of ${currentFile} has finished and SHA256 is correct`); - } else { - throw Error(`${currentFile} is downloaded and SHA256 is not correct`); + if (platform.sha256sum) { + if (currentSHA256 === platform.sha256sum) { + console.log(`Download of ${currentFile} has finished and SHA256 is correct`); + } else { + throw Error(`${currentFile} is downloaded and SHA256 is not correct`); + } } if (process.env.REMOTE_CONTAINERS === 'true') { await extractTool(toolsFolder, platform, currentFile); diff --git a/src/odo/odoWrapper.ts b/src/odo/odoWrapper.ts index ace6727e7..52cd39380 100644 --- a/src/odo/odoWrapper.ts +++ b/src/odo/odoWrapper.ts @@ -11,7 +11,7 @@ import { ToolsConfig } from '../tools'; import { ChildProcessUtil, CliExitData } from '../util/childProcessUtil'; import { VsCommandError } from '../vscommand'; import { Command } from './command'; -import { AnalyzeResponse, ComponentTypeAdapter, ComponentTypeDescription, DevfileComponentType, Registry } from './componentType'; +import { ComponentTypeAdapter, ComponentTypeDescription, DevfileComponentType, Registry } from './componentType'; import { ComponentDescription, StarterProject } from './componentTypeDescription'; import { BindableService } from './odoTypes'; @@ -194,15 +194,6 @@ export class Odo { ); } - public async analyze(currentFolderPath: string): Promise { - const cliData: CliExitData = await this.execute( - new CommandText('odo', 'analyze -o json'), - currentFolderPath, - ); - const parse = JSON.parse(cliData.stdout) as AnalyzeResponse[]; - return parse; - } - /** * Returns a list of starter projects for the given Devfile * diff --git a/src/tools.json b/src/tools.json index 27f1e83c6..37eca609f 100644 --- a/src/tools.json +++ b/src/tools.json @@ -173,6 +173,43 @@ } } }, + "alizer": { + "description": "Alizer CLI tool", + "vendor": "The Devfile Project", + "name": "alizer", + "version": "1.6.0", + "versionRange": "^1.6.0", + "versionRangeLabel": "version >= 1.6.0", + "dlFileName": "alizer", + "filePrefix": "", + "platform": { + "win32": { + "url": "https://github.com/devfile/alizer/releases/download/v1.6.0/alizer-v1.6.0-windows-amd64.exe", + "dlFileName": "alizer_windows_amd64.exe", + "cmdFileName": "alizer.exe" + }, + "darwin": { + "url": "https://github.com/devfile/alizer/releases/download/v1.6.0/alizer-v1.6.0-darwin-amd64", + "dlFileName": "alizer_darwin_amd64", + "cmdFileName": "alizer" + }, + "darwin-arm64": { + "url": "https://github.com/devfile/alizer/releases/download/v1.6.0/alizer-v1.6.0-darwin-arm64", + "dlFileName": "alizer_darwin_arm64", + "cmdFileName": "alizer" + }, + "linux": { + "url": "https://github.com/devfile/alizer/releases/download/v1.6.0/alizer-v1.6.0-linux-amd64", + "dlFileName": "alizer_linux_amd64", + "cmdFileName": "alizer" + }, + "linux-arm64": { + "url": "https://github.com/devfile/alizer/releases/download/v1.6.0/alizer-v1.6.0-linux-arm64", + "dlFileName": "alizer_linux_arm64", + "cmdFileName": "alizer" + } + } + }, "crc": { "description": "crc openshift container", "vendor": "Red Hat, Inc.", diff --git a/src/tools.ts b/src/tools.ts index 37f0cb9b5..9e87e6b3f 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -92,11 +92,12 @@ export class ToolsConfig { public static async selectTool(locations: string[], versionRange: string): Promise { let result: string; const funcValue = Platform.OS !== 'win32' ? 'func' : 'func.exe'; + const alizerValue = Platform.OS !== 'win32' ? 'alizer' : 'alizer.exe'; // Array.find cannot be used here because of async calls for (const location of locations) { // FIXME: see https://github.com/knative/func/issues/2067 // eslint-disable-next-line no-await-in-loop - if (location && (location.endsWith(funcValue) || semver.satisfies(await ToolsConfig.getVersion(location), versionRange))) { + if (location && (location.endsWith(funcValue) || location.endsWith(alizerValue) || semver.satisfies(await ToolsConfig.getVersion(location), versionRange))) { result = location; break; } diff --git a/src/webview/create-component/createComponentLoader.ts b/src/webview/create-component/createComponentLoader.ts index 04e22a071..a775d735c 100644 --- a/src/webview/create-component/createComponentLoader.ts +++ b/src/webview/create-component/createComponentLoader.ts @@ -11,7 +11,7 @@ import * as tmp from 'tmp'; import { promisify } from 'util'; import * as vscode from 'vscode'; import { extensions, Uri, ViewColumn, WebviewPanel, window } from 'vscode'; -import { AnalyzeResponse, ComponentTypeDescription } from '../../odo/componentType'; +import { ComponentTypeDescription } from '../../odo/componentType'; import { Endpoint } from '../../odo/componentTypeDescription'; import { Odo } from '../../odo/odoWrapper'; import { ComponentTypesView } from '../../registriesView'; @@ -30,6 +30,8 @@ import { } from '../common-ext/createComponentHelpers'; import { loadWebviewHtml, validateGitURL } from '../common-ext/utils'; import { Devfile, DevfileRegistry, TemplateProjectIdentifier } from '../common/devfile'; +import { AlizerAnalyzeResponse, Version } from '../../alizer/types'; +import { Alizer } from '../../alizer/alizerWrapper'; interface CloneProcess { status: boolean; @@ -518,14 +520,14 @@ export default class CreateComponentLoader { } static async getRecommendedDevfile(uri: Uri): Promise { - let analyzeRes: AnalyzeResponse[] = []; + let analyzeRes: AlizerAnalyzeResponse; let compDescriptions: ComponentTypeDescription[] = []; try { void CreateComponentLoader.panel.webview.postMessage({ action: 'getRecommendedDevfileStart' }); - analyzeRes = await Odo.Instance.analyze(uri.fsPath); - compDescriptions = await getCompDescription(analyzeRes); + const alizerAnalyzeRes: AlizerAnalyzeResponse = await Alizer.Instance.alizerAnalyze(uri); + compDescriptions = await getCompDescription(alizerAnalyzeRes); } catch (error) { if (error.message.toLowerCase().indexOf('failed to parse the devfile') !== -1) { const actions: Array = ['Yes', 'Cancel']; @@ -539,7 +541,7 @@ export default class CreateComponentLoader { const file = await fs.readFile(devFileV1Path, 'utf8'); const devfileV1 = JSYAML.load(file.toString()) as DevfileV1; await fs.unlink(devFileV1Path); - analyzeRes = await Odo.Instance.analyze(uri.fsPath); + analyzeRes = await Alizer.Instance.alizerAnalyze(uri); compDescriptions = await getCompDescription(analyzeRes); const endPoints = getEndPoints(compDescriptions[0]); const devfileV2 = DevfileConverter.getInstance().devfileV1toDevfileV2( @@ -587,18 +589,25 @@ export default class CreateComponentLoader { } } -async function getCompDescription(devfiles: AnalyzeResponse[]): Promise { +async function getCompDescription(devfile: AlizerAnalyzeResponse): Promise { const compDescriptions = await ComponentTypesView.instance.getCompDescriptions(); - if (devfiles.length === 0) { + if (!devfile) { return Array.from(compDescriptions); } - return Array.from(compDescriptions).filter(({ name, version, registry }) => - devfiles.some( - (res) => - res.devfile === name && - res.devfileVersion === version && - res.devfileRegistry === registry.name, - ), + return Array.from(compDescriptions).filter((compDesc) => { + if (devfile.Name === compDesc.name && getVersion(devfile.Versions, compDesc.version)) { + return compDesc; + } + } + ); +} + +function getVersion(devfileVersions: Version[], matchedVersion: string): Version { + return devfileVersions.find((devfileVersion) => { + if (devfileVersion.Version === matchedVersion) { + return devfileVersion; + } + } ); } diff --git a/test/integration/alizerWrapper.test.ts b/test/integration/alizerWrapper.test.ts new file mode 100644 index 000000000..900acf9e4 --- /dev/null +++ b/test/integration/alizerWrapper.test.ts @@ -0,0 +1,134 @@ +/*----------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat, Inc. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + *-----------------------------------------------------------------------------------------------*/ +import { fail } from 'assert'; +import { expect } from 'chai'; +import * as fs from 'fs/promises'; +import { suite, suiteSetup } from 'mocha'; +import * as tmp from 'tmp'; +import * as path from 'path'; +import { promisify } from 'util'; +import { Uri, workspace } from 'vscode'; +import { Oc } from '../../src/oc/ocWrapper'; +import { Odo } from '../../src/odo/odoWrapper'; +import { LoginUtil } from '../../src/util/loginUtil'; +import { Alizer } from '../../src/alizer/alizerWrapper'; + +suite('./alizer/alizerWrapper.ts', function () { + const isOpenShift: boolean = Boolean(parseInt(process.env.IS_OPENSHIFT, 10)) || false; + const clusterUrl = process.env.CLUSTER_URL || 'https://api.crc.testing:6443'; + const username = process.env.CLUSTER_USER || 'developer'; + const password = process.env.CLUSTER_PASSWORD || 'developer'; + + suiteSetup(async function () { + if (isOpenShift) { + try { + await LoginUtil.Instance.logout(); + } catch { + // do nothing + } + await Oc.Instance.loginWithUsernamePassword(clusterUrl, username, password); + } + }); + + suiteTeardown(async function () { + // ensure projects are cleaned up + try { + await Oc.Instance.deleteProject('my-test-project-1'); + } catch { + // do nothing + } + try { + await Oc.Instance.deleteProject('my-test-project-2'); + } catch { + // do nothing + } + + if (isOpenShift) { + await LoginUtil.Instance.logout(); + } + }); + + suite('analyse folder', function () { + const project1 = 'my-test-project-1'; + + let tmpFolder1: Uri; + let tmpFolder2: Uri; + + suiteSetup(async function () { + await Oc.Instance.createProject(project1); + tmpFolder1 = Uri.parse(await promisify(tmp.dir)()); + tmpFolder2 = Uri.parse(await promisify(tmp.dir)()); + await Odo.Instance.createComponentFromFolder( + 'nodejs', + undefined, + 'component1', + tmpFolder1, + 'nodejs-starter', + ); + await Odo.Instance.createComponentFromFolder( + 'go', + undefined, + 'component2', + tmpFolder2, + 'go-starter', + ); + }); + + suiteTeardown(async function () { + if (isOpenShift) { + await Oc.Instance.loginWithUsernamePassword(clusterUrl, username, password); + } + const newWorkspaceFolders = workspace.workspaceFolders.filter((workspaceFolder) => { + const fsPath = workspaceFolder.uri.fsPath; + return (fsPath !== tmpFolder1.fsPath && fsPath !== tmpFolder2.fsPath); + }); + workspace.updateWorkspaceFolders(0, workspace.workspaceFolders.length, ...newWorkspaceFolders); + await fs.rm(tmpFolder1.fsPath, { force: true, recursive: true }); + await fs.rm(tmpFolder2.fsPath, { force: true, recursive: true }); + await Oc.Instance.deleteProject(project1); + }); + + test('analyze()', async function () { + const analysis1 = await Alizer.Instance.alizerAnalyze(tmpFolder1); + expect(analysis1).to.exist; + expect(analysis1.Name).to.equal('nodejs'); + const analysis2 = await Alizer.Instance.alizerAnalyze(tmpFolder2); + expect(analysis2).to.exist; + expect(analysis2.Name).to.equal('go'); + }); + }); + + suite('create component', function() { + + const COMPONENT_TYPE = 'dotnet50'; + + let tmpFolder: string; + + suiteSetup(async function() { + tmpFolder = await promisify(tmp.dir)(); + }); + + suiteTeardown(async function() { + await fs.rm(tmpFolder, { recursive: true, force: true }); + }); + + test('createComponentFromTemplateProject()', async function() { + await Odo.Instance.createComponentFromTemplateProject(tmpFolder, 'my-component', 8080, COMPONENT_TYPE, 'DefaultDevfileRegistry', 'dotnet50-example'); + try { + await fs.access(path.join(tmpFolder, 'devfile.yaml')); + } catch { + fail('Expected devfile to be created'); + } + }); + + test('analyze()', async function() { + const analysis = await Alizer.Instance.alizerAnalyze(Uri.file(tmpFolder)); + expect(analysis.Name).to.equal(COMPONENT_TYPE); + }); + + }); + + test('deleteComponentConfiguration'); +}); diff --git a/test/integration/odoWrapper.test.ts b/test/integration/odoWrapper.test.ts index 4da4d21a8..2e28abeca 100644 --- a/test/integration/odoWrapper.test.ts +++ b/test/integration/odoWrapper.test.ts @@ -98,15 +98,6 @@ suite('./odo/odoWrapper.ts', function () { expect(componentDescription2).to.exist; expect(componentDescription2.managedBy).to.equal('odo'); }); - - test('analyze()', async function () { - const analysis1 = await Odo.Instance.analyze(tmpFolder1.fsPath); - expect(analysis1).to.exist; - expect(analysis1[0].devfile).to.equal('nodejs'); - const analysis2 = await Odo.Instance.analyze(tmpFolder2.fsPath); - expect(analysis2).to.exist; - expect(analysis2[0].devfile).to.equal('go'); - }); }); suite('registries', function () { @@ -207,12 +198,6 @@ suite('./odo/odoWrapper.ts', function () { } }); - test('analyze()', async function() { - const [analysis] = await Odo.Instance.analyze(tmpFolder); - expect(analysis.name).to.equal(path.basename(tmpFolder).toLocaleLowerCase()); - expect(analysis.devfile).to.equal(COMPONENT_TYPE); - }); - test('deleteComponentConfiguration()', async function() { await Odo.Instance.deleteComponentConfiguration(tmpFolder); try {