Skip to content

Commit

Permalink
feat: search image tags (podman-desktop#8170)
Browse files Browse the repository at this point in the history
* feat: search image tags
Signed-off-by: Philippe Martin <[email protected]>

* test: unit tests
Signed-off-by: Philippe Martin <[email protected]>
  • Loading branch information
feloy authored Jul 25, 2024
1 parent 8634ea7 commit 50cd4e1
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 3 deletions.
4 changes: 4 additions & 0 deletions packages/api/src/image-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ export interface ImageSearchResult {
star_count: number;
is_official: boolean;
}

export interface ImageTagsListOptions {
image: string;
}
26 changes: 26 additions & 0 deletions packages/main/src/plugin/image-registry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1022,3 +1022,29 @@ test('searchImages with https', async () => {
const result = await imageRegistry.searchImages({ registry: 'https://quay.io', query: 'http', limit: 10 });
expect(result).toEqual(list);
});

test('listImageTags', async () => {
vi.spyOn(imageRegistry, 'extractImageDataFromImageName').mockReturnValue({
name: 'a-name',
tag: 'a-tag',
registry: 'a-registry',
registryURL: 'https://registry.example.com/v2',
});
vi.spyOn(imageRegistry, 'getAuthInfo').mockResolvedValue({
authUrl: 'https://auth.example.com',
scheme: 'bearer',
});
vi.spyOn(imageRegistry, 'getOptions').mockReturnValue({});
vi.spyOn(imageRegistry, 'getToken').mockResolvedValue('a.token');
nock('https://registry.example.com', {
reqheaders: {
Authorization: 'Bearer a.token',
},
})
.get('/v2/a-name/tags/list')
.reply(200, {
tags: ['1', '2', '3'],
});
const result = await imageRegistry.listImageTags({ image: 'an-image' });
expect(result).toEqual(['1', '2', '3']);
});
25 changes: 24 additions & 1 deletion packages/main/src/plugin/image-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
import * as nodeTar from 'tar';
import validator from 'validator';

import type { ImageSearchOptions, ImageSearchResult } from '/@api/image-registry.js';
import type { ImageSearchOptions, ImageSearchResult, ImageTagsListOptions } from '/@api/image-registry.js';

import { isMac, isWindows } from '../util.js';
import type { ApiSenderType } from './api.js';
Expand Down Expand Up @@ -940,6 +940,29 @@ export class ImageRegistry {
);
return JSON.parse(resultJSON.body).results;
}

async listImageTags(options: ImageTagsListOptions): Promise<string[]> {
const imageData = this.extractImageDataFromImageName(options.image);

// grab auth info from the registry
const authInfo = await this.getAuthInfo(imageData.registry);
const token = await this.getToken(authInfo, imageData);
if (authInfo.scheme.toLowerCase() !== 'bearer') {
throw new Error(`Unsupported auth scheme: ${authInfo.scheme}`);
}
const opts = this.getOptions();
opts.headers = opts.headers ?? {};
// add the Bearer token
opts.headers.Authorization = `Bearer ${token}`;

try {
const catalog = await got.get(`${imageData.registryURL}/${imageData.name}/tags/list`, opts);
return JSON.parse(catalog.body).tags;
} catch (e: unknown) {
console.error('error getting tags of image', options.image, e);
return [];
}
}
}

interface ImageRegistryNameTag {
Expand Down
9 changes: 8 additions & 1 deletion packages/main/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ import type { ImageCheckerInfo } from '/@api/image-checker-info.js';
import type { ImageFilesInfo } from '/@api/image-files-info.js';
import type { ImageInfo } from '/@api/image-info.js';
import type { ImageInspectInfo } from '/@api/image-inspect-info.js';
import type { ImageSearchOptions, ImageSearchResult } from '/@api/image-registry.js';
import type { ImageSearchOptions, ImageSearchResult, ImageTagsListOptions } from '/@api/image-registry.js';
import type { ManifestCreateOptions, ManifestInspectInfo, ManifestPushOptions } from '/@api/manifest-info.js';
import type { NetworkInspectInfo } from '/@api/network-info.js';
import type { NotificationCard, NotificationCardOptions } from '/@api/notification.js';
Expand Down Expand Up @@ -1525,6 +1525,13 @@ export class PluginSystem {
},
);

this.ipcHandle(
'image-registry:listImageTags',
async (_listener, options: ImageTagsListOptions): Promise<string[]> => {
return imageRegistry.listImageTags(options);
},
);

this.ipcHandle(
'authentication-provider-registry:getAuthenticationProvidersInfo',
async (): Promise<readonly AuthenticationProviderInfo[]> => {
Expand Down
9 changes: 8 additions & 1 deletion packages/preload/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ import type { ImageCheckerInfo } from '/@api/image-checker-info';
import type { ImageFilesInfo } from '/@api/image-files-info';
import type { ImageInfo } from '/@api/image-info';
import type { ImageInspectInfo } from '/@api/image-inspect-info';
import type { ImageSearchOptions, ImageSearchResult } from '/@api/image-registry';
import type { ImageSearchOptions, ImageSearchResult, ImageTagsListOptions } from '/@api/image-registry';
import type { ManifestCreateOptions, ManifestInspectInfo, ManifestPushOptions } from '/@api/manifest-info';
import type { NetworkInspectInfo } from '/@api/network-info';
import type { NotificationCard, NotificationCardOptions } from '/@api/notification';
Expand Down Expand Up @@ -1244,6 +1244,13 @@ export function initExposure(): void {
},
);

contextBridge.exposeInMainWorld(
'listImageTagsInRegistry',
async (options: ImageTagsListOptions): Promise<string[]> => {
return ipcInvoke('image-registry:listImageTags', options);
},
);

contextBridge.exposeInMainWorld(
'getAuthenticationProvidersInfo',
async (): Promise<readonly AuthenticationProviderInfo[]> => {
Expand Down

0 comments on commit 50cd4e1

Please sign in to comment.