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

Replace odo analyze command #4330

Merged
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
35 changes: 35 additions & 0 deletions src/alizer/alizerWrapper.ts
Original file line number Diff line number Diff line change
@@ -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<AlizerAnalyzeResponse> {
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];
}
}
18 changes: 18 additions & 0 deletions src/alizer/types.ts
Original file line number Diff line number Diff line change
@@ -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[];
}
10 changes: 6 additions & 4 deletions src/downloadUtil/downloadBinaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 1 addition & 10 deletions src/odo/odoWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -194,15 +194,6 @@ export class Odo {
);
}

public async analyze(currentFolderPath: string): Promise<AnalyzeResponse[]> {
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
*
Expand Down
37 changes: 37 additions & 0 deletions src/tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
3 changes: 2 additions & 1 deletion src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,12 @@ export class ToolsConfig {
public static async selectTool(locations: string[], versionRange: string): Promise<string> {
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;
}
Expand Down
37 changes: 23 additions & 14 deletions src/webview/create-component/createComponentLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -518,14 +520,14 @@ export default class CreateComponentLoader {
}

static async getRecommendedDevfile(uri: Uri): Promise<void> {
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<string> = ['Yes', 'Cancel'];
Expand All @@ -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(
Expand Down Expand Up @@ -587,18 +589,25 @@ export default class CreateComponentLoader {
}
}

async function getCompDescription(devfiles: AnalyzeResponse[]): Promise<ComponentTypeDescription[]> {
async function getCompDescription(devfile: AlizerAnalyzeResponse): Promise<ComponentTypeDescription[]> {
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;
}
}
);
}

Expand Down
134 changes: 134 additions & 0 deletions test/integration/alizerWrapper.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});
15 changes: 0 additions & 15 deletions test/integration/odoWrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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 {
Expand Down
Loading