Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9c8ed8e
feat: add getRepoBranches endpoint
ferruhcihan Mar 4, 2025
742ac23
Merge branch 'main' into APL-535
ferruhcihan Mar 7, 2025
2b636e0
Merge branch 'main' into APL-535
ferruhcihan Mar 18, 2025
5b8f54f
feat: update build schema
ferruhcihan Mar 24, 2025
c3203b1
Merge branch 'main' into APL-535
ferruhcihan Mar 24, 2025
77d777f
feat: update build schema
ferruhcihan Mar 25, 2025
e26e8cd
feat: update codeRepoUtils tests
ferruhcihan Mar 25, 2025
2f120b5
feat: update create code repo
ferruhcihan Mar 30, 2025
9035230
fix: code repo name field
ferruhcihan Mar 31, 2025
44a4e08
Merge branch 'main' into APL-535
ferruhcihan Mar 31, 2025
e36cd36
Merge branch 'main' into APL-535
ferruhcihan Apr 1, 2025
66d7e8f
fix: get public repo branches
ferruhcihan Apr 1, 2025
dd8c395
fix: get gitea repos branch&tag names
ferruhcihan Apr 1, 2025
a2d1053
fix: get build status
ferruhcihan Apr 1, 2025
a6d51ae
Merge branch 'main' into APL-535
ferruhcihan Apr 4, 2025
e21daff
Merge branch 'main' into APL-535
ferruhcihan Apr 4, 2025
22946bc
feat: update create build & get repo branches
ferruhcihan Apr 4, 2025
7c41e70
fix: create code repos
ferruhcihan Apr 7, 2025
44ccc11
fix: code repo err public message
ferruhcihan Apr 7, 2025
861ac4d
Merge branch 'main' into APL-535
ferruhcihan Apr 9, 2025
af3330b
fix: get repo branches
ferruhcihan Apr 9, 2025
2f9f2ad
fix: update create/edit coderepos for gitea
ferruhcihan Apr 10, 2025
3f296fa
Merge branch 'main' into APL-535
ferruhcihan Apr 10, 2025
0be59e5
Merge branch 'main' into APL-535
dennisvankekem Apr 11, 2025
5066c49
feat: improve code repository functions
ferruhcihan Apr 11, 2025
ff8754c
Merge branch 'main' into APL-535
ferruhcihan Apr 11, 2025
5e4703f
fix: createAplBuild and tests
ferruhcihan Apr 11, 2025
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
19 changes: 19 additions & 0 deletions src/api/v1/repoBranches.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Debug from 'debug'
import { Operation, OperationHandlerArray } from 'express-openapi'
import { OpenApiRequestExt } from 'src/otomi-models'

const debug = Debug('otomi:api:repoBranches')

export default function (): OperationHandlerArray {
const get: Operation = [
async ({ otomi, query }: OpenApiRequestExt, res): Promise<void> => {
debug(`getRepoBranches`, query)
const { codeRepoName, teamId }: { codeRepoName: string; teamId: string } = query as any
res.json(await otomi.getRepoBranches(codeRepoName, teamId))
},
]
const api = {
get,
}
return api
}
30 changes: 29 additions & 1 deletion src/openapi/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2247,10 +2247,36 @@ paths:
schema:
$ref: '#/components/schemas/SettingsInfo'

/v1/repoBranches:
get:
operationId: getRepoBranches
description: Get repo branches from the git providers.
x-aclSchema: RepoBranches
parameters:
- name: codeRepoName
in: query
description: Name of the code repository
schema:
type: string
- name: teamId
in: query
description: Id of the team
schema:
type: string
responses:
'200':
description: The request is successful.
content:
application/json:
schema:
type: array
items:
type: string

/v1/testRepoConnect:
get:
operationId: getTestRepoConnect
description: Get settings information from the 'settings.yaml' file.
description: Get test repo connect information from the git providers.
x-aclSchema: TestRepoConnect
parameters:
- name: url
Expand Down Expand Up @@ -2912,6 +2938,8 @@ components:
$ref: 'settings.yaml#/Settings'
SettingsInfo:
$ref: 'settingsinfo.yaml#/SettingsInfo'
RepoBranches:
$ref: 'repobranches.yaml#/RepoBranches'
TestRepoConnect:
$ref: 'testrepoconnect.yaml#/TestRepoConnect'
InternalRepoUrls:
Expand Down
6 changes: 6 additions & 0 deletions src/openapi/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Build:
title: Name
$ref: 'definitions.yaml#/idName'
description: Results in image name harbor.<your-domain>/<team-name>/name.
imageName:
title: Image name
$ref: 'definitions.yaml#/idName'
tag:
$ref: 'definitions.yaml#/imageTag'
default: latest
Expand Down Expand Up @@ -53,6 +56,9 @@ Build:
AplBuildSpec:
type: object
properties:
imageName:
title: Image name
$ref: 'definitions.yaml#/idName'
tag:
$ref: 'definitions.yaml#/imageTag'
default: latest
Expand Down
7 changes: 7 additions & 0 deletions src/openapi/repobranches.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
RepoBranches:
x-acl:
platformAdmin: [read-any]
teamAdmin: [read-any]
teamMember: [read]
properties: {}
type: object
1 change: 1 addition & 0 deletions src/otomi-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type Session = components['schemas']['Session']
export type ObjWizard = components['schemas']['ObjWizard']
export type Settings = components['schemas']['Settings']
export type SettingsInfo = components['schemas']['SettingsInfo']
export type RepoBranches = components['schemas']['RepoBranches']
export type TestRepoConnect = components['schemas']['TestRepoConnect']
export type InternalRepoUrls = components['schemas']['InternalRepoUrls']
export type Team = components['schemas']['Team']
Expand Down
60 changes: 55 additions & 5 deletions src/otomi-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,17 @@ import { TeamConfigService } from './services/TeamConfigService'
import { validateBackupFields } from './utils/backupUtils'
import {
getGiteaRepoUrls,
getPrivateRepoBranches,
getPublicRepoBranches,
normalizeRepoUrl,
testPrivateRepoConnect,
testPublicRepoConnect,
} from './utils/codeRepoUtils'
import { getAplObjectFromV1, getV1MergeObject, getV1ObjectFromApl, getV1SealedSecretFromApl } from './utils/manifests'
import { getEncryptedData, sealedSecretManifest, SealedSecretManifestType } from './utils/sealedSecretUtils'
import { getKeycloakUsers, isValidUsername } from './utils/userUtils'
import { ObjectStorageClient } from './utils/wizardUtils'
import { fetchChartYaml, fetchWorkloadCatalog, NewHelmChartValues, sparseCloneChart } from './utils/workloadUtils'
import { getAplObjectFromV1, getV1MergeObject, getV1ObjectFromApl, getV1SealedSecretFromApl } from './utils/manifests'

interface ExcludedApp extends App {
managed: boolean
Expand Down Expand Up @@ -1275,8 +1277,11 @@ export default class OtomiStack {
}

async createAplCodeRepo(teamId: string, data: AplCodeRepoRequest): Promise<AplCodeRepoResponse> {
const allRepoUrls = this.getAllAplCodeRepos().map((repo) => repo.spec.repositoryUrl) || []
if (allRepoUrls.includes(data.spec.repositoryUrl)) throw new AlreadyExists('Code repository URL already exists')
if (!data.spec.private) unset(data.spec, 'secret')
if (data.spec.gitService === 'gitea') unset(data.spec, 'private')
try {
if (!data.spec.private) unset(data.spec, 'secret')
const codeRepo = this.repoService.getTeamConfigService(teamId).createCodeRepo(data)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that the try block should start from here. Then you do not need a complicated catch statement and throw AlreadyExists exception.

await this.saveTeamConfigItem(codeRepo)
await this.doTeamDeployment(
Expand All @@ -1288,7 +1293,9 @@ export default class OtomiStack {
)
return codeRepo
} catch (err) {
if (err.code === 409) err.publicMessage = 'Code repo label already exists'
if (err.code === 409) {
err.publicMessage = 'Code repo name already exists'
}
throw err
}
}
Expand All @@ -1314,6 +1321,7 @@ export default class OtomiStack {
patch = false,
): Promise<AplCodeRepoResponse> {
if (!data.spec?.private) unset(data.spec, 'secret')
if (data.spec?.gitService === 'gitea') unset(data.spec, 'private')
const codeRepo = patch
? this.repoService.getTeamConfigService(teamId).patchCodeRepo(name, data)
: this.repoService.getTeamConfigService(teamId).updateCodeRepo(name, data as AplCodeRepoRequest)
Expand Down Expand Up @@ -1341,7 +1349,10 @@ export default class OtomiStack {
)
}

async getTestRepoConnect(url: string, teamId: string, secretName: string): Promise<TestRepoConnect> {
async getRepoBranches(codeRepoName: string, teamId: string): Promise<string[]> {
if (!codeRepoName) return ['HEAD']
const coderepo = this.getCodeRepo(teamId, codeRepoName)
const { repositoryUrl, secret: secretName } = coderepo
try {
let sshPrivateKey = '',
username = '',
Expand All @@ -1355,6 +1366,38 @@ export default class OtomiStack {
}

const isPrivate = !!secretName
const isSSH = !!sshPrivateKey
const repoUrl = repositoryUrl.startsWith('https://gitea')
? repositoryUrl
: normalizeRepoUrl(repositoryUrl, isPrivate, isSSH)

if (!repoUrl) return ['HEAD']

if (isPrivate) return await getPrivateRepoBranches(repoUrl, sshPrivateKey, username, accessToken)
Copy link

Copilot AI Apr 11, 2025

Choose a reason for hiding this comment

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

For private repositories, secret values (sshPrivateKey, username, accessToken) are not being retrieved prior to calling getPrivateRepoBranches, which may lead to authentication failures. Consider fetching these secret values (using getSecretValues) when secretName is present.

Copilot uses AI. Check for mistakes.

return await getPublicRepoBranches(repoUrl)
} catch (error) {
const errorMessage = error.response?.data?.message || error?.message || 'Failed to get repo branches'
debug('Error getting branches:', errorMessage)
return []
Copy link
Contributor

Choose a reason for hiding this comment

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

No error log?

}
}

async getTestRepoConnect(url: string, teamId: string, secretName: string): Promise<TestRepoConnect> {
try {
let sshPrivateKey = '',
username = '',
accessToken = ''

const isPrivate = !!secretName

if (isPrivate) {
const secret = await getSecretValues(secretName, `team-${teamId}`)
sshPrivateKey = secret?.['ssh-privatekey'] || ''
username = secret?.username || ''
accessToken = secret?.password || ''
}

const isSSH = !!sshPrivateKey
const repoUrl = normalizeRepoUrl(url, isPrivate, isSSH)

Expand Down Expand Up @@ -1422,6 +1465,12 @@ export default class OtomiStack {
}

async createAplBuild(teamId: string, data: AplBuildRequest): Promise<AplBuildResponse> {
const buildName = `${data?.spec?.imageName}-${data?.spec?.tag}`
if (buildName.length > 128)
throw new HttpError(
400,
'Invalid container image name, the combined image name and tag must not exceed 128 characters.',
)
try {
const build = this.repoService.getTeamConfigService(teamId).createBuild(data)
await this.saveTeamConfigItem(build)
Expand All @@ -1434,7 +1483,8 @@ export default class OtomiStack {
)
return build
} catch (err) {
if (err.code === 409) err.publicMessage = 'Build name already exists'
if (err.code === 409)
err.publicMessage = 'Container image name already exists, the combined image name and tag must be unique.'
throw err
}
}
Expand Down
1 change: 1 addition & 0 deletions src/services/TeamConfigService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function mergeCustomizer(prev, next) {

export class TeamConfigService {
constructor(private teamConfig: TeamConfig) {
this.teamConfig.codeRepos ??= []
this.teamConfig.builds ??= []
this.teamConfig.workloads ??= []
this.teamConfig.services ??= []
Expand Down
Loading
Loading