image inspect: fix legacy fields for API < v1.52 response
| ... | ... |
@@ -425,13 +425,17 @@ func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWrite |
| 425 | 425 |
"DockerVersion": imageInspect.DockerVersion, //nolint:staticcheck // ignore SA1019: field is deprecated, but still included in response when present. |
| 426 | 426 |
"Author": imageInspect.Author, |
| 427 | 427 |
})) |
| 428 |
+ |
|
| 429 |
+ // preserve fields in the image Config that have an "omitempty" |
|
| 430 |
+ // in the OCI spec, but weren't omitted in API v1.51 and lower. |
|
| 428 | 431 |
if versions.LessThan(version, "1.50") {
|
| 429 |
- legacyOptions = append(legacyOptions, compat.WithExtraFields(legacyConfigFields["v1.49"])) |
|
| 432 |
+ legacyOptions = append(legacyOptions, compat.WithExtraFields(map[string]any{
|
|
| 433 |
+ "Config": legacyConfigFields["v1.49"], |
|
| 434 |
+ })) |
|
| 430 | 435 |
} else {
|
| 431 |
- // inspectResponse preserves fields in the response that have an |
|
| 432 |
- // "omitempty" in the OCI spec, but didn't omit such fields in |
|
| 433 |
- // legacy responses before API v1.50. |
|
| 434 |
- legacyOptions = append(legacyOptions, compat.WithExtraFields(legacyConfigFields["v1.50-v1.51"])) |
|
| 436 |
+ legacyOptions = append(legacyOptions, compat.WithExtraFields(map[string]any{
|
|
| 437 |
+ "Config": legacyConfigFields["v1.50-v1.51"], |
|
| 438 |
+ })) |
|
| 435 | 439 |
} |
| 436 | 440 |
} |
| 437 | 441 |
|
| ... | ... |
@@ -1,12 +1,5 @@ |
| 1 | 1 |
package image |
| 2 | 2 |
|
| 3 |
-import ( |
|
| 4 |
- "encoding/json" |
|
| 5 |
- "maps" |
|
| 6 |
- |
|
| 7 |
- "github.com/moby/moby/api/types/image" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 | 3 |
// legacyConfigFields defines legacy image-config fields to include in |
| 11 | 4 |
// API responses on older API versions. |
| 12 | 5 |
var legacyConfigFields = map[string]map[string]any{
|
| ... | ... |
@@ -31,9 +24,9 @@ var legacyConfigFields = map[string]map[string]any{
|
| 31 | 31 |
"Volumes": nil, |
| 32 | 32 |
"WorkingDir": "", |
| 33 | 33 |
}, |
| 34 |
- // Legacy fields for current API versions (v1.50 and v1.52). These fields |
|
| 35 |
- // did not have an "omitempty" and were always included in the response, |
|
| 36 |
- // even if not set; see https://github.com/moby/moby/issues/50134 |
|
| 34 |
+ // Legacy fields for API v1.50 and v1.51. These fields did not have |
|
| 35 |
+ // an "omitempty" and were always included in the response, even if |
|
| 36 |
+ // not set; see https://github.com/moby/moby/issues/50134 |
|
| 37 | 37 |
"v1.50-v1.51": {
|
| 38 | 38 |
"Cmd": nil, |
| 39 | 39 |
"Entrypoint": nil, |
| ... | ... |
@@ -45,41 +38,3 @@ var legacyConfigFields = map[string]map[string]any{
|
| 45 | 45 |
"WorkingDir": "", |
| 46 | 46 |
}, |
| 47 | 47 |
} |
| 48 |
- |
|
| 49 |
-// inspectCompatResponse is a wrapper around [image.InspectResponse] with a |
|
| 50 |
-// custom marshal function for legacy [api/types/container.Config} fields |
|
| 51 |
-// that have been removed, or did not have omitempty. |
|
| 52 |
-type inspectCompatResponse struct {
|
|
| 53 |
- *image.InspectResponse |
|
| 54 |
- legacyConfig map[string]any |
|
| 55 |
-} |
|
| 56 |
- |
|
| 57 |
-// MarshalJSON implements a custom marshaler to include legacy fields |
|
| 58 |
-// in API responses. |
|
| 59 |
-func (ir *inspectCompatResponse) MarshalJSON() ([]byte, error) {
|
|
| 60 |
- type tmp *image.InspectResponse |
|
| 61 |
- base, err := json.Marshal((tmp)(ir.InspectResponse)) |
|
| 62 |
- if err != nil {
|
|
| 63 |
- return nil, err |
|
| 64 |
- } |
|
| 65 |
- if len(ir.legacyConfig) == 0 {
|
|
| 66 |
- return base, nil |
|
| 67 |
- } |
|
| 68 |
- |
|
| 69 |
- type resp struct {
|
|
| 70 |
- *image.InspectResponse |
|
| 71 |
- Config map[string]any |
|
| 72 |
- } |
|
| 73 |
- |
|
| 74 |
- var merged resp |
|
| 75 |
- err = json.Unmarshal(base, &merged) |
|
| 76 |
- if err != nil {
|
|
| 77 |
- return base, nil |
|
| 78 |
- } |
|
| 79 |
- |
|
| 80 |
- // prevent mutating legacyConfigFields. |
|
| 81 |
- cfg := maps.Clone(ir.legacyConfig) |
|
| 82 |
- maps.Copy(cfg, merged.Config) |
|
| 83 |
- merged.Config = cfg |
|
| 84 |
- return json.Marshal(merged) |
|
| 85 |
-} |
| ... | ... |
@@ -6,6 +6,7 @@ import ( |
| 6 | 6 |
|
| 7 | 7 |
dockerspec "github.com/moby/docker-image-spec/specs-go/v1" |
| 8 | 8 |
"github.com/moby/moby/api/types/image" |
| 9 |
+ "github.com/moby/moby/v2/daemon/internal/compat" |
|
| 9 | 10 |
ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| 10 | 11 |
"gotest.tools/v3/assert" |
| 11 | 12 |
is "gotest.tools/v3/assert/cmp" |
| ... | ... |
@@ -59,10 +60,11 @@ func TestInspectResponse(t *testing.T) {
|
| 59 | 59 |
ImageConfig: *tc.cfg, |
| 60 | 60 |
} |
| 61 | 61 |
} |
| 62 |
- out, err := json.Marshal(&inspectCompatResponse{
|
|
| 63 |
- InspectResponse: imgInspect, |
|
| 64 |
- legacyConfig: tc.legacyConfig, |
|
| 65 |
- }) |
|
| 62 |
+ legacyConfigResponse := compat.Wrap(imgInspect, compat.WithExtraFields(map[string]any{
|
|
| 63 |
+ "Config": tc.legacyConfig, |
|
| 64 |
+ })) |
|
| 65 |
+ |
|
| 66 |
+ out, err := json.Marshal(&legacyConfigResponse) |
|
| 66 | 67 |
assert.NilError(t, err) |
| 67 | 68 |
|
| 68 | 69 |
var outMap struct{ Config json.RawMessage }
|