Skip to content

Commit 0b55c43

Browse files
jellymartinpitt
authored andcommitted
Fallback to docker.io, quay.io when no registries are specified
Arch Linux and Debian have no unqualified-search-registries by default which makes our new "Create container" feature not work out of the box and does not allow users to search for an image. So we now provide two fallback container registries: docker.io and quay.io, as podman's search API does not allow us to search for two things at once we search in parallel. Closes: #777
1 parent 8ece0c2 commit 0b55c43

File tree

3 files changed

+88
-51
lines changed

3 files changed

+88
-51
lines changed

src/ImageRunModal.jsx

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -474,19 +474,47 @@ export class ImageRunModal extends React.Component {
474474

475475
this.setState({ searchFinished: false, searchInProgress: true });
476476
this.activeConnection = rest.connect(client.getAddress(this.state.isSystem), this.state.isSystem);
477+
let searches = [];
478+
479+
// If there are registries configured search in them, or if a user searches for `docker.io/cockpit` let
480+
// podman search in the user specified registry.
481+
if (Object.keys(this.props.registries).length !== 0 || value.includes('/')) {
482+
searches.push(this.activeConnection.call({
483+
method: "GET",
484+
path: client.VERSION + "libpod/images/search",
485+
body: "",
486+
params: {
487+
term: value,
488+
}
489+
}));
490+
} else {
491+
searches = searches.concat(utils.fallbackRegistries.map(registry =>
492+
this.activeConnection.call({
493+
method: "GET",
494+
path: client.VERSION + "libpod/images/search",
495+
body: "",
496+
params: {
497+
term: registry + "/" + value
498+
}
499+
})));
500+
}
477501

478-
const options = {
479-
method: "GET",
480-
path: client.VERSION + "libpod/images/search",
481-
body: "",
482-
params: {
483-
term: value,
484-
},
485-
};
486-
this.activeConnection.call(options)
502+
Promise.allSettled(searches)
487503
.then(reply => {
488504
if (reply && this._isMounted) {
489-
const imageResults = JSON.parse(reply);
505+
let imageResults = [];
506+
let dialogError = "";
507+
let dialogErrorDetail = "";
508+
509+
for (const result of reply) {
510+
if (result.status === "fulfilled") {
511+
imageResults = imageResults.concat(JSON.parse(result.value));
512+
} else {
513+
dialogError = _("Failed to search for new images");
514+
// TODO: add registry context, podman does not include it in the reply.
515+
dialogErrorDetail = cockpit.format(_("Failed to search for images: $0"), result.reason);
516+
}
517+
}
490518
// Group images on registry
491519
const images = {};
492520
imageResults.forEach(image => {
@@ -516,17 +544,8 @@ export class ImageRunModal extends React.Component {
516544
imageResults: images || {},
517545
searchFinished: true,
518546
searchInProgress: false,
519-
dialogError: ""
520-
});
521-
}
522-
})
523-
.catch(ex => {
524-
if (this._isMounted) {
525-
this.setState({
526-
searchFinished: true,
527-
searchInProgress: false,
528-
dialogError: _("Failed to search for new images"),
529-
dialogErrorDetail: cockpit.format(_("Failed to search for images: $0"), ex.message ? ex.message : "")
547+
dialogError,
548+
dialogErrorDetail,
530549
});
531550
}
532551
});
@@ -665,6 +684,8 @@ export class ImageRunModal extends React.Component {
665684
imageListOptions = this.filterImages();
666685
}
667686

687+
const registries = this.props.registries && this.props.registries.search ? this.props.registries.search : utils.fallbackRegistries;
688+
668689
// Add the search component
669690
const footer = (
670691
<ToggleGroup className='image-search-footer' aria-label={_("Search by registry")}>
@@ -685,19 +706,18 @@ export class ImageRunModal extends React.Component {
685706
ev.stopPropagation();
686707
}}
687708
/>
688-
{this.props.registries && this.props.registries.search &&
689-
this.props.registries.search.map(registry => {
690-
const index = this.truncateRegistryDomain(registry);
691-
return (
692-
<ToggleGroupItem text={index} key={index} isSelected={this.state.searchByRegistry == index} onChange={(_, ev) => {
693-
ev.stopPropagation();
694-
this.setState({ searchByRegistry: index });
695-
}}
709+
{registries.map(registry => {
710+
const index = this.truncateRegistryDomain(registry);
711+
return (
712+
<ToggleGroupItem text={index} key={index} isSelected={this.state.searchByRegistry == index} onChange={(_, ev) => {
713+
ev.stopPropagation();
714+
this.setState({ searchByRegistry: index });
715+
}}
696716
onTouchStart={ev => {
697717
ev.stopPropagation();
698718
}}
699-
/>);
700-
})}
719+
/>);
720+
})}
701721
</ToggleGroup>
702722
);
703723

src/ImageSearchModal.jsx

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ErrorNotification } from './Notification.jsx';
1010
import cockpit from 'cockpit';
1111
import rest from './rest.js';
1212
import * as client from './client.js';
13+
import { fallbackRegistries } from './util.js';
1314

1415
import './ImageSearchModal.css';
1516

@@ -75,29 +76,43 @@ export class ImageSearchModal extends React.Component {
7576
this.setState({ searchInProgress: true });
7677

7778
this.activeConnection = rest.connect(client.getAddress(this.state.isSystem), this.state.isSystem);
79+
let registries = Object.keys(this.props.registries).length !== 0 ? [this.state.registry] : fallbackRegistries;
80+
// if a user searches for `docker.io/cockpit` let podman search in the user specified registry.
81+
if (this.state.imageIdentifier.includes('/')) {
82+
registries = [""];
83+
}
7884

79-
const rr = this.state.registry;
80-
const registry = rr.length < 1 || rr[rr.length - 1] === "/" ? rr : rr + "/";
81-
82-
const options = {
83-
method: "GET",
84-
path: client.VERSION + "libpod/images/search",
85-
body: "",
86-
params: {
87-
term: registry + this.state.imageIdentifier,
88-
},
89-
};
90-
this.activeConnection.call(options)
85+
const searches = registries.map(rr => {
86+
const registry = rr.length < 1 || rr[rr.length - 1] === "/" ? rr : rr + "/";
87+
return this.activeConnection.call({
88+
method: "GET",
89+
path: client.VERSION + "libpod/images/search",
90+
body: "",
91+
params: {
92+
term: registry + this.state.imageIdentifier
93+
}
94+
});
95+
});
96+
97+
Promise.allSettled(searches)
9198
.then(reply => {
92-
if (reply && this._isMounted)
93-
this.setState({ imageList: JSON.parse(reply) || [], searchInProgress: false, searchFinished: true, dialogError: "" });
94-
})
95-
.catch(ex => {
96-
if (this._isMounted) {
99+
if (reply && this._isMounted) {
100+
let results = [];
101+
let dialogError = "";
102+
let dialogErrorDetail = "";
103+
104+
for (const result of reply) {
105+
if (result.status === "fulfilled") {
106+
results = results.concat(JSON.parse(result.value));
107+
} else {
108+
dialogError = _("Failed to search for new images");
109+
dialogErrorDetail = cockpit.format(_("Failed to search for images: $0"), result.reason);
110+
}
111+
}
112+
97113
this.setState({
98-
searchInProgress: false,
99-
dialogError: _("Failed to search for new images"),
100-
dialogErrorDetail: cockpit.format(_("Failed to search for images: $0"), ex.message ? ex.message : "")
114+
imageList: results || [], searchInProgress: false,
115+
searchFinished: true, dialogError, dialogErrorDetail
101116
});
102117
}
103118
});

src/util.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export const states = [_("configured"), _("created"), _("running"), _("stopped")
1010
// https://github.com/containers/podman/blob/main/libpod/define/podstate.go
1111
export const podStates = [_("Created"), _("Running"), _("Stopped"), _("Paused"), _("Exited"), _("Error")];
1212

13+
export const fallbackRegistries = ["docker.io", "quay.io"];
14+
1315
export function truncate_id(id) {
1416
if (!id) {
1517
return "";

0 commit comments

Comments
 (0)