Files
renovate/lib/modules/manager/hermit/artifacts.ts
T
Yun Lai b696abb3c2 feat: add Hermit package manager (#16258)
* feat: add Hermit package manager

* fix: pass bin directory into getRepoStatus as string rather than an array

* fix: fix up hermit manager implementations

* add docker support in exec
* move fs related operations back into  util/fs
* remove ENVVar passed on by process.env
* set concurrency in pMap
* use for instead of pMap for concurrency = 1
* use regex to pick up package reference parts

* fix: fix manager updateArtifacts test after change

* Update lib/modules/manager/hermit/extract.ts

Co-authored-by: Philip <42116482+PhilipAbed@users.noreply.github.com>

* fix: fix up test and docker reference for hermit manager

* test refer to internal fs
* docker image change to sidecar
* only symlink are read for the changed file content after hermit
  install
* no more global mock in artifacts test

* fix: use warn instead of error so error better flows up in hermit manager

* fix: partial for test type, use throw instead of reject

* fix: update snapshot

* fix: combine install packages, also make extractPackageFile async

* fix: remove weird generated readLocalSynmlink in test

* fix: removes old test

* fix: use ensureLocalPath and fix test coverage

* fix: more test coverage

* fix: use ensureLocalPath in readLocalSymlink

* Apply suggestions from code review

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>

* fix: remove unused functions and types

* Apply suggestions from code review

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>

* Apply suggestions from code review

Co-authored-by: Sergei Zharinov <zharinov@users.noreply.github.com>

* fix: use execSnapshots and for of loop when returning the result

* Update lib/modules/manager/hermit/artifacts.spec.ts

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>

* fix: move exports below imports

Co-authored-by: Philip <42116482+PhilipAbed@users.noreply.github.com>
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
Co-authored-by: Sergei Zharinov <zharinov@users.noreply.github.com>
2022-07-30 08:41:45 +00:00

259 lines
6.5 KiB
TypeScript

import pMap from 'p-map';
import upath from 'upath';
import { logger } from '../../../logger';
import { exec } from '../../../util/exec';
import type { ExecOptions } from '../../../util/exec/types';
import { localPathIsSymbolicLink, readLocalSymlink } from '../../../util/fs';
import { getRepoStatus } from '../../../util/git';
import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
import type { ReadContentResult } from './types';
/**
* updateArtifacts runs hermit install for each updated dependencies
*/
export async function updateArtifacts(
update: UpdateArtifact
): Promise<UpdateArtifactsResult[] | null> {
const { packageFileName } = update;
logger.debug({ packageFileName }, `hermit.updateArtifacts()`);
try {
await updateHermitPackage(update);
} catch (err) {
const execErr: UpdateHermitError = err;
logger.debug({ err }, `error updating hermit packages.`);
return [
{
artifactError: {
lockFile: `from: ${execErr.from}, to: ${execErr.to}`,
stderr: execErr.stderr,
},
},
];
}
logger.debug(`scanning the changes after update`);
let updateResult: UpdateArtifactsResult[] | null = null;
try {
updateResult = await getUpdateResult(packageFileName);
logger.debug({ updateResult }, `update result for hermit`);
} catch (err) {
logger.debug({ err }, 'Error getting hermet update results');
return [
{
artifactError: {
stderr: err.message,
},
},
];
}
return updateResult;
}
/**
* getContent returns the content of either link or a normal file
*/
async function getContent(file: string): Promise<ReadContentResult> {
let contents: string | null = '';
const isSymlink = await localPathIsSymbolicLink(file);
if (isSymlink) {
contents = await readLocalSymlink(file);
}
if (contents === null) {
throw new Error(`error getting content for ${file}`);
}
return {
isSymlink,
contents,
};
}
/**
* getAddResult returns the UpdateArtifactsResult for the added files
*/
function getAddResult(
path: string,
contentRes: ReadContentResult
): UpdateArtifactsResult {
return {
file: {
type: 'addition',
path,
contents: contentRes.contents,
isSymlink: contentRes.isSymlink,
isExecutable: contentRes.isExecutable,
},
};
}
/**
* getDeleteResult returns the UpdateArtifactsResult for deleted files
*/
function getDeleteResult(path: string): UpdateArtifactsResult {
return {
file: {
type: 'deletion',
path,
},
};
}
/**
* getUpdateResult will return the update result after `hermit install`
* has been performed for all packages
*/
async function getUpdateResult(
packageFileName: string
): Promise<UpdateArtifactsResult[]> {
const hermitFolder = `${upath.dirname(packageFileName)}/`;
const hermitChanges = await getRepoStatus(hermitFolder);
logger.debug(
{ hermitChanges, hermitFolder },
`hermit changes after package update`
);
// handle added files
const added = await pMap(
[...hermitChanges.created, ...hermitChanges.not_added],
async (path: string): Promise<UpdateArtifactsResult> => {
const contents = await getContent(path);
return getAddResult(path, contents);
},
{ concurrency: 5 }
);
const deleted = hermitChanges.deleted.map(getDeleteResult);
const modified = await pMap(
hermitChanges.modified,
async (path: string): Promise<UpdateArtifactsResult[]> => {
const contents = await getContent(path);
return [
getDeleteResult(path), // delete existing link
getAddResult(path, contents), // add a new link
];
},
{ concurrency: 5 }
);
const renamed = await pMap(
hermitChanges.renamed,
async (renamed): Promise<UpdateArtifactsResult[]> => {
const from = renamed.from;
const to = renamed.to;
const toContents = await getContent(to);
return [getDeleteResult(from), getAddResult(to, toContents)];
},
{ concurrency: 5 }
);
return [
// rename will need to go first, because
// it needs to create the new link for the new version
// for the modified links to use
...renamed.flat(),
...modified.flat(),
...added,
...deleted,
];
}
/**
* getHermitPackage returns the hermit package for running the hermit install
*/
function getHermitPackage(name: string, version: string): string {
return `${name}-${version}`;
}
/**
* updateHermitPackage runs hermit install for the given package
*/
async function updateHermitPackage(update: UpdateArtifact): Promise<void> {
logger.trace({ update }, `hermit.updateHermitPackage()`);
const toInstall = [];
const from = [];
for (const pkg of update.updatedDeps) {
if (!pkg.depName || !pkg.currentVersion || !pkg.newValue) {
logger.debug(
{
depName: pkg.depName,
currentVersion: pkg.currentVersion,
newValue: pkg.newValue,
},
'missing package update information'
);
throw new UpdateHermitError(
getHermitPackage(pkg.depName ?? '', pkg.currentVersion ?? ''),
getHermitPackage(pkg.depName ?? '', pkg.newValue ?? ''),
'invalid package to update'
);
}
const depName = pkg.depName;
const currentVersion = pkg.currentVersion;
const newValue = pkg.newValue;
const fromPackage = getHermitPackage(depName, currentVersion);
const toPackage = getHermitPackage(depName, newValue);
toInstall.push(toPackage);
from.push(fromPackage);
}
const execOptions: ExecOptions = {
docker: {
image: 'sidecar',
},
cwdFile: update.packageFileName,
};
const packagesToInstall = toInstall.join(' ');
const fromPackages = from.join(' ');
const execCommands = `./hermit install ${packagesToInstall}`;
logger.debug(
{
packageFile: update.packageFileName,
packagesToInstall: packagesToInstall,
},
`performing updates`
);
try {
const result = await exec(execCommands, execOptions);
logger.trace({ stdout: result.stdout }, `hermit command stdout`);
} catch (e) {
logger.warn({ err: e }, `error updating hermit package`);
throw new UpdateHermitError(
fromPackages,
packagesToInstall,
e.stderr,
e.stdout
);
}
}
export class UpdateHermitError extends Error {
stdout: string;
stderr: string;
from: string;
to: string;
constructor(from: string, to: string, stderr: string, stdout = '') {
super();
this.stdout = stdout;
this.stderr = stderr;
this.from = from;
this.to = to;
}
}