From d2d356c801b16de31f56451093a4613cc6400e05 Mon Sep 17 00:00:00 2001 From: Andrei Nistor <andrei_nistor@smart-x.net> Date: Mon, 22 Nov 2021 17:36:48 +0200 Subject: [PATCH] feat: Add jsonnet-bundler support (#12720) --- docs/usage/configuration-options.md | 11 +- lib/manager/api.ts | 2 + .../jsonnetfile-local-dependencies.json | 15 ++ .../jsonnetfile-no-dependencies.json | 5 + .../__fixtures__/jsonnetfile-with-name.json | 16 ++ .../__fixtures__/jsonnetfile.json | 24 +++ .../__snapshots__/artifacts.spec.ts.snap | 129 ++++++++++++ .../__snapshots__/extract.spec.ts.snap | 39 ++++ lib/manager/jsonnet-bundler/artifacts.spec.ts | 196 ++++++++++++++++++ lib/manager/jsonnet-bundler/artifacts.ts | 110 ++++++++++ lib/manager/jsonnet-bundler/extract.spec.ts | 68 ++++++ lib/manager/jsonnet-bundler/extract.ts | 57 +++++ lib/manager/jsonnet-bundler/index.ts | 10 + lib/manager/jsonnet-bundler/readme.md | 5 + lib/manager/jsonnet-bundler/types.ts | 20 ++ lib/util/exec/buildpack.ts | 6 + 16 files changed, 712 insertions(+), 1 deletion(-) create mode 100644 lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-local-dependencies.json create mode 100644 lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-no-dependencies.json create mode 100644 lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-with-name.json create mode 100644 lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile.json create mode 100644 lib/manager/jsonnet-bundler/__snapshots__/artifacts.spec.ts.snap create mode 100644 lib/manager/jsonnet-bundler/__snapshots__/extract.spec.ts.snap create mode 100644 lib/manager/jsonnet-bundler/artifacts.spec.ts create mode 100644 lib/manager/jsonnet-bundler/artifacts.ts create mode 100644 lib/manager/jsonnet-bundler/extract.spec.ts create mode 100644 lib/manager/jsonnet-bundler/extract.ts create mode 100644 lib/manager/jsonnet-bundler/index.ts create mode 100644 lib/manager/jsonnet-bundler/readme.md create mode 100644 lib/manager/jsonnet-bundler/types.ts diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index ad98b8a588..35580f5723 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -1198,7 +1198,16 @@ With the above config, every PR raised by Renovate will have the label `dependen This feature can be used to refresh lock files and keep them up-to-date. "Maintaining" a lock file means recreating it so that every dependency version within it is updated to the latest. -Supported lock files are `package-lock.json`, `yarn.lock`, `composer.lock`, `Gemfile.lock`, `poetry.lock` and `Cargo.lock`. +Supported lock files are: + +- `package-lock.json` +- `yarn.lock` +- `composer.lock` +- `Gemfile.lock` +- `poetry.lock` +- `Cargo.lock` +- `jsonnetfile.lock.json` + Others may be added via feature request. This feature is disabled by default. diff --git a/lib/manager/api.ts b/lib/manager/api.ts index 1731c2500f..de7265d558 100644 --- a/lib/manager/api.ts +++ b/lib/manager/api.ts @@ -34,6 +34,7 @@ import * as helmv3 from './helmv3'; import * as homebrew from './homebrew'; import * as html from './html'; import * as jenkins from './jenkins'; +import * as jsonnetBundler from './jsonnet-bundler'; import * as kubernetes from './kubernetes'; import * as kustomize from './kustomize'; import * as leiningen from './leiningen'; @@ -103,6 +104,7 @@ api.set('helmv3', helmv3); api.set('homebrew', homebrew); api.set('html', html); api.set('jenkins', jenkins); +api.set('jsonnet-bundler', jsonnetBundler); api.set('kubernetes', kubernetes); api.set('kustomize', kustomize); api.set('leiningen', leiningen); diff --git a/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-local-dependencies.json b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-local-dependencies.json new file mode 100644 index 0000000000..f96725fc11 --- /dev/null +++ b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-local-dependencies.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "dependencies": [ + { + "source": { + "local": { + "directory": "jsonnet" + } + }, + "version": "" + } + ], + "legacyImports": true +} + diff --git a/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-no-dependencies.json b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-no-dependencies.json new file mode 100644 index 0000000000..4388812ca2 --- /dev/null +++ b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-no-dependencies.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "dependencies": [], + "legacyImports": true +} diff --git a/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-with-name.json b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-with-name.json new file mode 100644 index 0000000000..1294d50ee6 --- /dev/null +++ b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile-with-name.json @@ -0,0 +1,16 @@ +{ + "version": 1, + "dependencies": [ + { + "source": { + "git": { + "remote": "https://github.com/prometheus-operator/prometheus-operator", + "subdir": "jsonnet/mixin" + } + }, + "version": "v0.50.0", + "name": "prometheus-operator-mixin" + } + ], + "legacyImports": true +} diff --git a/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile.json b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile.json new file mode 100644 index 0000000000..09fcb11670 --- /dev/null +++ b/lib/manager/jsonnet-bundler/__fixtures__/jsonnetfile.json @@ -0,0 +1,24 @@ +{ + "version": 1, + "dependencies": [ + { + "source": { + "git": { + "remote": "https://github.com/prometheus-operator/prometheus-operator.git", + "subdir": "jsonnet/prometheus-operator" + } + }, + "version": "v0.50.0" + }, + { + "source": { + "git": { + "remote": "ssh://git@github.com/prometheus-operator/kube-prometheus.git", + "subdir": "jsonnet/kube-prometheus" + } + }, + "version": "v0.9.0" + } + ], + "legacyImports": true +} diff --git a/lib/manager/jsonnet-bundler/__snapshots__/artifacts.spec.ts.snap b/lib/manager/jsonnet-bundler/__snapshots__/artifacts.spec.ts.snap new file mode 100644 index 0000000000..79d70146d3 --- /dev/null +++ b/lib/manager/jsonnet-bundler/__snapshots__/artifacts.spec.ts.snap @@ -0,0 +1,129 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`manager/jsonnet-bundler/artifacts performs lock file maintenance 1`] = ` +Array [ + Object { + "file": Object { + "contents": "Updated jsonnetfile.lock.json", + "name": "jsonnetfile.lock.json", + }, + }, +] +`; + +exports[`manager/jsonnet-bundler/artifacts performs lock file maintenance 2`] = ` +Array [ + Object { + "cmd": "jb update", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + +exports[`manager/jsonnet-bundler/artifacts returns error when jb update fails 1`] = ` +Array [ + Object { + "artifactError": Object { + "lockFile": "jsonnetfile.lock.json", + "stderr": "jb released the magic smoke", + }, + }, +] +`; + +exports[`manager/jsonnet-bundler/artifacts returns error when jb update fails 2`] = ` +Array [ + Object { + "cmd": "jb update", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + +exports[`manager/jsonnet-bundler/artifacts returns null if there are no changes 1`] = `Array []`; + +exports[`manager/jsonnet-bundler/artifacts updates the vendor dir when dependencies change 1`] = ` +Array [ + Object { + "file": Object { + "contents": "Updated jsonnetfile.json", + "name": "jsonnetfile.json", + }, + }, + Object { + "file": Object { + "contents": "Updated jsonnetfile.lock.json", + "name": "jsonnetfile.lock.json", + }, + }, + Object { + "file": Object { + "contents": "New foo/main.jsonnet", + "name": "vendor/foo/main.jsonnet", + }, + }, + Object { + "file": Object { + "contents": "New bar/main.jsonnet", + "name": "vendor/bar/main.jsonnet", + }, + }, + Object { + "file": Object { + "contents": "vendor/baz/deleted.jsonnet", + "name": "|delete|", + }, + }, +] +`; + +exports[`manager/jsonnet-bundler/artifacts updates the vendor dir when dependencies change 2`] = ` +Array [ + Object { + "cmd": "jb update https://github.com/foo/foo.git ssh://git@github.com/foo/foo.git/bar", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; diff --git a/lib/manager/jsonnet-bundler/__snapshots__/extract.spec.ts.snap b/lib/manager/jsonnet-bundler/__snapshots__/extract.spec.ts.snap new file mode 100644 index 0000000000..2c3bb91b3d --- /dev/null +++ b/lib/manager/jsonnet-bundler/__snapshots__/extract.spec.ts.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`manager/jsonnet-bundler/extract extractPackageFile() extracts dependency 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "v0.50.0", + "depName": "prometheus-operator", + "lookupName": "https://github.com/prometheus-operator/prometheus-operator.git", + "managerData": Object { + "subdir": "jsonnet/prometheus-operator", + }, + }, + Object { + "currentValue": "v0.9.0", + "depName": "kube-prometheus", + "lookupName": "ssh://git@github.com/prometheus-operator/kube-prometheus.git", + "managerData": Object { + "subdir": "jsonnet/kube-prometheus", + }, + }, + ], +} +`; + +exports[`manager/jsonnet-bundler/extract extractPackageFile() extracts dependency with custom name 1`] = ` +Object { + "deps": Array [ + Object { + "currentValue": "v0.50.0", + "depName": "prometheus-operator-mixin", + "lookupName": "https://github.com/prometheus-operator/prometheus-operator", + "managerData": Object { + "subdir": "jsonnet/mixin", + }, + }, + ], +} +`; diff --git a/lib/manager/jsonnet-bundler/artifacts.spec.ts b/lib/manager/jsonnet-bundler/artifacts.spec.ts new file mode 100644 index 0000000000..f8f339fa0d --- /dev/null +++ b/lib/manager/jsonnet-bundler/artifacts.spec.ts @@ -0,0 +1,196 @@ +import { join } from 'upath'; +import { envMock, exec, mockExecAll } from '../../../test/exec-util'; +import { env, fs, git } from '../../../test/util'; +import { setGlobalConfig } from '../../config/global'; +import type { RepoGlobalConfig } from '../../config/types'; +import type { StatusResult } from '../../util/git'; +import type { UpdateArtifactsConfig } from '../types'; +import { updateArtifacts } from '.'; + +jest.mock('child_process'); +jest.mock('../../util/exec/env'); +jest.mock('../../util/fs'); +jest.mock('../../util/git'); + +const adminConfig: RepoGlobalConfig = { + // `join` fixes Windows CI + localDir: join('/tmp/github/some/repo'), + cacheDir: join('/tmp/renovate/cache'), +}; +const config: UpdateArtifactsConfig = {}; + +describe('manager/jsonnet-bundler/artifacts', () => { + beforeEach(() => { + env.getChildProcessEnv.mockReturnValue(envMock.basic); + + setGlobalConfig(adminConfig); + }); + + it('returns null if jsonnetfile.lock does not exist', async () => { + fs.readLocalFile.mockResolvedValueOnce(null); + expect( + await updateArtifacts({ + packageFileName: 'jsonnetfile.json', + updatedDeps: [], + newPackageFileContent: '', + config, + }) + ).toBeNull(); + }); + + it('returns null if there are no changes', async () => { + fs.readLocalFile.mockResolvedValueOnce('Current jsonnetfile.lock.json'); + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + modified: [], + not_added: [], + deleted: [], + isClean(): boolean { + return true; + }, + } as StatusResult); + expect( + await updateArtifacts({ + packageFileName: 'jsonnetfile.json', + updatedDeps: [], + newPackageFileContent: '', + config, + }) + ).toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('updates the vendor dir when dependencies change', async () => { + fs.readLocalFile.mockResolvedValueOnce('Current jsonnetfile.lock.json'); + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + not_added: ['vendor/foo/main.jsonnet', 'vendor/bar/main.jsonnet'], + modified: ['jsonnetfile.json', 'jsonnetfile.lock.json'], + deleted: ['vendor/baz/deleted.jsonnet'], + isClean(): boolean { + return false; + }, + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated jsonnetfile.json'); + fs.readLocalFile.mockResolvedValueOnce('Updated jsonnetfile.lock.json'); + fs.readLocalFile.mockResolvedValueOnce('New foo/main.jsonnet'); + fs.readLocalFile.mockResolvedValueOnce('New bar/main.jsonnet'); + expect( + await updateArtifacts({ + packageFileName: 'jsonnetfile.json', + updatedDeps: [ + { + depName: 'foo', + lookupName: 'https://github.com/foo/foo.git', + }, + { + depName: 'foo', + lookupName: 'ssh://git@github.com/foo/foo.git', + managerData: { + subdir: 'bar', + }, + }, + ], + newPackageFileContent: 'Updated jsonnetfile.json', + config, + }) + ).toMatchSnapshot([ + { + file: { + name: 'jsonnetfile.json', + contents: 'Updated jsonnetfile.json', + }, + }, + { + file: { + name: 'jsonnetfile.lock.json', + contents: 'Updated jsonnetfile.lock.json', + }, + }, + { + file: { + name: 'vendor/foo/main.jsonnet', + contents: 'New foo/main.jsonnet', + }, + }, + { + file: { + name: 'vendor/bar/main.jsonnet', + contents: 'New bar/main.jsonnet', + }, + }, + { + file: { + name: '|delete|', + contents: 'vendor/baz/deleted.jsonnet', + }, + }, + ]); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('performs lock file maintenance', async () => { + fs.readLocalFile.mockResolvedValueOnce('Current jsonnetfile.lock.json'); + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['jsonnetfile.lock.json'], + isClean(): boolean { + return false; + }, + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated jsonnetfile.lock.json'); + expect( + await updateArtifacts({ + packageFileName: 'jsonnetfile.json', + updatedDeps: [], + newPackageFileContent: '', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).toMatchSnapshot([ + { + file: { + name: 'jsonnetfile.lock.json', + contents: 'Updated jsonnetfile.lock.json', + }, + }, + ]); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('returns error when jb update fails', async () => { + const execError = new Error(); + (execError as any).stderr = 'jb released the magic smoke'; + + fs.readLocalFile.mockResolvedValueOnce('Current jsonnetfile.lock.json'); + const execSnapshots = mockExecAll(exec, execError); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['jsonnetfile.lock.json'], + isClean(): boolean { + return false; + }, + } as StatusResult); + fs.readLocalFile.mockResolvedValueOnce('Updated jsonnetfile.lock.json'); + expect( + await updateArtifacts({ + packageFileName: 'jsonnetfile.json', + updatedDeps: [], + newPackageFileContent: '', + config: { + ...config, + isLockFileMaintenance: true, + }, + }) + ).toMatchSnapshot([ + { + artifactError: { + lockFile: 'jsonnetfile.lock.json', + stderr: 'jb released the magic smoke', + }, + }, + ]); + expect(execSnapshots).toMatchSnapshot(); + }); +}); diff --git a/lib/manager/jsonnet-bundler/artifacts.ts b/lib/manager/jsonnet-bundler/artifacts.ts new file mode 100644 index 0000000000..5e3bc76056 --- /dev/null +++ b/lib/manager/jsonnet-bundler/artifacts.ts @@ -0,0 +1,110 @@ +import { quote } from 'shlex'; +import { TEMPORARY_ERROR } from '../../constants/error-messages'; +import { logger } from '../../logger'; +import { ExecOptions, exec } from '../../util/exec'; +import type { ToolConstraint } from '../../util/exec/types'; +import { readLocalFile } from '../../util/fs'; +import { getRepoStatus } from '../../util/git'; +import { regEx } from '../../util/regex'; +import type { + PackageDependency, + UpdateArtifact, + UpdateArtifactsResult, +} from '../types'; + +function dependencyUrl(dep: PackageDependency): string { + const url = dep.lookupName; + if (dep.managerData?.subdir) { + return url.concat('/', dep.managerData.subdir); + } + return url; +} + +export async function updateArtifacts( + updateArtifact: UpdateArtifact +): Promise<UpdateArtifactsResult[] | null> { + const { packageFileName, updatedDeps, config } = updateArtifact; + logger.trace({ packageFileName }, 'jsonnet-bundler.updateArtifacts()'); + + const lockFileName = packageFileName.replace(regEx(/\.json$/), '.lock.json'); + const existingLockFileContent = await readLocalFile(lockFileName, 'utf8'); + + if (!existingLockFileContent) { + logger.debug('No jsonnetfile.lock.json found'); + return null; + } + + const jsonnetBundlerToolConstraint: ToolConstraint = { + toolName: 'jb', + constraint: config.constraints?.jb, + }; + + const execOptions: ExecOptions = { + cwdFile: packageFileName, + docker: { + image: 'sidecar', + }, + toolConstraints: [jsonnetBundlerToolConstraint], + }; + + try { + if (config.isLockFileMaintenance) { + await exec('jb update', execOptions); + } else { + const dependencyUrls = updatedDeps.map(dependencyUrl); + if (dependencyUrls.length > 0) { + await exec( + `jb update ${dependencyUrls.map(quote).join(' ')}`, + execOptions + ); + } + } + + const status = await getRepoStatus(); + + if (status.isClean()) { + return null; + } + + const res: UpdateArtifactsResult[] = []; + + for (const f of status.modified ?? []) { + res.push({ + file: { + name: f, + contents: await readLocalFile(f), + }, + }); + } + for (const f of status.not_added ?? []) { + res.push({ + file: { + name: f, + contents: await readLocalFile(f), + }, + }); + } + for (const f of status.deleted ?? []) { + res.push({ + file: { + name: '|delete|', + contents: f, + }, + }); + } + + return res; + } catch (err) /* istanbul ignore next */ { + if (err.message === TEMPORARY_ERROR) { + throw err; + } + return [ + { + artifactError: { + lockFile: lockFileName, + stderr: err.stderr, + }, + }, + ]; + } +} diff --git a/lib/manager/jsonnet-bundler/extract.spec.ts b/lib/manager/jsonnet-bundler/extract.spec.ts new file mode 100644 index 0000000000..7264679f2e --- /dev/null +++ b/lib/manager/jsonnet-bundler/extract.spec.ts @@ -0,0 +1,68 @@ +import { loadFixture } from '../../../test/util'; +import { extractPackageFile } from '.'; + +const jsonnetfile = loadFixture('jsonnetfile.json'); +const jsonnetfileWithName = loadFixture('jsonnetfile-with-name.json'); +const jsonnetfileNoDependencies = loadFixture( + 'jsonnetfile-no-dependencies.json' +); +const jsonnetfileLocalDependencies = loadFixture( + 'jsonnetfile-local-dependencies.json' +); + +describe('manager/jsonnet-bundler/extract', () => { + describe('extractPackageFile()', () => { + it('returns null for invalid jsonnetfile', () => { + expect( + extractPackageFile('this is not a jsonnetfile', 'jsonnetfile.json') + ).toBeNull(); + }); + it('returns null for jsonnetfile with no dependencies', () => { + expect( + extractPackageFile(jsonnetfileNoDependencies, 'jsonnetfile.json') + ).toBeNull(); + }); + it('returns null for local dependencies', () => { + expect( + extractPackageFile(jsonnetfileLocalDependencies, 'jsonnetfile.json') + ).toBeNull(); + }); + it('returns null for vendored dependencies', () => { + expect( + extractPackageFile(jsonnetfile, 'vendor/jsonnetfile.json') + ).toBeNull(); + }); + it('extracts dependency', () => { + const res = extractPackageFile(jsonnetfile, 'jsonnetfile.json'); + expect(res).toMatchSnapshot({ + deps: [ + { + depName: 'prometheus-operator', + lookupName: + 'https://github.com/prometheus-operator/prometheus-operator.git', + currentValue: 'v0.50.0', + }, + { + depName: 'kube-prometheus', + lookupName: + 'ssh://git@github.com/prometheus-operator/kube-prometheus.git', + currentValue: 'v0.9.0', + }, + ], + }); + }); + it('extracts dependency with custom name', () => { + const res = extractPackageFile(jsonnetfileWithName, 'jsonnetfile.json'); + expect(res).toMatchSnapshot({ + deps: [ + { + depName: 'prometheus-operator-mixin', + lookupName: + 'https://github.com/prometheus-operator/prometheus-operator', + currentValue: 'v0.50.0', + }, + ], + }); + }); + }); +}); diff --git a/lib/manager/jsonnet-bundler/extract.ts b/lib/manager/jsonnet-bundler/extract.ts new file mode 100644 index 0000000000..7162f4cd88 --- /dev/null +++ b/lib/manager/jsonnet-bundler/extract.ts @@ -0,0 +1,57 @@ +import { logger } from '../../logger'; +import { regEx } from '../../util/regex'; +import type { PackageDependency, PackageFile } from '../types'; +import type { Dependency, JsonnetFile } from './types'; + +const gitUrl = regEx( + /(ssh:\/\/git@|https:\/\/)([\w.]+)\/([\w:/\-~]*)\/(?<depName>[\w:/-]+)(\.git)?/ +); + +export function extractPackageFile( + content: string, + packageFile: string +): PackageFile | null { + logger.trace({ packageFile }, 'jsonnet-bundler.extractPackageFile()'); + + if (packageFile.match(/vendor\//)) { + return null; + } + + const deps: PackageDependency[] = []; + let jsonnetFile: JsonnetFile; + try { + jsonnetFile = JSON.parse(content) as JsonnetFile; + } catch (err) { + logger.debug({ packageFile }, 'Invalid JSON'); + return null; + } + + for (const dependency of jsonnetFile.dependencies ?? []) { + const dep = extractDependency(dependency); + if (dep) { + deps.push(dep); + } + } + + if (!deps.length) { + return null; + } + + return { deps }; +} + +function extractDependency(dependency: Dependency): PackageDependency | null { + if (!dependency.source.git) { + return null; + } + + const match = gitUrl.exec(dependency.source.git.remote); + + return { + depName: + dependency.name || match.groups.depName || dependency.source.git.remote, + lookupName: dependency.source.git.remote, + currentValue: dependency.version, + managerData: { subdir: dependency.source.git.subdir }, + }; +} diff --git a/lib/manager/jsonnet-bundler/index.ts b/lib/manager/jsonnet-bundler/index.ts new file mode 100644 index 0000000000..aa3fd5480c --- /dev/null +++ b/lib/manager/jsonnet-bundler/index.ts @@ -0,0 +1,10 @@ +import { GitTagsDatasource } from '../../datasource/git-tags'; +export { updateArtifacts } from './artifacts'; +export { extractPackageFile } from './extract'; + +export const supportsLockFileMaintenance = true; + +export const defaultConfig = { + fileMatch: ['(^|/)jsonnetfile.json$'], + datasource: GitTagsDatasource.id, +}; diff --git a/lib/manager/jsonnet-bundler/readme.md b/lib/manager/jsonnet-bundler/readme.md new file mode 100644 index 0000000000..7a8d7da520 --- /dev/null +++ b/lib/manager/jsonnet-bundler/readme.md @@ -0,0 +1,5 @@ +Extracts dependencies from `jsonnetfile.json` files, updates `jsonnetfile.lock.json` and updates the `vendor` directory. + +Supports [lock file maintenance](https://docs.renovatebot.com/configuration-options/#lockfilemaintenance). + +This plugin requires `jsonnet-bundler >= v0.4.0` since previous versions don't support updating single dependencies. diff --git a/lib/manager/jsonnet-bundler/types.ts b/lib/manager/jsonnet-bundler/types.ts new file mode 100644 index 0000000000..9947e87b4d --- /dev/null +++ b/lib/manager/jsonnet-bundler/types.ts @@ -0,0 +1,20 @@ +// original spec https://github.com/jsonnet-bundler/jsonnet-bundler/tree/master/spec/v1 + +export interface JsonnetFile { + dependencies?: Dependency[]; +} + +export interface Dependency { + source: Source; + version: string; + name?: string; +} + +export interface Source { + git?: GitSource; +} + +export interface GitSource { + remote: string; + subdir?: string; +} diff --git a/lib/util/exec/buildpack.ts b/lib/util/exec/buildpack.ts index ae47f44e8c..be9b28b847 100644 --- a/lib/util/exec/buildpack.ts +++ b/lib/util/exec/buildpack.ts @@ -3,6 +3,7 @@ import { getPkgReleases } from '../../datasource'; import { logger } from '../../logger'; import * as allVersioning from '../../versioning'; import { id as composerVersioningId } from '../../versioning/composer'; +import { id as semverVersioningId } from '../../versioning/semver'; import type { ToolConfig, ToolConstraint } from './types'; const allToolConfig: Record<string, ToolConfig> = { @@ -11,6 +12,11 @@ const allToolConfig: Record<string, ToolConfig> = { depName: 'composer/composer', versioning: composerVersioningId, }, + jb: { + datasource: 'github-releases', + depName: 'jsonnet-bundler/jsonnet-bundler', + versioning: semverVersioningId, + }, }; export async function resolveConstraint( -- GitLab