diff --git a/backend/helm/cloudhost-platform/templates/backend-deployment.yaml b/backend/helm/cloudhost-platform/templates/backend-deployment.yaml index 967333b..a3f1241 100644 --- a/backend/helm/cloudhost-platform/templates/backend-deployment.yaml +++ b/backend/helm/cloudhost-platform/templates/backend-deployment.yaml @@ -125,6 +125,15 @@ spec: name: {{ include "cloudhost-platform.secretName" . }} key: mizbansms-password {{- end }} + {{- if .Values.registry.credentialsSecret }} + - name: REGISTRY_USERNAME + value: {{ .Values.registry.username | default "harbor_registry_user" | quote }} + - name: REGISTRY_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.registry.credentialsSecret | quote }} + key: {{ .Values.registry.credentialsPasswordKey | default "REGISTRY_CREDENTIAL_PASSWORD" | quote }} + {{- end }} {{- include "cloudhost-platform.buildEnv" . | nindent 12 }} {{- range $key, $val := .Values.backend.env }} - name: {{ $key }} diff --git a/backend/helm/cloudhost-platform/values.yaml b/backend/helm/cloudhost-platform/values.yaml index e82dcfe..78ea627 100644 --- a/backend/helm/cloudhost-platform/values.yaml +++ b/backend/helm/cloudhost-platform/values.yaml @@ -28,6 +28,12 @@ images: tag: "1.0.0" pullPolicy: IfNotPresent +# Kaniko push credentials — harbor_registry_user for harbor-registry:5000 (Harbor production). +registry: + credentialsSecret: "" + credentialsPasswordKey: REGISTRY_CREDENTIAL_PASSWORD + username: harbor_registry_user + # Kaniko job images — defaults pull from Harbor proxy-cache. # Override any line for a different registry/tag. build: diff --git a/backend/src/build/build.service.ts b/backend/src/build/build.service.ts index 3fe04c9..c5b49c7 100644 --- a/backend/src/build/build.service.ts +++ b/backend/src/build/build.service.ts @@ -434,10 +434,10 @@ export class BuildService { * Returns { imageUri, buildLog } — the full image URI and the build logs. */ async buildImage(app: Application, deploymentId?: string): Promise<{ imageUri: string; buildLog: string }> { - const registryUrl = this.registryService.getRegistryUrl(); + const registryPushUrl = this.registryService.getRegistryPushUrl(); const buildNamespace = this.registryService.getBuildNamespace(); const tag = `${Date.now()}`; - const imageUri = this.registryService.buildImageReference(app.userId, app.name, tag); + const imageUri = this.registryService.buildPushImageReference(app.userId, app.name, tag); this.logger.log(`Starting image build for ${app.name} → ${imageUri}`); @@ -534,7 +534,7 @@ export class BuildService { '--context=dir:///workspace/source', `--destination=${imageUri}`, '--cache=true', - `--cache-repo=${registryUrl}/${app.userId}/cache`, + `--cache-repo=${registryPushUrl}/${app.userId}/cache`, '--insecure', '--skip-tls-verify', '--single-snapshot', @@ -544,7 +544,10 @@ export class BuildService { const volumes: any[] = [ { name: 'docker-config', - secret: { secretName: 'registry-credentials' }, + secret: { + secretName: 'registry-credentials', + items: [{ key: '.dockerconfigjson', path: 'config.json' }], + }, }, { name: 'dockerfile', diff --git a/backend/src/kubernetes/registry.service.ts b/backend/src/kubernetes/registry.service.ts index 013597a..cdc9cc9 100644 --- a/backend/src/kubernetes/registry.service.ts +++ b/backend/src/kubernetes/registry.service.ts @@ -35,13 +35,20 @@ export class RegistryService { return slash === -1 ? url : url.slice(0, slash); } - /** Push target host:port (may differ from pull URL on Harbor setups). */ - getRegistryPushHost(): string { + /** Push target host[:port][/project] — Kaniko destination (may differ from pull URL on Harbor). */ + getRegistryPushUrl(): string { const buildNs = this.getBuildNamespace(); - const url = this.configService.get('registry.url') || `registry.${buildNs}.svc.cluster.local:5000`; - const normalized = url.replace(/^https?:\/\//, ''); - const slash = normalized.indexOf('/'); - return slash === -1 ? normalized : normalized.slice(0, slash); + const url = + this.configService.get('registry.url') || + `registry.${buildNs}.svc.cluster.local:5000`; + return url.replace(/^https?:\/\//, ''); + } + + /** Push target host:port only, no repository path prefix. */ + getRegistryPushHost(): string { + const url = this.getRegistryPushUrl(); + const slash = url.indexOf('/'); + return slash === -1 ? url : url.slice(0, slash); } getRegistryCredentials(): { username: string; password: string } { @@ -55,6 +62,11 @@ export class RegistryService { return `${this.getRegistryUrl()}/${userId}/${appName}:${tag}`; } + /** Kaniko push target — uses registry.url (in-cluster harbor-registry on Harbor setups). */ + buildPushImageReference(userId: string, appName: string, tag: string): string { + return `${this.getRegistryPushUrl()}/${userId}/${appName}:${tag}`; + } + parseImageReference(imageRef: string): ParsedImageReference { const normalized = imageRef.replace(/^https?:\/\//, ''); const slashIdx = normalized.indexOf('/'); @@ -72,10 +84,20 @@ export class RegistryService { }; } - /** Re-point any stored image (e.g. legacy external host) to the in-cluster registry. */ + /** Re-point any stored image (e.g. push host) to the pull registry URL for kubelet. */ normalizeImageReference(imageRef: string): string { const { repository, tag } = this.parseImageReference(imageRef); - return `${this.getRegistryUrl()}/${repository}:${tag}`; + const pullBase = this.getRegistryUrl().replace(/\/$/, ''); + const slash = pullBase.indexOf('/'); + const pullPath = slash === -1 ? '' : pullBase.slice(slash + 1); + let repo = repository; + if (pullPath && (repo === pullPath || repo.startsWith(`${pullPath}/`))) { + repo = repo === pullPath ? '' : repo.slice(pullPath.length + 1); + } + if (!repo) { + throw new Error(`Invalid image reference after normalization: ${imageRef}`); + } + return `${pullBase}/${repo}:${tag}`; } buildDockerConfigJson(): string { diff --git a/gitops/platform/values-abrban.example.yaml b/gitops/platform/values-abrban.example.yaml index 9a59a41..39a4868 100644 --- a/gitops/platform/values-abrban.example.yaml +++ b/gitops/platform/values-abrban.example.yaml @@ -11,6 +11,11 @@ namespace: cloudhost createNamespace: false +registry: + credentialsSecret: harbor-core + credentialsPasswordKey: REGISTRY_CREDENTIAL_PASSWORD + username: harbor_registry_user + global: storageClass: local-path