Skip to content

Commit 8ccc96f

Browse files
committed
Add support fot GitLab/BitBucket
1 parent de9f3a8 commit 8ccc96f

File tree

4 files changed

+259
-17
lines changed

4 files changed

+259
-17
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ npm run test:all
177177
# Run a specific test file
178178
npm test -- tests/tssc/full_workflow.test.ts
179179

180+
# Run import template tests (supports all Git providers)
181+
npm run test:import
182+
183+
180184
# View test report
181185
npm run test:report
182186
```
@@ -225,6 +229,10 @@ npm test -- tests/tssc/full_workflow.test.ts
225229
# Run UI tests
226230
npm run test:ui
227231

232+
# Run import template tests (supports all Git providers)
233+
npm run test:import
234+
235+
228236
# View test report
229237
npm run test:report
230238

src/rhtap/core/integration/git/providers/bitbucket.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,4 +952,98 @@ export class BitbucketProvider extends BaseGitProvider {
952952
public override getRepoOwner(): string {
953953
return this.getWorkspace();
954954
}
955+
956+
/**
957+
* Checks if a repository exists in Bitbucket
958+
* @param owner The repository owner (workspace)
959+
* @param repoName The repository name
960+
* @returns Promise<boolean> True if repository exists, false otherwise
961+
*/
962+
public async checkIfRepositoryExists(owner: string, repoName: string): Promise<boolean> {
963+
try {
964+
await this.bitbucketClient.repositories.getRepository(owner, repoName);
965+
return true;
966+
} catch (error: any) {
967+
if (error.status === 404 || error.message?.includes('not found')) {
968+
return false;
969+
}
970+
throw error;
971+
}
972+
}
973+
974+
/**
975+
* Checks if a file exists in a repository
976+
* @param owner The repository owner (workspace)
977+
* @param repoName The repository name
978+
* @param filePath The path to the file
979+
* @param branch The branch to check (default: 'main')
980+
* @returns Promise<boolean> True if file exists, false otherwise
981+
*/
982+
public async checkIfFileExistsInRepository(owner: string, repoName: string, filePath: string, branch: string = 'main'): Promise<boolean> {
983+
try {
984+
await this.bitbucketClient.repositories.getFileContent(owner, repoName, filePath, branch);
985+
return true;
986+
} catch (error: any) {
987+
if (error.status === 404 || error.message?.includes('not found')) {
988+
return false;
989+
}
990+
throw error;
991+
}
992+
}
993+
994+
/**
995+
* Deletes a file from a repository
996+
* @param owner The repository owner (workspace)
997+
* @param repoName The repository name
998+
* @param filePath The path to the file to delete
999+
* @param branch The branch to delete from (default: 'main')
1000+
* @param commitMessage The commit message for the deletion
1001+
* @returns Promise<void>
1002+
*/
1003+
public async deleteFileInRepository(owner: string, repoName: string, filePath: string, branch: string = 'main', commitMessage: string = 'Delete file'): Promise<void> {
1004+
try {
1005+
// Delete the file using Bitbucket API
1006+
await this.bitbucketClient.repositories.deleteFile(owner, repoName, filePath, branch, commitMessage);
1007+
1008+
console.log(`Successfully deleted file ${filePath} from ${owner}/${repoName}`);
1009+
} catch (error: any) {
1010+
console.error(`Failed to delete file ${filePath} from ${owner}/${repoName}: ${error.message}`);
1011+
throw error;
1012+
}
1013+
}
1014+
1015+
/**
1016+
* Deletes a folder from a repository by deleting all files in the folder
1017+
* @param owner The repository owner (workspace)
1018+
* @param repoName The repository name
1019+
* @param folderPath The path to the folder to delete
1020+
* @param branch The branch to delete from (default: 'main')
1021+
* @param commitMessage The commit message for the deletion
1022+
* @returns Promise<void>
1023+
*/
1024+
public async deleteFolderInRepository(owner: string, repoName: string, folderPath: string, branch: string = 'main', commitMessage: string = 'Delete folder'): Promise<void> {
1025+
try {
1026+
// Get the contents of the folder
1027+
const folderContents = await this.bitbucketClient.repositories.getDirectoryContent(owner, repoName, folderPath, branch);
1028+
1029+
if (!Array.isArray(folderContents)) {
1030+
throw new Error(`Path ${folderPath} is not a folder`);
1031+
}
1032+
1033+
// Delete each file in the folder
1034+
for (const item of folderContents) {
1035+
if (item.type === 'file') {
1036+
await this.deleteFileInRepository(owner, repoName, item.path, branch, `${commitMessage}: ${item.name}`);
1037+
} else if (item.type === 'directory') {
1038+
// Recursively delete subdirectories
1039+
await this.deleteFolderInRepository(owner, repoName, item.path, branch, `${commitMessage}: ${item.name}`);
1040+
}
1041+
}
1042+
1043+
console.log(`Successfully deleted folder ${folderPath} from ${owner}/${repoName}`);
1044+
} catch (error: any) {
1045+
console.error(`Failed to delete folder ${folderPath} from ${owner}/${repoName}: ${error.message}`);
1046+
throw error;
1047+
}
1048+
}
9551049
}

src/rhtap/core/integration/git/providers/gitlab.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,4 +898,103 @@ export class GitlabProvider extends BaseGitProvider {
898898
public async setProjectVariableOnGitOpsRepo(variables: Record<string, string>): Promise<void> {
899899
await this.setProjectVariables(variables, this.getGroup(), this.gitOpsRepoName);
900900
}
901+
902+
/**
903+
* Checks if a repository exists in GitLab
904+
* @param owner The repository owner (group)
905+
* @param repoName The repository name
906+
* @returns Promise<boolean> True if repository exists, false otherwise
907+
*/
908+
public async checkIfRepositoryExists(owner: string, repoName: string): Promise<boolean> {
909+
try {
910+
await this.gitlabClient.getProject(`${owner}/${repoName}`);
911+
return true;
912+
} catch (error: any) {
913+
if (error.status === 404 || error.message?.includes('not found')) {
914+
return false;
915+
}
916+
throw error;
917+
}
918+
}
919+
920+
/**
921+
* Checks if a file exists in a repository
922+
* @param owner The repository owner (group)
923+
* @param repoName The repository name
924+
* @param filePath The path to the file
925+
* @param branch The branch to check (default: 'main')
926+
* @returns Promise<boolean> True if file exists, false otherwise
927+
*/
928+
public async checkIfFileExistsInRepository(owner: string, repoName: string, filePath: string, branch: string = 'main'): Promise<boolean> {
929+
try {
930+
const project = await this.gitlabClient.getProject(`${owner}/${repoName}`);
931+
await this.gitlabClient.getFileContent(project.id, filePath, branch);
932+
return true;
933+
} catch (error: any) {
934+
if (error.status === 404 || error.message?.includes('not found')) {
935+
return false;
936+
}
937+
throw error;
938+
}
939+
}
940+
941+
/**
942+
* Deletes a file from a repository
943+
* @param owner The repository owner (group)
944+
* @param repoName The repository name
945+
* @param filePath The path to the file to delete
946+
* @param branch The branch to delete from (default: 'main')
947+
* @param commitMessage The commit message for the deletion
948+
* @returns Promise<void>
949+
*/
950+
public async deleteFileInRepository(owner: string, repoName: string, filePath: string, branch: string = 'main', commitMessage: string = 'Delete file'): Promise<void> {
951+
try {
952+
const project = await this.gitlabClient.getProject(`${owner}/${repoName}`);
953+
954+
// Delete the file using GitLab API
955+
await this.gitlabClient.deleteFile(project.id, filePath, branch, commitMessage);
956+
957+
console.log(`Successfully deleted file ${filePath} from ${owner}/${repoName}`);
958+
} catch (error: any) {
959+
console.error(`Failed to delete file ${filePath} from ${owner}/${repoName}: ${error.message}`);
960+
throw error;
961+
}
962+
}
963+
964+
/**
965+
* Deletes a folder from a repository by deleting all files in the folder
966+
* @param owner The repository owner (group)
967+
* @param repoName The repository name
968+
* @param folderPath The path to the folder to delete
969+
* @param branch The branch to delete from (default: 'main')
970+
* @param commitMessage The commit message for the deletion
971+
* @returns Promise<void>
972+
*/
973+
public async deleteFolderInRepository(owner: string, repoName: string, folderPath: string, branch: string = 'main', commitMessage: string = 'Delete folder'): Promise<void> {
974+
try {
975+
const project = await this.gitlabClient.getProject(`${owner}/${repoName}`);
976+
977+
// Get the contents of the folder
978+
const folderContents = await this.gitlabClient.getRepositoryTree(project.id, folderPath, branch);
979+
980+
if (!Array.isArray(folderContents)) {
981+
throw new Error(`Path ${folderPath} is not a folder`);
982+
}
983+
984+
// Delete each file in the folder
985+
for (const item of folderContents) {
986+
if (item.type === 'blob') {
987+
await this.deleteFileInRepository(owner, repoName, item.path, branch, `${commitMessage}: ${item.name}`);
988+
} else if (item.type === 'tree') {
989+
// Recursively delete subdirectories
990+
await this.deleteFolderInRepository(owner, repoName, item.path, branch, `${commitMessage}: ${item.name}`);
991+
}
992+
}
993+
994+
console.log(`Successfully deleted folder ${folderPath} from ${owner}/${repoName}`);
995+
} catch (error: any) {
996+
console.error(`Failed to delete folder ${folderPath} from ${owner}/${repoName}: ${error.message}`);
997+
throw error;
998+
}
999+
}
9011000
}

tests/templates/import_templates.test.ts

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { TestItem } from '../../src/playwright/testItem';
99
import { createBasicFixture } from '../../src/utils/test/fixtures';
1010
import { Environment } from '../../src/rhtap/core/integration/cd/argocd';
1111
import { GithubProvider } from '../../src/rhtap/core/integration/git/providers/github';
12+
import { GitlabProvider } from '../../src/rhtap/core/integration/git/providers/gitlab';
13+
import { BitbucketProvider } from '../../src/rhtap/core/integration/git/providers/bitbucket';
1214
import { expect } from '@playwright/test';
1315
import { Git } from '../../src/rhtap/core/integration/git';
1416

@@ -33,7 +35,7 @@ test.describe.serial('Import Template Tests', () => {
3335
let git: Git;
3436
const templateName = 'go'; // You can change this to test different templates
3537

36-
test(`verifies if ${templateName} template exists in the catalog`, async ({ testItem }) => {
38+
test(`verifies if ${templateName} template exists in the catalog`, async () => {
3739
// This would require implementing a method to get golden path templates
3840
// For now, we'll assume the template exists if we can create a component
3941
expect(templateName).toBeDefined();
@@ -83,40 +85,66 @@ test.describe.serial('Import Template Tests', () => {
8385
console.log(`ArgoCD application for ${component.getName()} is synced and healthy`);
8486
});
8587

86-
test(`verifies if component ${templateName} was created in GitHub and contains 'catalog-info.yaml' file`, async () => {
88+
test(`verifies if component ${templateName} was created in Git provider and contains 'catalog-info.yaml' file`, async () => {
8789
if (!component) {
8890
throw new Error('Component was not created successfully');
8991
}
9092

91-
const git = component.getGit() as GithubProvider;
93+
const git = component.getGit();
9294
const componentName = component.getName();
95+
const gitType = git.getGitType();
9396

94-
// Check if repository exists in GitHub
95-
const repositoryExists = await git.checkIfRepositoryExists(git.getOrganization(), componentName);
97+
// Get the appropriate owner based on Git provider type
98+
let owner: string;
99+
if (git instanceof GithubProvider) {
100+
owner = git.getOrganization();
101+
} else if (git instanceof GitlabProvider) {
102+
owner = git.getGroup();
103+
} else if (git instanceof BitbucketProvider) {
104+
owner = git.getWorkspace();
105+
} else {
106+
throw new Error(`Unsupported Git provider type: ${gitType}`);
107+
}
108+
109+
// Check if repository exists
110+
const repositoryExists = await git.checkIfRepositoryExists(owner, componentName);
96111
expect(repositoryExists).toBe(true);
97112

98113
// Check if catalog-info.yaml exists
99-
const catalogFileExists = await git.checkIfFileExistsInRepository(git.getOrganization(), componentName, 'catalog-info.yaml');
114+
const catalogFileExists = await git.checkIfFileExistsInRepository(owner, componentName, 'catalog-info.yaml');
100115
expect(catalogFileExists).toBe(true);
101116

102-
console.log(`Repository ${componentName} and catalog-info.yaml verified in GitHub`);
117+
console.log(`Repository ${componentName} and catalog-info.yaml verified in ${gitType}`);
103118
});
104119

105120
test(`deletes catalog file and tekton folder`, async () => {
106121
if (!component) {
107122
throw new Error('Component was not created successfully');
108123
}
109124

110-
const git = component.getGit() as GithubProvider;
125+
const git = component.getGit();
111126
const componentName = component.getName();
127+
const gitType = git.getGitType();
128+
129+
// Get the appropriate owner based on Git provider type
130+
let owner: string;
131+
if (git instanceof GithubProvider) {
132+
owner = git.getOrganization();
133+
} else if (git instanceof GitlabProvider) {
134+
owner = git.getGroup();
135+
} else if (git instanceof BitbucketProvider) {
136+
owner = git.getWorkspace();
137+
} else {
138+
throw new Error(`Unsupported Git provider type: ${gitType}`);
139+
}
112140

113141
// Delete .tekton folder
114-
await git.deleteFolderInRepository(git.getOrganization(), componentName, '.tekton');
142+
await git.deleteFolderInRepository(owner, componentName, '.tekton');
115143

116144
// Delete catalog-info.yaml file
117-
await git.deleteFileInRepository(git.getOrganization(), componentName, 'catalog-info.yaml');
145+
await git.deleteFileInRepository(owner, componentName, 'catalog-info.yaml');
118146

119-
console.log(`Deleted .tekton, gitops folders and catalog-info.yaml from ${componentName}`);
147+
console.log(`Deleted .tekton, gitops folders and catalog-info.yaml from ${componentName} in ${gitType}`);
120148
});
121149

122150
test(`deletes location from backstage`, async () => {
@@ -191,22 +219,35 @@ test.describe.serial('Import Template Tests', () => {
191219
console.log(`ArgoCD application for imported ${importedComponent.getName()} is synced and healthy`);
192220
});
193221

194-
test(`verifies if imported component ${templateName} was created in GitHub and contains 'catalog-info.yaml' file`, async () => {
222+
test(`verifies if imported component ${templateName} was created in Git provider and contains 'catalog-info.yaml' file`, async () => {
195223
if (!importedComponent) {
196224
throw new Error('Imported component was not created successfully');
197225
}
198226

199-
const git = importedComponent.getGit() as GithubProvider;
227+
const git = importedComponent.getGit();
200228
const importedComponentName = importedComponent.getName();
229+
const gitType = git.getGitType();
230+
231+
// Get the appropriate owner based on Git provider type
232+
let owner: string;
233+
if (git instanceof GithubProvider) {
234+
owner = git.getOrganization();
235+
} else if (git instanceof GitlabProvider) {
236+
owner = git.getGroup();
237+
} else if (git instanceof BitbucketProvider) {
238+
owner = git.getWorkspace();
239+
} else {
240+
throw new Error(`Unsupported Git provider type: ${gitType}`);
241+
}
201242

202-
// Check if imported repository exists in GitHub
203-
const repositoryExists = await git.checkIfRepositoryExists(git.getOrganization(), importedComponentName);
243+
// Check if imported repository exists
244+
const repositoryExists = await git.checkIfRepositoryExists(owner, importedComponentName);
204245
expect(repositoryExists).toBe(true);
205246

206247
// Check if catalog-info.yaml exists in imported repository
207-
const catalogFileExists = await git.checkIfFileExistsInRepository(git.getOrganization(), importedComponentName, 'catalog-info.yaml');
248+
const catalogFileExists = await git.checkIfFileExistsInRepository(owner, importedComponentName, 'catalog-info.yaml');
208249
expect(catalogFileExists).toBe(true);
209250

210-
console.log(`Imported repository ${importedComponentName} and catalog-info.yaml verified in GitHub`);
251+
console.log(`Imported repository ${importedComponentName} and catalog-info.yaml verified in ${gitType}`);
211252
});
212253
});

0 commit comments

Comments
 (0)