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

RHTAP-2547 Add test for importing location/component #69

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
76 changes: 75 additions & 1 deletion src/apis/backstage/developer-hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as https from 'https';
*/
export class DeveloperHubClient extends Utils {
private RHDHUrl: string;
private axiosInstance : Axios;
private axiosInstance: Axios;

/**
* Constructs a new instance of DeveloperHubClient.
Expand Down Expand Up @@ -135,4 +135,78 @@ export class DeveloperHubClient extends Utils {
}
return false;
}

public async unregisterComponentById(id: string) {
try {
const response = await this.axiosInstance.delete(`${this.RHDHUrl}/api/catalog/entities/by-uid/${id}`);

if (response.status === 204) {
console.log('Component deleted successfully');
} else {
console.log('Failed to delete component:', response.status, response.statusText);
}
} catch (error) {
console.error('Error deleting component:', error);
}
}

public async unregisterComponentByName(name: string): Promise<boolean> {
const componentId = await this.getComponentUid(name);
if (componentId) {
await this.unregisterComponentById(componentId);
return true;
}
return false;
}

public async getComponentUid(name: string): Promise<string | null> {
try {
const response = await this.axiosInstance.get(`${this.RHDHUrl}/api/catalog/entities`, {
params: {
filter: `metadata.name=${name}`
}
});

const entities = response.data;
if (entities.length > 0) {
return entities[0].metadata.uid;
} else {
console.log('Component not found');
return null;
}
} catch (error) {
console.error('Error fetching component ID:', error);
return null;
}
}

public async registerLocation(repositoryName: string): Promise<boolean> {


try {
const response = await this.axiosInstance.post(
`${this.RHDHUrl}/api/catalog/locations`,
{
type: 'url',
target: `https://github.com/${process.env.GITHUB_ORGANIZATION}/${repositoryName}/blob/main/catalog-info.yaml`,
},
{
headers: {
'Content-Type': 'application/json'
},
}
);

console.log('Location registered successfully:', response.data);
return true;
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
console.error('Error registering location:', error.response.data);
} else {
console.error('Error registering location:', error);
}
}
return false;
}

}
46 changes: 45 additions & 1 deletion src/utils/test.utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { GitLabProvider } from "../../src/apis/git-providers/gitlab";
import { GitHubProvider } from "../../src/apis/git-providers/github";
import { Kubernetes } from "../../src/apis/kubernetes/kube";
import { DeveloperHubClient } from "../../src/apis/backstage/developer-hub";
import { TaskIdReponse } from "../../src/apis/backstage/types";


export async function cleanAfterTestGitHub(gitHubClient: GitHubProvider, kubeClient: Kubernetes, rootNamespace: string, githubOrganization: string, repositoryName: string) {
Expand Down Expand Up @@ -53,4 +55,46 @@ export async function waitForStringInPageContent(
}
// Return false if the timeout is reached and the string was not found
return false;
}
}

export async function beforeChecks(componentRootNamespace: string, githubOrganization: string, quayImageOrg: string, developmentNamespace: string, kubeClient: Kubernetes) {
if (componentRootNamespace === '') {
throw new Error("The 'APPLICATION_TEST_NAMESPACE' environment variable is not set. Please ensure that the environment variable is defined properly or you have cluster connection.");
}

if (githubOrganization === '') {
throw new Error("The 'GITHUB_ORGANIZATION' environment variable is not set. Please ensure that the environment variable is defined properly or you have cluster connection.");
}

if (quayImageOrg === '') {
throw new Error("The 'QUAY_IMAGE_ORG' environment variable is not set. Please ensure that the environment variable is defined properly or you have cluster connection.");
}

const namespaceExists = await kubeClient.namespaceExists(developmentNamespace)

if (!namespaceExists) {
throw new Error(`The development namespace was not created. Make sure you have created ${developmentNamespace} is created and all secrets are created. Example: 'https://github.com/jduimovich/rhdh/blob/main/default-rhtap-ns-configure'`);
}
}

export async function checkComponentInBackstage(backstageClient: DeveloperHubClient, repositoryName: string, developerHubTask: TaskIdReponse) {
// Check location in developer hub
const taskCreated = await backstageClient.getTaskProcessed(developerHubTask.id, 120000)

if (taskCreated.status !== 'completed') {

try {
const logs = await backstageClient.getEventStreamLog(taskCreated.id)
await backstageClient.writeLogsToArtifactDir('backstage-tasks-logs', `github-${repositoryName}.log`, logs);

throw new Error("failed to create backstage tasks. Please check Developer Hub tasks logs...");

} catch (error) {
throw new Error(`failed to write files to console: ${error}`);
}
} else {
console.log("Task created successfully in backstage");
}
const componentUid = await backstageClient.getComponentUid(repositoryName);
expect(componentUid).toBeDefined()
}
18 changes: 18 additions & 0 deletions tests/gpts/github/go.unregister.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { gitHubImportTemplateTests } from "./test-config/github_import_template.ts";
import { skipSuite } from "../../test-utils.ts";
import { loadSoftwareTemplatesTestsGlobals } from "./test-config/config.ts";

const golangTemplateName = 'go';

const runGolangBasicTests = () => {
const configuration = loadSoftwareTemplatesTestsGlobals()

if (configuration.templates.includes(golangTemplateName) && configuration.github.active) {

gitHubImportTemplateTests(golangTemplateName);
} else {
skipSuite(golangTemplateName);
}
}

runGolangBasicTests();
35 changes: 3 additions & 32 deletions tests/gpts/github/test-config/github_advanced_scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { syncArgoApplication } from '../../../../src/utils/argocd';
import { GitHubProvider } from "../../../../src/apis/git-providers/github";
import { Kubernetes } from "../../../../src/apis/kubernetes/kube";
import { ScaffolderScaffoldOptions } from '@backstage/plugin-scaffolder-react';
import { cleanAfterTestGitHub } from "../../../../src/utils/test.utils";
import { beforeChecks, checkComponentInBackstage, cleanAfterTestGitHub } from "../../../../src/utils/test.utils";

/**
* Advanced end-to-end test scenario for Red Hat Trusted Application Pipelines:
Expand Down Expand Up @@ -64,23 +64,7 @@ export const githubSoftwareTemplatesAdvancedScenarios = (gptTemplate: string) =>
gitHubClient = new GitHubProvider()
kubeClient = new Kubernetes()

if (componentRootNamespace === '') {
throw new Error("The 'APPLICATION_TEST_NAMESPACE' environment variable is not set. Please ensure that the environment variable is defined properly or you have cluster connection.");
}

if (githubOrganization === '') {
throw new Error("The 'GITHUB_ORGANIZATION' environment variable is not set. Please ensure that the environment variable is defined properly or you have cluster connection.");
}

if (quayImageOrg === '') {
throw new Error("The 'QUAY_IMAGE_ORG' environment variable is not set. Please ensure that the environment variable is defined properly or you have cluster connection.");
}

const namespaceExists = await kubeClient.namespaceExists(developmentNamespace)

if (!namespaceExists) {
throw new Error(`The development namespace was not created. Make sure you have created ${developmentNamespace} is created and all secrets are created. Example: 'https://github.com/jduimovich/rhdh/blob/main/default-rhtap-ns-configure'`);
}
await beforeChecks(componentRootNamespace, githubOrganization, quayImageOrg, developmentNamespace, kubeClient)
})

/**
Expand Down Expand Up @@ -136,20 +120,7 @@ export const githubSoftwareTemplatesAdvancedScenarios = (gptTemplate: string) =>
* Waits for the specified component task to be processed by Developer Hub and retrieves logs upon completion.
*/
it(`wait ${gptTemplate} component to be finished`, async () => {
// Retrieve the processed task from Developer Hub
const taskCreated = await backstageClient.getTaskProcessed(developerHubTask.id, 120000);

if (taskCreated.status !== 'completed') {
console.log("failed to create backstage tasks. creating logs...")
try {
const logs = await backstageClient.getEventStreamLog(taskCreated.id)
await backstageClient.writeLogsToArtifactDir('backstage-tasks-logs', `github-${repositoryName}.log`, logs)
} catch (error) {
throw new Error(`failed to write files to console: ${error}`);
}
} else {
console.log("Task created successfully in backstage");
}
await checkComponentInBackstage(backstageClient, repositoryName, developerHubTask)
}, 600000);

/**
Expand Down
160 changes: 160 additions & 0 deletions tests/gpts/github/test-config/github_import_template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';
import { DeveloperHubClient } from '../../../../src/apis/backstage/developer-hub'
import { TaskIdReponse } from '../../../../src/apis/backstage/types';
import { generateRandomChars } from '../../../../src/utils/generator';
import { GitHubProvider } from "../../../../src/apis/git-providers/github";
import { Kubernetes } from "../../../../src/apis/kubernetes/kube";
import { ScaffolderScaffoldOptions } from '@backstage/plugin-scaffolder-react';
import { beforeChecks, checkComponentInBackstage, cleanAfterTestGitHub } from "../../../../src/utils/test.utils";

/**
* 1. Components get created in Red Hat Developer Hub
* 2. Check that components gets created successfully in Red Hat Developer Hub
* 3. Red Hat Developer Hub created GitHub repository
* 4. Remove component/location from RHDH
* 5. Add component/location to RHDH
* 6. Check that components gets created successfully in Red Hat Developer Hub
*/
export const gitHubImportTemplateTests = (gptTemplate: string) => {
describe(`Red Hat Trusted Application Pipeline ${gptTemplate} GPT tests GitHub provider with public/private image registry`, () => {
jest.retryTimes(2);

const backstageClient = new DeveloperHubClient();
const componentRootNamespace = process.env.APPLICATION_ROOT_NAMESPACE || '';
const RHTAPRootNamespace = process.env.RHTAP_ROOT_NAMESPACE || 'rhtap';
const developmentNamespace = `${componentRootNamespace}-development`;

const githubOrganization = process.env.GITHUB_ORGANIZATION || '';
const repositoryName = `${generateRandomChars(9)}-${gptTemplate}`;

const quayImageName = "rhtap-qe";
const quayImageOrg = process.env.QUAY_IMAGE_ORG || '';
const imageRegistry = process.env.IMAGE_REGISTRY || 'quay.io';

let developerHubTask: TaskIdReponse;
let gitHubClient: GitHubProvider;
let kubeClient: Kubernetes;

/**
* Initializes Github and Kubernetes client for interaction. After clients initialization will start to create a test namespace.
* This namespace should have gitops label: 'argocd.argoproj.io/managed-by': 'openshift-gitops' to allow ArgoCD to create
* resources
*/
beforeAll(async () => {
gitHubClient = new GitHubProvider()
kubeClient = new Kubernetes()

await beforeChecks(componentRootNamespace, githubOrganization, quayImageOrg, developmentNamespace, kubeClient)
})

/**
* Creates a request to Developer Hub and check if the gpt really exists in the catalog
*/
it(`verifies if ${gptTemplate} gpt exists in the catalog`, async () => {
const goldenPathTemplates = await backstageClient.getGoldenPathTemplates();

expect(goldenPathTemplates.some(gpt => gpt.metadata.name === gptTemplate)).toBe(true)
})

/**
* Creates a task in Developer Hub to generate a new component using specified git and kube options.
*
* @param templateRef Refers to the Developer Hub template name.
* @param values Set of options to create the component.
* @param owner Developer Hub username who initiates the task.
* @param name Name of the repository to be created in GitHub.
* @param branch Default git branch for the component.
* @param repoUrl Complete URL of the git provider where the component will be created.
* @param imageRegistry Image registry provider. Default is Quay.io.
* @param namespace Kubernetes namespace where ArgoCD will create component manifests.
* @param imageName Registry image name for the component to be pushed.
* @param imageOrg Registry organization name for the component to be pushed.
*/
it(`creates ${gptTemplate} component`, async () => {
const taskCreatorOptions: ScaffolderScaffoldOptions = {
templateRef: `template:default/${gptTemplate}`,
values: {
branch: 'main',
githubServer: 'github.com',
hostType: 'GitHub',
imageName: quayImageName,
imageOrg: quayImageOrg,
imageRegistry: imageRegistry,
name: repositoryName,
namespace: componentRootNamespace,
owner: "user:guest",
repoName: repositoryName,
repoOwner: githubOrganization,
ciType: "tekton"
}
};

// Creating a task in Developer Hub to scaffold the component
developerHubTask = await backstageClient.createDeveloperHubTask(taskCreatorOptions);
}, 120000);

/**
* Once test send a task to Developer Hub, test start to look for the task until all the steps are processed. Once all the steps are processed
* test will grab logs in $ROOT_DIR/artifacts/backstage/xxxxx-component-name.log
*/
it(`wait ${gptTemplate} component to be finished`, async () => {
await checkComponentInBackstage(backstageClient, repositoryName, developerHubTask)
}, 120000);

/**
* Once a DeveloperHub task is processed should create an argocd application in openshift-gitops namespace.
* Need to wait until application is synced until commit something to github and trigger a pipelinerun
*/
it(`wait ${gptTemplate} argocd to be synced in the cluster`, async () => {
const argoCDAppISSynced = await kubeClient.waitForArgoCDApplicationToBeHealthy(`${repositoryName}-development`, 500000)
expect(argoCDAppISSynced).toBe(true)
}, 600000);

/**
* Start to verify if Red Hat Developer Hub created repository from our template in GitHub. This repository should contain the source code of
* my application. Also verifies if the repository contains a '.tekton' folder.
*/
it(`verifies if component ${gptTemplate} was created in GitHub and contains 'catalog-info.yaml' file`, async () => {
const repositoryExists = await gitHubClient.checkIfRepositoryExists(githubOrganization, repositoryName)
expect(repositoryExists).toBe(true)

const tektonFolderExists = await gitHubClient.checkIfFolderExistsInRepository(githubOrganization, repositoryName, 'catalog-info.yaml')
expect(tektonFolderExists).toBe(true)
}, 120000)

/**
* Delete location from backstage
*/
it(`Delete location from backstage`, async () => {
// Unregister component from developer hub
const componentIsUnregistered = await backstageClient.unregisterComponentByName(repositoryName);
expect(componentIsUnregistered).toBe(true)
}, 120000)

/**
* Register existing location in backstage
*/
it(`Register location in backstage`, async () => {
// Register repo in developer hub
const componentIsRegistered = await backstageClient.registerLocation(repositoryName);
expect(componentIsRegistered).toBe(true)
}, 120000)

/**
* Check, if (new) location is added successfully
*/
it(`Check imported location(component in backstage) backstage`, async () => {
await checkComponentInBackstage(backstageClient, repositoryName, developerHubTask)
}, 120000)

/**
* Deletes created applications
*/
afterAll(async () => {
if (process.env.CLEAN_AFTER_TESTS === 'true') {
await cleanAfterTestGitHub(gitHubClient, kubeClient, RHTAPRootNamespace, githubOrganization, repositoryName)
}
})
})

}
Loading