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 <github@gone.nl>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,39 @@ |
| 0 |
+// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: |
|
| 1 |
+//go:build go1.23 |
|
| 2 |
+ |
|
| 3 |
+package system |
|
| 4 |
+ |
|
| 5 |
+import ( |
|
| 6 |
+ "encoding/json" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/api/types/system" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// infoResponse is a wrapper around [system.Info] with a custom |
|
| 12 |
+// marshal function for legacy fields. |
|
| 13 |
+type infoResponse struct {
|
|
| 14 |
+ *system.Info |
|
| 15 |
+ |
|
| 16 |
+ // extraFields is for internal use to include deprecated fields on older API versions. |
|
| 17 |
+ extraFields map[string]any |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+// MarshalJSON implements a custom marshaler to include legacy fields |
|
| 21 |
+// in API responses. |
|
| 22 |
+func (sc *infoResponse) MarshalJSON() ([]byte, error) {
|
|
| 23 |
+ type tmp *system.Info |
|
| 24 |
+ base, err := json.Marshal((tmp)(sc.Info)) |
|
| 25 |
+ if err != nil {
|
|
| 26 |
+ return nil, err |
|
| 27 |
+ } |
|
| 28 |
+ if len(sc.extraFields) == 0 {
|
|
| 29 |
+ return base, nil |
|
| 30 |
+ } |
|
| 31 |
+ var merged map[string]any |
|
| 32 |
+ _ = json.Unmarshal(base, &merged) |
|
| 33 |
+ |
|
| 34 |
+ for k, v := range sc.extraFields {
|
|
| 35 |
+ merged[k] = v |
|
| 36 |
+ } |
|
| 37 |
+ return json.Marshal(merged) |
|
| 38 |
+} |
| 0 | 39 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,33 @@ |
| 0 |
+package system |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "strings" |
|
| 5 |
+ "testing" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/api/types/system" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func TestLegacyFields(t *testing.T) {
|
|
| 11 |
+ infoResp := &infoResponse{
|
|
| 12 |
+ Info: &system.Info{
|
|
| 13 |
+ Containers: 10, |
|
| 14 |
+ }, |
|
| 15 |
+ extraFields: map[string]any{
|
|
| 16 |
+ "LegacyFoo": false, |
|
| 17 |
+ "LegacyBar": true, |
|
| 18 |
+ }, |
|
| 19 |
+ } |
|
| 20 |
+ |
|
| 21 |
+ data, err := json.MarshalIndent(infoResp, "", " ") |
|
| 22 |
+ if err != nil {
|
|
| 23 |
+ t.Fatal(err) |
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ if expected := `"LegacyFoo": false`; !strings.Contains(string(data), expected) {
|
|
| 27 |
+ t.Errorf("legacy fields should contain %s: %s", expected, string(data))
|
|
| 28 |
+ } |
|
| 29 |
+ if expected := `"LegacyBar": true`; !strings.Contains(string(data), expected) {
|
|
| 30 |
+ t.Errorf("legacy fields should contain %s: %s", expected, string(data))
|
|
| 31 |
+ } |
|
| 32 |
+} |
| ... | ... |
@@ -5,7 +5,6 @@ package system // import "github.com/docker/docker/api/server/router/system" |
| 5 | 5 |
|
| 6 | 6 |
import ( |
| 7 | 7 |
"github.com/docker/docker/api/server/router" |
| 8 |
- "github.com/docker/docker/api/types/system" |
|
| 9 | 8 |
"resenje.org/singleflight" |
| 10 | 9 |
) |
| 11 | 10 |
|
| ... | ... |
@@ -21,7 +20,7 @@ type systemRouter struct {
|
| 21 | 21 |
// collectSystemInfo is a single-flight for the /info endpoint, |
| 22 | 22 |
// unique per API version (as different API versions may return |
| 23 | 23 |
// a different API response). |
| 24 |
- collectSystemInfo singleflight.Group[string, *system.Info] |
|
| 24 |
+ collectSystemInfo singleflight.Group[string, *infoResponse] |
|
| 25 | 25 |
} |
| 26 | 26 |
|
| 27 | 27 |
// NewRouter initializes a new system router |
| ... | ... |
@@ -63,7 +63,7 @@ func (s *systemRouter) swarmStatus() string {
|
| 63 | 63 |
|
| 64 | 64 |
func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| 65 | 65 |
version := httputils.VersionFromContext(ctx) |
| 66 |
- info, _, _ := s.collectSystemInfo.Do(ctx, version, func(ctx context.Context) (*system.Info, error) {
|
|
| 66 |
+ info, _, _ := s.collectSystemInfo.Do(ctx, version, func(ctx context.Context) (*infoResponse, error) {
|
|
| 67 | 67 |
info, err := s.backend.SystemInfo(ctx) |
| 68 | 68 |
if err != nil {
|
| 69 | 69 |
return nil, err |
| ... | ... |
@@ -117,6 +117,7 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht |
| 117 | 117 |
info.FirewallBackend = nil |
| 118 | 118 |
} |
| 119 | 119 |
|
| 120 |
+ extraFields := map[string]any{}
|
|
| 120 | 121 |
if versions.LessThan(version, "1.49") {
|
| 121 | 122 |
// Expected commits are omitted in API 1.49, but should still be |
| 122 | 123 |
// included in older versions. |
| ... | ... |
@@ -129,9 +130,17 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht |
| 129 | 129 |
} |
| 130 | 130 |
if versions.LessThan(version, "1.50") {
|
| 131 | 131 |
info.DiscoveredDevices = nil |
| 132 |
+ |
|
| 133 |
+ // These fields are omitted in > API 1.49, and always false |
|
| 134 |
+ // older API versions. |
|
| 135 |
+ extraFields = map[string]any{
|
|
| 136 |
+ "BridgeNfIptables": json.RawMessage("false"),
|
|
| 137 |
+ "BridgeNfIp6tables": json.RawMessage("false"),
|
|
| 138 |
+ } |
|
| 132 | 139 |
} |
| 133 |
- return info, nil |
|
| 140 |
+ return &infoResponse{Info: info, extraFields: extraFields}, nil
|
|
| 134 | 141 |
}) |
| 142 |
+ |
|
| 135 | 143 |
return httputils.WriteJSON(w, http.StatusOK, info) |
| 136 | 144 |
} |
| 137 | 145 |
|
| ... | ... |
@@ -29,8 +29,6 @@ type Info struct {
|
| 29 | 29 |
CPUSet bool |
| 30 | 30 |
PidsLimit bool |
| 31 | 31 |
IPv4Forwarding bool |
| 32 |
- 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. |
|
| 33 |
- 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. |
|
| 34 | 32 |
Debug bool |
| 35 | 33 |
NFd int |
| 36 | 34 |
OomKillDisable bool |
| ... | ... |
@@ -21,6 +21,9 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 21 | 21 |
`DeviceInfo` objects, each providing details about a device discovered by a |
| 22 | 22 |
device driver. |
| 23 | 23 |
Currently only the CDI device driver is supported. |
| 24 |
+* Deprecated: The `BridgeNfIptables` and `BridgeNfIp6tables` fields in the |
|
| 25 |
+ `GET /info` response were deprecated in API v1.48, and are now omitted |
|
| 26 |
+ in API v1.50. |
|
| 24 | 27 |
|
| 25 | 28 |
## v1.49 API changes |
| 26 | 29 |
|
| ... | ... |
@@ -3,9 +3,13 @@ |
| 3 | 3 |
package system // import "github.com/docker/docker/integration/system" |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 |
+ "encoding/json" |
|
| 7 |
+ "io" |
|
| 8 |
+ "net/http" |
|
| 6 | 9 |
"testing" |
| 7 | 10 |
|
| 8 | 11 |
"github.com/docker/docker/client" |
| 12 |
+ "github.com/docker/docker/testutil/request" |
|
| 9 | 13 |
"gotest.tools/v3/assert" |
| 10 | 14 |
is "gotest.tools/v3/assert/cmp" |
| 11 | 15 |
) |
| ... | ... |
@@ -47,3 +51,56 @@ func TestInfoBinaryCommits(t *testing.T) {
|
| 47 | 47 |
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. |
| 48 | 48 |
}) |
| 49 | 49 |
} |
| 50 |
+ |
|
| 51 |
+func TestInfoLegacyFields(t *testing.T) {
|
|
| 52 |
+ ctx := setupTest(t) |
|
| 53 |
+ |
|
| 54 |
+ const notPresent = "expected field to not be present" |
|
| 55 |
+ |
|
| 56 |
+ tests := []struct {
|
|
| 57 |
+ name string |
|
| 58 |
+ url string |
|
| 59 |
+ expectedFields map[string]any |
|
| 60 |
+ }{
|
|
| 61 |
+ {
|
|
| 62 |
+ name: "api v1.49 legacy bridge-nftables", |
|
| 63 |
+ url: "/v1.49/info", |
|
| 64 |
+ expectedFields: map[string]any{
|
|
| 65 |
+ "BridgeNfIp6tables": false, |
|
| 66 |
+ "BridgeNfIptables": false, |
|
| 67 |
+ }, |
|
| 68 |
+ }, |
|
| 69 |
+ {
|
|
| 70 |
+ name: "api v1.50 legacy bridge-nftables", |
|
| 71 |
+ url: "/v1.50/info", |
|
| 72 |
+ expectedFields: map[string]any{
|
|
| 73 |
+ "BridgeNfIp6tables": notPresent, |
|
| 74 |
+ "BridgeNfIptables": notPresent, |
|
| 75 |
+ }, |
|
| 76 |
+ }, |
|
| 77 |
+ } |
|
| 78 |
+ for _, tc := range tests {
|
|
| 79 |
+ t.Run(tc.name, func(t *testing.T) {
|
|
| 80 |
+ res, _, err := request.Get(ctx, tc.url) |
|
| 81 |
+ assert.NilError(t, err) |
|
| 82 |
+ assert.Equal(t, res.StatusCode, http.StatusOK) |
|
| 83 |
+ body, err := io.ReadAll(res.Body) |
|
| 84 |
+ assert.NilError(t, err) |
|
| 85 |
+ |
|
| 86 |
+ actual := map[string]any{}
|
|
| 87 |
+ err = json.Unmarshal(body, &actual) |
|
| 88 |
+ assert.NilError(t, err, string(body)) |
|
| 89 |
+ |
|
| 90 |
+ for field, expectedValue := range tc.expectedFields {
|
|
| 91 |
+ if expectedValue == notPresent {
|
|
| 92 |
+ _, found := actual[field] |
|
| 93 |
+ assert.Assert(t, !found, "field %s should not be present", field) |
|
| 94 |
+ } else {
|
|
| 95 |
+ _, found := actual[field] |
|
| 96 |
+ assert.Assert(t, found, "field %s should be present", field) |
|
| 97 |
+ assert.Check(t, is.DeepEqual(actual[field], expectedValue)) |
|
| 98 |
+ } |
|
| 99 |
+ } |
|
| 100 |
+ }) |
|
| 101 |
+ } |
|
| 102 |
+} |
| ... | ... |
@@ -24,16 +24,6 @@ type SysInfo struct {
|
| 24 | 24 |
// Whether IPv4 forwarding is supported or not, if this was disabled, networking will not work |
| 25 | 25 |
IPv4ForwardingDisabled bool |
| 26 | 26 |
|
| 27 |
- // Whether bridge-nf-call-iptables is supported or not |
|
| 28 |
- // |
|
| 29 |
- // 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. |
|
| 30 |
- BridgeNFCallIPTablesDisabled bool |
|
| 31 |
- |
|
| 32 |
- // Whether bridge-nf-call-ip6tables is supported or not |
|
| 33 |
- // |
|
| 34 |
- // 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. |
|
| 35 |
- BridgeNFCallIP6TablesDisabled bool |
|
| 36 |
- |
|
| 37 | 27 |
// Whether the cgroup has the mountpoint of "devices" or not |
| 38 | 28 |
CgroupDevicesEnabled bool |
| 39 | 29 |
|