Skip to content

Commit

Permalink
Fallback to docker.io, quay.io when no registries are specified
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jelly authored and martinpitt committed Nov 9, 2021
1 parent 8ece0c2 commit 0b55c43
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 51 deletions.
82 changes: 51 additions & 31 deletions src/ImageRunModal.jsx
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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,
});
}
});
Expand Down Expand Up @@ -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 = (
<ToggleGroup className='image-search-footer' aria-label={_("Search by registry")}>
Expand All @@ -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 (
<ToggleGroupItem text={index} key={index} isSelected={this.state.searchByRegistry == index} onChange={(_, ev) => {
ev.stopPropagation();
this.setState({ searchByRegistry: index });
}}
{registries.map(registry => {
const index = this.truncateRegistryDomain(registry);
return (
<ToggleGroupItem text={index} key={index} isSelected={this.state.searchByRegistry == index} onChange={(_, ev) => {
ev.stopPropagation();
this.setState({ searchByRegistry: index });
}}
onTouchStart={ev => {
ev.stopPropagation();
}}
/>);
})}
/>);
})}
</ToggleGroup>
);

Expand Down
55 changes: 35 additions & 20 deletions src/ImageSearchModal.jsx
Expand Up @@ -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';

Expand Down Expand Up @@ -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
});
}
});
Expand Down
2 changes: 2 additions & 0 deletions src/util.js
Expand Up @@ -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 "";
Expand Down

0 comments on commit 0b55c43

Please sign in to comment.