Skip to content
29 changes: 29 additions & 0 deletions src/devfile/commandResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/
import { Command, Data } from '../odo/componentTypeDescription';

export class CommandResolver {
public static getCommand(devfile: Data, commandId: string): Command {
const command = devfile.commands.find(
(c) => c.id.toLowerCase() === commandId.toLowerCase(),
);

if (!command) {
throw new Error(`Command '${commandId}' not found`);
}

return command;
}

public static getAllCommandsMap(devfile: Data): Map<string, Command> {
const map = new Map<string, Command>();

for (const command of devfile.commands) {
map.set(command.id.toLowerCase(), command);
}

return map;
}
}
24 changes: 24 additions & 0 deletions src/devfile/compositeCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { ComponentWorkspaceFolder } from '../odo/workspace';
import { Command } from '../odo/componentTypeDescription';
import { DevfileCommandRunner } from './devfileCommandRunner';

export class CompositeCommand {

public static async execute(
componentFolder: ComponentWorkspaceFolder,
command: Command,
): Promise<void> {

for (const childId of command.composite.commands) {
await DevfileCommandRunner.execute(
componentFolder,
childId,
);
}
}
}
109 changes: 109 additions & 0 deletions src/devfile/devfileCommandRunner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { ComponentWorkspaceFolder } from '../odo/workspace';
import { Command } from '../odo/componentTypeDescription';
import { CommandResolver } from './commandResolver';
import { ExecCommandExecutor } from './execCommand';

export class DevfileCommandRunner {

public static async execute(
componentFolder: ComponentWorkspaceFolder,
commandId: string,
): Promise<void> {

const devfile =
componentFolder.component.devfileData.devfile;

const command =
CommandResolver.getCommand(
devfile,
commandId,
);

await this.executeCommand(
componentFolder,
command,
);
}

private static async executeCommand(
componentFolder: ComponentWorkspaceFolder,
command: Command,
): Promise<void> {

if (command.exec) {

await ExecCommandExecutor.execute(
componentFolder,
command.exec,
);

return;
}

if (command.composite) {

const devfile =
componentFolder.component.devfileData.devfile;

const commandMap =
CommandResolver.getAllCommandsMap(
devfile,
);

const children =
command.composite.commands.map(id => {

const child =
commandMap.get(
id.toLowerCase(),
);

if (!child) {
throw new Error(
`Command '${id}' not found`,
);
}

return child;
});

const isParallel =
(command.composite as {
parallel?: boolean;
}).parallel === true;

if (isParallel) {

await Promise.all(
children.map(child =>
this.executeCommand(
componentFolder,
child,
),
),
);

} else {

for (const child of children) {

await this.executeCommand(
componentFolder,
child,
);
}
}

return;
}

throw new Error(
`Unsupported command '${command.id}'`,
);
}
}
48 changes: 48 additions & 0 deletions src/devfile/execCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { ComponentWorkspaceFolder } from '../odo/workspace';
import { Exec } from '../odo/componentTypeDescription';
import { VariableResolver } from './variableResolver';
import { Oc } from '../oc/ocWrapper';
import { CommandText } from '../base/command';
import { OpenShiftTerminalManager } from '../webview/openshift-terminal/openShiftTerminal';

export class ExecCommandExecutor {

public static async execute(
componentFolder: ComponentWorkspaceFolder,
exec: Exec,
): Promise<void> {

const devfile =
componentFolder.component.devfileData.devfile;

const resolvedExec =
VariableResolver.resolveExec(
devfile,
exec,
);

const componentName =
devfile.metadata.name;

const podName =
await Oc.Instance.getComponentPod(
componentName,
);

const command = new CommandText(
'oc',
`exec ${podName} -c ${resolvedExec.component} -- sh -c "cd ${resolvedExec.workingDir} && ${resolvedExec.commandLine}"`,
);

void OpenShiftTerminalManager.getInstance().createTerminal(
command,
`Component ${componentName}: Run '${resolvedExec.commandLine}' Command`,
componentFolder.contextPath
);
}
}
27 changes: 27 additions & 0 deletions src/devfile/parallelCompositeCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { ComponentWorkspaceFolder } from 'src/odo/workspace';
import { Command } from '../odo/componentTypeDescription';
import { DevfileCommandRunner } from './devfileCommandRunner';

export class ParallelCompositeCommand {

public static async execute(
componentFolder: ComponentWorkspaceFolder,
command: Command,
): Promise<void> {

await Promise.all(
command.composite.commands.map(
childId =>
DevfileCommandRunner.execute(
componentFolder,
childId,
),
),
);
}
}
68 changes: 68 additions & 0 deletions src/devfile/variableResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { Container, Data, Exec } from '../odo/componentTypeDescription';

export class VariableResolver {
private static readonly VARIABLE_REGEX = /\$\{([^}]+)\}/g;

public static resolveExec(devfile: Data, exec: Exec): Exec {
return {
...exec,
workingDir: this.resolveValue(devfile, exec.workingDir ?? '/projects', exec.component),
commandLine: this.resolveValue(devfile, exec.commandLine, exec.component),
};
}

public static resolveValue(devfile: Data, value: string, componentName?: string): string {
if (!value) {
return value;
}

return value.replace(this.VARIABLE_REGEX, (_, variable) =>
this.resolveVariable(devfile, variable, componentName),
);
}

private static resolveVariable(
devfile: Data,
variable: string,
componentName?: string,
): string {
if (variable === 'PROJECT_SOURCE') {
return '/projects';
}

if (componentName) {
const component = devfile.components.find((c) => c.name === componentName);

const container = component?.container;

const envValue = this.findEnvValue(container, variable);

if (envValue) {
return envValue;
}
}

return process.env[variable] ?? `\${${variable}}`;
}

private static findEnvValue(
container: Container | undefined,
variable: string,
): string | undefined {
const env = (
container as unknown as {
env?: {
name: string;
value: string;
}[];
}
)?.env;

return env?.find((e) => e.name === variable)?.value;
}
}
28 changes: 28 additions & 0 deletions src/oc/ocWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1131,4 +1131,32 @@ export class Oc {

await this.deleteOdoFiles(componentPath, componentName);
}

public async getComponentPod(componentName: string): Promise<string> {

const selectors = [
`app.kubernetes.io/instance=${componentName}`,
`app.kubernetes.io/component=${componentName}`,
`component=${componentName}`,
`app=${componentName}`
];

for (const selector of selectors) {

const pods =
await this.getKubernetesObjects(
'pods',
undefined,
selector
);

if (pods.length > 0) {
return pods[0].metadata.name as string;
}
}

throw new Error(
`No running pod found for component '${componentName}'`
);
}
}
4 changes: 0 additions & 4 deletions src/odo/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,4 @@ export class Command {
}
return cTxt;
}

static runComponentCommand(commandId : string): CommandText {
return new CommandText('odo', `run ${commandId}`);
}
}
1 change: 1 addition & 0 deletions src/odo/componentTypeDescription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export interface Exec {

export type Composite = {
commands: string[];
parallel?: boolean;
group: Group;
}

Expand Down
Loading
Loading