diff --git a/src/ImageRunModal.jsx b/src/ImageRunModal.jsx index 707a3339c..ab8c81c5f 100644 --- a/src/ImageRunModal.jsx +++ b/src/ImageRunModal.jsx @@ -474,19 +474,47 @@ export class ImageRunModal extends React.Component { this.setState({ searchFinished: false, searchInProgress: true }); this.activeConnection = rest.connect(client.getAddress(this.state.isSystem), this.state.isSystem); + let searches = []; + + // If there are registries configured search in them, or if a user searches for `docker.io/cockpit` let + // podman search in the user specified registry. + if (Object.keys(this.props.registries).length !== 0 || value.includes('/')) { + searches.push(this.activeConnection.call({ + method: "GET", + path: client.VERSION + "libpod/images/search", + body: "", + params: { + term: value, + } + })); + } else { + searches = searches.concat(utils.fallbackRegistries.map(registry => + this.activeConnection.call({ + method: "GET", + path: client.VERSION + "libpod/images/search", + body: "", + params: { + term: registry + "/" + value + } + }))); + } - const options = { - method: "GET", - path: client.VERSION + "libpod/images/search", - body: "", - params: { - term: value, - }, - }; - this.activeConnection.call(options) + Promise.allSettled(searches) .then(reply => { if (reply && this._isMounted) { - const imageResults = JSON.parse(reply); + let imageResults = []; + let dialogError = ""; + let dialogErrorDetail = ""; + + for (const result of reply) { + if (result.status === "fulfilled") { + imageResults = imageResults.concat(JSON.parse(result.value)); + } else { + dialogError = _("Failed to search for new images"); + // TODO: add registry context, podman does not include it in the reply. + dialogErrorDetail = cockpit.format(_("Failed to search for images: $0"), result.reason); + } + } // Group images on registry const images = {}; imageResults.forEach(image => { @@ -516,17 +544,8 @@ export class ImageRunModal extends React.Component { imageResults: images || {}, searchFinished: true, searchInProgress: false, - dialogError: "" - }); - } - }) - .catch(ex => { - if (this._isMounted) { - this.setState({ - searchFinished: true, - searchInProgress: false, - dialogError: _("Failed to search for new images"), - dialogErrorDetail: cockpit.format(_("Failed to search for images: $0"), ex.message ? ex.message : "") + dialogError, + dialogErrorDetail, }); } }); @@ -665,6 +684,8 @@ export class ImageRunModal extends React.Component { imageListOptions = this.filterImages(); } + const registries = this.props.registries && this.props.registries.search ? this.props.registries.search : utils.fallbackRegistries; + // Add the search component const footer = ( @@ -685,19 +706,18 @@ export class ImageRunModal extends React.Component { ev.stopPropagation(); }} /> - {this.props.registries && this.props.registries.search && - this.props.registries.search.map(registry => { - const index = this.truncateRegistryDomain(registry); - return ( - { - ev.stopPropagation(); - this.setState({ searchByRegistry: index }); - }} + {registries.map(registry => { + const index = this.truncateRegistryDomain(registry); + return ( + { + ev.stopPropagation(); + this.setState({ searchByRegistry: index }); + }} onTouchStart={ev => { ev.stopPropagation(); }} - />); - })} + />); + })} ); diff --git a/src/ImageSearchModal.jsx b/src/ImageSearchModal.jsx index 0168a9dd1..0e724438a 100644 --- a/src/ImageSearchModal.jsx +++ b/src/ImageSearchModal.jsx @@ -10,6 +10,7 @@ import { ErrorNotification } from './Notification.jsx'; import cockpit from 'cockpit'; import rest from './rest.js'; import * as client from './client.js'; +import { fallbackRegistries } from './util.js'; import './ImageSearchModal.css'; @@ -75,29 +76,43 @@ export class ImageSearchModal extends React.Component { this.setState({ searchInProgress: true }); this.activeConnection = rest.connect(client.getAddress(this.state.isSystem), this.state.isSystem); + let registries = Object.keys(this.props.registries).length !== 0 ? [this.state.registry] : fallbackRegistries; + // if a user searches for `docker.io/cockpit` let podman search in the user specified registry. + if (this.state.imageIdentifier.includes('/')) { + registries = [""]; + } - const rr = this.state.registry; - const registry = rr.length < 1 || rr[rr.length - 1] === "/" ? rr : rr + "/"; - - const options = { - method: "GET", - path: client.VERSION + "libpod/images/search", - body: "", - params: { - term: registry + this.state.imageIdentifier, - }, - }; - this.activeConnection.call(options) + const searches = registries.map(rr => { + const registry = rr.length < 1 || rr[rr.length - 1] === "/" ? rr : rr + "/"; + return this.activeConnection.call({ + method: "GET", + path: client.VERSION + "libpod/images/search", + body: "", + params: { + term: registry + this.state.imageIdentifier + } + }); + }); + + Promise.allSettled(searches) .then(reply => { - if (reply && this._isMounted) - this.setState({ imageList: JSON.parse(reply) || [], searchInProgress: false, searchFinished: true, dialogError: "" }); - }) - .catch(ex => { - if (this._isMounted) { + if (reply && this._isMounted) { + let results = []; + let dialogError = ""; + let dialogErrorDetail = ""; + + for (const result of reply) { + if (result.status === "fulfilled") { + results = results.concat(JSON.parse(result.value)); + } else { + dialogError = _("Failed to search for new images"); + dialogErrorDetail = cockpit.format(_("Failed to search for images: $0"), result.reason); + } + } + this.setState({ - searchInProgress: false, - dialogError: _("Failed to search for new images"), - dialogErrorDetail: cockpit.format(_("Failed to search for images: $0"), ex.message ? ex.message : "") + imageList: results || [], searchInProgress: false, + searchFinished: true, dialogError, dialogErrorDetail }); } }); diff --git a/src/util.js b/src/util.js index c46650732..804ebd71d 100644 --- a/src/util.js +++ b/src/util.js @@ -10,6 +10,8 @@ export const states = [_("configured"), _("created"), _("running"), _("stopped") // https://github.com/containers/podman/blob/main/libpod/define/podstate.go export const podStates = [_("Created"), _("Running"), _("Stopped"), _("Paused"), _("Exited"), _("Error")]; +export const fallbackRegistries = ["docker.io", "quay.io"]; + export function truncate_id(id) { if (!id) { return "";