diff --git a/src/git.ts b/src/git.ts index b9959db29..93d013844 100644 --- a/src/git.ts +++ b/src/git.ts @@ -25,7 +25,7 @@ import { BASEURL } from './constants' import { GitPullError, HttpError, ValidationError } from './error' import { DbMessage, getIo } from './middleware' import { Core } from './otomi-models' -import { FileMap, getFilePath, renderManifest, renderManifestForSecrets } from './repo' +import { FileMap, getFilePath, getResourceName, renderManifest, renderManifestForSecrets } from './repo' import { getSanitizedErrorMessage, removeBlankAttributes } from './utils' const debug = Debug('otomi:repo') @@ -283,7 +283,8 @@ export class Git { const nodeValue = node.value try { const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') - const manifest = renderManifestForSecrets(fileMap, nodeValue) + const resourceName = getResourceName(fileMap, nodePath, nodeValue) + const manifest = renderManifestForSecrets(fileMap, resourceName, nodeValue) await this.writeFile(filePath, manifest, unsetBlankAttributes) } catch (e) { console.log(nodePath) diff --git a/src/openapi/api.yaml b/src/openapi/api.yaml index 1986bf45a..c407e0ca5 100644 --- a/src/openapi/api.yaml +++ b/src/openapi/api.yaml @@ -5,6 +5,8 @@ info: title: The otomi API description: Holds the entire schema needed by console to autogenerate forms version: 2.0.0b1 +servers: + - url: '/' externalDocs: description: 'This is the base url of the external documentation' @@ -1473,6 +1475,7 @@ paths: schema: type: array items: + type: object properties: id: $ref: '#/components/schemas/User/properties/id' @@ -2218,7 +2221,7 @@ paths: /v1/apiDocs: get: - operationId: 'apiDocs' + operationId: 'v1apiDocs' security: [] description: Get OpenAPIDoc document responses: @@ -2722,7 +2725,7 @@ components: type: string enum: [AplTeamProject] spec: - $ref: 'project.yaml#/AplProjectSpec' + type: object required: - kind - spec diff --git a/src/openapi/backup.yaml b/src/openapi/backup.yaml index ae52cdb03..3751c5a4d 100644 --- a/src/openapi/backup.yaml +++ b/src/openapi/backup.yaml @@ -47,13 +47,6 @@ Backup: AplBackupSpec: type: object properties: - id: - type: string - teamId: - $ref: 'definitions.yaml#/idName' - name: - title: Name - $ref: 'definitions.yaml#/idName' schedule: $ref: 'definitions.yaml#/backupSchedule' snapshotVolumes: diff --git a/src/openapi/build.yaml b/src/openapi/build.yaml index a92a2c494..11bcc542a 100644 --- a/src/openapi/build.yaml +++ b/src/openapi/build.yaml @@ -53,14 +53,6 @@ Build: AplBuildSpec: type: object properties: - id: - type: string - teamId: - $ref: 'definitions.yaml#/idName' - name: - title: Name - $ref: 'definitions.yaml#/idName' - description: Results in image name harbor.//name. tag: $ref: 'definitions.yaml#/imageTag' default: latest diff --git a/src/openapi/codeRepo.yaml b/src/openapi/codeRepo.yaml index d966c3822..c237a90f9 100644 --- a/src/openapi/codeRepo.yaml +++ b/src/openapi/codeRepo.yaml @@ -45,12 +45,6 @@ CodeRepo: AplCodeRepoSpec: type: object properties: - id: - type: string - teamId: - $ref: 'definitions.yaml#/idName' - name: - $ref: 'definitions.yaml#/idName' gitService: type: string default: gitea diff --git a/src/openapi/internalRepoUrls.yaml b/src/openapi/internalRepoUrls.yaml index 57df654f1..db915ec53 100644 --- a/src/openapi/internalRepoUrls.yaml +++ b/src/openapi/internalRepoUrls.yaml @@ -3,5 +3,6 @@ InternalRepoUrls: platformAdmin: [read-any] teamAdmin: [read-any] teamMember: [read-any] - properties: {} type: array + items: + type: string diff --git a/src/openapi/netpol.yaml b/src/openapi/netpol.yaml index 62e199cd4..602f536d5 100644 --- a/src/openapi/netpol.yaml +++ b/src/openapi/netpol.yaml @@ -33,13 +33,6 @@ Netpol: AplNetpolSpec: type: object properties: - id: - type: string - teamId: - $ref: 'definitions.yaml#/idName' - name: - title: Name - $ref: 'definitions.yaml#/idName' ruleType: $ref: '#/ruleType' @@ -115,7 +108,7 @@ ruleType: protocol: title: Protocol type: string - enum: [ HTTPS, HTTP, TCP ] + enum: [HTTPS, HTTP, TCP] default: HTTPS required: - number diff --git a/src/openapi/project.yaml b/src/openapi/project.yaml index c8152b70d..7c025862c 100644 --- a/src/openapi/project.yaml +++ b/src/openapi/project.yaml @@ -35,14 +35,3 @@ Project: $ref: 'service.yaml#/Service' required: - name - -AplProjectSpec: - type: object - properties: - id: - type: string - teamId: - $ref: 'definitions.yaml#/idName' - name: - title: Name - $ref: 'definitions.yaml#/idName' diff --git a/src/openapi/service.yaml b/src/openapi/service.yaml index 72fdaca10..f189e9b25 100644 --- a/src/openapi/service.yaml +++ b/src/openapi/service.yaml @@ -206,14 +206,6 @@ AplServiceSpec: allOf: - type: object properties: - id: - type: string - teamId: - $ref: 'definitions.yaml#/idName' - name: - title: Name - $ref: 'definitions.yaml#/idName' - example: some-service namespace: $ref: 'definitions.yaml#/idName' description: A Kubernetes namespace. diff --git a/src/openapi/workload.yaml b/src/openapi/workload.yaml index de637747f..77587ed3b 100644 --- a/src/openapi/workload.yaml +++ b/src/openapi/workload.yaml @@ -175,13 +175,6 @@ AplWorkloadSpec: type: object description: Define the location of the Workloads's manifests or Helm chart. properties: - id: - type: string - teamId: - $ref: 'definitions.yaml#/idName' - name: - title: Name - $ref: 'definitions.yaml#/idName' icon: $ref: 'definitions.yaml#/wordCharacterPattern' url: @@ -203,7 +196,7 @@ AplWorkloadSpec: $ref: 'definitions.yaml#/wordCharacterPattern' default: HEAD chartMetadata: - title: '' + title: Chart metadata properties: helmChartVersion: type: string diff --git a/src/otomi-models.ts b/src/otomi-models.ts index 25174ff42..140e1d5f1 100644 --- a/src/otomi-models.ts +++ b/src/otomi-models.ts @@ -3,8 +3,6 @@ import { JSONSchema4 } from 'json-schema' import { components, external, operations, paths } from 'src/generated-schema' import OtomiStack from 'src/otomi-stack' -export type ResourceMetadata = components['schemas']['aplMetadata']['metadata'] -export type ResourceTeamMetadata = components['schemas']['aplTeamMetadata']['metadata'] export type App = components['schemas']['App'] export type AppList = components['schemas']['AppList'] export type Backup = components['schemas']['Backup'] diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index 580bc6265..8ced209e3 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -545,7 +545,6 @@ describe('Code repositories tests', () => { }, }, spec: { - name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', }, @@ -585,7 +584,6 @@ describe('Code repositories tests', () => { }, }, spec: { - name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', }, @@ -635,7 +633,6 @@ describe('Code repositories tests', () => { }, }, spec: { - name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', }, @@ -670,7 +667,6 @@ describe('Code repositories tests', () => { kind: 'AplTeamCodeRepo', metadata: { name: 'code-1-updated', labels: { 'apl.io/teamId': 'demo' } }, spec: { - name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', }, @@ -780,7 +776,6 @@ describe('Code repositories tests', () => { }, }, spec: { - name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, @@ -830,7 +825,6 @@ describe('Code repositories tests', () => { }, }, spec: { - name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index e00d96166..3b8c5e3d2 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -2409,8 +2409,8 @@ export default class OtomiStack { dns, }) return removeBlankAttributes({ - ...serviceMeta, ...inService, + ...serviceMeta, ingress: { ...pick(serviceSpec, publicIngressFields), domain: url.domain, diff --git a/src/repo.ts b/src/repo.ts index dcc4f7a35..7109ce3e1 100755 --- a/src/repo.ts +++ b/src/repo.ts @@ -2,10 +2,10 @@ import { rmSync } from 'fs' import { rm } from 'fs/promises' import { globSync } from 'glob' import jsonpath from 'jsonpath' -import { cloneDeep, get, merge, set } from 'lodash' +import { cloneDeep, get, merge, omit, set } from 'lodash' import path from 'path' -import { getDirNames, loadYaml } from './utils' import { AplKind } from './otomi-models' +import { getDirNames, loadYaml } from './utils' export async function getTeamNames(envDir: string): Promise> { const teamsDir = path.join(envDir, 'env', 'teams') @@ -417,7 +417,7 @@ export function renderManifest(fileMap: FileMap, jsonPath: jsonpath.PathComponen name: getResourceName(fileMap, jsonPath, data), labels: {}, }, - spec: data, + spec: omit(data, ['id', 'teamId', 'name']), } if (fileMap.resourceGroup === 'team') { manifest.metadata.labels['apl.io/teamId'] = getTeamNameFromJsonPath(jsonPath) @@ -426,10 +426,13 @@ export function renderManifest(fileMap: FileMap, jsonPath: jsonpath.PathComponen return manifest } -export function renderManifestForSecrets(fileMap: FileMap, data: Record) { +export function renderManifestForSecrets(fileMap: FileMap, resourceName: string, data: Record) { return { kind: fileMap.kind, - spec: data, + metadata: { + name: resourceName, + }, + spec: omit(data, ['id', 'teamId', 'name']), } } @@ -445,6 +448,25 @@ export function unsetValuesFileSync(envDir: string): string { return valuesPath } +function isKindValid(kind: string | undefined, fileMap: FileMap): boolean { + return ( + kind === fileMap.kind || + (kind === 'SealedSecret' && fileMap.kind === 'AplTeamSecret') || + fileMap.kind === 'AplTeamWorkloadValues' + ) +} + +function isNameValid(data: Record, fileMap: FileMap, fileName: string | undefined): boolean { + //TODO Remove users exception once name has been set in metadata consistently + return ( + fileMap.resourceGroup === 'users' || fileMap.kind === 'AplTeamWorkloadValues' || data.metadata?.name === fileName + ) +} + +function isTeamValid(data: Record, fileMap: FileMap, teamName: string | undefined): boolean { + return ['AplTeamWorkloadValues', 'AplTeamSecret'].includes(fileMap.kind) || data?.metadata?.labels?.['apl.io/teamId'] +} + export async function loadFileToSpec( filePath: string, fileMap: FileMap, @@ -452,31 +474,47 @@ export async function loadFileToSpec( deps = { loadYaml }, ): Promise { const jsonPath = getJsonPath(fileMap, filePath) - const data = await deps.loadYaml(filePath) - if (fileMap.processAs === 'arrayItem') { - const ref: Record[] = get(spec, jsonPath) - const name = filePath.match(/\/([^/]+)\.yaml$/)?.[1] - if (fileMap.kind === 'AplTeamWorkloadValues') { - //TODO remove this custom workaround for workloadValues as it has no spec - ref.push({ ...data, name }) - } else if (fileMap.v2) { - if (data?.kind !== fileMap.kind && !(data?.kind === 'SealedSecret' && fileMap.kind === 'AplTeamSecret')) { + try { + const data = (await deps.loadYaml(filePath)) || {} + if (fileMap.processAs === 'arrayItem') { + const ref: Record[] = get(spec, jsonPath) + const name = filePath.match(/\/([^/]+)\.yaml$/)?.[1] + if (!isKindValid(data?.kind, fileMap)) { console.error(`Unexpected manifest kind in ${filePath}: ${data?.kind}`) return } - if (data.metadata.name !== name) { - console.error(`Unexpected name in ${filePath}: ${data.metadata.name}`) + if (!isNameValid(data, fileMap, name)) { + console.error(`Unexpected name in ${filePath}: ${data.metadata?.name}`) return } - ref.push(data) + if (fileMap.kind !== 'AplTeamWorkloadValues' && fileMap.resourceGroup === 'team') { + const teamName = filePath.match(/\/env\/teams\/([^/]+)\//)?.[1] + if (!isTeamValid(data, fileMap, teamName)) { + console.error(`Unexpected team in ${filePath}: ${data?.metadata?.labels?.['apl.io/teamId']}`) + return + } + } + if (fileMap.kind === 'AplUser') { + data.spec.id = data.metadata?.name + } + if (fileMap.kind === 'AplTeamWorkloadValues') { + //TODO remove this custom workaround for workloadValues as it has no spec + ref.push({ ...data, name }) + } else if (fileMap.v2) { + ref.push(data) + } else { + ref.push(data?.spec) + } } else { - ref.push(data?.spec) + const ref: Record = get(spec, jsonPath) + // Decrypted secrets may need to be merged with plain text specs + const newRef = merge(cloneDeep(ref), data?.spec) + set(spec, jsonPath, newRef) } - } else { - const ref: Record = get(spec, jsonPath) - // Decrypted secrets may need to be merged with plain text specs - const newRef = merge(cloneDeep(ref), data?.spec) - set(spec, jsonPath, newRef) + } catch (e) { + console.log(filePath) + console.log(fileMap) + throw e } } diff --git a/src/utils/manifests.ts b/src/utils/manifests.ts index afab613ea..716e2092e 100644 --- a/src/utils/manifests.ts +++ b/src/utils/manifests.ts @@ -22,10 +22,11 @@ export function getAplObjectFromV1(kind: AplKind, spec: V1ApiObject | ServiceSpe } export function getV1ObjectFromApl(aplObject: AplResponseObject): V1ApiObject { + const teamId = aplObject.metadata.labels['apl.io/teamId'] return { - teamId: aplObject.metadata.labels['apl.io/teamId'], name: aplObject.metadata.name, - ...aplObject.spec, + ...(teamId && { teamId }), + ...omit(aplObject.spec, ['id', 'name', 'teamId']), } }