From 8302fb7c5564b4876dd4d2ec967bb39de1e243e2 Mon Sep 17 00:00:00 2001 From: Rhys Arkins Date: Fri, 26 Jun 2020 10:59:00 +0200 Subject: [PATCH] feat(pip): support hash updating (#6460) --- .../pip_requirements/artifacts.spec.ts | 64 ++++++++++++++++++ lib/manager/pip_requirements/artifacts.ts | 65 +++++++++++++++++++ lib/manager/pip_requirements/extract.ts | 2 +- lib/manager/pip_requirements/index.ts | 1 + 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 lib/manager/pip_requirements/artifacts.spec.ts create mode 100644 lib/manager/pip_requirements/artifacts.ts diff --git a/lib/manager/pip_requirements/artifacts.spec.ts b/lib/manager/pip_requirements/artifacts.spec.ts new file mode 100644 index 00000000000000..573e158ea56805 --- /dev/null +++ b/lib/manager/pip_requirements/artifacts.spec.ts @@ -0,0 +1,64 @@ +import _fs from 'fs-extra'; +import { updateArtifacts } from './artifacts'; + +const fs: jest.Mocked = _fs as any; + +jest.mock('fs-extra'); +jest.mock('child_process'); +jest.mock('../../util/exec'); + +const config = {}; + +const newPackageFileContent = `atomicwrites==1.4.0 \ +--hash=sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4 \ +--hash=sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6`; + +describe('.updateArtifacts()', () => { + beforeEach(() => { + jest.resetAllMocks(); + jest.resetModules(); + }); + it('returns null if no updatedDeps were provided', async () => { + expect( + await updateArtifacts({ + packageFileName: 'requirements.txt', + updatedDeps: [], + newPackageFileContent, + config, + }) + ).toBeNull(); + }); + it('returns null if unchanged', async () => { + fs.readFile.mockResolvedValueOnce(newPackageFileContent as any); + expect( + await updateArtifacts({ + packageFileName: 'requirements.txt', + updatedDeps: ['atomicwrites'], + newPackageFileContent, + config, + }) + ).toBeNull(); + }); + it('returns updated file', async () => { + fs.readFile.mockResolvedValueOnce('new content' as any); + expect( + await updateArtifacts({ + packageFileName: 'requirements.txt', + updatedDeps: ['atomicwrites'], + newPackageFileContent, + config, + }) + ).toHaveLength(1); + }); + it('catches and returns errors', async () => { + fs.readFile.mockResolvedValueOnce('new content' as any); + expect( + await updateArtifacts({ + packageFileName: null, + updatedDeps: ['atomicwrites'], + newPackageFileContent, + config, + }) + ).toHaveLength(1); + }); +}); diff --git a/lib/manager/pip_requirements/artifacts.ts b/lib/manager/pip_requirements/artifacts.ts new file mode 100644 index 00000000000000..3ee8206455e0d4 --- /dev/null +++ b/lib/manager/pip_requirements/artifacts.ts @@ -0,0 +1,65 @@ +import is from '@sindresorhus/is'; +import { logger } from '../../logger'; +import { ExecOptions, exec } from '../../util/exec'; +import { readLocalFile } from '../../util/fs'; +import { UpdateArtifact, UpdateArtifactsResult } from '../common'; + +export async function updateArtifacts({ + packageFileName, + updatedDeps, + newPackageFileContent, + config, +}: UpdateArtifact): Promise { + logger.debug(`pip_requirements.updateArtifacts(${packageFileName})`); + if (!is.nonEmptyArray(updatedDeps)) { + logger.debug('No updated pip_requirements deps - returning null'); + return null; + } + try { + const cmd: string[] = []; + const rewrittenContent = newPackageFileContent.replace(/\\\n/g, ''); + const lines = rewrittenContent.split('\n').map((line) => line.trim()); + for (const dep of updatedDeps) { + const hashLine = lines.find( + (line) => line.startsWith(`${dep}==`) && line.includes('--hash=') + ); + if (hashLine) { + const depConstraint = hashLine.split(' ')[0]; + cmd.push(`hashin ${depConstraint} -r ${packageFileName}`); + } + } + const execOptions: ExecOptions = { + cwdFile: packageFileName, + docker: { + image: 'renovate/python', + tagScheme: 'pip_requirements', + preCommands: ['pip install hashin'], + }, + }; + await exec(cmd, execOptions); + const newContent = await readLocalFile(packageFileName, 'utf8'); + if (newContent === newPackageFileContent) { + logger.debug(`${packageFileName} is unchanged`); + return null; + } + logger.debug(`Returning updated ${packageFileName}`); + return [ + { + file: { + name: packageFileName, + contents: newContent, + }, + }, + ]; + } catch (err) { + logger.debug({ err }, `Failed to update ${packageFileName} file`); + return [ + { + artifactError: { + lockFile: packageFileName, + stderr: err.stdout + '\n' + err.stderr, + }, + }, + ]; + } +} diff --git a/lib/manager/pip_requirements/extract.ts b/lib/manager/pip_requirements/extract.ts index 7901a0d9437b7a..acde62f0b3486b 100644 --- a/lib/manager/pip_requirements/extract.ts +++ b/lib/manager/pip_requirements/extract.ts @@ -57,7 +57,7 @@ export function extractPackageFile( dep.skipReason = SkipReason.Ignored; } regex.lastIndex = 0; - const matches = regex.exec(line); + const matches = regex.exec(line.split(' \\')[0]); if (!matches) { return null; } diff --git a/lib/manager/pip_requirements/index.ts b/lib/manager/pip_requirements/index.ts index 3b321a2fc92ff2..b2c951aa37c73d 100644 --- a/lib/manager/pip_requirements/index.ts +++ b/lib/manager/pip_requirements/index.ts @@ -1,5 +1,6 @@ import { LANGUAGE_PYTHON } from '../../constants/languages'; +export { updateArtifacts } from './artifacts'; export { extractPackageFile } from './extract'; export { getRangeStrategy } from './range';