Skip to content

Commit

Permalink
feat(manager/composer): support git-tags hostRules for github.com whe…
Browse files Browse the repository at this point in the history
…n updating artifacts (#16193)
  • Loading branch information
etremblay committed Aug 30, 2022
1 parent 7b1c117 commit 781b8fb
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 5 deletions.
15 changes: 15 additions & 0 deletions docs/usage/getting-started/private-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
90 changes: 88 additions & 2 deletions lib/modules/manager/composer/artifacts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 '.';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -165,14 +172,93 @@ 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',
},
},
},
]);
});

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();
Expand Down
18 changes: 15 additions & 3 deletions lib/modules/manager/composer/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
};
}

Expand Down
63 changes: 63 additions & 0 deletions lib/modules/manager/composer/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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();
});
});
});
16 changes: 16 additions & 0 deletions lib/modules/manager/composer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}

0 comments on commit 781b8fb

Please sign in to comment.