fix: Docker API compatible bool deserialization

In Docker anything but "", "0", "no", "false", "none" (ignoring case) is considered to be true.

Signed-off-by: Matej Vasek <mvasek@redhat.com>
This commit is contained in:
Matej Vasek
2023-08-10 23:47:09 +02:00
parent 4cb2d48ca4
commit f33b01b731
27 changed files with 82 additions and 52 deletions

View File

@ -8,11 +8,10 @@ import (
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/api/handlers/utils"
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/gorilla/schema"
)
func Changes(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
query := struct {

View File

@ -27,12 +27,11 @@ import (
"github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
"github.com/gorilla/schema"
"github.com/sirupsen/logrus"
)
func RemoveContainer(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
query := struct {
Force bool `schema:"force"`
Ignore bool `schema:"ignore"`
@ -99,7 +98,7 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) {
func ListContainers(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
query := struct {
All bool `schema:"all"`
Limit int `schema:"limit"`
@ -179,7 +178,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) {
func GetContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
query := struct {
Size bool `schema:"size"`
}{
@ -208,7 +207,7 @@ func GetContainer(w http.ResponseWriter, r *http.Request) {
func KillContainer(w http.ResponseWriter, r *http.Request) {
// /{version}/containers/(name)/kill
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
query := struct {
Signal string `schema:"signal"`
}{
@ -627,7 +626,7 @@ func formatCapabilities(slice []string) {
func RenameContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
name := utils.GetName(r)
query := struct {

View File

@ -21,7 +21,7 @@ import (
)
func Archive(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
switch r.Method {

View File

@ -10,13 +10,12 @@ import (
"github.com/containers/podman/v4/pkg/api/handlers/utils"
"github.com/containers/podman/v4/pkg/api/server/idle"
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/gorilla/schema"
"github.com/sirupsen/logrus"
)
func AttachContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
query := struct {
DetachKeys string `schema:"detachKeys"`

View File

@ -28,12 +28,11 @@ import (
"github.com/containers/podman/v4/pkg/specgenutil"
"github.com/containers/storage"
"github.com/docker/docker/api/types/mount"
"github.com/gorilla/schema"
)
func CreateContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
query := struct {
Name string `schema:"name"`
Platform string `schema:"platform"`

View File

@ -15,12 +15,11 @@ import (
"github.com/containers/podman/v4/pkg/api/handlers/utils"
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/containers/podman/v4/pkg/util"
"github.com/gorilla/schema"
log "github.com/sirupsen/logrus"
)
func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
query := struct {

View File

@ -11,12 +11,11 @@ import (
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/domain/infra/abi"
"github.com/gorilla/schema"
)
func RestartContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
// Now use the ABI implementation to prevent us from having duplicate
// code.
containerEngine := abi.ContainerEngine{Libpod: runtime}

View File

@ -9,11 +9,10 @@ import (
"github.com/containers/podman/v4/libpod"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/api/handlers/utils"
"github.com/gorilla/schema"
)
func StartContainer(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
query := struct {
DetachKeys string `schema:"detachKeys"`
}{

View File

@ -13,7 +13,6 @@ import (
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/containers/storage/pkg/system"
docker "github.com/docker/docker/api/types"
"github.com/gorilla/schema"
runccgroups "github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/sirupsen/logrus"
)
@ -22,7 +21,7 @@ const DefaultStatsPeriod = 5 * time.Second
func StatsContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
query := struct {
Stream bool `schema:"stream"`

View File

@ -12,12 +12,11 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/domain/infra/abi"
"github.com/containers/podman/v4/pkg/util"
"github.com/gorilla/schema"
)
func StopContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
// Now use the ABI implementation to prevent us from having duplicate
// code.
containerEngine := abi.ContainerEngine{Libpod: runtime}

View File

@ -11,13 +11,12 @@ import (
"github.com/containers/podman/v4/pkg/api/handlers"
"github.com/containers/podman/v4/pkg/api/handlers/utils"
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/gorilla/schema"
"github.com/sirupsen/logrus"
)
func TopContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
psArgs := "-ef"
if utils.IsLibpodRequest(r) {

View File

@ -10,7 +10,6 @@ import (
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/util"
"github.com/gorilla/schema"
jsoniter "github.com/json-iterator/go"
"github.com/sirupsen/logrus"
)
@ -19,7 +18,7 @@ import (
func GetEvents(w http.ResponseWriter, r *http.Request) {
var (
fromStart bool
decoder = r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder = utils.GetDecoder(r)
runtime = r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
json = jsoniter.ConfigCompatibleWithStandardLibrary // FIXME: this should happen on the package level
)

View File

@ -25,7 +25,6 @@ import (
"github.com/containers/storage"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/gorilla/schema"
"github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
)
@ -95,7 +94,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) {
}
func CommitContainer(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
query := struct {
@ -174,7 +173,7 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
// 200 no error
// 404 repo does not exist or no read access
// 500 internal
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
query := struct {
@ -258,7 +257,7 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
// 200 no error
// 404 repo does not exist or no read access
// 500 internal
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
query := struct {
@ -428,7 +427,7 @@ func GetImage(w http.ResponseWriter, r *http.Request) {
}
func GetImages(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
query := struct {
All bool
@ -489,7 +488,7 @@ func GetImages(w http.ResponseWriter, r *http.Request) {
}
func LoadImages(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
query := struct {
@ -547,7 +546,7 @@ func LoadImages(w http.ResponseWriter, r *http.Request) {
func ExportImages(w http.ResponseWriter, r *http.Request) {
// 200 OK
// 500 Error
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
query := struct {

View File

@ -27,7 +27,6 @@ import (
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/storage/pkg/archive"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/gorilla/schema"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
@ -151,7 +150,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
SkipUnusedStages: true,
}
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, err)
return

View File

@ -16,13 +16,12 @@ import (
"github.com/containers/podman/v4/pkg/domain/infra/abi"
"github.com/containers/storage"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/gorilla/schema"
"github.com/sirupsen/logrus"
)
// PushImage is the handler for the compat http endpoint for pushing images.
func PushImage(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
// Now use the ABI implementation to prevent us from having duplicate

View File

@ -11,11 +11,10 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/domain/infra/abi"
"github.com/containers/storage"
"github.com/gorilla/schema"
)
func RemoveImage(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
query := struct {

View File

@ -12,12 +12,11 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/domain/infra/abi"
"github.com/containers/storage"
"github.com/gorilla/schema"
)
func SearchImages(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
query := struct {
Term string `json:"term"`
Limit int `json:"limit"`

View File

@ -19,7 +19,6 @@ import (
"github.com/docker/docker/api/types"
dockerNetwork "github.com/docker/docker/api/types/network"
"github.com/gorilla/schema"
"github.com/sirupsen/logrus"
)
@ -41,7 +40,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
}{
scope: "local",
}
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
return
@ -315,7 +314,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
// This is where you can override the golang default value for one of fields
}
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
return

View File

@ -12,12 +12,11 @@ import (
"github.com/containers/podman/v4/pkg/api/handlers/utils"
api "github.com/containers/podman/v4/pkg/api/types"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
)
func ResizeTTY(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
// /containers/{id}/resize
query := struct {

View File

@ -15,7 +15,6 @@ import (
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/domain/infra/abi"
"github.com/containers/podman/v4/pkg/util"
"github.com/gorilla/schema"
)
func ListSecrets(w http.ResponseWriter, r *http.Request) {
@ -52,7 +51,7 @@ func ListSecrets(w http.ResponseWriter, r *http.Request) {
}
func InspectSecret(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder := utils.GetDecoder(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
name := utils.GetName(r)
names := []string{name}

View File

@ -19,7 +19,6 @@ import (
"github.com/containers/podman/v4/pkg/util"
docker_api_types "github.com/docker/docker/api/types"
docker_api_types_volume "github.com/docker/docker/api/types/volume"
"github.com/gorilla/schema"
)
func ListVolumes(w http.ResponseWriter, r *http.Request) {
@ -85,7 +84,7 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) {
var (
volumeOptions []libpod.VolumeCreateOption
runtime = r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder = r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder = utils.GetDecoder(r)
)
/* No query string data*/
query := struct{}{}
@ -213,7 +212,7 @@ func InspectVolume(w http.ResponseWriter, r *http.Request) {
func RemoveVolume(w http.ResponseWriter, r *http.Request) {
var (
runtime = r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder = r.Context().Value(api.DecoderKey).(*schema.Decoder)
decoder = utils.GetDecoder(r)
)
query := struct {
Force bool `schema:"force"`

View File

@ -3,6 +3,7 @@ package handlers
import (
"encoding/json"
"reflect"
"strings"
"syscall"
"time"
@ -28,6 +29,18 @@ func NewAPIDecoder() *schema.Decoder {
return d
}
func NewCompatAPIDecoder() *schema.Decoder {
dec := NewAPIDecoder()
// mimic behaviour of github.com/docker/docker/api/server/httputils.BoolValue()
dec.RegisterConverter(true, func(s string) reflect.Value {
s = strings.ToLower(strings.TrimSpace(s))
return reflect.ValueOf(!(s == "" || s == "0" || s == "no" || s == "false" || s == "none"))
})
return dec
}
// On client:
//
// v := map[string][]string{

View File

@ -13,8 +13,11 @@ import (
"github.com/blang/semver/v4"
"github.com/containers/podman/v4/version"
"github.com/gorilla/mux"
"github.com/gorilla/schema"
jsoniter "github.com/json-iterator/go"
"github.com/sirupsen/logrus"
api "github.com/containers/podman/v4/pkg/api/types"
)
var (
@ -188,3 +191,10 @@ func GetVar(r *http.Request, k string) string {
func GetName(r *http.Request) string {
return GetVar(r, "name")
}
func GetDecoder(r *http.Request) *schema.Decoder {
if IsLibpodRequest(r) {
return r.Context().Value(api.DecoderKey).(*schema.Decoder)
}
return r.Context().Value(api.CompatDecoderKey).(*schema.Decoder)
}

View File

@ -90,6 +90,7 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser
server.BaseContext = func(l net.Listener) context.Context {
ctx := context.WithValue(context.Background(), types.DecoderKey, handlers.NewAPIDecoder())
ctx = context.WithValue(ctx, types.CompatDecoderKey, handlers.NewCompatAPIDecoder())
ctx = context.WithValue(ctx, types.RuntimeKey, runtime)
ctx = context.WithValue(ctx, types.IdleTrackerKey, tracker)
return ctx

View File

@ -7,4 +7,5 @@ const (
RuntimeKey
IdleTrackerKey
ConnKey
CompatDecoderKey
)

View File

@ -234,6 +234,11 @@ t POST "build?dockerfile=containerfile" $CONTAINERFILE_TAR application/json 200
response_headers=$(cat "$WORKDIR/curl.headers.out")
like "$response_headers" ".*application/json.*" "header does not contain application/json"
# Build api response header must contain Content-type: application/json
t POST "build?dockerfile=containerfile&pull=1" $CONTAINERFILE_TAR application/json 200
response_headers=$(cat "$WORKDIR/curl.headers.out")
like "$response_headers" ".*application/json.*" "header does not contain application/json"
# PR #12091: output from compat API must now include {"aux":{"ID":"sha..."}}
t POST "build?dockerfile=containerfile" $CONTAINERFILE_TAR 200 \
'.aux|select(has("ID")).ID~^sha256:[0-9a-f]\{64\}$'

View File

@ -239,6 +239,26 @@ class TestContainers(common.DockerTestCase):
ret, _ = ctr.exec_run(["stat", "/workspace/scratch/test"])
self.assertEqual(ret, 0, "Working directory created if it doesn't exist")
def test_build_pull(self):
dockerfile = (
b"FROM quay.io/libpod/alpine:latest\n"
b"USER 1000:1000\n"
)
img: Image
img, logs = self.docker.images.build(fileobj=io.BytesIO(dockerfile), quiet=False, pull=True)
has_tried_pull = False
for e in logs:
if "stream" in e and "trying to pull" in e["stream"].lower():
has_tried_pull = True
self.assertTrue(has_tried_pull, "the build process has not tried to pull the base image")
img, logs = self.docker.images.build(fileobj=io.BytesIO(dockerfile), quiet=False, pull=False)
has_tried_pull = False
for e in logs:
if "stream" in e and "trying to pull" in e["stream"].lower():
has_tried_pull = True
self.assertFalse(has_tried_pull, "the build process has tried tried to pull the base image")
def test_mount_rw_by_default(self):
ctr: Optional[Container] = None
vol: Optional[Volume] = None