diff --git a/docs/usage/getting-started/private-packages.md b/docs/usage/getting-started/private-packages.md index 451cb54b4ecb64846ed2c31f77b2f8d34ac24f43..c6d4c6a0e64f66e6f3c428faaec5b902cec88ee3 100644 --- a/docs/usage/getting-started/private-packages.md +++ b/docs/usage/getting-started/private-packages.md @@ -179,6 +179,21 @@ The following details the most common/popular manager artifacts updating and how Any `hostRules` token for `github.com` or `gitlab.com` are found and written out to `COMPOSER_AUTH` in env for Composer to parse. Any `hostRules` with `hostType=packagist` are also included. +For dependencies on `github.com` without a packagist server, a hostRule with `hostType=git-tags` should be used with a personal access token (not an application token). +Do not add a hostRule with `hostType=github` because it can override the default renovate application token for everything else and cause unwanted side effects. + +The repository in `composer.json` should have the `vcs` type with a `https` url. Ex: + +```json +{ + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/organization/private-repository" + } + ] +} +``` ### gomod diff --git a/lib/modules/manager/composer/artifacts.spec.ts b/lib/modules/manager/composer/artifacts.spec.ts index 5626a6508599c04fdbc07cb3fa05627d1b26d770..f28bdec566600f8393811b253607f10c0397f7a1 100644 --- a/lib/modules/manager/composer/artifacts.spec.ts +++ b/lib/modules/manager/composer/artifacts.spec.ts @@ -8,6 +8,7 @@ import * as docker from '../../../util/exec/docker'; import type { StatusResult } from '../../../util/git/types'; import * as hostRules from '../../../util/host-rules'; import * as _datasource from '../../datasource'; +import { GitTagsDatasource } from '../../datasource/git-tags'; import { PackagistDatasource } from '../../datasource/packagist'; import type { UpdateArtifactsConfig } from '../types'; import * as composer from '.'; @@ -113,7 +114,13 @@ describe('modules/manager/composer/artifacts', () => { hostRules.add({ hostType: PlatformId.Github, matchHost: 'api.github.com', - token: 'github-token', + token: 'ghp_github-token', + }); + // This rule should not affect the result the Github rule has priority to avoid breaking changes. + hostRules.add({ + hostType: GitTagsDatasource.id, + matchHost: 'github.com', + token: 'ghp_git-tags-token', }); hostRules.add({ hostType: PlatformId.Gitlab, @@ -165,7 +172,14 @@ describe('modules/manager/composer/artifacts', () => { cwd: '/tmp/github/some/repo', env: { COMPOSER_AUTH: - '{"github-oauth":{"github.com":"github-token"},"gitlab-token":{"gitlab.com":"gitlab-token"},"gitlab-domains":["gitlab.com"],"http-basic":{"packagist.renovatebot.com":{"username":"some-username","password":"some-password"},"artifactory.yyyyyyy.com":{"username":"some-other-username","password":"some-other-password"}},"bearer":{"packages-bearer.example.com":"abcdef0123456789"}}', + '{"github-oauth":{"github.com":"ghp_git-tags-token"},' + + '"gitlab-token":{"gitlab.com":"gitlab-token"},' + + '"gitlab-domains":["gitlab.com"],' + + '"http-basic":{' + + '"packagist.renovatebot.com":{"username":"some-username","password":"some-password"},' + + '"artifactory.yyyyyyy.com":{"username":"some-other-username","password":"some-other-password"}' + + '},' + + '"bearer":{"packages-bearer.example.com":"abcdef0123456789"}}', COMPOSER_CACHE_DIR: '/tmp/renovate/cache/others/composer', }, }, @@ -173,6 +187,78 @@ describe('modules/manager/composer/artifacts', () => { ]); }); + it('git-tags hostRule for github.com set github-token in COMPOSER_AUTH', async () => { + hostRules.add({ + hostType: GitTagsDatasource.id, + matchHost: 'github.com', + token: 'ghp_token', + }); + fs.readLocalFile.mockResolvedValueOnce('{}'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce('{}'); + const authConfig = { + ...config, + registryUrls: ['https://packagist.renovatebot.com'], + }; + git.getRepoStatus.mockResolvedValueOnce(repoStatus); + expect( + await composer.updateArtifacts({ + packageFileName: 'composer.json', + updatedDeps: [], + newPackageFileContent: '{}', + config: authConfig, + }) + ).toBeNull(); + + expect(execSnapshots).toMatchObject([ + { + options: { + env: { + COMPOSER_AUTH: '{"github-oauth":{"github.com":"ghp_token"}}', + }, + }, + }, + ]); + }); + + it('Skip github application access token hostRules in COMPOSER_AUTH', async () => { + hostRules.add({ + hostType: PlatformId.Github, + matchHost: 'api.github.com', + token: 'ghs_token', + }); + hostRules.add({ + hostType: GitTagsDatasource.id, + matchHost: 'github.com', + token: 'ghp_token', + }); + fs.readLocalFile.mockResolvedValueOnce('{}'); + const execSnapshots = mockExecAll(); + fs.readLocalFile.mockResolvedValueOnce('{}'); + const authConfig = { + ...config, + registryUrls: ['https://packagist.renovatebot.com'], + }; + git.getRepoStatus.mockResolvedValueOnce(repoStatus); + expect( + await composer.updateArtifacts({ + packageFileName: 'composer.json', + updatedDeps: [], + newPackageFileContent: '{}', + config: authConfig, + }) + ).toBeNull(); + expect(execSnapshots).toMatchObject([ + { + options: { + env: { + COMPOSER_AUTH: '{"github-oauth":{"github.com":"ghp_token"}}', + }, + }, + }, + ]); + }); + it('returns updated composer.lock', async () => { fs.readLocalFile.mockResolvedValueOnce('{}'); const execSnapshots = mockExecAll(); diff --git a/lib/modules/manager/composer/artifacts.ts b/lib/modules/manager/composer/artifacts.ts index 9e0d55a8064c5ae5483f1b971ce0f4d432942b4d..a81478f1360bc4bf3665eb6549218cf5f1511306 100644 --- a/lib/modules/manager/composer/artifacts.ts +++ b/lib/modules/manager/composer/artifacts.ts @@ -19,12 +19,14 @@ import { import { getRepoStatus } from '../../../util/git'; import * as hostRules from '../../../util/host-rules'; import { regEx } from '../../../util/regex'; +import { GitTagsDatasource } from '../../datasource/git-tags'; import { PackagistDatasource } from '../../datasource/packagist'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; import type { AuthJson, ComposerLock } from './types'; import { composerVersioningId, extractConstraints, + findGithubPersonalAccessToken, getComposerArguments, getPhpConstraint, requireComposerDependencyInstallation, @@ -33,13 +35,23 @@ import { function getAuthJson(): string | null { const authJson: AuthJson = {}; - const githubCredentials = hostRules.find({ + const githubToken = findGithubPersonalAccessToken({ hostType: PlatformId.Github, url: 'https://api.github.com/', }); - if (githubCredentials?.token) { + if (githubToken) { authJson['github-oauth'] = { - 'github.com': githubCredentials.token.replace('x-access-token:', ''), + 'github.com': githubToken, + }; + } + + const gitTagsGithubToken = findGithubPersonalAccessToken({ + hostType: GitTagsDatasource.id, + url: 'https://github.com', + }); + if (gitTagsGithubToken) { + authJson['github-oauth'] = { + 'github.com': gitTagsGithubToken, }; } diff --git a/lib/modules/manager/composer/utils.spec.ts b/lib/modules/manager/composer/utils.spec.ts index 4f2e0fd5ff76aa3a10f3a8cce4a356888044b45b..a6d02e0c74ba67d6ab4c38bc3e090d84a8a040c7 100644 --- a/lib/modules/manager/composer/utils.spec.ts +++ b/lib/modules/manager/composer/utils.spec.ts @@ -1,13 +1,21 @@ import { GlobalConfig } from '../../../config/global'; +import * as hostRules from '../../../util/host-rules'; +import { GitTagsDatasource } from '../../datasource/git-tags'; import { extractConstraints, + findGithubPersonalAccessToken, getComposerArguments, + isPersonalAccessToken, requireComposerDependencyInstallation, } from './utils'; jest.mock('../../datasource'); describe('modules/manager/composer/utils', () => { + beforeEach(() => { + hostRules.clear(); + }); + describe('extractConstraints', () => { it('returns from require', () => { expect( @@ -288,4 +296,59 @@ describe('modules/manager/composer/utils', () => { ).toBeFalse(); }); }); + + describe('findGithubPersonalAccessToken', () => { + it('returns the token string when hostRule match search with a valid personal access token', () => { + const TOKEN_STRING = 'ghp_TOKEN'; + hostRules.add({ + hostType: GitTagsDatasource.id, + matchHost: 'github.com', + token: TOKEN_STRING, + }); + expect( + findGithubPersonalAccessToken({ + hostType: GitTagsDatasource.id, + url: 'https://github.com', + }) + ).toEqual(TOKEN_STRING); + }); + + it('returns undefined when hostRule match search with a invalid personal access token', () => { + const TOKEN_STRING = 'NOT_A_PERSONAL_ACCESS_TOKEN'; + hostRules.add({ + hostType: GitTagsDatasource.id, + matchHost: 'github.com', + token: TOKEN_STRING, + }); + expect( + findGithubPersonalAccessToken({ + hostType: GitTagsDatasource.id, + url: 'https://github.com', + }) + ).toBeUndefined(); + }); + + it('returns undefined when no hostRule match search', () => { + expect( + findGithubPersonalAccessToken({ + hostType: GitTagsDatasource.id, + url: 'https://github.com', + }) + ).toBeUndefined(); + }); + }); + + describe('isPersonalAccessToken', () => { + it('returns true when string is a github personnal access token', () => { + expect(isPersonalAccessToken('ghp_XXXXXX')).toBeTrue(); + }); + + it('returns false when string is a github application token', () => { + expect(isPersonalAccessToken('ghs_XXXXXX')).toBeFalse(); + }); + + it('returns false when string is not a token at all', () => { + expect(isPersonalAccessToken('XXXXXX')).toBeFalse(); + }); + }); }); diff --git a/lib/modules/manager/composer/utils.ts b/lib/modules/manager/composer/utils.ts index 3571196ad30ae71abd787cdcc94c624bc9bcccb9..0657eb9c88a6a7f632a073de47153fe1cfc5ea8d 100644 --- a/lib/modules/manager/composer/utils.ts +++ b/lib/modules/manager/composer/utils.ts @@ -4,6 +4,8 @@ import { quote } from 'shlex'; import { GlobalConfig } from '../../../config/global'; import { logger } from '../../../logger'; import type { ToolConstraint } from '../../../util/exec/types'; +import { HostRuleSearch, find as findHostRule } from '../../../util/host-rules'; +import { regEx } from '../../../util/regex'; import { api, id as composerVersioningId } from '../../versioning/composer'; import type { UpdateArtifactsConfig } from '../types'; import type { ComposerConfig, ComposerLock } from './types'; @@ -109,3 +111,17 @@ export function extractConstraints( } return res; } + +export function findGithubPersonalAccessToken( + search: HostRuleSearch +): string | undefined { + const token = findHostRule(search)?.token; + if (token && isPersonalAccessToken(token)) { + return token; + } + return undefined; +} + +export function isPersonalAccessToken(token: string): boolean { + return regEx(/^ghp_/).test(token); +}