diff --git a/src/config.mts b/src/config.mts index 8e86587..788996f 100644 --- a/src/config.mts +++ b/src/config.mts @@ -1,19 +1,10 @@ import path from 'path'; import { pathToFileURL } from 'url'; -import type { ArtifactProviderConfig } from './artifact-provider-config.mjs'; -import type { DebBuilderConfig } from './deb/deb-builder-config.mjs'; -import type { Repo } from './repo.mjs'; - -interface BaseConfig { - out: string; - repo: Repo; -} +import type { Deployer } from './deployer.mjs'; export interface Config { - base: BaseConfig; - artifactsProvider: ArtifactProviderConfig; - debBuilder: DebBuilderConfig; + deployers: Deployer[]; } export class ConfigProvider { diff --git a/src/deb/deb-builder-config.mts b/src/deb/deb-builder-config.mts index 07e02b3..9977e7e 100644 --- a/src/deb/deb-builder-config.mts +++ b/src/deb/deb-builder-config.mts @@ -1,7 +1,28 @@ +import type { Artifact } from '../artifact-provider.mjs'; + export interface DebBuilderConfig { + out: string, gpgPublicKeyPath: string; gpgKeyName: string; applicationName: string; - component: string; origin: string; + repo: DebRepo; +} + +type Distribution = string; +type UbuntuComponentsEnum = 'main' | 'universe' | 'restricted' | 'multiverse'; + +export type UbuntuComponent = { + [key in UbuntuComponentsEnum]: string; +}; + +export interface DebDescriptor { + version: string, + artifact: Artifact +} + +export interface DebRepo { + [key: Distribution]: { + [key in keyof UbuntuComponent]?: DebDescriptor[] + } } diff --git a/src/deb/deb-builder.mts b/src/deb/deb-builder.mts index 8b0c207..45d6332 100644 --- a/src/deb/deb-builder.mts +++ b/src/deb/deb-builder.mts @@ -5,12 +5,10 @@ import * as ini from 'ini'; import * as path from 'path'; import * as tar from 'tar'; -import type { Artifact, ArtifactProvider } from '../artifact-provider.mjs'; import { createDir, execToolToFile, removeDir } from '../fs.mjs'; -import type { Config } from '../config.mjs'; -import { createMetapointerFile } from '../s3-metapointer.mjs'; +import type { DebBuilderConfig, DebDescriptor, DebRepo } from './deb-builder-config.mjs'; +import type { ArtifactProvider } from '../artifact-provider.mjs'; import type { Deployer } from '../deployer.mjs'; -import type { Packages } from '../repo.mjs'; const ReleaseFileTemplate = `Origin: $ORIGIN @@ -19,19 +17,32 @@ Architecture: $ARCH Component: $COMPONENT Codename: $DISTRIBUTION\n`; -interface DebDescriptor { - version: string, - artifact: Artifact +function iterateComponents(repo: DebRepo, callback: (distribution: string, component: string, deb: DebDescriptor[]) => void): void { + const distributions = Object.keys(repo); + + distributions.forEach(distribution => { + const componentsFordistribution = repo[distribution]; + if (componentsFordistribution) { + Object.entries(componentsFordistribution).forEach(entry => { + const [component, debs] = entry; + callback(distribution, component, debs); + }); + } + }); } -interface DistributionItem { - distribution: string; - debs: DebDescriptor[] +function iterateDebs(repo: DebRepo, callback: (distribution: string, component: string, deb: DebDescriptor) => void): void { + iterateComponents(repo, (distribution: string, component: string, debs: DebDescriptor[]) => { + debs.forEach(deb => { + callback(distribution, component, deb); + }); + }); } export class DebBuilder implements Deployer { - private readonly config: Config; + private readonly config: DebBuilderConfig; private readonly artifactProvider: ArtifactProvider; + private readonly metapointerCreator: (md5: string, path: string) => void; private readonly root: string; private readonly temp: string; @@ -39,24 +50,22 @@ export class DebBuilder implements Deployer { private readonly dists: string; private readonly keys: string; - private debRepo: DistributionItem[] = []; + private archesByDistComp: Map> = new Map(); - private archesByDistribution: Map> = new Map(); - - constructor(artifactProvider: ArtifactProvider, config: Config) { + constructor(config: DebBuilderConfig, artifactProvider: ArtifactProvider, metapointerCreator: (md5: string, path: string) => void) { this.config = config; + this.artifactProvider = artifactProvider; + this.metapointerCreator = metapointerCreator; - this.root = path.join(this.config.base.out, 'repo', this.config.debBuilder.applicationName, 'deb'); - this.temp = path.join(this.config.base.out, 'temp'); + this.root = path.join(this.config.out); + this.temp = path.join(this.config.out, 'temp'); this.pool = path.join(this.root, 'pool'); this.dists = path.join(this.root, 'dists'); this.keys = path.join(this.root, 'keys'); - this.artifactProvider = artifactProvider; } public async plan(): Promise { try { - await this.prepareMetaRepository(); await this.dpkgScanpackages(); await this.makeRelease(); } finally { @@ -69,86 +78,42 @@ export class DebBuilder implements Deployer { } private debName(version: string, arch: string): string { - return `${this.config.debBuilder.applicationName}-${version}_${arch}.deb`; + return `${this.config.applicationName}-${version}_${arch}.deb`; } - private async makeReleaseFileAndSign(distribution: string, arch: string): Promise { + private async makeReleaseFileAndSign(distribution: string, component: string, arch: string): Promise { const publicKeyPath = path.join(this.keys, 'desktop.asc'); createDir(this.keys); - await fs.promises.copyFile(this.config.debBuilder.gpgPublicKeyPath, publicKeyPath); + await fs.promises.copyFile(this.config.gpgPublicKeyPath, publicKeyPath); const releaseContent = ReleaseFileTemplate - .replace('$ORIGIN', this.config.debBuilder.origin) + .replace('$ORIGIN', this.config.origin) .replace('$DISTRIBUTION', distribution) .replace('$ARCH', arch) - .replace('$COMPONENT', this.config.debBuilder.component); + .replace('$COMPONENT', component); - const releaseFilePath = path.join(this.dists, distribution, 'Release'); - const releaseGpgFilePath = path.join(this.dists, distribution, 'Release.gpg'); - const inReleaseFilePath = path.join(this.dists, distribution, 'InRelease'); + const releaseFilePath = path.join(this.dists, distribution, component, 'Release'); + const releaseGpgFilePath = path.join(this.dists, distribution, component, 'Release.gpg'); + const inReleaseFilePath = path.join(this.dists, distribution, component, 'InRelease'); await fs.promises.writeFile(releaseFilePath, releaseContent); await execToolToFile('apt-ftparchive', ['release', `${this.dists}/${distribution}`], releaseFilePath, true); - await execToolToFile('gpg', ['--no-tty', '--default-key', this.config.debBuilder.gpgKeyName, '-abs', '-o', releaseGpgFilePath, releaseFilePath]); - await execToolToFile('gpg', ['--no-tty', '--default-key', this.config.debBuilder.gpgKeyName, '--clearsign', '-o', inReleaseFilePath, releaseFilePath]); - } - - private async prepareMetaRepository(): Promise { - const debsPromises: Promise<{distribution: string, debs: DebDescriptor[]}>[] = []; - - this.config.base.repo.forEach(distributionEntry => { - debsPromises.push((async(): Promise<{ distribution: string, debs: DebDescriptor[] }> => ({ - distribution: distributionEntry.channel, - debs: await this.debsByPackages(distributionEntry.packages), - }))()); - }); - - const debs = await Promise.all(debsPromises); - - debs.forEach(entry => { - this.debRepo.push(entry); - }); - } - - private async debsByPackages(packs: Packages): Promise { - const debs: DebDescriptor[] = []; - const artsByBuildNumbersPromises: Promise<{version: string, artifacts: Artifact[]}>[] = []; - - packs.packages.forEach(pack => { - artsByBuildNumbersPromises.push((async(): Promise<{ version: string, artifacts: Artifact[] }> => ({ - version: pack.version, - artifacts: await this.artifactProvider.artifactsByBuildNumber(pack.buildNumber), - }))()); - }); - - const artsByBuildNumbers = await Promise.all(artsByBuildNumbersPromises); - - artsByBuildNumbers.forEach(value => { - value.artifacts.filter(artifact => artifact.type === 'deb').forEach(artifact => { - debs.push({ - version: value.version, - artifact, - }); - }); - }); - - return debs; + await execToolToFile('gpg', ['--no-tty', '--default-key', this.config.gpgKeyName, '-abs', '-o', releaseGpgFilePath, releaseFilePath]); + await execToolToFile('gpg', ['--no-tty', '--default-key', this.config.gpgKeyName, '--clearsign', '-o', inReleaseFilePath, releaseFilePath]); } private async dpkgScanpackages(): Promise { const promises: Promise[] = []; - this.debRepo.forEach(distribution => { - distribution.debs.forEach(deb => { - promises.push(this.handleDeb(distribution.distribution, deb)); - }); + iterateDebs(this.config.repo, (distribution, component, deb) => { + promises.push(this.handleDeb(distribution, component, deb)); }); await Promise.all(promises); } - private async handleDeb(distribution: string, deb: DebDescriptor): Promise { + private async handleDeb(distribution: string, component: string, deb: DebDescriptor): Promise { const controlTarSizeRange = { start: 120, end: 129 }; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call @@ -171,17 +136,17 @@ export class DebBuilder implements Deployer { const controlMeta = ini.parse(controlMetaContent); const arch = controlMeta['Architecture']; - const archesSet = this.archesByDistribution.get(distribution); + const archesSet = this.archesByDistComp.get(`${distribution}/${component}`); if (archesSet) { archesSet.add(arch); } else { - this.archesByDistribution.set(distribution, new Set([arch])); + this.archesByDistComp.set(`${distribution}/${component}`, new Set([arch])); } const targetMetaPath = path.join(this.dists, distribution, - this.config.debBuilder.component, + component, `binary-${arch}`, `${this.debName(deb.version, arch)}.meta`); createDir(path.dirname(targetMetaPath)); @@ -190,13 +155,13 @@ export class DebBuilder implements Deployer { removeDir(whereExtract); const debPath = path.join(this.pool, - this.config.debBuilder.component, - `${this.config.debBuilder.applicationName[0]}`, - this.config.debBuilder.applicationName, + component, + `${this.config.applicationName[0]}`, + this.config.applicationName, distribution, this.debName(deb.version, arch)); const relativeDebPath = path.relative(this.root, debPath); - createMetapointerFile(deb.artifact.md5, debPath); + this.metapointerCreator(deb.artifact.md5, debPath); const debSize = controlTar.headers['content-range']?.split('/')[1]; const sha1 = controlTar.headers['x-checksum-sha1']; const sha256 = controlTar.headers['x-checksum-sha256']; @@ -228,11 +193,11 @@ export class DebBuilder implements Deployer { const compressPromises: Promise[] = []; - this.debRepo.forEach(distributionEntry => { - const distsRoot = path.join(this.dists, distributionEntry.distribution, this.config.debBuilder.component); - const distsByArch = fs.readdirSync(distsRoot).map(dist => path.join(distsRoot, dist)); + iterateComponents(this.config.repo, (distribution, component) => { + const componentssRoot = path.join(this.dists, distribution, component); + const componentsByArch = fs.readdirSync(componentssRoot).map(dist => path.join(componentssRoot, dist)); - distsByArch.forEach(dist => { + componentsByArch.forEach(dist => { const targetPackagesFile = path.join(dist, 'Packages'); const metaFiles = fs.readdirSync(dist) .filter(fileName => fileName.endsWith('.meta')) @@ -256,13 +221,13 @@ export class DebBuilder implements Deployer { const releasesPromises: Promise[] = []; - this.debRepo.forEach(chan => { - const archesSet = this.archesByDistribution.get(chan.distribution); + iterateComponents(this.config.repo, (distribution, component) => { + const archesSet = this.archesByDistComp.get(`${distribution}/${component}`); if (!archesSet) { throw new Error('No arch was found for distribution'); } - releasesPromises.push(this.makeReleaseFileAndSign(chan.distribution, [...archesSet.values()].join(' '))); + releasesPromises.push(this.makeReleaseFileAndSign(distribution, component, [...archesSet.values()].join(' '))); }); return Promise.all(releasesPromises); diff --git a/src/index.mts b/src/index.mts index 4087883..1400061 100644 --- a/src/index.mts +++ b/src/index.mts @@ -1,23 +1,12 @@ import type { Config } from './config.mjs'; -import { DebBuilder } from './deb/deb-builder.mjs'; -import type { Deployer } from './deployer.mjs'; -import JfrogArtifactProvider from './jfrog/artifact-provider.mjs'; export { type Config, createConfigProvider } from './config.mjs'; export function plan(config: Config): Promise { - const artifactsProvider = new JfrogArtifactProvider(config.artifactsProvider); - - const builders: Deployer[] = []; - - if (config.debBuilder) { - builders.push(new DebBuilder(artifactsProvider, config)); - } - const planPromises: Promise[] = []; - for (const builder of builders) { - planPromises.push(builder.plan()); + for (const deployer of config.deployers) { + planPromises.push(deployer.plan()); } return Promise.all(planPromises); diff --git a/tests/index.spec.mts b/tests/index.spec.mts index 01c72ce..c3c00bf 100644 --- a/tests/index.spec.mts +++ b/tests/index.spec.mts @@ -10,6 +10,6 @@ describe('apply', () => { describe('plan', () => { it('should return undefined', () => { - expect(plan()).toBe(undefined); + expect(plan({deployers: []})).toBe(undefined); }); });