From 1cd1c93a9e1d95663e51f4de24a5b518fa2bee1a Mon Sep 17 00:00:00 2001
From: ArnaudTa <33383276+ArnaudTA@users.noreply.github.com>
Date: Thu, 4 Apr 2024 10:42:18 +0200
Subject: [PATCH] feat: :sparkles: allow repo sync from console ui
---
.../cypress/components/specs/repo-form.ct.ts | 2 +-
apps/client/cypress/e2e/specs/repos.e2e.ts | 55 +++++++++++++-
apps/client/cypress/e2e/support/commands.ts | 2 +-
apps/client/src/api/repositories.ts | 7 +-
apps/client/src/components/RepoForm.vue | 8 +--
apps/client/src/stores/project-repository.ts | 8 ++-
apps/client/src/views/projects/DsoRepos.vue | 71 ++++++++++++++++---
apps/server/.env.integ-example | 1 +
.../src/resources/repository/business.ts | 27 ++++++-
.../resources/repository/controllers.spec.ts | 32 +++++++++
.../src/resources/repository/queries.ts | 13 ++++
.../server/src/resources/repository/router.ts | 24 +++++--
apps/server/src/utils/hook-wrapper.ts | 16 +++--
apps/server/src/utils/mocks.ts | 3 +-
packages/hooks/src/hooks/hook-misc.ts | 5 +-
packages/shared/src/contracts/repository.ts | 12 ++++
packages/shared/src/schemas/repository.ts | 17 +++++
packages/shared/tsconfig.json | 3 +-
plugins/gitlab/src/class.ts | 55 +++++++++++---
plugins/gitlab/src/functions.ts | 24 ++++++-
plugins/gitlab/src/index.ts | 13 +++-
21 files changed, 352 insertions(+), 46 deletions(-)
diff --git a/apps/client/cypress/components/specs/repo-form.ct.ts b/apps/client/cypress/components/specs/repo-form.ct.ts
index 08f7368bc..8e2a32397 100644
--- a/apps/client/cypress/components/specs/repo-form.ct.ts
+++ b/apps/client/cypress/components/specs/repo-form.ct.ts
@@ -115,7 +115,7 @@ describe('RepoForm.vue', () => {
cy.mount(RepoForm, { props })
- cy.get('h1').should('contain', 'Modifier le dépôt')
+ cy.get('h2').should('contain', 'Modifier le dépôt')
cy.getByDataTestid('repoFieldset').should('have.length', 1)
cy.getByDataTestid('internalRepoNameInput').find('input').should('have.value', props.repo.internalRepoName)
.and('be.disabled')
diff --git a/apps/client/cypress/e2e/specs/repos.e2e.ts b/apps/client/cypress/e2e/specs/repos.e2e.ts
index 6d0186b99..e56236c6b 100644
--- a/apps/client/cypress/e2e/specs/repos.e2e.ts
+++ b/apps/client/cypress/e2e/specs/repos.e2e.ts
@@ -28,7 +28,7 @@ describe('Add repos into project', () => {
cy.url().should('contain', '/repositories')
cy.getByDataTestid('addRepoLink').click({ timeout: 30_000 })
- cy.get('h1').should('contain', 'Ajouter un dépôt au projet')
+ cy.get('h2').should('contain', 'Ajouter un dépôt au projet')
cy.getByDataTestid('addRepoBtn').should('be.disabled')
cy.getByDataTestid('internalRepoNameInput').find('input').clear().type(repo.internalRepoName)
cy.getByDataTestid('addRepoBtn').should('be.disabled')
@@ -139,7 +139,7 @@ describe('Add repos into project', () => {
cy.wait('@getProjects').its('response').then(response => {
repos = response?.body.find(resProject => resProject.name === project.name).repositories
cy.getByDataTestid(`repoTile-${repos[0].internalRepoName}`).click()
- .get('h1').should('contain', 'Modifier le dépôt')
+ .get('h2').should('contain', 'Modifier le dépôt')
.getByDataTestid('internalRepoNameInput').should('be.disabled')
.getByDataTestid('externalRepoUrlInput').find('input').clear().type('https://github.com/externalUser04/new-repo.git')
@@ -162,6 +162,57 @@ describe('Add repos into project', () => {
})
})
+ it('Should synchronise a repo', () => {
+ cy.intercept('GET', '/api/v1/projects').as('getProjects')
+ cy.intercept('GET', '/api/v1/projects/*/repositories/*/sync/*').as('syncRepo')
+ let repos
+
+ cy.goToProjects()
+ .getByDataTestid(`projectTile-${project.name}`).click()
+ .getByDataTestid('menuRepos').click()
+ .url().should('contain', '/repositories')
+
+ cy.wait('@getProjects').its('response').then(response => {
+ repos = response?.body.find(resProject => resProject.name === project.name).repositories
+
+ cy.getByDataTestid(`repoTile-${repos[0].internalRepoName}`)
+ .click()
+
+ cy.get('h2').should('contain', 'Synchroniser le dépôt')
+ cy.getByDataTestid('branchNameInput')
+ .should('have.value', 'main')
+
+ cy.getByDataTestid('syncRepoBtn')
+ .should('be.enabled')
+ .click()
+
+ cy.wait('@syncRepo').its('response.statusCode').should('match', /^20\d$/)
+
+ cy.getByDataTestid('snackbar').within(() => {
+ cy.get('p').should('contain', `Dépôt ${repos[0].internalRepoName} synchronisé`)
+ })
+
+ cy.getByDataTestid('branchNameInput')
+ .clear()
+
+ cy.getByDataTestid('syncRepoBtn')
+ .should('be.disabled')
+
+ cy.getByDataTestid('branchNameInput')
+ .type('develop')
+
+ cy.getByDataTestid('syncRepoBtn')
+ .should('be.enabled')
+ .click()
+
+ cy.wait('@syncRepo').its('response.statusCode').should('match', /^20\d$/)
+
+ cy.getByDataTestid('snackbar').within(() => {
+ cy.get('p').should('contain', `Dépôt ${repos[0].internalRepoName} synchronisé`)
+ })
+ })
+ })
+
it('Should generate a GitLab CI for a repo', () => {
cy.intercept('POST', '/api/v1/projects/*/repositories').as('postRepo')
cy.intercept('GET', '/api/v1/projects').as('getProjects')
diff --git a/apps/client/cypress/e2e/support/commands.ts b/apps/client/cypress/e2e/support/commands.ts
index b1d1211a9..06bc509c0 100644
--- a/apps/client/cypress/e2e/support/commands.ts
+++ b/apps/client/cypress/e2e/support/commands.ts
@@ -116,7 +116,7 @@ Cypress.Commands.add('addRepos', (project, repos) => {
newRepos.forEach((repo) => {
cy.getByDataTestid('addRepoLink').click()
- .get('h1').should('contain', 'Ajouter un dépôt au projet')
+ .get('h2').should('contain', 'Ajouter un dépôt au projet')
.getByDataTestid('internalRepoNameInput').find('input').type(repo.internalRepoName)
.getByDataTestid('externalRepoUrlInput').find('input').clear().type(repo.externalRepoUrl)
diff --git a/apps/client/src/api/repositories.ts b/apps/client/src/api/repositories.ts
index c9afb90e1..840a90808 100644
--- a/apps/client/src/api/repositories.ts
+++ b/apps/client/src/api/repositories.ts
@@ -1,4 +1,4 @@
-import type { CreateRepositoryBody, UpdateRepositoryBody, RepositoryParams } from '@cpn-console/shared'
+import type { CreateRepositoryBody, UpdateRepositoryBody, SyncRepositoryParams, RepositoryParams } from '@cpn-console/shared'
import { apiClient } from './xhr-client.js'
export const addRepo = async (projectId: RepositoryParams['projectId'], data: CreateRepositoryBody) => {
@@ -11,6 +11,11 @@ export const getRepos = async (projectId: RepositoryParams['projectId']) => {
if (response.status === 200) return response.body
}
+export const syncRepository = async (projectId: SyncRepositoryParams['projectId'], repositoryId: SyncRepositoryParams['repositoryId'], branchName: SyncRepositoryParams['branchName']) => {
+ const response = await apiClient.Repositories.syncRepository({ params: { projectId, repositoryId, branchName } })
+ return response.body
+}
+
export const updateRepo = async (projectId: RepositoryParams['projectId'], repositoryId: RepositoryParams['repositoryId'], data: UpdateRepositoryBody) => {
if (!data.id) return
const response = await apiClient.Repositories.updateRepository({ body: data, params: { projectId, repositoryId } })
diff --git a/apps/client/src/components/RepoForm.vue b/apps/client/src/components/RepoForm.vue
index dd5a93193..30fbc8e53 100644
--- a/apps/client/src/components/RepoForm.vue
+++ b/apps/client/src/components/RepoForm.vue
@@ -59,11 +59,11 @@ const cancel = () => {
data-testid="repo-form"
class="relative"
>
-
- {{ localRepo.id ? 'Modifier le dépôt' : 'Ajouter un dépôt au projet' }}
-
+ {{ localRepo.id ? `Modifier le dépôt ${localRepo.internalRepoName}` : 'Ajouter un dépôt au projet' }}
+
{
const projectStore = useProjectStore()
+ const syncRepository = async (repoId: string, branchName: string) => {
+ if (!projectStore.selectedProject) throw new Error(projectMissing)
+ await api.syncRepository(projectStore.selectedProject.id, repoId, branchName)
+ }
+
const addRepoToProject = async (newRepo: CreateRepositoryBody) => {
if (!projectStore.selectedProject) throw new Error(projectMissing)
await api.addRepo(projectStore.selectedProject.id, newRepo)
@@ -29,5 +34,6 @@ export const useProjectRepositoryStore = defineStore('project-repository', () =>
addRepoToProject,
updateRepo,
deleteRepo,
+ syncRepository,
}
})
diff --git a/apps/client/src/views/projects/DsoRepos.vue b/apps/client/src/views/projects/DsoRepos.vue
index f36707e1e..32e8bf048 100644
--- a/apps/client/src/views/projects/DsoRepos.vue
+++ b/apps/client/src/views/projects/DsoRepos.vue
@@ -23,6 +23,10 @@ const isOwner = computed(() => project.value?.roles?.some(role => role.userId ==
const repos = ref([])
const selectedRepo = ref()
const isNewRepoForm = ref(false)
+const branchName = ref('main')
+
+const repoFormId = 'repoFormId'
+const syncFormId = 'syncFormId'
const setReposTiles = (project: Project) => {
// @ts-ignore
@@ -75,6 +79,15 @@ const deleteRepo = async (repoId: Repo['id']) => {
snackbarStore.isWaitingForResponse = false
}
+const syncRepository = async () => {
+ if (!selectedRepo.value) return
+ if (!branchName.value) branchName.value = 'main'
+ snackbarStore.isWaitingForResponse = true
+ projectRepositoryStore.syncRepository(selectedRepo.value.id, branchName.value)
+ snackbarStore.isWaitingForResponse = false
+ snackbarStore.setMessage(`Dépôt ${selectedRepo.value.internalRepoName} synchronisé`, 'success')
+}
+
onMounted(() => {
if (!project.value) return
setReposTiles(project.value)
@@ -149,15 +162,57 @@ watch(project, () => {
@click="setSelectedRepo(repo.data)"
/>
- saveRepo(repo)"
- @delete="(repoId) => deleteRepo(repoId)"
- @cancel="cancel()"
- />
+ >
+
+
+
+ Synchroniser le dépôt {{ selectedRepo?.internalRepoName }}
+
+
+
+
+ saveRepo(repo)"
+ @delete="(repoId) => deleteRepo(repoId)"
+ @cancel="cancel()"
+ />
+
{
+ try {
+ await getProjectAndcheckRole(userId, projectId)
+
+ const { results } = await hook.misc.syncRepository(repositoryId, { branchName })
+
+ await addLogs('Sync Repository', results, userId, requestId)
+ if (results.failed) {
+ throw new UnprocessableContentError('Echec des opérations', undefined)
+ }
+ } catch (error) {
+ if (error instanceof DsoError) throw error
+ throw new Error('Echec de la synchronisation du dépôt')
+ }
+}
+
export const checkUpsertRepository = async (
userId: User['id'],
projectId: Project['id'],
@@ -85,6 +107,7 @@ export const createRepository = async (
return repo
} catch (error) {
+ if (error instanceof DsoError) throw error
throw new Error('Echec de la création du dépôt')
}
}
@@ -118,6 +141,7 @@ export const updateRepository = async (
return repo
} catch (error) {
+ if (error instanceof DsoError) throw error
throw new Error('Echec de la mise à jour du dépôt')
}
}
@@ -139,6 +163,7 @@ export const deleteRepository = async (
throw new UnprocessableContentError('Echec des opérations', undefined)
}
} catch (error) {
+ if (error instanceof DsoError) throw error
throw new Error('Echec de la mise à jour du dépôt')
}
}
diff --git a/apps/server/src/resources/repository/controllers.spec.ts b/apps/server/src/resources/repository/controllers.spec.ts
index fafd9fa34..34436075e 100644
--- a/apps/server/src/resources/repository/controllers.spec.ts
+++ b/apps/server/src/resources/repository/controllers.spec.ts
@@ -60,6 +60,38 @@ describe('Repository routes', () => {
})
})
+ describe('syncRepositoryController', () => {
+ it('Should sync a repository', async () => {
+ const projectInfos = createRandomDbSetup({}).project
+ projectInfos.roles = [...projectInfos.roles, getRandomRole(getRequestor().id, projectInfos.id, 'owner')]
+ const repoToSync = projectInfos.repositories[0]
+ const branchName = 'main'
+
+ prisma.project.findUnique.mockResolvedValue(projectInfos)
+
+ const response = await app.inject()
+ .get(`/api/v1/projects/${projectInfos.id}/repositories/${repoToSync.id}/sync/${branchName}`)
+ .end()
+
+ expect(response.statusCode).toEqual(204)
+ })
+
+ it('Should not sync a repository if not project member', async () => {
+ const projectInfos = createRandomDbSetup({}).project
+ const repoToSync = projectInfos.repositories[0]
+ const branchName = 'main'
+
+ prisma.project.findUnique.mockResolvedValue(projectInfos)
+
+ const response = await app.inject()
+ .get(`/api/v1/projects/${projectInfos.id}/repositories/${repoToSync.id}/sync/${branchName}`)
+ .end()
+
+ expect(response.statusCode).toEqual(403)
+ expect(JSON.parse(response.body).error).toEqual('Vous n’avez pas les permissions suffisantes dans le projet')
+ })
+ })
+
// POST
describe('createRepositoryController', () => {
it('Should create a repository', async () => {
diff --git a/apps/server/src/resources/repository/queries.ts b/apps/server/src/resources/repository/queries.ts
index 84a575de1..60e722946 100644
--- a/apps/server/src/resources/repository/queries.ts
+++ b/apps/server/src/resources/repository/queries.ts
@@ -26,6 +26,19 @@ export const initializeRepository = async ({ projectId, internalRepoName, extern
})
}
+export const getHookRepository = (id: Repository['id']) => prisma.repository.findUniqueOrThrow({
+ where: {
+ id,
+ },
+ include: {
+ project: {
+ include: {
+ organization: true,
+ },
+ },
+ },
+})
+
// UPDATE
export const updateRepository = async (id: Repository['id'], infos: Partial
) => {
return prisma.repository.update({ where: { id }, data: { ...infos } })
diff --git a/apps/server/src/resources/repository/router.ts b/apps/server/src/resources/repository/router.ts
index 0871188f8..e3991d895 100644
--- a/apps/server/src/resources/repository/router.ts
+++ b/apps/server/src/resources/repository/router.ts
@@ -1,16 +1,17 @@
-import { filterObjectByKeys } from '@/utils/queries-tools.js'
+import { repositoryContract } from '@cpn-console/shared'
+import { serverInstance } from '@/app.js'
+import { BadRequestError } from '@/utils/errors.js'
import { addReqLogs } from '@/utils/logger.js'
+import { filterObjectByKeys } from '@/utils/queries-tools.js'
import {
+ checkUpsertRepository,
createRepository,
deleteRepository,
getProjectRepositories,
getRepositoryById,
+ syncRepository,
updateRepository,
- checkUpsertRepository,
} from './business.js'
-import { BadRequestError } from '@/utils/errors.js'
-import { repositoryContract } from '@cpn-console/shared'
-import { serverInstance } from '@/app.js'
export const repositoryRouter = () => serverInstance.router(repositoryContract, {
@@ -57,6 +58,19 @@ export const repositoryRouter = () => serverInstance.router(repositoryContract,
}
},
+ // Synchroniser un repository
+ syncRepository: async ({ request: req, params }) => {
+ const userId = req.session.user.id
+ const { projectId, repositoryId, branchName } = params
+
+ await syncRepository(projectId, repositoryId, userId, branchName, req.id)
+
+ return {
+ body: null,
+ status: 204,
+ }
+ },
+
// Créer un repository
createRepository: async ({ request: req, params, body: data }) => {
const userId = req.session.user.id
diff --git a/apps/server/src/utils/hook-wrapper.ts b/apps/server/src/utils/hook-wrapper.ts
index b736502ed..7bba1d7d5 100644
--- a/apps/server/src/utils/hook-wrapper.ts
+++ b/apps/server/src/utils/hook-wrapper.ts
@@ -2,7 +2,7 @@ import type { Cluster, Project } from '@prisma/client'
import type { ClusterObject, KubeCluster, KubeUser, PluginResult, Project as ProjectPayload, RepoCreds, Repository } from '@cpn-console/hooks'
import { hooks } from '@cpn-console/hooks'
import { AsyncReturnType } from '@cpn-console/shared'
-import { archiveProject, getClusterByIdOrThrow, getHookProjectInfos, getHookPublicClusters, updateProjectCreated, updateProjectFailed, updateProjectServices } from '@/resources/queries-index.js'
+import { archiveProject, getClusterByIdOrThrow, getHookProjectInfos, getHookPublicClusters, getHookRepository, updateProjectCreated, updateProjectFailed, updateProjectServices } from '@/resources/queries-index.js'
import { genericProxy } from './proxy.js'
type ReposCreds = Record
@@ -74,9 +74,17 @@ const cluster = {
}
const misc = {
- fetchOrganizations: () => hooks.fetchOrganizations.execute({}),
- retrieveUserByEmail: (email: string) => hooks.retrieveUserByEmail.execute({ email }),
- checkServices: () => hooks.checkServices.execute({}),
+ fetchOrganizations: async () => hooks.fetchOrganizations.execute({}),
+ retrieveUserByEmail: async (email: string) => hooks.retrieveUserByEmail.execute({ email }),
+ checkServices: async () => hooks.checkServices.execute({}),
+ syncRepository: async (repoId: string, { branchName }: {branchName: string}) => {
+ const { project, ...repoInfos } = await getHookRepository(repoId)
+ const payload = {
+ repo: { ...repoInfos, branchName },
+ ...project,
+ }
+ return hooks.syncRepository.execute(payload)
+ },
}
export const hook = {
diff --git a/apps/server/src/utils/mocks.ts b/apps/server/src/utils/mocks.ts
index 538cbb16d..c54dac418 100644
--- a/apps/server/src/utils/mocks.ts
+++ b/apps/server/src/utils/mocks.ts
@@ -232,6 +232,7 @@ const misc = {
fetchOrganizations: async () => resultsFetch,
retrieveUserByEmail: async (_email: string) => resultsBase,
checkServices: async () => resultsBase,
+ syncRepository: async () => resultsBase,
}
const project = {
@@ -259,7 +260,7 @@ const cluster = {
export const mockHookWrapper = () => ({
hook: {
- misc: genericProxy(misc, { checkServices: [], fetchOrganizations: [], retrieveUserByEmail: [] }),
+ misc: genericProxy(misc, { checkServices: [], fetchOrganizations: [], retrieveUserByEmail: [], syncRepository: [] }),
project: genericProxy(project, { delete: ['upsert'], upsert: ['delete'], getSecrets: ['delete'] }),
cluster: genericProxy(cluster, { delete: ['upsert'], upsert: ['delete'] }),
},
diff --git a/packages/hooks/src/hooks/hook-misc.ts b/packages/hooks/src/hooks/hook-misc.ts
index f9ab72187..70f2029a1 100644
--- a/packages/hooks/src/hooks/hook-misc.ts
+++ b/packages/hooks/src/hooks/hook-misc.ts
@@ -1,4 +1,4 @@
-import { Project } from './hook-project.js'
+import { Project, Repository } from './hook-project.js'
import { Hook, createHook } from './hook.js'
import { UserObject } from './index.js'
@@ -14,3 +14,6 @@ export const retrieveUserByEmail: Hook = createHook()
export type ProjectLite = Pick
export const getProjectSecrets: Hook = createHook()
+
+export type UniqueRepo = ProjectLite & { repo: Omit & { branchName: string } }
+export const syncRepository: Hook = createHook()
diff --git a/packages/shared/src/contracts/repository.ts b/packages/shared/src/contracts/repository.ts
index d9bde6769..3af3ff540 100644
--- a/packages/shared/src/contracts/repository.ts
+++ b/packages/shared/src/contracts/repository.ts
@@ -6,6 +6,7 @@ import {
GetRepoByIdSchema,
UpdateRepoSchema,
DeleteRepoSchema,
+ SyncRepoSchema,
} from '../schemas/index.js'
export const repositoryContract = contractInstance.router({
@@ -38,6 +39,15 @@ export const repositoryContract = contractInstance.router({
responses: GetRepoByIdSchema.responses,
},
+ syncRepository: {
+ method: 'GET',
+ path: `${apiPrefix}/projects/:projectId/repositories/:repositoryId/sync/:branchName`,
+ pathParams: SyncRepoSchema.params,
+ summary: 'application/json',
+ description: 'Trigger a gitlab synchronization for a repository',
+ responses: SyncRepoSchema.responses,
+ },
+
updateRepository: {
method: 'PUT',
path: `${apiPrefix}/projects/:projectId/repositories/:repositoryId`,
@@ -63,4 +73,6 @@ export type CreateRepositoryBody = ClientInferRequest['body']
+export type SyncRepositoryParams = ClientInferRequest['params']
+
export type RepositoryParams = ClientInferRequest['params']
diff --git a/packages/shared/src/schemas/repository.ts b/packages/shared/src/schemas/repository.ts
index 378970052..f60fa35b2 100644
--- a/packages/shared/src/schemas/repository.ts
+++ b/packages/shared/src/schemas/repository.ts
@@ -93,6 +93,23 @@ export const UpdateRepoSchema = {
},
}
+export const SyncRepoSchema = {
+ params: z.object({
+ projectId: z.string()
+ .uuid(),
+ repositoryId: z.string()
+ .uuid(),
+ branchName: z.string(),
+ }),
+ responses: {
+ 204: null,
+ 400: ErrorSchema,
+ 401: ErrorSchema,
+ 403: ErrorSchema,
+ 500: ErrorSchema,
+ },
+}
+
export const DeleteRepoSchema = {
params: z.object({
projectId: z.string()
diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json
index 130d3b49e..8d2e0bd8b 100644
--- a/packages/shared/tsconfig.json
+++ b/packages/shared/tsconfig.json
@@ -15,6 +15,7 @@
"src/**/*.ts"
],
"exclude": [
- "coverage"
+ "coverage",
+ "**/*.spec.ts",
]
}
\ No newline at end of file
diff --git a/plugins/gitlab/src/class.ts b/plugins/gitlab/src/class.ts
index c9d2f22cf..fb49f54f9 100644
--- a/plugins/gitlab/src/class.ts
+++ b/plugins/gitlab/src/class.ts
@@ -1,8 +1,8 @@
-import { PluginApi, Project, RepoCreds } from '@cpn-console/hooks'
+import { PluginApi, type Project, type RepoCreds, type UniqueRepo } from '@cpn-console/hooks'
import { getApi, getConfig, infraAppsRepoName, internalMirrorRepoName } from './utils.js'
import { AccessTokenScopes, GroupSchema, GroupStatisticsSchema, MemberSchema, ProjectVariableSchema, VariableSchema } from '@gitbeaker/rest'
import { getOrganizationId } from './group.js'
-import { AccessLevel, Gitlab } from '@gitbeaker/core'
+import { AccessLevel, CondensedProjectSchema, Gitlab } from '@gitbeaker/core'
import { VaultProjectApi } from '@cpn-console/vault-plugin/types/class.js'
type setVariableResult = 'created' | 'updated' | 'already up-to-date'
@@ -13,14 +13,19 @@ type GitlabMirrorSecret = {
MIRROR_TOKEN: string,
}
+type RepoSelect = {
+ mirror?: CondensedProjectSchema
+ target?: CondensedProjectSchema
+}
+
export class GitlabProjectApi extends PluginApi {
private api: Gitlab
- private project: Project
+ private project: Project | UniqueRepo
private gitlabGroup: GroupSchema & { statistics: GroupStatisticsSchema } | undefined
private specialRepositories: string[] = [infraAppsRepoName, internalMirrorRepoName]
// private organizationGroup: GroupSchema & { statistics: GroupStatisticsSchema } | undefined
- constructor (project: Project) {
+ constructor (project: Project | UniqueRepo) {
super()
this.project = project
this.api = getApi()
@@ -39,7 +44,6 @@ export class GitlabProjectApi extends PluginApi {
projectCreationLevel: 'maintainer',
subgroupCreationLevel: 'owner',
defaultBranchProtection: 0,
- description: this.project.description ?? '',
})
}
@@ -200,9 +204,9 @@ export class GitlabProjectApi extends PluginApi {
} else {
if (
currentVariable.masked !== toSetVariable.masked ||
- currentVariable.value !== toSetVariable.value ||
- currentVariable.protected !== toSetVariable.protected ||
- currentVariable.variable_type !== toSetVariable.variable_type
+ currentVariable.value !== toSetVariable.value ||
+ currentVariable.protected !== toSetVariable.protected ||
+ currentVariable.variable_type !== toSetVariable.variable_type
) {
await this.api.GroupVariables.edit(
group.id,
@@ -231,9 +235,9 @@ export class GitlabProjectApi extends PluginApi {
if (currentVariable) {
if (
currentVariable.masked !== toSetVariable.masked ||
- currentVariable.value !== toSetVariable.value ||
- currentVariable.protected !== toSetVariable.protected ||
- currentVariable.variable_type !== toSetVariable.variable_type
+ currentVariable.value !== toSetVariable.value ||
+ currentVariable.protected !== toSetVariable.protected ||
+ currentVariable.variable_type !== toSetVariable.variable_type
) {
await this.api.ProjectVariables.edit(
repository.id,
@@ -263,4 +267,33 @@ export class GitlabProjectApi extends PluginApi {
return 'created'
}
}
+
+ // Mirror
+ public async triggerMirror (targetRepo: string, branchName: string) {
+ if ((await this.getSpecialRepositories()).includes(targetRepo)) throw new Error('User requested for invalid mirroring')
+ const repos = await this.listRepositories()
+ const { mirror, target }: RepoSelect = repos.reduce((acc, repository) => {
+ if (repository.name === 'mirror') {
+ acc.mirror = repository
+ }
+ if (repository.name === targetRepo) {
+ acc.target = repository
+ }
+ return acc
+ }, {} as RepoSelect)
+ if (!mirror) throw new Error('Unable to find mirror repository')
+ if (!target) throw new Error('Unable to find target repository')
+ return this.api.Pipelines.create(mirror.id, 'main', {
+ variables: [
+ {
+ key: 'GIT_BRANCH_DEPLOY',
+ value: branchName,
+ },
+ {
+ key: 'PROJECT_NAME',
+ value: targetRepo,
+ },
+ ],
+ })
+ }
}
diff --git a/plugins/gitlab/src/functions.ts b/plugins/gitlab/src/functions.ts
index 1fe2c41c6..25fc3ef77 100644
--- a/plugins/gitlab/src/functions.ts
+++ b/plugins/gitlab/src/functions.ts
@@ -1,4 +1,4 @@
-import { type StepCall, type Project, type ProjectLite, parseError } from '@cpn-console/hooks'
+import { type StepCall, type Project, type ProjectLite, parseError, type UniqueRepo } from '@cpn-console/hooks'
import { deleteGroup } from './group.js'
import { createUsername, getUser } from './user.js'
import { ensureMembers } from './members.js'
@@ -131,3 +131,25 @@ export const deleteDsoProject: StepCall = async (payload) => {
}
}
}
+
+export const syncRepository: StepCall = async (payload) => {
+ const targetRepo = payload.args.repo
+ const gitlabApi = payload.apis.gitlab
+ try {
+ await gitlabApi.triggerMirror(targetRepo.internalRepoName, targetRepo.branchName)
+ return {
+ status: {
+ result: 'OK',
+ message: 'Ci launched',
+ },
+ }
+ } catch (error) {
+ return {
+ error: parseError(error),
+ status: {
+ result: 'KO',
+ message: 'Failed to trigger sync',
+ },
+ }
+ }
+}
diff --git a/plugins/gitlab/src/index.ts b/plugins/gitlab/src/index.ts
index 1fd676d41..ecf1a4252 100644
--- a/plugins/gitlab/src/index.ts
+++ b/plugins/gitlab/src/index.ts
@@ -1,16 +1,17 @@
-import type { Plugin, Project, DefaultArgs } from '@cpn-console/hooks'
+import type { Plugin, Project, DefaultArgs, UniqueRepo } from '@cpn-console/hooks'
import {
checkApi,
getDsoProjectSecrets,
deleteDsoProject,
upsertDsoProject,
+ syncRepository,
} from './functions.js'
import { getGroupRootId } from './utils.js'
import infos from './infos.js'
import monitor from './monitor.js'
import { GitlabProjectApi } from './class.js'
-const onlyApi = { api: (project: Project) => new GitlabProjectApi(project) }
+const onlyApi = { api: (project: Project | UniqueRepo) => new GitlabProjectApi(project) }
const start = () => {
getGroupRootId()
@@ -31,6 +32,12 @@ export const plugin: Plugin = {
},
},
getProjectSecrets: { steps: { main: getDsoProjectSecrets } },
+ syncRepository: {
+ ...onlyApi,
+ steps: {
+ main: syncRepository,
+ },
+ },
},
monitor,
start,
@@ -38,7 +45,7 @@ export const plugin: Plugin = {
declare module '@cpn-console/hooks' {
interface HookPayloadApis {
- gitlab: Args extends Project
+ gitlab: Args extends Project | UniqueRepo
? GitlabProjectApi
: undefined
}