diff --git a/lib/modules/datasource/api.ts b/lib/modules/datasource/api.ts index 04feb3811df0c7..923e9983981785 100644 --- a/lib/modules/datasource/api.ts +++ b/lib/modules/datasource/api.ts @@ -31,6 +31,7 @@ import { NugetDatasource } from './nuget'; import { OrbDatasource } from './orb'; import { PackagistDatasource } from './packagist'; import { PodDatasource } from './pod'; +import { PuppetDatasource } from './puppet'; import { PypiDatasource } from './pypi'; import { RepologyDatasource } from './repology'; import { RubyVersionDatasource } from './ruby-version'; @@ -77,6 +78,7 @@ api.set(NugetDatasource.id, new NugetDatasource()); api.set(OrbDatasource.id, new OrbDatasource()); api.set(PackagistDatasource.id, new PackagistDatasource()); api.set(PodDatasource.id, new PodDatasource()); +api.set(PuppetDatasource.id, new PuppetDatasource()); api.set(PypiDatasource.id, new PypiDatasource()); api.set(RepologyDatasource.id, new RepologyDatasource()); api.set(RubyVersionDatasource.id, new RubyVersionDatasource()); diff --git a/lib/modules/datasource/puppet/__fixtures__/puppetforge-response.json b/lib/modules/datasource/puppet/__fixtures__/puppetforge-response.json new file mode 100644 index 00000000000000..7d9acf0c89d128 --- /dev/null +++ b/lib/modules/datasource/puppet/__fixtures__/puppetforge-response.json @@ -0,0 +1,66 @@ +{ + "uri": "/v3/modules/puppetlabs-apache", + "slug": "puppetlabs-apache", + "name": "apache", + "downloads": 11063567, + "created_at": "2010-05-20 22:43:19 -0700", + "updated_at": "2021-10-11 07:47:24 -0700", + "deprecated_at": null, + "deprecated_for": null, + "superseded_by": null, + "supported": true, + "endorsement": "supported", + "module_group": "base", + "owner": { + "uri": "/v3/users/puppetlabs", + "slug": "puppetlabs", + "username": "puppetlabs", + "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" + }, + "premium": false, + "releases": [ + { + "uri": "/v3/releases/puppetlabs-apache-7.0.0", + "slug": "puppetlabs-apache-7.0.0", + "version": "7.0.0", + "supported": false, + "created_at": "2021-10-11 07:47:24 -0700", + "deleted_at": null, + "file_uri": "/v3/files/puppetlabs-apache-7.0.0.tar.gz", + "file_size": 331833 + }, + { + "uri": "/v3/releases/puppetlabs-apache-6.5.1", + "slug": "puppetlabs-apache-6.5.1", + "version": "6.5.1", + "supported": false, + "created_at": "2021-08-25 04:16:27 -0700", + "deleted_at": null, + "file_uri": "/v3/files/puppetlabs-apache-6.5.1.tar.gz", + "file_size": 331386 + }, + { + "uri": "/v3/releases/puppetlabs-apache-6.5.0", + "slug": "puppetlabs-apache-6.5.0", + "version": "6.5.0", + "supported": false, + "created_at": "2021-08-24 08:20:22 -0700", + "deleted_at": null, + "file_uri": "/v3/files/puppetlabs-apache-6.5.0.tar.gz", + "file_size": 331330 + }, + { + "uri": "/v3/releases/puppetlabs-apache-6.4.0", + "slug": "puppetlabs-apache-6.4.0", + "version": "6.4.0", + "supported": false, + "created_at": "2021-08-02 06:49:41 -0700", + "deleted_at": null, + "file_uri": "/v3/files/puppetlabs-apache-6.4.0.tar.gz", + "file_size": 331201 + } + ], + "feedback_score": 83, + "homepage_url": "https://github.com/puppetlabs/puppetlabs-apache", + "issues_url": "https://tickets.puppetlabs.com/browse/MODULES" +} diff --git a/lib/modules/datasource/puppet/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/puppet/__snapshots__/index.spec.ts.snap new file mode 100644 index 00000000000000..88b617f01232ce --- /dev/null +++ b/lib/modules/datasource/puppet/__snapshots__/index.spec.ts.snap @@ -0,0 +1,149 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`modules/datasource/puppet/index getReleases parses real data 1`] = ` +Object { + "deprecationMessage": null, + "registryUrl": "https://forgeapi.puppet.com", + "releases": Array [ + Object { + "downloadUrl": "/v3/files/puppetlabs-apache-6.4.0.tar.gz", + "registryUrl": "https://forgeapi.puppet.com", + "releaseTimestamp": "2021-08-02T13:49:41.000Z", + "version": "6.4.0", + }, + Object { + "downloadUrl": "/v3/files/puppetlabs-apache-6.5.0.tar.gz", + "registryUrl": "https://forgeapi.puppet.com", + "releaseTimestamp": "2021-08-24T15:20:22.000Z", + "version": "6.5.0", + }, + Object { + "downloadUrl": "/v3/files/puppetlabs-apache-6.5.1.tar.gz", + "registryUrl": "https://forgeapi.puppet.com", + "releaseTimestamp": "2021-08-25T11:16:27.000Z", + "version": "6.5.1", + }, + Object { + "downloadUrl": "/v3/files/puppetlabs-apache-7.0.0.tar.gz", + "registryUrl": "https://forgeapi.puppet.com", + "releaseTimestamp": "2021-10-11T14:47:24.000Z", + "version": "7.0.0", + }, + ], + "sourceUrl": "https://github.com/puppetlabs/puppetlabs-apache", + "tags": Object { + "endorsement": "supported", + "moduleGroup": "base", + "premium": "false", + }, +} +`; + +exports[`modules/datasource/puppet/index getReleases parses real data 2`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "forgeapi.puppet.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://forgeapi.puppet.com/v3/modules/puppetlabs-apache", + }, +] +`; + +exports[`modules/datasource/puppet/index should fetch package info from custom registry 1`] = ` +Object { + "deprecationMessage": null, + "registryUrl": "https://puppet.mycustomregistry.com", + "releases": Array [ + Object { + "downloadUrl": "/v3/files/puppetlabs-apache-6.4.0.tar.gz", + "registryUrl": "https://puppet.mycustomregistry.com", + "releaseTimestamp": "2021-08-02T13:49:41.000Z", + "version": "6.4.0", + }, + Object { + "downloadUrl": "/v3/files/puppetlabs-apache-6.5.0.tar.gz", + "registryUrl": "https://puppet.mycustomregistry.com", + "releaseTimestamp": "2021-08-24T15:20:22.000Z", + "version": "6.5.0", + }, + Object { + "downloadUrl": "/v3/files/puppetlabs-apache-6.5.1.tar.gz", + "registryUrl": "https://puppet.mycustomregistry.com", + "releaseTimestamp": "2021-08-25T11:16:27.000Z", + "version": "6.5.1", + }, + Object { + "downloadUrl": "/v3/files/puppetlabs-apache-7.0.0.tar.gz", + "registryUrl": "https://puppet.mycustomregistry.com", + "releaseTimestamp": "2021-10-11T14:47:24.000Z", + "version": "7.0.0", + }, + ], + "sourceUrl": "https://github.com/puppetlabs/puppetlabs-apache", + "tags": Object { + "endorsement": "supported", + "moduleGroup": "base", + "premium": "false", + }, +} +`; + +exports[`modules/datasource/puppet/index should fetch package info from custom registry 2`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "puppet.mycustomregistry.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://puppet.mycustomregistry.com/v3/modules/foobar", + }, +] +`; + +exports[`modules/datasource/puppet/index should return null if lookup fails 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "forgeapi.puppet.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://forgeapi.puppet.com/v3/modules/foobar", + }, +] +`; + +exports[`modules/datasource/puppet/index should return null if lookup fails 400 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "forgeapi.puppet.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://forgeapi.puppet.com/v3/modules/foobar", + }, +] +`; + +exports[`modules/datasource/puppet/index should return null if lookup fails 401 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "forgeapi.puppet.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://forgeapi.puppet.com/v3/modules/foobar", + }, +] +`; diff --git a/lib/modules/datasource/puppet/index.spec.ts b/lib/modules/datasource/puppet/index.spec.ts new file mode 100644 index 00000000000000..bd9b8cb3f10233 --- /dev/null +++ b/lib/modules/datasource/puppet/index.spec.ts @@ -0,0 +1,83 @@ +import { getPkgReleases } from '..'; +import * as httpMock from '../../../../test/http-mock'; +import { loadFixture } from '../../../../test/util'; +import { PuppetDatasource } from './index'; + +const puppetforgeReleases = loadFixture('puppetforge-response.json'); + +const datasource = PuppetDatasource.id; + +describe('modules/datasource/puppet/index', () => { + describe('getReleases', () => { + it('parses real data', async () => { + httpMock + .scope('https://forgeapi.puppet.com') + .get('/v3/modules/puppetlabs-apache') + .reply(200, puppetforgeReleases); + + const res = await getPkgReleases({ + datasource, + depName: 'puppetlabs/apache', + packageName: 'puppetlabs/apache', + registryUrls: ['https://forgeapi.puppet.com'], + }); + const release = res.releases[res.releases.length - 1]; + + expect(res.releases).toHaveLength(4); + expect(release.version).toBe('7.0.0'); + expect(release.downloadUrl).toBe( + '/v3/files/puppetlabs-apache-7.0.0.tar.gz' + ); + expect(release.releaseTimestamp).toBe('2021-10-11T14:47:24.000Z'); + expect(release.registryUrl).toBe('https://forgeapi.puppet.com'); + expect(res).toMatchSnapshot(); + expect(res.sourceUrl).toBeDefined(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + }); + + // https://forgeapi.puppet.com/#operation/getModule + it('should return null if lookup fails 400', async () => { + httpMock + .scope('https://forgeapi.puppet.com') + .get('/v3/modules/foobar') + .reply(400); + const res = await getPkgReleases({ + datasource, + depName: 'foobar', + registryUrls: ['https://forgeapi.puppet.com'], + }); + expect(res).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + // https://forgeapi.puppet.com/#operation/getModule + it('should return null if lookup fails', async () => { + httpMock + .scope('https://forgeapi.puppet.com') + .get('/v3/modules/foobar') + .reply(404); + const res = await getPkgReleases({ + datasource, + depName: 'foobar', + registryUrls: ['https://forgeapi.puppet.com'], + }); + expect(res).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + + it('should fetch package info from custom registry', async () => { + httpMock + .scope('https://puppet.mycustomregistry.com', {}) + .get('/v3/modules/foobar') + .reply(200, puppetforgeReleases); + const registryUrls = ['https://puppet.mycustomregistry.com']; + const res = await getPkgReleases({ + datasource, + depName: 'foobar', + registryUrls, + }); + expect(res).toMatchSnapshot(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); +}); diff --git a/lib/modules/datasource/puppet/index.ts b/lib/modules/datasource/puppet/index.ts new file mode 100644 index 00000000000000..0e383d7d4899c4 --- /dev/null +++ b/lib/modules/datasource/puppet/index.ts @@ -0,0 +1,52 @@ +import { logger } from '../../../logger'; +import { Datasource } from '../datasource'; +import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; +import type { PuppetModule } from './types'; + +export class PuppetDatasource extends Datasource { + static id = 'puppet'; + + constructor() { + super(PuppetDatasource.id); + } + + async getReleases({ + packageName, + registryUrl, + }: GetReleasesConfig): Promise { + // https://forgeapi.puppet.com + const moduleSlug = packageName.replace('/', '-'); + const url = `${registryUrl}/v3/modules/${moduleSlug}`; + + let moduleResponse; + try { + moduleResponse = await this.http.get(url); + } catch (e) { + logger.warn( + `ignore dependency ${packageName} because of faulty response for ${url}: ${e}` + ); + return null; + } + + const module: PuppetModule = JSON.parse(moduleResponse.body); + + const releases: Release[] = module.releases.map((release) => ({ + version: release.version, + downloadUrl: release.file_uri, + releaseTimestamp: release.created_at, + registryUrl, + })); + + return { + releases, + deprecationMessage: module.deprecated_for, + homepage: module.homepage_url, + tags: { + // is this the correct use of tags? + endorsement: module.endorsement, + moduleGroup: module.module_group, + premium: `${module.premium}`, + }, + }; + } +} diff --git a/lib/modules/datasource/puppet/types.ts b/lib/modules/datasource/puppet/types.ts new file mode 100644 index 00000000000000..cdf3b9e94b45b8 --- /dev/null +++ b/lib/modules/datasource/puppet/types.ts @@ -0,0 +1,94 @@ +export interface PuppetModule { + uri: string, + slug: string, + name: string, + deprecated_at: string | null, + owner: PuppetModuleOwner | null, + downloads: number, + created_at: string, + updated_at: string, + deprecated_for: string | null, + superseded_by: PuppetSupercededBy | null, + endorsement: PuppetEndorsement | null, + module_group: PuppetModuleGroup | null, + premium: boolean, + current_release: PuppetRelease | null, + releases: PuppetReleaseAbbreviated[], + feedback_score: IntRange<101>, + homepage_url: string, + issues_url: string, +} + +export type PuppetModuleAbbreviated = Pick; + +export interface PuppetRelease { + uri: string, + slug: string, + module: PuppetModuleAbbreviated, + version: string, + metadata: Record, + tags: string[], + pdk: boolean, + validation_score: IntRange<101>, + file_uri: string, + file_size: number, + file_md5: string, + file_sha256: string, + downloads: number, + readme: string, + changelog: string, + license: string, + reference: string, + pe_compatibility: string[] | null, + tasks: PuppetBoltTask[], + plans: PuppetBoltPlan[], + created_at: string, + updated_at: string, + deleted_at: string | null, + deleted_for: string | null, +} + +export type PuppetReleaseAbbreviated = Pick; + +export interface PuppetBoltPlan { + uri: string, + name: string, + private: boolean, +} + +export interface PuppetBoltTask { + name: string, + executables: string[], + description: string, + metadata: Record, +} + +export interface PuppetSupercededBy { + uri: string, + slug: string, +} + +export interface PuppetModuleOwner { + uri: string, + slug: string, + username: string, + gravatar_id: string, +} + +export type PuppetEndorsement = 'supported' | 'approved' | 'partner'; +export type PuppetModuleGroup = 'base' | 'pe_only'; + +type IntRange = number extends MAX_EXCLUSIVE ? number : _IntRangeHelper; +type _IntRangeHelper = R['length'] extends T ? R[number] : _IntRangeHelper; diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts index 3f45b28b1b1380..d1db36cd1d3ca4 100644 --- a/lib/modules/manager/api.ts +++ b/lib/modules/manager/api.ts @@ -55,6 +55,7 @@ import * as pipenv from './pipenv'; import * as poetry from './poetry'; import * as preCommit from './pre-commit'; import * as pub from './pub'; +import * as puppet from './puppet'; import * as pyenv from './pyenv'; import * as regex from './regex'; import * as rubyVersion from './ruby-version'; @@ -128,6 +129,7 @@ api.set('pipenv', pipenv); api.set('poetry', poetry); api.set('pre-commit', preCommit); api.set('pub', pub); +api.set('puppet', puppet); api.set('pyenv', pyenv); api.set('regex', regex); api.set('ruby-version', rubyVersion); diff --git a/lib/modules/manager/puppet/__docs__/puppet_overview.dio.png b/lib/modules/manager/puppet/__docs__/puppet_overview.dio.png new file mode 100644 index 00000000000000..f26507cf8b9310 Binary files /dev/null and b/lib/modules/manager/puppet/__docs__/puppet_overview.dio.png differ diff --git a/lib/modules/manager/puppet/__fixtures__/Puppetfile_multiple_forges b/lib/modules/manager/puppet/__fixtures__/Puppetfile_multiple_forges new file mode 100644 index 00000000000000..f55e46173dfa89 --- /dev/null +++ b/lib/modules/manager/puppet/__fixtures__/Puppetfile_multiple_forges @@ -0,0 +1,19 @@ +forge "https://forgeapi.puppetlabs.com" + +######################### +## Puppetforge Modules ## +######################### + +mod 'puppetlabs/stdlib', '8.0.0' +mod 'puppetlabs/apache', '6.5.1' +mod 'puppetlabs/puppetdb', '7.9.0' + +forge "https://some-other-puppet-forge.com" + +########################### +## Some Other Mock Forge ## +########################### + +mod 'mock/mockstdlib', '10.0.0' +mod 'mock/mockapache', '2.5.1' +mod 'mock/mockpuppetdb', '1.9.0' diff --git a/lib/modules/manager/puppet/__fixtures__/Puppetfile_no_forge b/lib/modules/manager/puppet/__fixtures__/Puppetfile_no_forge new file mode 100644 index 00000000000000..0c5afdb53a7054 --- /dev/null +++ b/lib/modules/manager/puppet/__fixtures__/Puppetfile_no_forge @@ -0,0 +1,3 @@ +mod 'puppetlabs/stdlib', '8.0.0' +mod 'puppetlabs/apache', '6.5.1' +mod 'puppetlabs/puppetdb', '7.9.0' diff --git a/lib/modules/manager/puppet/__fixtures__/Puppetfile_single_forge b/lib/modules/manager/puppet/__fixtures__/Puppetfile_single_forge new file mode 100644 index 00000000000000..f9b61900df6cc4 --- /dev/null +++ b/lib/modules/manager/puppet/__fixtures__/Puppetfile_single_forge @@ -0,0 +1,4 @@ +forge "https://forgeapi.puppetlabs.com" +mod 'puppetlabs/stdlib', '8.0.0' +mod 'puppetlabs/apache', '6.5.1' +mod 'puppetlabs/puppetdb', '7.9.0' diff --git a/lib/modules/manager/puppet/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/puppet/__snapshots__/extract.spec.ts.snap new file mode 100644 index 00000000000000..6e2013407a9ffb --- /dev/null +++ b/lib/modules/manager/puppet/__snapshots__/extract.spec.ts.snap @@ -0,0 +1,92 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`modules/manager/puppet/extract extractPackageFile() extracts multiple modules from Puppetfile with multiple forges/registries 1`] = ` +Array [ + Object { + "currentValue": "8.0.0", + "datasource": "puppet", + "depName": "puppetlabs/stdlib", + "packageName": "puppetlabs/stdlib", + "registryUrls": Array [ + "https://forgeapi.puppetlabs.com", + ], + }, + Object { + "currentValue": "6.5.1", + "datasource": "puppet", + "depName": "puppetlabs/apache", + "packageName": "puppetlabs/apache", + "registryUrls": Array [ + "https://forgeapi.puppetlabs.com", + ], + }, + Object { + "currentValue": "7.9.0", + "datasource": "puppet", + "depName": "puppetlabs/puppetdb", + "packageName": "puppetlabs/puppetdb", + "registryUrls": Array [ + "https://forgeapi.puppetlabs.com", + ], + }, + Object { + "currentValue": "10.0.0", + "datasource": "puppet", + "depName": "mock/mockstdlib", + "packageName": "mock/mockstdlib", + "registryUrls": Array [ + "https://some-other-puppet-forge.com", + ], + }, + Object { + "currentValue": "2.5.1", + "datasource": "puppet", + "depName": "mock/mockapache", + "packageName": "mock/mockapache", + "registryUrls": Array [ + "https://some-other-puppet-forge.com", + ], + }, + Object { + "currentValue": "1.9.0", + "datasource": "puppet", + "depName": "mock/mockpuppetdb", + "packageName": "mock/mockpuppetdb", + "registryUrls": Array [ + "https://some-other-puppet-forge.com", + ], + }, +] +`; + +exports[`modules/manager/puppet/extract extractPackageFile() extracts multiple modules from Puppetfile without a forge 1`] = ` +Array [ + Object { + "currentValue": "8.0.0", + "datasource": "puppet", + "depName": "puppetlabs/stdlib", + "packageName": "puppetlabs/stdlib", + "registryUrls": Array [ + "https://forgeapi.puppet.com", + ], + }, + Object { + "currentValue": "6.5.1", + "datasource": "puppet", + "depName": "puppetlabs/apache", + "packageName": "puppetlabs/apache", + "registryUrls": Array [ + "https://forgeapi.puppet.com", + ], + }, + Object { + "currentValue": "7.9.0", + "datasource": "puppet", + "depName": "puppetlabs/puppetdb", + "packageName": "puppetlabs/puppetdb", + "registryUrls": Array [ + "https://forgeapi.puppet.com", + ], + }, +] +`; diff --git a/lib/modules/manager/puppet/constants.ts b/lib/modules/manager/puppet/constants.ts new file mode 100644 index 00000000000000..83c030d718e083 --- /dev/null +++ b/lib/modules/manager/puppet/constants.ts @@ -0,0 +1,3 @@ +export const simpleModuleLineRegexFactory = (): RegExp => /^mod\s*'([^']+)',\s*'([^']+)'$/gm; +export const gitModuleRegexFactory = (): RegExp => /^mod '(?[^']+)',(?![^\n])(?:\n\s+:(?\w+)\s+=>\s+'(?[^']+)',?)(?![^\n])(?:\n\s+:(?\w+)\s+=>\s+'(?[^']+)',?)?(?![^\n])(?:\n\s+:(?\w+)\s+=>\s+'(?[^']+)',?)?/gm; +export const forgeRegexFactory = (): RegExp => /^forge\s+['"]([^'"]+)['"]$((?:\n|(?!^forge).)*)/gm; diff --git a/lib/modules/manager/puppet/extract.spec.ts b/lib/modules/manager/puppet/extract.spec.ts new file mode 100644 index 00000000000000..adf23e9e077f95 --- /dev/null +++ b/lib/modules/manager/puppet/extract.spec.ts @@ -0,0 +1,30 @@ +import { Fixtures } from '../../../../test/fixtures'; +import { extractPackageFile } from './extract'; + +describe('modules/manager/puppet/extract', () => { + describe('extractPackageFile()', () => { + it('returns null for empty Puppetfile', () => { + expect(extractPackageFile('')).toBeNull(); + }); + + it('extracts multiple modules from Puppetfile without a forge', () => { + const res = extractPackageFile(Fixtures.get('Puppetfile_no_forge')); + expect(res.deps).toHaveLength(3); + expect(res.deps).toMatchSnapshot(); + }); + + it('extracts multiple modules from Puppetfile with multiple forges/registries', () => { + const res = extractPackageFile(Fixtures.get('Puppetfile_multiple_forges')); + expect(res.deps).toHaveLength(6); + + const forgeApiDeps = res.deps.filter(dep => dep.registryUrls.includes('https://forgeapi.puppetlabs.com')); + const mockDeps = res.deps.filter(dep => dep.registryUrls.includes('https://some-other-puppet-forge.com')); + + expect(forgeApiDeps).toHaveLength(3); + expect(mockDeps).toHaveLength(3); + + expect(res.deps).toMatchSnapshot(); + }); + + }); +}); diff --git a/lib/modules/manager/puppet/extract.ts b/lib/modules/manager/puppet/extract.ts new file mode 100644 index 00000000000000..e974977f9f0d50 --- /dev/null +++ b/lib/modules/manager/puppet/extract.ts @@ -0,0 +1,64 @@ +import { logger } from '../../../logger'; +import { PuppetDatasource } from '../../datasource/puppet'; +import type { PackageDependency, PackageFile } from '../types'; +import { forgeRegexFactory, simpleModuleLineRegexFactory } from './constants'; + +interface ForgeContent { + forgeUrl: string; + moduleContent: string; +} + +export function extractPackageFile(content: string): PackageFile | null { + logger.trace('puppet.extractPackageFile()'); + + const deps: PackageDependency[] = []; + + const forgeContents: ForgeContent[] = []; + + const forgeRegex = forgeRegexFactory(); + let forge: RegExpExecArray; + + if(!forgeRegexFactory().test(content)) { + forgeContents.push({ + forgeUrl: 'https://forgeapi.puppet.com', // TODO: default Registry + moduleContent: content, + }); + } + + while ((forge = forgeRegex.exec(content)) !== null) { + const forgeUrl = forge[1]; + const moduleContent = forge[2]; + + forgeContents.push({ + forgeUrl, + moduleContent, + }); + } + + for (const {forgeUrl, moduleContent} of forgeContents) { + + const simpleModuleLineRegex = simpleModuleLineRegexFactory(); + + let line: RegExpExecArray; + while ((line = simpleModuleLineRegex.exec(moduleContent)) !== null) { + const module = line[1]; + const version = line[2]; + + const dep: PackageDependency = { + depName: module, + datasource: PuppetDatasource.id, + packageName: module, + currentValue: version, + registryUrls: [forgeUrl], + }; + + deps.push(dep); + } + } + + if (deps.length) { + return { deps }; + } + + return null; +} diff --git a/lib/modules/manager/puppet/index.ts b/lib/modules/manager/puppet/index.ts new file mode 100644 index 00000000000000..4ed9e25c352c43 --- /dev/null +++ b/lib/modules/manager/puppet/index.ts @@ -0,0 +1,12 @@ +import { ProgrammingLanguage } from '../../../constants'; +import { PuppetDatasource } from '../../datasource/puppet'; + +export { extractPackageFile } from './extract'; + +export const language = ProgrammingLanguage.Ruby; + +export const defaultConfig = { + fileMatch: ['^Puppetfile$'], +}; + +export const supportedDatasources = [PuppetDatasource.id]; diff --git a/lib/modules/manager/puppet/readme.md b/lib/modules/manager/puppet/readme.md new file mode 100644 index 00000000000000..6aa54b2ef02aae --- /dev/null +++ b/lib/modules/manager/puppet/readme.md @@ -0,0 +1,66 @@ +simply keeps Puppetfiles updated + +# supported Puppetfile formats + +the manager extracts the deps from one Puppetfile + +the Puppetfile supports at the moment different ways to configure forges + +1. no forge defined + +```ruby +mod 'puppetlabs/apt', '8.3.0' +mod 'puppetlabs/apache', '7.0.0' +``` + +2. one forge defined: `forge "https://forgeapi.puppetlabs.com"` + +```ruby +forge "https://forgeapi.puppetlabs.com" + +mod 'puppetlabs/apt', '8.3.0' +mod 'puppetlabs/apache', '7.0.0' +mod 'puppetlabs/concat', '7.1.1' +``` + +3. multiple forges defined + +```ruby +forge "https://forgeapi.puppetlabs.com" + +mod 'puppetlabs/apt', '8.3.0' +mod 'puppetlabs/apache', '7.0.0' +mod 'puppetlabs/concat', '7.1.1' + +# private forge +forge "https://forgeapi.example.com" + +mod 'example/infra', '3.3.0' +``` + +# general overview + +![puppet overview](__docs__/puppet_overview.dio.png) + +# possible improvements + +## monorepo + +at the moment we support only one Puppetfile per repository + +## git-support + +usually you can add the versions to a forge and use the already provided +way of updating + +```ruby +# tag based +mod 'example/standalone_jar', + :git => 'git@gitlab.example.de:puppet/example-standalone_jar', + :tag => '0.9.0' + +# branch based +mod 'example/samba', + :git => 'https://github.com/example/puppet-samba', + :branch => 'stable_version' +``` diff --git a/lib/workers/repository/update/branch/auto-replace.ts b/lib/workers/repository/update/branch/auto-replace.ts index 60c30a4bf0379f..f5ca7379108671 100644 --- a/lib/workers/repository/update/branch/auto-replace.ts +++ b/lib/workers/repository/update/branch/auto-replace.ts @@ -139,6 +139,7 @@ export async function doAutoReplace( return existingContent; } const replaceString = upgrade.replaceString || currentValue; + logger.trace({ depName, replaceString }, 'autoReplace replaceString'); let searchIndex = existingContent.indexOf(replaceString); if (searchIndex === -1) {