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

chore: allow to grab extensions from private registries #5473

Merged
merged 1 commit into from Jan 10, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
90 changes: 89 additions & 1 deletion packages/main/src/plugin/image-registry.spec.ts
Expand Up @@ -19,7 +19,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */

import { beforeAll, beforeEach, describe, expect, expectTypeOf, test, vi } from 'vitest';
import { beforeAll, beforeEach, describe, expect, expectTypeOf, test, vi, vitest } from 'vitest';
import type { ApiSenderType } from './api.js';

import { ImageRegistry } from './image-registry.js';
Expand Down Expand Up @@ -660,3 +660,91 @@ test('getManifestFromUrl returns the expected manifest without mediaType but wit
expect(manifest).toHaveProperty('endManifest', true);
expect(spyGetBestManifest).toHaveBeenCalled();
});

test('getAuthconfigForServer returns the expected authconfig', async () => {
imageRegistry.registerRegistry({
serverUrl: 'my-podman-desktop-fake-registry.io',
username: 'foo',
secret: 'my-secret',
source: 'podman-desktop',
});
const config = imageRegistry.getAuthconfigForServer('my-podman-desktop-fake-registry.io');

expect(config).toBeDefined();
expect(config?.username).toBe('foo');
expect(config?.password).toBe('my-secret');
expect(config?.serveraddress).toBe('my-podman-desktop-fake-registry.io');
});

test('getAuthconfigForServer returns the expected authconfig', async () => {
imageRegistry.registerRegistry({
serverUrl: 'my-podman-desktop-fake-registry.io',
username: 'foo',
secret: 'my-secret',
source: 'podman-desktop',
});
const config = imageRegistry.getAuthconfigForServer('my-podman-desktop-fake-registry.io');

expect(config).toBeDefined();
expect(config?.username).toBe('foo');
expect(config?.password).toBe('my-secret');
expect(config?.serveraddress).toBe('my-podman-desktop-fake-registry.io');
});

test('getToken with registry auth', async () => {
imageRegistry.registerRegistry({
serverUrl: 'my-podman-desktop-fake-registry.io',
username: 'foo',
secret: 'my-secret',
source: 'podman-desktop',
});

// expect that the authorization header will be set by the getToken method
nock('https://my-podman-desktop-fake-registry.io', {
reqheaders: {
authorization: 'Basic Zm9vOm15LXNlY3JldA==',
},
})
.get('/?scope=repository%3Afoo%2Fbar%3Apull')
.reply(200, {
token: '12345',
});

const token = await imageRegistry.getToken(
{
authUrl: 'https://my-podman-desktop-fake-registry.io',
scheme: 'http',
},
{
name: 'foo/bar',
tag: 'latest',
registry: 'my-podman-desktop-fake-registry.io',
registryURL: 'https://my-podman-desktop-fake-registry.io/v2',
},
);

expect(token).toBeDefined();
expect(token).toBe('12345');
});

test('getToken without registry auth', async () => {
nock('https://my-podman-desktop-fake-registry.io').get('/?scope=repository%3Afoo%2Fbar%3Apull').reply(200, {
token: '12345',
});

const token = await imageRegistry.getToken(
{
authUrl: 'https://my-podman-desktop-fake-registry.io',
scheme: 'http',
},
{
name: 'foo/bar',
tag: 'latest',
registry: 'my-podman-desktop-fake-registry.io',
registryURL: 'https://my-podman-desktop-fake-registry.io/v2',
},
);

expect(token).toBeDefined();
expect(token).toBe('12345');
});
44 changes: 27 additions & 17 deletions packages/main/src/plugin/image-registry.ts
Expand Up @@ -101,26 +101,29 @@ export class ImageRegistry {
*/
getAuthconfigForImage(imageName: string): Dockerode.AuthConfig | undefined {
const registryServer = this.extractRegistryServerFromImage(imageName);
let authconfig;
if (registryServer) {
const matchingUrl = registryServer;
// grab authentication data for this server
const matchingRegistry = this.getRegistries().find(
registry => registry.serverUrl.toLowerCase() === matchingUrl.toLowerCase(),
);
if (matchingRegistry) {
let serveraddress = matchingRegistry.serverUrl.toLowerCase();
if (serveraddress === 'docker.io') {
serveraddress = 'https://index.docker.io/v2/';
}
authconfig = {
username: matchingRegistry.username,
password: matchingRegistry.secret,
serveraddress,
};
return this.getAuthconfigForServer(registryServer);
}
return undefined;
}

getAuthconfigForServer(registryServer: string): Dockerode.AuthConfig | undefined {
const matchingUrl = registryServer;
// grab authentication data for this server
const matchingRegistry = this.getRegistries().find(
registry => registry.serverUrl.toLowerCase() === matchingUrl.toLowerCase(),
);
if (matchingRegistry) {
let serveraddress = matchingRegistry.serverUrl.toLowerCase();
if (serveraddress === 'docker.io') {
serveraddress = 'https://index.docker.io/v2/';
}
return {
username: matchingRegistry.username,
password: matchingRegistry.secret,
serveraddress,
};
}
return authconfig;
}

/**
Expand Down Expand Up @@ -790,6 +793,13 @@ export class ImageRegistry {
let rawResponse: string | undefined;
const options = this.getOptions();

// if we have auth for this registry, add basic auth to the headers
const authServer = this.getAuthconfigForServer(imageData.registry);
if (authServer) {
options.headers = options.headers || {};
const loginAndPassWord = `${authServer.username}:${authServer.password}`;
options.headers.Authorization = `Basic ${Buffer.from(loginAndPassWord).toString('base64')}`;
}
// need to replace repository%3Auser with repository:user coming from imageData
let tokenUrl = authInfo.authUrl.replace('user%2Fimage', imageData.name.replaceAll('/', '%2F'));

Expand Down