Skip to content

Commit

Permalink
move artifactory related logic from deb builder to config
Browse files Browse the repository at this point in the history
  • Loading branch information
origin-yaropolk committed Mar 18, 2024
1 parent 0f0c7b4 commit 69d541f
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 121 deletions.
13 changes: 2 additions & 11 deletions src/config.mts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
23 changes: 22 additions & 1 deletion src/deb/deb-builder-config.mts
Original file line number Diff line number Diff line change
@@ -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[]
}
}
145 changes: 55 additions & 90 deletions src/deb/deb-builder.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,44 +17,55 @@ 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;
private readonly pool: string;
private readonly dists: string;
private readonly keys: string;

private debRepo: DistributionItem[] = [];
private archesByDistComp: Map<string, Set<string>> = new Map();

private archesByDistribution: Map<string, Set<string>> = 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<void> {
try {
await this.prepareMetaRepository();
await this.dpkgScanpackages();
await this.makeRelease();
} finally {
Expand All @@ -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<void> {
private async makeReleaseFileAndSign(distribution: string, component: string, arch: string): Promise<void> {
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<void> {
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<DebDescriptor[]> {
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<void> {
const promises: Promise<void>[] = [];

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<void> {
private async handleDeb(distribution: string, component: string, deb: DebDescriptor): Promise<void> {
const controlTarSizeRange = { start: 120, end: 129 };

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
Expand All @@ -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<string>([arch]));
this.archesByDistComp.set(`${distribution}/${component}`, new Set<string>([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));
Expand All @@ -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'];
Expand Down Expand Up @@ -228,11 +193,11 @@ export class DebBuilder implements Deployer {

const compressPromises: Promise<void>[] = [];

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'))
Expand All @@ -256,13 +221,13 @@ export class DebBuilder implements Deployer {

const releasesPromises: Promise<void>[] = [];

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);
Expand Down
19 changes: 4 additions & 15 deletions src/index.mts
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
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<void[]> {
const artifactsProvider = new JfrogArtifactProvider(config.artifactsProvider);

const builders: Deployer[] = [];

if (config.debBuilder) {
builders.push(new DebBuilder(artifactsProvider, config));
}

export async function plan(config: Config): Promise<void> {
const planPromises: Promise<void>[] = [];

for (const builder of builders) {
planPromises.push(builder.plan());
for (const deployer of config.deployers) {
planPromises.push(deployer.plan());
}

return Promise.all(planPromises);
await Promise.all(planPromises);
}

export function apply(): void {
Expand Down
4 changes: 2 additions & 2 deletions src/index.spec.mts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('apply', () => {
});

describe('plan', () => {
it('should return undefined', () => {
expect(plan()).toBe(undefined);
it('should return undefined', async() => {
expect(await plan({ deployers: [] })).toBe(undefined);
});
});
4 changes: 2 additions & 2 deletions tests/index.spec.mts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('apply', () => {
});

describe('plan', () => {
it('should return undefined', () => {
expect(plan()).toBe(undefined);
it('should return undefined', async() => {
expect(await plan({ deployers: [] })).toBe(undefined);
});
});

0 comments on commit 69d541f

Please sign in to comment.