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

Create POC of Fix dev spaces devfile api tests to avoid using an absolute devfile registry for PHP stack #23200

Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion tests/e2e/specs/api/EmptyWorkspaceAPI.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ suite('Empty workspace API test', function (): void {
suiteSetup(`Create empty workspace with OC client ${BASE_TEST_CONSTANTS.TEST_ENVIRONMENT}`, async function (): Promise<void> {
const workspaceName: string = 'empty-' + Math.floor(Math.random() * 1000);
const devfileContent: string = 'schemaVersion: 2.2.0\n' + 'metadata:\n' + ` name: ${workspaceName}\n`;
const editorContent:string='';

devWorkspaceConfigurationHelper = new DevWorkspaceConfigurationHelper({
devfileContent
devfileContent, editorContent
});
devfileContext = await devWorkspaceConfigurationHelper.generateDevfileContext();
devWorkspaceName = devfileContext?.devWorkspace?.metadata?.name;
Expand Down
70 changes: 70 additions & 0 deletions tests/e2e/specs/api/PhpDevFileAPI.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/** *******************************************************************
* copyright (c) 2023 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
import { BASE_TEST_CONSTANTS } from '../../constants/BASE_TEST_CONSTANTS';
import { e2eContainer } from '../../configs/inversify.config';
import { CLASSES } from '../../configs/inversify.types';
import {Logger} from "../../utils/Logger";
import {DevfilesRegistryHelper} from "../../utils/DevfilesRegistryHelper";
import {ContainerTerminal, KubernetesCommandLineToolsExecutor} from "../../utils/KubernetesCommandLineToolsExecutor";
import {ShellExecutor} from "../../utils/ShellExecutor";
import {DevWorkspaceConfigurationHelper} from "../../utils/DevWorkspaceConfigurationHelper";
import {DevfileContext} from "@eclipse-che/che-devworkspace-generator/lib/api/devfile-context";
import {ShellString} from "shelljs";
import {expect} from "chai";
import {API_TEST_CONSTANTS} from "../../constants/API_TEST_CONSTANTS";
import YAML from "yaml";
import {StringUtil} from "../../utils/StringUtil";
import {string} from "yaml/dist/schema/common/string";


suite('PHP devfile API test', function (): void {
const devfilesRegistryHelper: DevfilesRegistryHelper = e2eContainer.get(CLASSES.DevfilesRegistryHelper);
const kubernetesCommandLineToolsExecutor: KubernetesCommandLineToolsExecutor = e2eContainer.get(CLASSES.KubernetesCommandLineToolsExecutor);
let containerTerminal: ContainerTerminal = e2eContainer.get(CLASSES.ContainerTerminal);
let devWorkspaceConfigurationHelper: DevWorkspaceConfigurationHelper;
let devfileContext: DevfileContext;
let devfileContent: string ='';

suiteSetup(`Prepare login ${BASE_TEST_CONSTANTS.TEST_ENVIRONMENT}`, async function (): Promise<void> {
kubernetesCommandLineToolsExecutor.loginToOcp();
});

test('Create PHP DevSpace', async function (): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean PHP workspace?

kubernetesCommandLineToolsExecutor.namespace = 'admin-devspaces';
Copy link
Contributor

@dmytro-ndp dmytro-ndp Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test will be supporting both Eclipse Che and Dev Spaces if use TS_API_TEST_NAMESPACE constant:

kubernetesCommandLineToolsExecutor.namespace: string = API_TEST_CONSTANTS.TS_API_TEST_NAMESPACE || "admin-devspaces"

devfileContent = devfilesRegistryHelper.getDevfileContent('php');
Copy link
Contributor

@dmytro-ndp dmytro-ndp Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'php' would better to extract as a DEVFILE_ID constant to make localizing the sample name simpler when investigate the test failure.
You can extract devfile ids of Dev Spaces 3.16 from the next json obtained using request `curl -X GET http://localhost:8081/dashboard/api/airgap-sample -H 'accept: /' -H 'Authorization: Bearer ...:

 [INFO] Devfiles metadata content:
 [
   {
     "id": "php",
     "displayName": "PHP",
     "description": "PHP Stack with PHP",
     "tags": [
       "Tech-Preview",
       "PHP",
       "UBI8"
     ],
     "url": "http://devspaces-dashboard.openshift-devspaces.svc:8080/dashboard/api/airgap-sample/devfile/download?id=php",
     "icon": {
       "mediatype": "image/svg+xml"
     },
     "project": {
       "zip": {
         "filename": "devspaces-samples-php-hello-world-devspaces-3.16-rhel-8.zip"
       }
     },
     "devfile": {
       "filename": "devspaces-samples-php-hello-world-devspaces-3.16-rhel-8-devfile.yaml"
     }
   },
   {
     "id": "dotnet",
     "displayName": ".NET",
     "description": ".NET stack with .NET Core SDK 8, Runtime, C# Language Support and Debugger",
     "tags": [
       "Tech-Preview",
       ".NET",
       "C#",
       ".NET SDK",
       ".NET Runtime",
       "Netcoredbg",
       "Omnisharp",
       "UBI8"
     ],
     "url": "http://devspaces-dashboard.openshift-devspaces.svc:8080/dashboard/api/airgap-sample/devfile/download?id=dotnet",
     "icon": {
       "mediatype": "image/svg+xml"
     },
     "project": {
       "zip": {
         "filename": "devspaces-samples-dotnet-web-simple-devspaces-3.16-rhel-8.zip"
       }
     },
     "devfile": {
       "filename": "devspaces-samples-dotnet-web-simple-devspaces-3.16-rhel-8-devfile.yaml"
     }
   },
   {
     "id": "nodejs-mongodb",
     "displayName": "Node.js MongoDB",
     "description": "Node.js stack with NPM 8, Node.js 16 and MongoDB 3.6",
     "tags": [
       "Node.js",
       "NPM",
       "Express",
       "MongoDB",
       "UBI8"
     ],
     "url": "http://devspaces-dashboard.openshift-devspaces.svc:8080/dashboard/api/airgap-sample/devfile/download?id=nodejs-mongodb",
     "icon": {
       "mediatype": "image/svg+xml"
     },
     "project": {
       "zip": {
         "filename": "devspaces-samples-nodejs-mongodb-sample-devspaces-3.16-rhel-8.zip"
       }
     },
     "devfile": {
       "filename": "devspaces-samples-nodejs-mongodb-sample-devspaces-3.16-rhel-8-devfile.yaml"
     }
   },
   {
     "id": "java-lombok",
     "displayName": "Java Lombok",
     "description": "Java Stack with Project Lombok",
     "tags": [
       "Java",
       "OpenJDK",
       "Maven",
       "Lombok",
       "UBI8"
     ],
     "url": "http://devspaces-dashboard.openshift-devspaces.svc:8080/dashboard/api/airgap-sample/devfile/download?id=java-lombok",
     "icon": {
       "mediatype": "image/svg+xml"
     },
     "project": {
       "zip": {
         "filename": "devspaces-samples-lombok-project-sample-devspaces-3.16-rhel-8.zip"
       }
     },
     "devfile": {
       "filename": "devspaces-samples-lombok-project-sample-devspaces-3.16-rhel-8-devfile.yaml"
     }
   },
   {
     "id": "nodejs-express",
     "displayName": "Node.js Express",
     "description": "Node.js stack with NPM 8, Node.js 16 and Express Web Application",
     "tags": [
       "Node.js",
       "NPM",
       "Express",
       "UBI8"
     ],
     "url": "http://devspaces-dashboard.openshift-devspaces.svc:8080/dashboard/api/airgap-sample/devfile/download?id=nodejs-express",
     "icon": {
       "mediatype": "image/svg+xml"
     },
     "project": {
       "zip": {
         "filename": "devspaces-samples-web-nodejs-sample-devspaces-3.16-rhel-8.zip"
       }
     },
     "devfile": {
       "filename": "devspaces-samples-web-nodejs-sample-devspaces-3.16-rhel-8-devfile.yaml"
     }
   },
   {
     "id": "cpp",
     "displayName": "C/C++",
     "description": "C and C++ Developer Tools stack with GCC, cmake and make",
     "tags": [
       "Tech-Preview",
       "C",
       "C++",
       "Clang",
       "GCC",
       "g++",
       "make",
       "cmake",
       "UBI8"
     ],
     "url": "http://devspaces-dashboard.openshift-devspaces.svc:8080/dashboard/api/airgap-sample/devfile/download?id=cpp",
     "icon": {
       "mediatype": "image/svg+xml"
     },
     "project": {
       "zip": {
         "filename": "devspaces-samples-c-plus-plus-devspaces-3.16-rhel-8.zip"
       }
     },
     "devfile": {
       "filename": "devspaces-samples-c-plus-plus-devspaces-3.16-rhel-8-devfile.yaml"
     }
   },
   {
     "id": "ansible",
     "displayName": "Ansible",
     "description": "Development environment for Ansible playbook creation, testing with Molecule, and ansible-lint checks",
     "tags": [
       "Community",
       "Ansible",
       "Molecule"
     ],
     "url": "http://devspaces-dashboard.openshift-devspaces.svc:8080/dashboard/api/airgap-sample/devfile/download?id=ansible",
     "icon": {
       "mediatype": "image/svg+xml"
     },
     "project": {
       "zip": {
         "filename": "devspaces-samples-ansible-devspaces-demo-devspaces-3.16-rhel-8.zip"
       }
     },
     "devfile": {
       "filename": "devspaces-samples-ansible-devspaces-demo-devspaces-3.16-rhel-8-devfile.yaml"
     }
   },
   {
     "id": "quarkus-rest-api",
     "displayName": "Quarkus REST API",
     "description": "Quarkus stack with a default REST endpoint application sample",
     "tags": [
       "Java",
       "OpenJDK",
       "Maven",
       "Quarkus",
       "UBI8"
     ],
     "url": "http://devspaces-dashboard.openshift-devspaces.svc:8080/dashboard/api/airgap-sample/devfile/download?id=quarkus-rest-api",
     "icon": {
       "mediatype": "image/svg+xml"
     },
     "project": {
       "zip": {
         "filename": "devspaces-samples-quarkus-quickstarts-devspaces-3.16-rhel-8.zip"
       }
     },
     "devfile": {
       "filename": "devspaces-samples-quarkus-quickstarts-devspaces-3.16-rhel-8-devfile.yaml"
     }
   },
   {
     "id": "go",
     "displayName": "Go",
     "description": "Stack with Go",
     "tags": [
       "Tech-Preview",
       "Go",
       "Golang",
       "UBI8"
     ],
     "url": "http://devspaces-dashboard.openshift-devspaces.svc:8080/dashboard/api/airgap-sample/devfile/download?id=go",
     "icon": {
       "mediatype": "image/svg+xml"
     },
     "project": {
       "zip": {
         "filename": "devspaces-samples-golang-health-check-devspaces-3.16-rhel-8.zip"
       }
     },
     "devfile": {
       "filename": "devspaces-samples-golang-health-check-devspaces-3.16-rhel-8-devfile.yaml"
     }
   },
   {
     "id": "python",
     "displayName": "Python",
     "description": "Python Stack with Python 3.9 and pip",
     "tags": [
       "Python",
       "pip",
       "UBI8"
     ],
     "url": "http://devspaces-dashboard.openshift-devspaces.svc:8080/dashboard/api/airgap-sample/devfile/download?id=python",
     "icon": {
       "mediatype": "image/svg+xml"
     },
     "project": {
       "zip": {
         "filename": "devspaces-samples-python-hello-world-devspaces-3.16-rhel-8.zip"
       }
     },
     "devfile": {
       "filename": "devspaces-samples-python-hello-world-devspaces-3.16-rhel-8-devfile.yaml"
     }
   }
 ]

const editorDevfileContent: string = devfilesRegistryHelper.obtainCheDevFileEditorFromCheConfigMap('editors-definitions');
kubernetesCommandLineToolsExecutor.workspaceName = YAML.parse(devfileContent).metadata.name;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add a random string to the end of workspace name to avoid conflict with existed workspaces having the same name.


devWorkspaceConfigurationHelper = new DevWorkspaceConfigurationHelper({
editorContent: editorDevfileContent, devfileContent: devfileContent
});
devfileContext = await devWorkspaceConfigurationHelper.generateDevfileContext();

const devWorkspaceConfigurationYamlString: string =
devWorkspaceConfigurationHelper.getDevWorkspaceConfigurationYamlAsString(devfileContext);
const output: ShellString = kubernetesCommandLineToolsExecutor.applyAndWaitDevWorkspace(devWorkspaceConfigurationYamlString);
expect(output.stdout).contains('condition met');
});

test('Check running application', async function (): Promise<void> {
const workdir:string = YAML.parse(devfileContent).commands[0].exec.workingDir;
const commandLine:string = YAML.parse(devfileContent).commands[0].exec.commandLine;
const containerName:string = YAML.parse(devfileContent).commands[0].exec.component;
const runCommandInBash:string = `cd ${workdir} && ${commandLine}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would also be useful to see the actual values of workdir and commandLine: in the logs to recognize original values and the command created from them.

const output: ShellString = containerTerminal.execInContainerCommand(runCommandInBash, containerName);
expect(output.code).eqls(0);
expect(output.stdout.trim()).contains('Hello, world!');

});

suiteTeardown('Delete workspace', function (): void {
kubernetesCommandLineToolsExecutor.deleteDevWorkspace();
});
});
2 changes: 2 additions & 0 deletions tests/e2e/utils/DevWorkspaceConfigurationHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { IContextParams } from './IContextParams';
import { e2eContainer } from '../configs/inversify.config';
import { CLASSES, EXTERNAL_CLASSES } from '../configs/inversify.types';
import getDecorators from 'inversify-inject-decorators';
import {DevfilesRegistryHelper} from "./DevfilesRegistryHelper";

const { lazyInject } = getDecorators(e2eContainer);

Expand Down Expand Up @@ -98,6 +99,7 @@ export class DevWorkspaceConfigurationHelper {

return content;
}

patchDevWorkspaceConfigWithBuildContainerAttribute(devfileContextDevWorkspace: any): void {
Logger.debug();
devfileContextDevWorkspace.spec.template.attributes = YAML.parse(`
Expand Down
63 changes: 60 additions & 3 deletions tests/e2e/utils/DevfilesRegistryHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,20 @@ import YAML from 'yaml';
import { API_TEST_CONSTANTS, SUPPORTED_DEVFILE_REGISTRIES } from '../constants/API_TEST_CONSTANTS';
import { injectable } from 'inversify';
import { BASE_TEST_CONSTANTS, Platform } from '../constants/BASE_TEST_CONSTANTS';
import {ShellExecutor} from "./ShellExecutor";
import {e2eContainer} from "../configs/inversify.config";
import {CLASSES} from "../configs/inversify.types";

@injectable()
export class DevfilesRegistryHelper {

private getShellExecutor(): ShellExecutor {
return e2eContainer.get(CLASSES.ShellExecutor);
}
/**
* @deprecated applicable only for inbuilt devfiles
* @param sampleNamePatterns
*/
async getInbuiltDevfilesRegistryContent(sampleNamePatterns?: string[]): Promise<any[]> {
Logger.trace();

Expand All @@ -35,6 +46,12 @@ export class DevfilesRegistryHelper {
return await this.getContent(url);
}

/**
*
* @deprecated applicable only for inbuilt devfiles
* @param isInbuilt
* @param sampleNamePatterns
*/
async collectPathsToDevfilesFromRegistry(isInbuilt: boolean, sampleNamePatterns?: string[]): Promise<object[]> {
Logger.debug();

Expand Down Expand Up @@ -86,9 +103,6 @@ export class DevfilesRegistryHelper {
return devfileSamples;
}

async getEditorContent(entry: string): Promise<any> {
return await this.getContent(`${BASE_TEST_CONSTANTS.TS_SELENIUM_BASE_URL}/${entry}`);
}

private filterSamples(sampleNamePatterns: string[] | undefined, content: any): Promise<any[]> {
if (sampleNamePatterns) {
Expand Down Expand Up @@ -120,4 +134,47 @@ export class DevfilesRegistryHelper {
}
return response?.data;
}

public getInternalClusterURLToDevFile(devFileName:string): string {
let devfileSampleURIPrefix:string = `/dashboard/api/airgap-sample/devfile/download?id=${devFileName}`;
Fixed Show fixed Hide fixed
let serviceClusterIp:string = ''
let servicePort:string = ''
serviceClusterIp = this.getShellExecutor().executeArbitraryShellScript(`oc get svc devspaces-dashboard -n ${BASE_TEST_CONSTANTS.TS_PLATFORM}-${BASE_TEST_CONSTANTS.TESTING_APPLICATION_NAME()} -o=jsonpath='{.spec.clusterIP}'`);
servicePort = this.getShellExecutor().executeArbitraryShellScript(`oc get svc devspaces-dashboard -n ${BASE_TEST_CONSTANTS.TS_PLATFORM}-${BASE_TEST_CONSTANTS.TESTING_APPLICATION_NAME()} -o=jsonpath='{.spec.ports[*].port}'`);
return `http://${serviceClusterIp}:${servicePort}${devfileSampleURIPrefix}`;
}

/**
* grab devfile content from the Dashboard pod (currently, in the image of dashboard builds with devfile content and we use it for getting devfile description)
* @param podName
* @param containerName
* @param devFileName
*/
public obtainDevFileContentUsingPod(podName:string, containerName:string, devFileName:string): string {
const clusterURL:string = this.getInternalClusterURLToDevFile(devFileName);
this.getShellExecutor().executeCommand(`oc exec -i ${podName} -n ${BASE_TEST_CONSTANTS.TS_PLATFORM}-${BASE_TEST_CONSTANTS.TESTING_APPLICATION_NAME()} -c ${containerName} -- sh -c 'curl -o /tmp/${devFileName}-devfile.yaml ${clusterURL}'`);
Fixed Show fixed Hide fixed
return this.getShellExecutor().executeArbitraryShellScript(`oc exec -i ${podName} -n ${BASE_TEST_CONSTANTS.TS_PLATFORM}-${BASE_TEST_CONSTANTS.TESTING_APPLICATION_NAME()} -c ${containerName} -- cat /tmp/${devFileName}-devfile.yaml`).toString();
Fixed Show fixed Hide fixed
}

/**
* use internal CHE config map for getting description of CHE editor
* @param configMapName
*/
public obtainCheDevFileEditorFromCheConfigMap(configMapName: string): string {
return this.getShellExecutor().executeCommand(`oc get configmap ${configMapName} -o jsonpath="{.data.che-code\\.yaml}" -n ${BASE_TEST_CONSTANTS.TS_PLATFORM}-${BASE_TEST_CONSTANTS.TESTING_APPLICATION_NAME()}`);
Fixed Show fixed Hide fixed
}

/**
* find the Dashboard pod and container name and grab devfile content from it
* @param devSample
*/
public getDevfileContent(devSample: string): string {
const command: string = `oc get pods -n ${BASE_TEST_CONSTANTS.TS_PLATFORM}-${BASE_TEST_CONSTANTS.TESTING_APPLICATION_NAME()}`;
console.log(`command: ${command}`);
const podName: string = this.getShellExecutor().executeArbitraryShellScript(`oc get pods -n ${BASE_TEST_CONSTANTS.TS_PLATFORM}-${BASE_TEST_CONSTANTS.TESTING_APPLICATION_NAME()} | grep dashboard | awk \'{print $1}\'`).trim()
const containerName: string = this.getShellExecutor().executeArbitraryShellScript(`oc get pod -n ${BASE_TEST_CONSTANTS.TS_PLATFORM}-${BASE_TEST_CONSTANTS.TESTING_APPLICATION_NAME()} ${podName} -o jsonpath=\'{.spec.containers[*].name}\'`)
const devfileContent: string = this.obtainDevFileContentUsingPod(podName, containerName, devSample);
return devfileContent;
}

}
Loading