Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support url replace via env #1189

Merged
merged 4 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,19 @@ RUN install-tool node 14.17.3
# renovate: datasource=github-releases packageName=moby/moby
RUN install-tool docker 20.10.7
```

### Url replacement

You can replace the default urls used to download the tools.
This is currently only supported by the `docker` tool installer.
Checkout #1067 for additional support.

```Dockerfile
FROM containerbase/base

ENV URL_REPLACE_0_FROM=https://download.docker.com/linux/static/stable
ENV URL_REPLACE_0_TO=https://artifactory.proxy.test/some/virtual/patch/docker

# renovate: datasource=github-releases packageName=moby/moby
RUN install-tool docker v24.0.2
```
37 changes: 30 additions & 7 deletions src/cli/services/env.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { arch } from 'node:os';
import { geteuid } from 'node:process';
import { env, geteuid } from 'node:process';
import { injectable } from 'inversify';
import type { Arch } from '../utils';
import { type Arch, logger } from '../utils';

export type Replacements = Record<string, string>;

@injectable()
export class EnvService {
readonly arch: Arch;
private uid: number;
private replacements: Replacements | undefined;

constructor() {
this.uid = geteuid?.() ?? 0; // fallback should never happen on linux
Expand All @@ -24,30 +27,50 @@ export class EnvService {
}

get cacheDir(): string | null {
return process.env.CONTAINERBASE_CACHE_DIR ?? null;
return env.CONTAINERBASE_CACHE_DIR ?? null;
}

get isRoot(): boolean {
return this.uid === 0;
}

get userHome(): string {
return process.env.USER_HOME ?? `/home/${this.userName}`;
return env.USER_HOME ?? `/home/${this.userName}`;
}

get userName(): string {
return process.env.USER_NAME ?? 'ubuntu';
return env.USER_NAME ?? 'ubuntu';
}

get userId(): number {
return parseInt(process.env.USER_ID ?? '1000', 10);
return parseInt(env.USER_ID ?? '1000', 10);
}

get umask(): number {
return this.isRoot ? 0o755 : 0o775;
}

get skipTests(): boolean {
return !!process.env.SKIP_VERSION;
return !!env.SKIP_VERSION;
}

get urlReplacements(): Record<string, string> {
if (this.replacements) {
return this.replacements;
}
const replacements: Record<string, string> = {};
const fromRe = /^URL_REPLACE_\d+_FROM$/;
for (const from of Object.keys(env).filter((key) => fromRe.test(key))) {
const to = from.replace(/_FROM$/, '_TO');
if (env[from] && env[to]) {
replacements[env[from]!] = env[to]!;
} else {
logger.warn(
`Invalid URL replacement: ${from}=${env[from]!} ${to}=${env[to]!}`
);
}
}

return (this.replacements = replacements);
}
}
45 changes: 34 additions & 11 deletions src/cli/services/http.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,60 @@ import { beforeEach, describe, expect, test } from 'vitest';
import { HttpService, rootContainer } from '.';
import { scope } from '~test/http-mock';

const baseUrl = 'https://example.com';
describe('http.service', () => {
let child!: Container;

beforeEach(() => {
child = rootContainer.createChild();

for (const key of Object.keys(env)) {
if (key.startsWith('URL_REPLACE_')) {
delete env[key];
}
}
});

test('throws', async () => {
scope('https://example.com').get('/fail.txt').times(3).reply(404);
scope(baseUrl).get('/fail.txt').times(6).reply(404);
const http = child.get(HttpService);

await expect(
http.download({ url: 'https://example.com/fail.txt' })
http.download({ url: `${baseUrl}/fail.txt` })
).rejects.toThrow();
await expect(
http.download({ url: `${baseUrl}/fail.txt` })
).rejects.toThrow();
// bug, currently resolves
// await expect(
// http.download({ url: 'https://example.com/fail.txt' })
// ).rejects.toThrow();
});

test('download', async () => {
scope('https://example.com').get('/test.txt').reply(200, 'ok');
scope(baseUrl).get('/test.txt').reply(200, 'ok');

const http = child.get(HttpService);

expect(await http.download({ url: 'https://example.com/test.txt' })).toBe(
expect(await http.download({ url: `${baseUrl}/test.txt` })).toBe(
`${env.CONTAINERBASE_CACHE_DIR}/d1dc63218c42abba594fff6450457dc8c4bfdd7c22acf835a50ca0e5d2693020/test.txt`
);
// uses cache
// expect(await http.download({ url: 'https://example.com/test.txt' })).toBe(
// `${env.CONTAINERBASE_CACHE_DIR}/d1dc63218c42abba594fff6450457dc8c4bfdd7c22acf835a50ca0e5d2693020/test.txt`
// );
expect(await http.download({ url: `${baseUrl}/test.txt` })).toBe(
`${env.CONTAINERBASE_CACHE_DIR}/d1dc63218c42abba594fff6450457dc8c4bfdd7c22acf835a50ca0e5d2693020/test.txt`
);
});

test('replaces url', async () => {
scope('https://example.org').get('/replace.txt').reply(200, 'ok');

env.URL_REPLACE_0_FROM = baseUrl;
env.URL_REPLACE_0_TO = 'https://example.org';

const http = child.get(HttpService);

expect(await http.download({ url: `${baseUrl}/replace.txt` })).toBe(
`${env.CONTAINERBASE_CACHE_DIR}/f4eba41457a330d0fa5289e49836326c6a0208bbc639862e70bb378c88c62642/replace.txt`
);
// uses cache
expect(await http.download({ url: `${baseUrl}/replace.txt` })).toBe(
`${env.CONTAINERBASE_CACHE_DIR}/f4eba41457a330d0fa5289e49836326c6a0208bbc639862e70bb378c88c62642/replace.txt`
);
});
});
19 changes: 17 additions & 2 deletions src/cli/services/http.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createWriteStream } from 'node:fs';
import { mkdir } from 'node:fs/promises';
import { mkdir, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { pipeline } from 'node:stream/promises';
import { got } from 'got';
Expand Down Expand Up @@ -65,9 +65,11 @@ export class HttpService {

await mkdir(cachePath, { recursive: true });

const nUrl = this.replaceUrl(url);

for (const run of [1, 2, 3]) {
try {
await pipeline(got.stream(url), createWriteStream(filePath));
await pipeline(got.stream(nUrl), createWriteStream(filePath));
return filePath;
} catch (err) {
if (run === 3) {
Expand All @@ -77,6 +79,19 @@ export class HttpService {
}
}
}
await rm(cachePath, { recursive: true });
throw new Error('download failed');
}
private replaceUrl(src: string): string {
let tgt = src;
const replacements = this.envSvc.urlReplacements;

for (const from of Object.keys(replacements)) {
tgt = tgt.replace(from, replacements[from]);
}
if (tgt !== src) {
logger.debug({ src, tgt }, 'url replaced');
}
return tgt;
}
}