Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkg/api/handlers/compat/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ func GetContainer(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
if _, err := utils.SupportedVersion(r, ">=1.52.0"); err == nil || errors.Is(err, apiutil.ErrVersionNotGiven) {
if api.NetworkSettings != nil {
api.NetworkSettings.SecondaryIPAddresses = nil
api.NetworkSettings.SecondaryIPv6Addresses = nil
}
}
utils.WriteResponse(w, http.StatusOK, api)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/api/handlers/compat/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ func imageDataToImageInspect(ctx context.Context, l *libimage.Image, r *http.Req
}

if _, err := apiutil.SupportedVersion(r, "<1.45.0"); err == nil {
imageInspect.ContainerConfig = cc
imageInspect.ContainerConfig = cc //nolint:staticcheck // Deprecated field
Comment thread
Honny1 marked this conversation as resolved.
}

return &imageInspect, nil
Expand Down
20 changes: 20 additions & 0 deletions pkg/api/handlers/compat/images_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net/http"

"github.com/moby/moby/api/types/registry"
"go.podman.io/image/v5/types"
"go.podman.io/podman/v6/libpod"
"go.podman.io/podman/v6/pkg/api/handlers/utils"
Expand Down Expand Up @@ -77,7 +78,26 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
utils.ImageNotFound(w, query.Term, storage.ErrImageUnknown)
return
}
compatResults := make([]registry.SearchResult, len(reports))
for i, r := range reports {
compatResults[i] = registry.SearchResult{
Name: r.Name,
Description: r.Description,
StarCount: r.Stars,
IsOfficial: isOK(r.Official),
IsAutomated: isOK(r.Automated),
}
}
utils.WriteResponse(w, http.StatusOK, compatResults)
return
}

utils.WriteResponse(w, http.StatusOK, reports)
}

// isOK converts the libimage format of
// string flags to bool to be compatible with
// the Docker API.
func isOK(s string) bool {
return s == "[OK]"
}
8 changes: 8 additions & 0 deletions pkg/api/handlers/swagger/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/moby/moby/api/types/container"
dockerImage "github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/volume"
"go.podman.io/common/libnetwork/types"
"go.podman.io/image/v5/manifest"
Expand Down Expand Up @@ -93,6 +94,13 @@ type imageDeleteResponse struct {
// Registry Search
// swagger:response
type registrySearchResponse struct {
// in:body
Body []registry.SearchResult
}

// Registry Search
// swagger:response
type registrySearchResponseLibpod struct {
// in:body
Body struct {
// Index is the image index
Expand Down
7 changes: 6 additions & 1 deletion pkg/api/handlers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ type ImageInspect struct {
// If a field in the outer struct has the same name as a field in the embedded struct,
// the outer struct's field will shadow or override the embedded one allowing for a clean way to
// hide fields from the swagger spec that still exist in the libraries struct.
Container string `json:"Container,omitempty"`
//
// Deprecated: since API v1.44, will no longer be included in API v1.45.
// https://docs.docker.com/reference/api/engine/version-history/#v144-api-changes
Container string `json:"Container,omitempty"`
// Deprecated: since API v1.44, will no longer be included in API v1.45.
// https://docs.docker.com/reference/api/engine/version-history/#v144-api-changes
ContainerConfig *dockerContainer.Config `json:"ContainerConfig,omitempty"`
VirtualSize int64 `json:"VirtualSize,omitempty"`
Parent string `json:"Parent"`
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/server/register_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - application/json
// responses:
// 200:
// $ref: "#/responses/registrySearchResponse"
// $ref: "#/responses/registrySearchResponseLibpod"
// 500:
// $ref: '#/responses/internalError'
r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(compat.SearchImages)).Methods(http.MethodGet)
Expand Down
13 changes: 11 additions & 2 deletions test/apiv2/python/rest_api/test_v2_0_0_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,13 +535,22 @@ def test_inspect_network(self):
r = requests.post(self.uri(self.resolve_container("/containers/{}/start")))
self.assertIn(r.status_code, (204, 304), r.text)

r = requests.get(self.compat_uri(self.resolve_container("/containers/{}/json")))
container_uri = self.resolve_container("/containers/{}/json")

# SecondaryIPAddresses present for API < v1.52
r = requests.get(self.podman_url + "/v1.44/" + container_uri)
self.assertEqual(r.status_code, 200, r.text)
self.assertId(r.content)
out = r.json()

self.assertEqual("10.0.2.0", out["NetworkSettings"]["SecondaryIPAddresses"][0]["Addr"])
self.assertEqual(24, out["NetworkSettings"]["SecondaryIPAddresses"][0]["PrefixLen"])

# SecondaryIPAddresses removed for API >= v1.52
r = requests.get(self.podman_url + "/v1.52/" + container_uri)
self.assertEqual(r.status_code, 200, r.text)
self.assertId(r.content)
out = r.json()
self.assertIsNone(out["NetworkSettings"].get("SecondaryIPAddresses"))
finally:
delete_named_network_ns(network_ns_name)

Expand Down
51 changes: 25 additions & 26 deletions test/apiv2/python/rest_api/test_v2_0_0_image.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import json
import unittest
import multiprocessing as mp

import requests
from dateutil.parser import parse
from .fixtures import APITestCase
Expand Down Expand Up @@ -164,18 +162,33 @@ def test_create(self):
self.assertEqual(r.status_code, 200, r.text)

def test_search_compat(self):
url = self.podman_url + "/v1.40/images/search"
url = self.podman_url + "/v1.44/images/search"

# Had issues with this test hanging when repositories not happy
def do_search1():
required_keys = (
"description",
"is_automated", # Deprecated: always false.
"is_official",
"name",
"star_count",
)
payload = {"term": "alpine"}
r = requests.get(url, params=payload, timeout=5)
r = requests.get(url, params=payload, timeout=30)
self.assertEqual(r.status_code, 200, f"#1: {r.text}")
self.assertIsInstance(r.json(), list)

results = r.json()
self.assertIsInstance(results, list)
for item in results:
for k in required_keys:
self.assertIn(k, item)

def do_search2():
payload = {"term": "alpine", "limit": 1}
r = requests.get(url, params=payload, timeout=5)
# The containers.conf uses:
# compat_api_enforce_docker_hub=false
# and full name needs to be used here.
payload = {"term": "docker.io/library/alpine", "limit": 1}
r = requests.get(url, params=payload, timeout=30)
self.assertEqual(r.status_code, 200, f"#2: {r.text}")

results = r.json()
Expand All @@ -186,7 +199,7 @@ def do_search3():
# FIXME: Research if quay.io supports is-official and which image is "official"
return
payload = {"term": "thanos", "filters": '{"is-official":["true"]}'}
r = requests.get(url, params=payload, timeout=5)
r = requests.get(url, params=payload, timeout=30)
self.assertEqual(r.status_code, 200, f"#3: {r.text}")

results = r.json()
Expand All @@ -198,32 +211,18 @@ def do_search3():
def do_search4():
headers = {"X-Registry-Auth": "null"}
payload = {"term": "alpine"}
r = requests.get(url, params=payload, headers=headers, timeout=5)
r = requests.get(url, params=payload, headers=headers, timeout=30)
self.assertEqual(r.status_code, 200, f"#4: {r.text}")

def do_search5():
headers = {"X-Registry-Auth": "invalid value"}
payload = {"term": "alpine"}
r = requests.get(url, params=payload, headers=headers, timeout=5)
r = requests.get(url, params=payload, headers=headers, timeout=30)
self.assertEqual(r.status_code, 400, f"#5: {r.text}")

i = 1
# Need to explicitly set start method
# # https://docs.python.org/dev/library/multiprocessing.html#contexts-and-start-methods
mp.set_start_method('fork')

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The multiprocessing sub-tests weren't actually correctly asserted and they failed silently. Simplified that with sequential sub-tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simek-m you should rebase to main if you didn't so ci jobs will show up and run the tests

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@inknos I did, but I think the CI workflow needs to be approved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't see the ci runs, but if you do, everything's correct, so apologies if that's the case and if you did rebase already 🥲

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, need to apologize :) I checked that the CI changes are in this branch, but rebased again to the latest.

It looks like it won't run automatically, though:
Screenshot 2026-06-02 at 15 17 27

for fn in [do_search1, do_search2, do_search3, do_search4, do_search5]:
for i, t in enumerate([do_search1, do_search2, do_search3, do_search4, do_search5], start=1):
with self.subTest(i=i):
search = mp.Process(target=fn)
search.start()
search.join(timeout=10)
self.assertFalse(search.is_alive(), f"#{i} /images/search took too long")

# search_methods = [do_search1, do_search2, do_search3, do_search4, do_search5]
# for search_method in search_methods:
# search = Process(target=search_method)
# search.start()
# search.join(timeout=10)
# self.assertFalse(search.is_alive(), "/images/search took too long")
t()

def test_history(self):
r = requests.get(self.podman_url + "/v1.40/images/alpine/history")
Expand Down