Skip to content

Commit

Permalink
feat(jenkins-plugins): support yaml file format (#9069)
Browse files Browse the repository at this point in the history
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
  • Loading branch information
3 people committed Mar 22, 2021
1 parent af1e4ee commit 62e7fe3
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 33 deletions.
Empty file.
4 changes: 4 additions & 0 deletions lib/manager/jenkins/__fixtures__/invalid.yaml
@@ -0,0 +1,4 @@
## This is an invalid YAML document

text and plugins are not supported and
invalid-plugin:0.0.0
26 changes: 26 additions & 0 deletions lib/manager/jenkins/__fixtures__/plugins.yaml
@@ -0,0 +1,26 @@
plugins:
- artifactId: git
source:
version: latest
- artifactId: job-import-plugin
source:
version: '2.10'
- artifactId: invalid-version-plugin
source:
version: 2.10 # Float will cause the version to be 2.1 and will be ignored with a warning
- artifactId: ignore-plugin
source:
version: '2.10'
renovate:
ignore: true
- artifactId: docker
- artifactId: cloudbees-bitbucket-branch-source
source:
version: experimental
- artifactId: script-security
source:
url: http://ftp-chi.osuosl.org/pub/jenkins/plugins/script-security/1.56/script-security.hpi
- artifactId: workflow-step-api
groupId: org.jenkins-ci.plugins.workflow
source:
version: 2.19-rc289.d09828a05a74
60 changes: 59 additions & 1 deletion lib/manager/jenkins/__snapshots__/extract.spec.ts.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`manager/jenkins/extract extractPackageFile() extracts multiple image lines 1`] = `
exports[`manager/jenkins/extract extractPackageFile() extracts multiple image lines in text format 1`] = `
Array [
Object {
"currentValue": "1.2.3",
Expand Down Expand Up @@ -42,3 +42,61 @@ Array [
},
]
`;

exports[`manager/jenkins/extract extractPackageFile() extracts multiple image lines in yaml format 1`] = `
Array [
Object {
"currentValue": "latest",
"datasource": "jenkins-plugins",
"depName": "git",
"skipReason": "unsupported-version",
"versioning": "docker",
},
Object {
"currentValue": "2.10",
"datasource": "jenkins-plugins",
"depName": "job-import-plugin",
"versioning": "docker",
},
Object {
"currentValue": "2.1",
"datasource": "jenkins-plugins",
"depName": "invalid-version-plugin",
"skipReason": "invalid-version",
"versioning": "docker",
},
Object {
"currentValue": "2.10",
"datasource": "jenkins-plugins",
"depName": "ignore-plugin",
"skipReason": "ignored",
"versioning": "docker",
},
Object {
"datasource": "jenkins-plugins",
"depName": "docker",
"skipReason": "no-version",
"versioning": "docker",
},
Object {
"currentValue": "experimental",
"datasource": "jenkins-plugins",
"depName": "cloudbees-bitbucket-branch-source",
"skipReason": "unsupported-version",
"versioning": "docker",
},
Object {
"datasource": "jenkins-plugins",
"depName": "script-security",
"skipReason": "internal-package",
"versioning": "docker",
},
Object {
"currentValue": "2.19-rc289.d09828a05a74",
"datasource": "jenkins-plugins",
"depName": "workflow-step-api",
"skipReason": "unsupported-version",
"versioning": "docker",
},
]
`;
43 changes: 36 additions & 7 deletions lib/manager/jenkins/extract.spec.ts
Expand Up @@ -2,27 +2,56 @@ import { readFileSync } from 'fs';
import { getName } from '../../../test/util';
import { extractPackageFile } from './extract';

const pluginsFile = readFileSync(
const invalidYamlFile = readFileSync(
'lib/manager/jenkins/__fixtures__/invalid.yaml',
'utf8'
);

const pluginsTextFile = readFileSync(
'lib/manager/jenkins/__fixtures__/plugins.txt',
'utf8'
);
const pluginsYamlFile = readFileSync(
'lib/manager/jenkins/__fixtures__/plugins.yaml',
'utf8'
);

const pluginsEmptyFile = readFileSync(
const pluginsEmptyTextFile = readFileSync(
'lib/manager/jenkins/__fixtures__/empty.txt',
'utf8'
);
const pluginsEmptyYamlFile = readFileSync(
'lib/manager/jenkins/__fixtures__/empty.yaml',
'utf8'
);

describe(getName(__filename), () => {
describe('extractPackageFile()', () => {
it('returns empty list for an empty file', () => {
const res = extractPackageFile(pluginsEmptyFile);
expect(res.deps).toHaveLength(0);
it('returns empty list for an empty text file', () => {
const res = extractPackageFile(pluginsEmptyTextFile, 'path/file.txt');
expect(res).toBeNull();
});

it('extracts multiple image lines', () => {
const res = extractPackageFile(pluginsFile);
it('returns empty list for an empty yaml file', () => {
const res = extractPackageFile(pluginsEmptyYamlFile, 'path/file.yaml');
expect(res).toBeNull();
});

it('returns empty list for an invalid yaml file', () => {
const res = extractPackageFile(invalidYamlFile, 'path/file.yaml');
expect(res).toBeNull();
});

it('extracts multiple image lines in text format', () => {
const res = extractPackageFile(pluginsTextFile, 'path/file.txt');
expect(res.deps).toMatchSnapshot();
expect(res.deps).toHaveLength(6);
});

it('extracts multiple image lines in yaml format', () => {
const res = extractPackageFile(pluginsYamlFile, 'path/file.yml');
expect(res.deps).toMatchSnapshot();
expect(res.deps).toHaveLength(8);
});
});
});
103 changes: 91 additions & 12 deletions lib/manager/jenkins/extract.ts
@@ -1,33 +1,112 @@
import yaml from 'js-yaml';
import * as datasourceJenkins from '../../datasource/jenkins-plugins';
import { logger } from '../../logger';
import { SkipReason } from '../../types';
import { isSkipComment } from '../../util/ignore';
import * as dockerVersioning from '../../versioning/docker';
import type { PackageDependency, PackageFile } from '../types';
import type { JenkinsPlugin, JenkinsPlugins } from './types';

export function extractPackageFile(content: string): PackageFile | null {
logger.trace('jenkins.extractPackageFile()');
const YamlExtension = /\.ya?ml$/;

function getDependency(plugin: JenkinsPlugin): PackageDependency {
const dep: PackageDependency = {
datasource: datasourceJenkins.id,
versioning: dockerVersioning.id,
depName: plugin.artifactId,
};

if (plugin.source?.version) {
dep.currentValue = plugin.source.version.toString();
if (typeof plugin.source.version !== 'string') {
dep.skipReason = SkipReason.InvalidVersion;
logger.warn(
{ dep },
'Jenkins plugin dependency version is not a string and will be ignored'
);
}
} else {
dep.skipReason = SkipReason.NoVersion;
}

if (
plugin.source?.version === 'latest' ||
plugin.source?.version === 'experimental' ||
plugin.groupId
) {
dep.skipReason = SkipReason.UnsupportedVersion;
}

if (plugin.source?.url) {
dep.skipReason = SkipReason.InternalPackage;
}

if (!dep.skipReason && plugin.renovate?.ignore) {
dep.skipReason = SkipReason.Ignored;
}

logger.debug({ dep }, 'Jenkins plugin dependency');
return dep;
}

function extractYaml(content: string): PackageDependency[] {
const deps: PackageDependency[] = [];

try {
const doc = yaml.safeLoad(content, { json: true }) as JenkinsPlugins;
if (doc?.plugins) {
for (const plugin of doc.plugins) {
if (plugin.artifactId) {
const dep = getDependency(plugin);
deps.push(dep);
}
}
}
} catch (err) {
logger.warn({ err }, 'Error parsing Jenkins plugins');
}
return deps;
}

function extractText(content: string): PackageDependency[] {
const deps: PackageDependency[] = [];
const regex = /^\s*(?<depName>[\d\w-]+):(?<currentValue>[^#\s]+)[#\s]*(?<comment>.*)$/;

for (const line of content.split('\n')) {
const match = regex.exec(line);

if (match) {
const { depName, currentValue, comment } = match.groups;
const dep: PackageDependency = {
datasource: datasourceJenkins.id,
versioning: dockerVersioning.id,
depName,
currentValue,
const plugin: JenkinsPlugin = {
artifactId: depName,
source: {
version: currentValue,
},
renovate: {
ignore: isSkipComment(comment),
},
};

if (isSkipComment(comment)) {
dep.skipReason = SkipReason.Ignored;
}
const dep = getDependency(plugin);
deps.push(dep);
}
}
return deps;
}

export function extractPackageFile(
content: string,
fileName: string
): PackageFile | null {
logger.trace('jenkins.extractPackageFile()');
const deps: PackageDependency[] = [];

if (YamlExtension.test(fileName)) {
deps.push(...extractYaml(content));
} else {
deps.push(...extractText(content));
}

if (deps.length === 0) {
return null;
}
return { deps };
}
2 changes: 1 addition & 1 deletion lib/manager/jenkins/index.ts
@@ -1,5 +1,5 @@
export { extractPackageFile } from './extract';

export const defaultConfig = {
fileMatch: ['(^|/)plugins\\.txt'],
fileMatch: ['(^|/)plugins\\.(txt|ya?ml)$'],
};
15 changes: 3 additions & 12 deletions lib/manager/jenkins/readme.md
@@ -1,13 +1,4 @@
The Jenkins manager supports the following format of the plugin list:
The Jenkins manager supports a custom text or YAML format of the plugin list as described [here](https://github.com/jenkinsci/plugin-installation-manager-tool#plugin-input-format).
Only versions from the main [Jenkins update center](https://updates.jenkins.io/) are supported.

```text
plugin1:1.2.3
plugin2:4.5 # this is a comment
# this line is ignored
# Renovate will not upgrade the following dependency:
plugin3:7.8.9 # renovate:ignore
```

There's no strict specification on the name of the files, but usually it's `plugins.txt`
There are no strict filename rules, the convention is to name the file `plugins.txt` or `plugins.yaml`.
19 changes: 19 additions & 0 deletions lib/manager/jenkins/types.ts
@@ -0,0 +1,19 @@
export interface JenkinsPluginRenovate {
ignore?: boolean;
}

export interface JenkinsPluginSource {
version?: string;
url?: string;
}

export interface JenkinsPlugin {
artifactId?: string;
groupId?: string;
source?: JenkinsPluginSource;
renovate?: JenkinsPluginRenovate;
}

export interface JenkinsPlugins {
plugins?: JenkinsPlugin[];
}

0 comments on commit 62e7fe3

Please sign in to comment.