From 6505d3877cbcd39c426cb7b92be2f94037313c6a Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 30 Apr 2025 15:44:08 +0200 Subject: [PATCH] API: /info: remove BridgeNfIptables, BridgeNfIp6tables fields The `BridgeNfIptables` and `BridgeNfIp6tables` fields in the `GET /info` response were deprecated in API v1.48, and are now omitted in API v1.50. With this patch, old API version continue to return the field: curl -s --unix-socket /var/run/docker.sock http://localhost/v1.48/info | jq .BridgeNfIp6tables false curl -s --unix-socket /var/run/docker.sock http://localhost/v1.48/info | jq .BridgeNfIptables false Omitting the field in API v1.50 and above curl -s --unix-socket /var/run/docker.sock http://localhost/v1.50/info | jq .BridgeNfIp6tables null curl -s --unix-socket /var/run/docker.sock http://localhost/v1.50/info | jq .BridgeNfIptables null This reverts commit eacbbdeec68779be81983f61f5de5f91d52f656a, and re-applies a variant of 5d2006256f15f7252c11bd72d632de26a8b2ff06 Signed-off-by: Sebastiaan van Stijn --- api/server/router/system/info_response.go | 39 +++++++++++++ .../router/system/info_response_test.go | 33 +++++++++++ api/server/router/system/system.go | 3 +- api/server/router/system/system_routes.go | 13 ++++- api/types/system/info.go | 2 - docs/api/version-history.md | 3 + integration/system/info_linux_test.go | 57 +++++++++++++++++++ pkg/sysinfo/sysinfo.go | 10 ---- 8 files changed, 144 insertions(+), 16 deletions(-) create mode 100644 api/server/router/system/info_response.go create mode 100644 api/server/router/system/info_response_test.go diff --git a/api/server/router/system/info_response.go b/api/server/router/system/info_response.go new file mode 100644 index 0000000000..6d7f03d7e4 --- /dev/null +++ b/api/server/router/system/info_response.go @@ -0,0 +1,39 @@ +// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: +//go:build go1.23 + +package system + +import ( + "encoding/json" + + "github.com/docker/docker/api/types/system" +) + +// infoResponse is a wrapper around [system.Info] with a custom +// marshal function for legacy fields. +type infoResponse struct { + *system.Info + + // extraFields is for internal use to include deprecated fields on older API versions. + extraFields map[string]any +} + +// MarshalJSON implements a custom marshaler to include legacy fields +// in API responses. +func (sc *infoResponse) MarshalJSON() ([]byte, error) { + type tmp *system.Info + base, err := json.Marshal((tmp)(sc.Info)) + if err != nil { + return nil, err + } + if len(sc.extraFields) == 0 { + return base, nil + } + var merged map[string]any + _ = json.Unmarshal(base, &merged) + + for k, v := range sc.extraFields { + merged[k] = v + } + return json.Marshal(merged) +} diff --git a/api/server/router/system/info_response_test.go b/api/server/router/system/info_response_test.go new file mode 100644 index 0000000000..e04c7b613b --- /dev/null +++ b/api/server/router/system/info_response_test.go @@ -0,0 +1,33 @@ +package system + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/docker/docker/api/types/system" +) + +func TestLegacyFields(t *testing.T) { + infoResp := &infoResponse{ + Info: &system.Info{ + Containers: 10, + }, + extraFields: map[string]any{ + "LegacyFoo": false, + "LegacyBar": true, + }, + } + + data, err := json.MarshalIndent(infoResp, "", " ") + if err != nil { + t.Fatal(err) + } + + if expected := `"LegacyFoo": false`; !strings.Contains(string(data), expected) { + t.Errorf("legacy fields should contain %s: %s", expected, string(data)) + } + if expected := `"LegacyBar": true`; !strings.Contains(string(data), expected) { + t.Errorf("legacy fields should contain %s: %s", expected, string(data)) + } +} diff --git a/api/server/router/system/system.go b/api/server/router/system/system.go index b311d37a46..99842fa082 100644 --- a/api/server/router/system/system.go +++ b/api/server/router/system/system.go @@ -5,7 +5,6 @@ package system // import "github.com/docker/docker/api/server/router/system" import ( "github.com/docker/docker/api/server/router" - "github.com/docker/docker/api/types/system" "resenje.org/singleflight" ) @@ -21,7 +20,7 @@ type systemRouter struct { // collectSystemInfo is a single-flight for the /info endpoint, // unique per API version (as different API versions may return // a different API response). - collectSystemInfo singleflight.Group[string, *system.Info] + collectSystemInfo singleflight.Group[string, *infoResponse] } // NewRouter initializes a new system router diff --git a/api/server/router/system/system_routes.go b/api/server/router/system/system_routes.go index 89d7c65d3b..7c39724929 100644 --- a/api/server/router/system/system_routes.go +++ b/api/server/router/system/system_routes.go @@ -63,7 +63,7 @@ func (s *systemRouter) swarmStatus() string { func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { version := httputils.VersionFromContext(ctx) - info, _, _ := s.collectSystemInfo.Do(ctx, version, func(ctx context.Context) (*system.Info, error) { + info, _, _ := s.collectSystemInfo.Do(ctx, version, func(ctx context.Context) (*infoResponse, error) { info, err := s.backend.SystemInfo(ctx) if err != nil { return nil, err @@ -117,6 +117,7 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht info.FirewallBackend = nil } + extraFields := map[string]any{} if versions.LessThan(version, "1.49") { // Expected commits are omitted in API 1.49, but should still be // included in older versions. @@ -129,9 +130,17 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht } if versions.LessThan(version, "1.50") { info.DiscoveredDevices = nil + + // These fields are omitted in > API 1.49, and always false + // older API versions. + extraFields = map[string]any{ + "BridgeNfIptables": json.RawMessage("false"), + "BridgeNfIp6tables": json.RawMessage("false"), + } } - return info, nil + return &infoResponse{Info: info, extraFields: extraFields}, nil }) + return httputils.WriteJSON(w, http.StatusOK, info) } diff --git a/api/types/system/info.go b/api/types/system/info.go index aac43f5819..047639ed91 100644 --- a/api/types/system/info.go +++ b/api/types/system/info.go @@ -29,8 +29,6 @@ type Info struct { CPUSet bool PidsLimit bool IPv4Forwarding bool - BridgeNfIptables bool `json:"BridgeNfIptables"` // Deprecated: netfilter module is now loaded on-demand and no longer during daemon startup, making this field obsolete. This field is always false and will be removed in the next release. - BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` // Deprecated: netfilter module is now loaded on-demand and no longer during daemon startup, making this field obsolete. This field is always false and will be removed in the next release. Debug bool NFd int OomKillDisable bool diff --git a/docs/api/version-history.md b/docs/api/version-history.md index 2cd6fef1a8..41dde8e175 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -21,6 +21,9 @@ keywords: "API, Docker, rcli, REST, documentation" `DeviceInfo` objects, each providing details about a device discovered by a device driver. Currently only the CDI device driver is supported. +* Deprecated: The `BridgeNfIptables` and `BridgeNfIp6tables` fields in the + `GET /info` response were deprecated in API v1.48, and are now omitted + in API v1.50. ## v1.49 API changes diff --git a/integration/system/info_linux_test.go b/integration/system/info_linux_test.go index 27cccc77cc..ce05a01fc7 100644 --- a/integration/system/info_linux_test.go +++ b/integration/system/info_linux_test.go @@ -3,9 +3,13 @@ package system // import "github.com/docker/docker/integration/system" import ( + "encoding/json" + "io" + "net/http" "testing" "github.com/docker/docker/client" + "github.com/docker/docker/testutil/request" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) @@ -47,3 +51,56 @@ func TestInfoBinaryCommits(t *testing.T) { assert.Check(t, is.Equal(info.RuncCommit.Expected, info.RuncCommit.ID)) //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.49. }) } + +func TestInfoLegacyFields(t *testing.T) { + ctx := setupTest(t) + + const notPresent = "expected field to not be present" + + tests := []struct { + name string + url string + expectedFields map[string]any + }{ + { + name: "api v1.49 legacy bridge-nftables", + url: "/v1.49/info", + expectedFields: map[string]any{ + "BridgeNfIp6tables": false, + "BridgeNfIptables": false, + }, + }, + { + name: "api v1.50 legacy bridge-nftables", + url: "/v1.50/info", + expectedFields: map[string]any{ + "BridgeNfIp6tables": notPresent, + "BridgeNfIptables": notPresent, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + res, _, err := request.Get(ctx, tc.url) + assert.NilError(t, err) + assert.Equal(t, res.StatusCode, http.StatusOK) + body, err := io.ReadAll(res.Body) + assert.NilError(t, err) + + actual := map[string]any{} + err = json.Unmarshal(body, &actual) + assert.NilError(t, err, string(body)) + + for field, expectedValue := range tc.expectedFields { + if expectedValue == notPresent { + _, found := actual[field] + assert.Assert(t, !found, "field %s should not be present", field) + } else { + _, found := actual[field] + assert.Assert(t, found, "field %s should be present", field) + assert.Check(t, is.DeepEqual(actual[field], expectedValue)) + } + } + }) + } +} diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 9ab8519cfc..44faded863 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -24,16 +24,6 @@ type SysInfo struct { // Whether IPv4 forwarding is supported or not, if this was disabled, networking will not work IPv4ForwardingDisabled bool - // Whether bridge-nf-call-iptables is supported or not - // - // Deprecated: netfilter module is now loaded on-demand and no longer during daemon startup, making this field obsolete. This field is always false and will be removed in the next release. - BridgeNFCallIPTablesDisabled bool - - // Whether bridge-nf-call-ip6tables is supported or not - // - // Deprecated: netfilter module is now loaded on-demand and no longer during daemon startup, making this field obsolete. This field is always false and will be removed in the next release. - BridgeNFCallIP6TablesDisabled bool - // Whether the cgroup has the mountpoint of "devices" or not CgroupDevicesEnabled bool