Skip to content

Commit 3ec1919

Browse files
authored
Merge pull request #896 from dcbw/empty-result-version-regression
invoke: if Result CNIVersion is empty use netconf CNIVersion
2 parents 940e662 + 55fe94e commit 3ec1919

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

pkg/invoke/exec.go

+44-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package invoke
1616

1717
import (
1818
"context"
19+
"encoding/json"
1920
"fmt"
2021
"os"
2122

@@ -33,6 +34,43 @@ type Exec interface {
3334
Decode(jsonBytes []byte) (version.PluginInfo, error)
3435
}
3536

37+
// Plugin must return result in same version as specified in netconf; but
38+
// for backwards compatibility reasons if the result version is empty use
39+
// config version (rather than technically correct 0.1.0).
40+
// https://github.com/containernetworking/cni/issues/895
41+
func fixupResultVersion(netconf, result []byte) (string, []byte, error) {
42+
versionDecoder := &version.ConfigDecoder{}
43+
confVersion, err := versionDecoder.Decode(netconf)
44+
if err != nil {
45+
return "", nil, err
46+
}
47+
48+
var rawResult map[string]interface{}
49+
if err := json.Unmarshal(result, &rawResult); err != nil {
50+
return "", nil, fmt.Errorf("failed to unmarshal raw result: %w", err)
51+
}
52+
53+
// Manually decode Result version; we need to know whether its cniVersion
54+
// is empty, while built-in decoders (correctly) substitute 0.1.0 for an
55+
// empty version per the CNI spec.
56+
if resultVerRaw, ok := rawResult["cniVersion"]; ok {
57+
resultVer, ok := resultVerRaw.(string)
58+
if ok && resultVer != "" {
59+
return resultVer, result, nil
60+
}
61+
}
62+
63+
// If the cniVersion is not present or empty, assume the result is
64+
// the same CNI spec version as the config
65+
rawResult["cniVersion"] = confVersion
66+
newBytes, err := json.Marshal(rawResult)
67+
if err != nil {
68+
return "", nil, fmt.Errorf("failed to remarshal fixed result: %w", err)
69+
}
70+
71+
return confVersion, newBytes, nil
72+
}
73+
3674
// For example, a testcase could pass an instance of the following fakeExec
3775
// object to ExecPluginWithResult() to verify the incoming stdin and environment
3876
// and provide a tailored response:
@@ -84,7 +122,12 @@ func ExecPluginWithResult(ctx context.Context, pluginPath string, netconf []byte
84122
return nil, err
85123
}
86124

87-
return create.CreateFromBytes(stdoutBytes)
125+
resultVersion, fixedBytes, err := fixupResultVersion(netconf, stdoutBytes)
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
return create.Create(resultVersion, fixedBytes)
88131
}
89132

90133
func ExecPluginWithoutResult(ctx context.Context, pluginPath string, netconf []byte, args CNIArgs, exec Exec) error {

pkg/invoke/exec_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,38 @@ var _ = Describe("Executing a plugin, unit tests", func() {
9696
_, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, nil)
9797
Expect(err).To(HaveOccurred())
9898
})
99+
100+
It("assumes config version if result version is missing", func() {
101+
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
102+
r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
103+
Expect(err).NotTo(HaveOccurred())
104+
Expect(r.Version()).To(Equal("0.3.1"))
105+
106+
result, err := current.GetResult(r)
107+
Expect(err).NotTo(HaveOccurred())
108+
Expect(len(result.IPs)).To(Equal(1))
109+
Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
110+
})
111+
112+
It("assumes config version if result version is empty", func() {
113+
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "cniVersion": "", "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
114+
r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
115+
Expect(err).NotTo(HaveOccurred())
116+
Expect(r.Version()).To(Equal("0.3.1"))
117+
118+
result, err := current.GetResult(r)
119+
Expect(err).NotTo(HaveOccurred())
120+
Expect(len(result.IPs)).To(Equal(1))
121+
Expect(result.IPs[0].Address.IP.String()).To(Equal("1.2.3.4"))
122+
})
123+
124+
It("assumes 0.1.0 if config and result version are empty", func() {
125+
netconf = []byte(`{ "some": "stdin" }`)
126+
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "some": "version-info" }`)
127+
r, err := invoke.ExecPluginWithResult(ctx, pluginPath, netconf, cniargs, pluginExec)
128+
Expect(err).NotTo(HaveOccurred())
129+
Expect(r.Version()).To(Equal("0.1.0"))
130+
})
99131
})
100132

101133
Describe("without returning a result", func() {

0 commit comments

Comments
 (0)