Skip to content
Merged
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
20 changes: 20 additions & 0 deletions apps/server-nestjs/src/modules/argocd/argocd-datastore.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ export const projectSelect = {
internalRepoName: true,
},
},
internalValueSources: {
orderBy: { order: 'asc' },
select: {
order: true,
path: true,
},
},
externalValueSource: {
select: {
order: true,
path: true,
ref: true,
targetRevision: true,
repository: {
select: {
internalRepoName: true,
},
},
},
},
},
},
},
Expand Down
27 changes: 27 additions & 0 deletions apps/server-nestjs/src/modules/argocd/argocd-testing.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,35 @@ export function makeProjectDeploymentSource(
targetRevision: 'HEAD',
helmValuesFiles: '',
repository: makeProjectRepository(),
internalValueSources: [],
externalValueSource: null,
...overrides,
} satisfies ProjectWithDetails['deployments'][number]['deploymentSources'][number]
}

export function makeProjectInternalValueSource(
overrides: Partial<ProjectWithDetails['deployments'][number]['deploymentSources'][number]['internalValueSources'][number]> = {},
): ProjectWithDetails['deployments'][number]['deploymentSources'][number]['internalValueSources'][number] {
return {
order: 0,
path: 'values.yaml',
...overrides,
} satisfies ProjectWithDetails['deployments'][number]['deploymentSources'][number]['internalValueSources'][number]
}

export function makeProjectExternalValueSource(
overrides: Partial<NonNullable<ProjectWithDetails['deployments'][number]['deploymentSources'][number]['externalValueSource']>> = {},
): NonNullable<ProjectWithDetails['deployments'][number]['deploymentSources'][number]['externalValueSource']> {
return {
order: 0,
path: 'values.yaml',
ref: '',
targetRevision: '',
repository: { internalRepoName: faker.word.noun() },
...overrides,
} satisfies NonNullable<ProjectWithDetails['deployments'][number]['deploymentSources'][number]['externalValueSource']>
}

export function makeProjectDeployment(
overrides: Partial<ProjectWithDetails['deployments'][number]> = {},
): ProjectWithDetails['deployments'][number] {
Expand All @@ -30,6 +55,8 @@ export function makeProjectDeployment(
targetRevision: 'HEAD',
helmValuesFiles: '',
repository: makeProjectRepository(),
internalValueSources: [],
externalValueSource: null,
},
],
...overrides,
Expand Down
89 changes: 87 additions & 2 deletions apps/server-nestjs/src/modules/argocd/argocd.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@ import { generateNamespaceName } from '@cpn-console/shared'
import { Test } from '@nestjs/testing'
import { beforeEach, describe, expect, it } from 'vitest'
import { mockDeep } from 'vitest-mock-extended'
import { stringify } from 'yaml'
import { parse, stringify } from 'yaml'
import { GitlabClientService } from '../gitlab/gitlab-client.service'
import { makeCommitAction, makeProjectSchema, makeRepositoryTreeSchema } from '../gitlab/gitlab-testing.utils'
import { ConfigurationService } from '../infrastructure/configuration/configuration.service'
import { VaultClientService } from '../vault/vault-client.service'
import { ArgoCDDatastoreService } from './argocd-datastore.service'
import { makeProjectDeployment, makeProjectDeploymentSource, makeProjectEnvironment, makeProjectRepository, makeProjectWithDetails } from './argocd-testing.utils'
import {
makeProjectDeployment,
makeProjectDeploymentSource,
makeProjectEnvironment,
makeProjectExternalValueSource,
makeProjectInternalValueSource,
makeProjectRepository,
makeProjectWithDetails,
} from './argocd-testing.utils'
import { ArgoCDService } from './argocd.service'

describe('argoCDService', () => {
Expand Down Expand Up @@ -206,6 +214,7 @@ describe('argoCDService', () => {
targetRevision: 'HEAD',
path: '.',
valueFiles: [],
valueSources: [],
},
],
},
Expand Down Expand Up @@ -281,6 +290,7 @@ describe('argoCDService', () => {
targetRevision: 'HEAD',
path: '.',
valueFiles: [],
valueSources: [],
},
],
},
Expand Down Expand Up @@ -541,6 +551,7 @@ describe('argoCDService', () => {
targetRevision: 'dev',
path: '.',
valueFiles: [],
valueSources: [],
},
{
name: 'infra-repo',
Expand All @@ -549,6 +560,7 @@ describe('argoCDService', () => {
targetRevision: '1.0.0',
path: 'service-1',
valueFiles: [],
valueSources: [],
},
],
},
Expand All @@ -570,4 +582,77 @@ describe('argoCDService', () => {

expect(gitlab.generateCreateOrUpdateAction).toHaveBeenCalledTimes(4) // 2 environments + 2 deployments
})

it('should generate multi-source valueSources (internal + external) for a deployment', async () => {
const mockDevEnv = makeProjectEnvironment({
name: 'dev',
cluster: {
id: 'c1',
label: 'cluster-1',
zone: { slug: 'zone-1' },
},
})
const mockAppRepo = makeProjectRepository({ internalRepoName: 'app-repo' })
const mockValueRepo = makeProjectRepository({ internalRepoName: 'infra-values' })
const mockProject = makeProjectWithDetails({
name: 'Project 1',
slug: 'project-1',
environments: [mockDevEnv],
repositories: [mockAppRepo, mockValueRepo],
plugins: [],
deployments: [
makeProjectDeployment({
environment: mockDevEnv,
deploymentSources: [
makeProjectDeploymentSource({
repository: mockAppRepo,
targetRevision: 'dev',
internalValueSources: [
makeProjectInternalValueSource({ order: 0, path: 'values.yaml' }),
],
externalValueSource: makeProjectExternalValueSource({
order: 1,
ref: 'infra-values',
path: 'envs/dev/values.yaml',
targetRevision: 'main',
repository: { internalRepoName: 'infra-values' },
}),
}),
],
}),
],
})

const infraProject = makeProjectSchema({ id: 100, http_url_to_repo: 'https://gitlab.internal/infra' })
datastore.getAllProjects.mockResolvedValue([mockProject])
gitlab.getOrCreateInfraGroupRepo.mockResolvedValue(infraProject)
gitlab.getOrCreateProjectGroupPublicUrl.mockResolvedValue('https://gitlab.internal/group')
gitlab.getOrCreateInfraGroupRepoPublicUrl.mockResolvedValue('https://gitlab.internal/infra-repo')
gitlab.listFiles.mockResolvedValue([])
vault.getAuthApproleRoleRoleId.mockResolvedValue('role-id')
vault.createAuthApproleRoleSecretId.mockResolvedValue('secret-id')
gitlab.generateCreateOrUpdateAction.mockImplementation(async (_repoId, _ref, filePath: string, content: string) => {
return makeCommitAction({ filePath, content })
})

await expect(service.handleCron()).resolves.not.toThrow()

const actions = gitlab.maybeCreateCommit.mock.calls[0][2]
const parsedValues = actions
.filter((action): action is typeof action & { content: string } => 'content' in action)
.map(action => parse(action.content))
const values = parsedValues.find(v => v.application?.repositories?.[0]?.valueSources?.length)
expect(values).toBeDefined()

expect(values.application.repositories[0].valueSources).toStrictEqual([
{ type: 'internal', path: 'values.yaml' },
{
type: 'external',
ref: 'infra-values',
repoURL: 'https://gitlab.internal/group/project-1/infra-values.git',
targetRevision: 'main',
path: 'envs/dev/values.yaml',
},
])
})
})
35 changes: 35 additions & 0 deletions apps/server-nestjs/src/modules/argocd/argocd.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,10 @@ export class ArgoCDService {
}
}

type ValueSource
= | { type: 'internal', path: string }
| { type: 'external', ref: string, repoURL: string, targetRevision: string, path: string }
Comment thread
KepoParis marked this conversation as resolved.

interface ValuesSchema {
common: {
'dso/project': string
Expand Down Expand Up @@ -401,6 +405,7 @@ interface ValuesSchema {
targetRevision: string
path: string
valueFiles: string[]
valueSources: ValueSource[]
}[]
}
features: {
Expand Down Expand Up @@ -459,10 +464,38 @@ function formatRepositoriesValues(
targetRevision: repository.deployRevision || 'HEAD',
path: repository.deployPath || '.',
valueFiles,
valueSources: [],
} satisfies ValuesSchema['application']['repositories'][number]
})
}

type DeploymentSource = ProjectWithDetails['deployments'][number]['deploymentSources'][number]

function formatDeploymentSourceValueSources(
source: DeploymentSource,
gitlabPublicProjectUrl: string,
): ValueSource[] {
const internalValueSources = source.internalValueSources.map(valueSource => ({
order: valueSource.order,
value: { type: 'internal', path: valueSource.path } satisfies ValueSource,
}))
const externalValueSource = source.externalValueSource
? [{
order: source.externalValueSource.order,
value: {
type: 'external',
ref: source.externalValueSource.ref,
repoURL: `${gitlabPublicProjectUrl}/${source.externalValueSource.repository.internalRepoName}.git`,
targetRevision: source.externalValueSource.targetRevision || 'HEAD',
path: source.externalValueSource.path,
} satisfies ValueSource,
}]
: []
return [...internalValueSources, ...externalValueSource]
.sort((a, b) => a.order - b.order)
.map(entry => entry.value)
}

function formatRepositoriesValuesFromDeployments(
deployments: ProjectWithDetails['deployments'][number][],
gitlabPublicProjectUrl: string,
Expand All @@ -472,13 +505,15 @@ function formatRepositoriesValuesFromDeployments(
deployment.deploymentSources
.map((source) => {
const valueFiles = splitExtraRepositories(source.helmValuesFiles?.replaceAll('<env>', envName))
const valueSources = formatDeploymentSourceValueSources(source, gitlabPublicProjectUrl)
return {
name: source.repository.internalRepoName,
id: source.repository.id,
repoURL: `${gitlabPublicProjectUrl}/${source.repository.internalRepoName}.git`,
targetRevision: source.targetRevision || 'HEAD',
path: source.path || '.',
valueFiles,
valueSources,
} satisfies ValuesSchema['application']['repositories'][number]
}),
)
Expand Down
20 changes: 20 additions & 0 deletions apps/server-nestjs/src/modules/project/project-queries.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,26 @@ export const projectSelect = {
internalRepoName: true,
},
},
internalValueSources: {
orderBy: { order: 'asc' },
select: {
order: true,
path: true,
},
},
externalValueSource: {
select: {
order: true,
path: true,
ref: true,
targetRevision: true,
repository: {
select: {
internalRepoName: true,
},
},
},
},
},
},
},
Expand Down