Skip to content

Commit

Permalink
feat: export UV_EXTRA_INDEX_URL before doing 'uv lock'
Browse files Browse the repository at this point in the history
This commit solves the challenge when a repo uses 'uv' to manage Python
packages, and the repo also includes any dependencies in private
repositories.

Current configuration options lets the repo provide secrets that allow
Renovate to look for new versions of these dependencies, but it would
fail on the step that updates the uv.lock lockfile, since the 'uv lock'
subcommand has no knowledge of the Renovate configuration. The 'uv lock'
command supports setting UV_EXTRA_INDEX_URL (a space-separated value of
URLs to check before the default pypi index).

This commit then solves the challenge by using a combination of having
Renovate configuration like so:

```
  "packageRules": [
    {
      "matchDatasources": ["pypi"],
      "registryUrls":
["https://europe-python.pkg.dev/project-123/python/simple/"],
    }
  ],
  hostRules: [
    {
      matchHost: "https://europe-python.pkg.dev",
      username: "_json_key_64",
      password: "{{ secrets.ARTIFACT_REGISTRY_JSON_KEY }}",
    },
  ]
```

The 'uv' processor will now, when it can match a `hostRule` with a
`registryUrl` inject whatever `username` and `password` from the
`hostRule` into the `registryUrl`. The URLs will be exported as
`UV_EXTRA_INDEX_URL` before calling 'uv lock'.
  • Loading branch information
torarvid committed Oct 7, 2024
1 parent 03a8531 commit 9b01ce9
Showing 1 changed file with 35 additions and 0 deletions.
35 changes: 35 additions & 0 deletions lib/modules/manager/pep621/processors/uv.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import is from '@sindresorhus/is';
import { PypiDatasource } from '../../../datasource/pypi';
import { find } from '../../../../util/host-rules';
import { quote } from 'shlex';
import { TEMPORARY_ERROR } from '../../../../constants/error-messages';
import { logger } from '../../../../logger';
Expand All @@ -15,6 +17,7 @@ import type {
import { type PyProject, UvLockfileSchema } from '../schema';
import { depTypes, parseDependencyList } from '../utils';
import type { PyProjectProcessor } from './types';
import { HostRule } from '../../../../types';

const uvUpdateCMD = 'uv lock';

Expand Down Expand Up @@ -115,8 +118,12 @@ export class UvProcessor implements PyProjectProcessor {
constraint: config.constraints?.uv,
};

const extraEnv = {
...getUvExtraIndexUrl(updateArtifact.updatedDeps),
};
const execOptions: ExecOptions = {
cwdFile: packageFileName,
extraEnv,
docker: {},
userConfiguredEnv: config.env,
toolConstraints: [pythonConstraint, uvConstraint],
Expand Down Expand Up @@ -191,3 +198,31 @@ function generateCMD(updatedDeps: Upgrade[]): string {

return `${uvUpdateCMD} ${deps.map((dep) => `--upgrade-package ${quote(dep)}`).join(' ')}`;
}

function getMatchingHostRule(url: string | undefined): HostRule {
const scopedMatch = find({ hostType: PypiDatasource.id, url });
return is.nonEmptyObject(scopedMatch) ? scopedMatch : find({ url });
}

function getUvExtraIndexUrl(updatedDeps: Upgrade[]): NodeJS.ProcessEnv {
const registryUrls = updatedDeps.map((dep) => dep.registryUrls).flat();
const extraIndexUrls: string[] = [];

for (const registryUrl of registryUrls) {
const parsedUrl = new URL(registryUrl ?? '');

const rule = getMatchingHostRule(parsedUrl.toString());
if (rule.username) {
parsedUrl.username = rule.username;
}
if (rule.password) {
parsedUrl.password = rule.password;
}

extraIndexUrls.push(parsedUrl.toString());
}

return {
UV_EXTRA_INDEX_URL: extraIndexUrls.join(' '),
};
}

0 comments on commit 9b01ce9

Please sign in to comment.