Repository: containernetworking/cni
Pull request #783 (2020-08-12 21:54:39)

types changes for 1.0.0
Moving to spec 1.0.0 requires more complicated conversions, so
put the converter determination logic in a separate module that
any of the types can call to convert between arbitrary versions.
This is necessary to ensure the types don't have import cycles.

This also implements downconversion, which we never claimed
*not* to support, but didn't implement.

It also verifies that the Result object unmarshalled by
each result type's NewResult() function is actually supported
by the result type. This is a potentially breaking change as
this was not previously done, and for example may now fail
attempts to read a cached PrevResult written by a previous
version.

Signed-off-by: Dan Williams <dcbw@redhat.com>

coveralls: 
[![Coverage Status](https://coveralls.io/builds/32736353/badge)](https://coveralls.io/builds/32736353)

Coverage decreased (-2.9%) to 72.336% when pulling **3d29b0b87efa5407fafb13ced0190e54dbc4b645 on dcbw:100** into **6fc72a49e9ad357698b64634d60e0a83300284bf on containernetworking:master**.

dcbw: @containernetworking/cni-maintainers no longer WIP, please review. Thanks!

Patch:
diff --git a/SPEC.md b/SPEC.md
index f0f1e7d..2ee89ae 100644
--- a/SPEC.md
+++ b/SPEC.md
@@ -29,7 +29,7 @@
 
 ## Version
 
-This is CNI **spec** version **0.4.0**.
+This is CNI **spec** version **1.0.0 pre-release** and is still under active development.
 
 Note that this is **independent from the version of the CNI library and plugins** in this repository (e.g. the versions of [releases](https://github.com/containernetworking/cni/releases)).
 
@@ -152,8 +152,8 @@ Plugins should generally complete a `DEL` action without error even if some reso
 
       ```
       {
-        "cniVersion": "0.4.0", // the version of the CNI spec in use for this output
-        "supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0" ] // the list of CNI spec versions that this plugin supports
+        "cniVersion": "1.0.0", // the version of the CNI spec in use for this output
+        "supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0" ] // the list of CNI spec versions that this plugin supports
       }
       ```
 
@@ -178,7 +178,7 @@ Plugins must indicate success with a return code of zero and the following JSON
 
 ```
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "interfaces": [                                            (this key omitted by IPAM plugins)
       {
           "name": "<name>",
@@ -188,7 +188,6 @@ Plugins must indicate success with a return code of zero and the following JSON
   ],
   "ips": [
       {
-          "version": "<4-or-6>",
           "address": "<ip-and-prefix-in-CIDR>",
           "gateway": "<ip-address-of-the-gateway>",          (optional)
           "interface": <numeric index into 'interfaces' list>
@@ -236,7 +235,7 @@ Examples include generating an `/etc/resolv.conf` file to be injected into the c
 Errors must be indicated by a non-zero return code and the following JSON being printed to stdout:
 ```
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "code": <numeric-error-code>,
   "msg": <short-error-message>,
   "details": <long-error-message> (optional)
@@ -273,7 +272,7 @@ Plugins may define additional fields that they accept and may generate an error
 
 ```jsonc
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "name": "dbnet",
   "type": "bridge",
   // type (plugin) specific
@@ -294,7 +293,7 @@ Plugins may define additional fields that they accept and may generate an error
 
 ```jsonc
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "name": "pci",
   "type": "ovs",
   // type (plugin) specific
@@ -317,7 +316,7 @@ Plugins may define additional fields that they accept and may generate an error
 
 ```jsonc
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "name": "wan",
   "type": "macvlan",
   // ipam specific
@@ -367,7 +366,7 @@ If an `ADD` action fails, when the runtime decides to handle the failure it shou
 
 ```jsonc
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "name": "dbnet",
   "plugins": [
     {
@@ -409,7 +408,7 @@ Note that the runtime adds the `cniVersion` and `name` fields from configuration
 
 ```jsonc
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "name": "dbnet",
   "type": "bridge",
   "bridge": "cni0",
@@ -434,7 +433,7 @@ Note that the runtime adds the `cniVersion` and `name` fields from configuration
 
 ```json
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "name": "dbnet",
   "type": "tuning",
   "sysctl": {
@@ -443,7 +442,6 @@ Note that the runtime adds the `cniVersion` and `name` fields from configuration
   "prevResult": {
     "ips": [
         {
-          "version": "4",
           "address": "10.0.0.5/32",
           "interface": 2
         }
@@ -476,7 +474,7 @@ Given the same network configuration JSON list, the container runtime would perf
 
 ```jsonc
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "name": "dbnet",
   "type": "bridge",
   "bridge": "cni0",
@@ -497,7 +495,6 @@ Given the same network configuration JSON list, the container runtime would perf
   "prevResult": {
     "ips": [
         {
-          "version": "4",
           "address": "10.0.0.5/32",
           "interface": 2
         }
@@ -528,7 +525,7 @@ Given the same network configuration JSON list, the container runtime would perf
 
 ```json
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "name": "dbnet",
   "type": "tuning",
   "sysctl": {
@@ -537,7 +534,6 @@ Given the same network configuration JSON list, the container runtime would perf
   "prevResult": {
     "ips": [
         {
-          "version": "4",
           "address": "10.0.0.5/32",
           "interface": 2
         }
@@ -571,7 +567,7 @@ Note that plugins are executed in reverse order from the `ADD` and `CHECK` actio
 
 ```json
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "name": "dbnet",
   "type": "tuning",
   "sysctl": {
@@ -580,7 +576,6 @@ Note that plugins are executed in reverse order from the `ADD` and `CHECK` actio
   "prevResult": {
     "ips": [
         {
-          "version": "4",
           "address": "10.0.0.5/32",
           "interface": 2
         }
@@ -611,7 +606,7 @@ Note that plugins are executed in reverse order from the `ADD` and `CHECK` actio
 
 ```jsonc
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "name": "dbnet",
   "type": "bridge",
   "bridge": "cni0",
@@ -632,7 +627,6 @@ Note that plugins are executed in reverse order from the `ADD` and `CHECK` actio
   "prevResult": {
     "ips": [
         {
-          "version": "4",
           "address": "10.0.0.5/32",
           "interface": 2
         }
@@ -673,10 +667,9 @@ Success must be indicated by a zero return code and the following JSON being pri
 
 ```
 {
-  "cniVersion": "0.4.0",
+  "cniVersion": "1.0.0",
   "ips": [
       {
-          "version": "<4-or-6>",
           "address": "<ip-and-prefix-in-CIDR>",
           "gateway": "<ip-address-of-the-gateway>"  (optional)
       },
@@ -728,7 +721,6 @@ IPAM plugin examples:
 ```
   "ips": [
       {
-          "version": "<4-or-6>",
           "address": "<ip-and-prefix-in-CIDR>",
           "gateway": "<ip-address-of-the-gateway>",      (optional)
           "interface": <numeric index into 'interfaces' list> (not required for IPAM plugins)
@@ -740,8 +732,6 @@ IPAM plugin examples:
 The `ips` field is a list of IP configuration information determined by the plugin. Each item is a dictionary describing of IP configuration for a network interface.
 IP configuration for multiple network interfaces and multiple IP configurations for a single interface may be returned as separate items in the `ips` list.
 All properties known to the plugin should be provided, even if not strictly required.
-- `version` (string): either "4" or "6" and corresponds to the IP version of the addresses in the entry.
-   All IP addresses and gateways provided must be valid for the given `version`.
 - `address` (string): an IP address in CIDR notation (eg "192.168.1.3/24").
 - `gateway` (string): the default gateway for this subnet, if one exists.
    It does not instruct the CNI plugin to add any routes with this gateway: routes to add are specified separately via the `routes` field.
diff --git a/pkg/types/020/types.go b/pkg/types/020/types.go
index 36f3167..0b68a70 100644
--- a/pkg/types/020/types.go
+++ b/pkg/types/020/types.go
@@ -22,25 +22,41 @@ import (
 	"os"
 
 	"github.com/containernetworking/cni/pkg/types"
+	convert "github.com/containernetworking/cni/pkg/types/internal"
 )
 
 const ImplementedSpecVersion string = "0.2.0"
 
 var SupportedVersions = []string{"", "0.1.0", ImplementedSpecVersion}
 
+// Register converters for all versions less than the implemented spec version
+func init() {
+	convert.Register("0.1.0", []string{ImplementedSpecVersion}, convertFrom010)
+	convert.Register(ImplementedSpecVersion, []string{"0.1.0"}, convertTo010)
+}
+
 // Compatibility types for CNI version 0.1.0 and 0.2.0
 
+// NewResult creates a new Result object from JSON data. The JSON data
+// must be compatible with the CNI versions implemented by this type.
 func NewResult(data []byte) (types.Result, error) {
 	result := &Result{}
 	if err := json.Unmarshal(data, result); err != nil {
 		return nil, err
 	}
-	return result, nil
+	for _, v := range SupportedVersions {
+		if result.CNIVersion == v {
+			return result, nil
+		}
+	}
+	return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q",
+		SupportedVersions, result.CNIVersion)
 }
 
+// GetResult converts the given Result object to the ImplementedSpecVersion
+// and returns the concrete type or an error
 func GetResult(r types.Result) (*Result, error) {
-	// We expect version 0.1.0/0.2.0 results
-	result020, err := r.GetAsVersion(ImplementedSpecVersion)
+	result020, err := convert.Convert(r, ImplementedSpecVersion)
 	if err != nil {
 		return nil, err
 	}
@@ -51,6 +67,32 @@ func GetResult(r types.Result) (*Result, error) {
 	return result, nil
 }
 
+func convertFrom010(from types.Result, toVersion string) (types.Result, error) {
+	if toVersion != "0.2.0" {
+		panic("only converts to version 0.2.0")
+	}
+	fromResult := from.(*Result)
+	return &Result{
+		CNIVersion: ImplementedSpecVersion,
+		IP4:        fromResult.IP4.Copy(),
+		IP6:        fromResult.IP6.Copy(),
+		DNS:        *fromResult.DNS.Copy(),
+	}, nil
+}
+
+func convertTo010(from types.Result, toVersion string) (types.Result, error) {
+	if toVersion != "0.1.0" {
+		panic("only converts to version 0.1.0")
+	}
+	fromResult := from.(*Result)
+	return &Result{
+		CNIVersion: "0.1.0",
+		IP4:        fromResult.IP4.Copy(),
+		IP6:        fromResult.IP6.Copy(),
+		DNS:        *fromResult.DNS.Copy(),
+	}, nil
+}
+
 // Result is what gets returned from the plugin (via stdout) to the caller
 type Result struct {
 	CNIVersion string    `json:"cniVersion,omitempty"`
@@ -60,17 +102,16 @@ type Result struct {
 }
 
 func (r *Result) Version() string {
-	return ImplementedSpecVersion
+	return r.CNIVersion
 }
 
 func (r *Result) GetAsVersion(version string) (types.Result, error) {
-	for _, supportedVersion := range SupportedVersions {
-		if version == supportedVersion {
-			r.CNIVersion = version
-			return r, nil
-		}
+	// If the creator of the result did not set the CNIVersion, assume it
+	// should be the highest spec version implemented by this Result
+	if r.CNIVersion == "" {
+		r.CNIVersion = ImplementedSpecVersion
 	}
-	return nil, fmt.Errorf("cannot convert version %q to %s", SupportedVersions, version)
+	return convert.Convert(r, version)
 }
 
 func (r *Result) Print() error {
@@ -93,6 +134,22 @@ type IPConfig struct {
 	Routes  []types.Route
 }
 
+func (i *IPConfig) Copy() *IPConfig {
+	if i == nil {
+		return nil
+	}
+
+	var routes []types.Route
+	for _, fromRoute := range i.Routes {
+		routes = append(routes, *fromRoute.Copy())
+	}
+	return &IPConfig{
+		IP:      i.IP,
+		Gateway: i.Gateway,
+		Routes:  routes,
+	}
+}
+
 // net.IPNet is not JSON (un)marshallable so this duality is needed
 // for our custom IPNet type
 
diff --git a/pkg/types/040/types.go b/pkg/types/040/types.go
new file mode 100644
index 0000000..97566dc
--- /dev/null
+++ b/pkg/types/040/types.go
@@ -0,0 +1,302 @@
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package types040
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net"
+	"os"
+
+	"github.com/containernetworking/cni/pkg/types"
+	types020 "github.com/containernetworking/cni/pkg/types/020"
+	convert "github.com/containernetworking/cni/pkg/types/internal"
+)
+
+const ImplementedSpecVersion string = "0.4.0"
+
+var SupportedVersions = []string{"0.3.0", "0.3.1", ImplementedSpecVersion}
+
+// Register converters for all versions less than the implemented spec version
+func init() {
+	// Up-converters
+	convert.Register("0.1.0", SupportedVersions, convertFrom02x)
+	convert.Register("0.2.0", SupportedVersions, convertFrom02x)
+	convert.Register("0.3.0", SupportedVersions, convertInternal)
+	convert.Register("0.3.1", SupportedVersions, convertInternal)
+
+	// Down-converters
+	convert.Register("0.4.0", []string{"0.3.0", "0.3.1"}, convertInternal)
+	convert.Register("0.4.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
+	convert.Register("0.3.1", []string{"0.1.0", "0.2.0"}, convertTo02x)
+	convert.Register("0.3.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
+}
+
+func NewResult(data []byte) (types.Result, error) {
+	result := &Result{}
+	if err := json.Unmarshal(data, result); err != nil {
+		return nil, err
+	}
+	for _, v := range SupportedVersions {
+		if result.CNIVersion == v {
+			return result, nil
+		}
+	}
+	return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q",
+		SupportedVersions, result.CNIVersion)
+}
+
+func GetResult(r types.Result) (*Result, error) {
+	resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
+	if err != nil {
+		return nil, err
+	}
+	result, ok := resultCurrent.(*Result)
+	if !ok {
+		return nil, fmt.Errorf("failed to convert result")
+	}
+	return result, nil
+}
+
+func NewResultFromResult(result types.Result) (*Result, error) {
+	newResult, err := convert.Convert(result, ImplementedSpecVersion)
+	if err != nil {
+		return nil, err
+	}
+	return newResult.(*Result), nil
+}
+
+// Result is what gets returned from the plugin (via stdout) to the caller
+type Result struct {
+	CNIVersion string         `json:"cniVersion,omitempty"`
+	Interfaces []*Interface   `json:"interfaces,omitempty"`
+	IPs        []*IPConfig    `json:"ips,omitempty"`
+	Routes     []*types.Route `json:"routes,omitempty"`
+	DNS        types.DNS      `json:"dns,omitempty"`
+}
+
+func convert020IPConfig(from *types020.IPConfig, ipVersion string) *IPConfig {
+	return &IPConfig{
+		Version: ipVersion,
+		Address: from.IP,
+		Gateway: from.Gateway,
+	}
+}
+
+func convertFrom02x(from types.Result, toVersion string) (types.Result, error) {
+	fromResult := from.(*types020.Result)
+	toResult := &Result{
+		CNIVersion: toVersion,
+		DNS:        *fromResult.DNS.Copy(),
+		Routes:     []*types.Route{},
+	}
+	if fromResult.IP4 != nil {
+		toResult.IPs = append(toResult.IPs, convert020IPConfig(fromResult.IP4, "4"))
+		for _, fromRoute := range fromResult.IP4.Routes {
+			toResult.Routes = append(toResult.Routes, fromRoute.Copy())
+		}
+	}
+
+	if fromResult.IP6 != nil {
+		toResult.IPs = append(toResult.IPs, convert020IPConfig(fromResult.IP6, "6"))
+		for _, fromRoute := range fromResult.IP6.Routes {
+			toResult.Routes = append(toResult.Routes, fromRoute.Copy())
+		}
+	}
+
+	return toResult, nil
+}
+
+func convertInternal(from types.Result, toVersion string) (types.Result, error) {
+	fromResult := from.(*Result)
+	toResult := &Result{
+		CNIVersion: toVersion,
+		DNS:        *fromResult.DNS.Copy(),
+		Routes:     []*types.Route{},
+	}
+	for _, fromIntf := range fromResult.Interfaces {
+		toResult.Interfaces = append(toResult.Interfaces, fromIntf.Copy())
+	}
+	for _, fromIPC := range fromResult.IPs {
+		toResult.IPs = append(toResult.IPs, fromIPC.Copy())
+	}
+	for _, fromRoute := range fromResult.Routes {
+		toResult.Routes = append(toResult.Routes, fromRoute.Copy())
+	}
+	return toResult, nil
+}
+
+func convertTo02x(from types.Result, toVersion string) (types.Result, error) {
+	fromResult := from.(*Result)
+	toResult := &types020.Result{
+		CNIVersion: toVersion,
+		DNS:        *fromResult.DNS.Copy(),
+	}
+
+	for _, fromIP := range fromResult.IPs {
+		// Only convert the first IP address of each version as 0.2.0
+		// and earlier cannot handle multiple IP addresses
+		if fromIP.Version == "4" && toResult.IP4 == nil {
+			toResult.IP4 = &types020.IPConfig{
+				IP:      fromIP.Address,
+				Gateway: fromIP.Gateway,
+			}
+		} else if fromIP.Version == "6" && toResult.IP6 == nil {
+			toResult.IP6 = &types020.IPConfig{
+				IP:      fromIP.Address,
+				Gateway: fromIP.Gateway,
+			}
+		}
+		if toResult.IP4 != nil && toResult.IP6 != nil {
+			break
+		}
+	}
+
+	for _, fromRoute := range fromResult.Routes {
+		is4 := fromRoute.Dst.IP.To4() != nil
+		if is4 && toResult.IP4 != nil {
+			toResult.IP4.Routes = append(toResult.IP4.Routes, types.Route{
+				Dst: fromRoute.Dst,
+				GW:  fromRoute.GW,
+			})
+		} else if !is4 && toResult.IP6 != nil {
+			toResult.IP6.Routes = append(toResult.IP6.Routes, types.Route{
+				Dst: fromRoute.Dst,
+				GW:  fromRoute.GW,
+			})
+		}
+	}
+
+	if toResult.IP4 == nil && toResult.IP6 == nil {
+		return nil, fmt.Errorf("cannot convert: no valid IP addresses")
+	}
+
+	return toResult, nil
+}
+
+func (r *Result) Version() string {
+	return r.CNIVersion
+}
+
+func (r *Result) GetAsVersion(version string) (types.Result, error) {
+	// If the creator of the result did not set the CNIVersion, assume it
+	// should be the highest spec version implemented by this Result
+	if r.CNIVersion == "" {
+		r.CNIVersion = ImplementedSpecVersion
+	}
+	return convert.Convert(r, version)
+}
+
+func (r *Result) Print() error {
+	return r.PrintTo(os.Stdout)
+}
+
+func (r *Result) PrintTo(writer io.Writer) error {
+	data, err := json.MarshalIndent(r, "", "    ")
+	if err != nil {
+		return err
+	}
+	_, err = writer.Write(data)
+	return err
+}
+
+// Interface contains values about the created interfaces
+type Interface struct {
+	Name    string `json:"name"`
+	Mac     string `json:"mac,omitempty"`
+	Sandbox string `json:"sandbox,omitempty"`
+}
+
+func (i *Interface) String() string {
+	return fmt.Sprintf("%+v", *i)
+}
+
+func (i *Interface) Copy() *Interface {
+	if i == nil {
+		return nil
+	}
+	newIntf := *i
+	return &newIntf
+}
+
+// Int returns a pointer to the int value passed in.  Used to
+// set the IPConfig.Interface field.
+func Int(v int) *int {
+	return &v
+}
+
+// IPConfig contains values necessary to configure an IP address on an interface
+type IPConfig struct {
+	// IP version, either "4" or "6"
+	Version string
+	// Index into Result structs Interfaces list
+	Interface *int
+	Address   net.IPNet
+	Gateway   net.IP
+}
+
+func (i *IPConfig) String() string {
+	return fmt.Sprintf("%+v", *i)
+}
+
+func (i *IPConfig) Copy() *IPConfig {
+	if i == nil {
+		return nil
+	}
+
+	ipc := &IPConfig{
+		Version: i.Version,
+		Address: i.Address,
+		Gateway: i.Gateway,
+	}
+	if i.Interface != nil {
+		intf := *i.Interface
+		ipc.Interface = &intf
+	}
+	return ipc
+}
+
+// JSON (un)marshallable types
+type ipConfig struct {
+	Version   string      `json:"version"`
+	Interface *int        `json:"interface,omitempty"`
+	Address   types.IPNet `json:"address"`
+	Gateway   net.IP      `json:"gateway,omitempty"`
+}
+
+func (c *IPConfig) MarshalJSON() ([]byte, error) {
+	ipc := ipConfig{
+		Version:   c.Version,
+		Interface: c.Interface,
+		Address:   types.IPNet(c.Address),
+		Gateway:   c.Gateway,
+	}
+
+	return json.Marshal(ipc)
+}
+
+func (c *IPConfig) UnmarshalJSON(data []byte) error {
+	ipc := ipConfig{}
+	if err := json.Unmarshal(data, &ipc); err != nil {
+		return err
+	}
+
+	c.Version = ipc.Version
+	c.Interface = ipc.Interface
+	c.Address = net.IPNet(ipc.Address)
+	c.Gateway = ipc.Gateway
+	return nil
+}
diff --git a/pkg/types/100/types.go b/pkg/types/100/types.go
new file mode 100644
index 0000000..cc9eee1
--- /dev/null
+++ b/pkg/types/100/types.go
@@ -0,0 +1,304 @@
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package types100
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net"
+	"os"
+
+	"github.com/containernetworking/cni/pkg/types"
+	types040 "github.com/containernetworking/cni/pkg/types/040"
+	convert "github.com/containernetworking/cni/pkg/types/internal"
+)
+
+const ImplementedSpecVersion string = "1.0.0"
+
+var SupportedVersions = []string{ImplementedSpecVersion}
+
+// Register converters for all versions less than the implemented spec version
+func init() {
+	// Up-converters
+	convert.Register("0.1.0", SupportedVersions, convertFrom02x)
+	convert.Register("0.2.0", SupportedVersions, convertFrom02x)
+	convert.Register("0.3.0", SupportedVersions, convertFrom04x)
+	convert.Register("0.3.1", SupportedVersions, convertFrom04x)
+	convert.Register("0.4.0", SupportedVersions, convertFrom04x)
+
+	// Down-converters
+	convert.Register("1.0.0", []string{"0.3.0", "0.3.1", "0.4.0"}, convertTo04x)
+	convert.Register("1.0.0", []string{"0.1.0", "0.2.0"}, convertTo02x)
+}
+
+func NewResult(data []byte) (types.Result, error) {
+	result := &Result{}
+	if err := json.Unmarshal(data, result); err != nil {
+		return nil, err
+	}
+	for _, v := range SupportedVersions {
+		if result.CNIVersion == v {
+			return result, nil
+		}
+	}
+	return nil, fmt.Errorf("result type supports %v but unmarshalled CNIVersion is %q",
+		SupportedVersions, result.CNIVersion)
+}
+
+func GetResult(r types.Result) (*Result, error) {
+	resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
+	if err != nil {
+		return nil, err
+	}
+	result, ok := resultCurrent.(*Result)
+	if !ok {
+		return nil, fmt.Errorf("failed to convert result")
+	}
+	return result, nil
+}
+
+func NewResultFromResult(result types.Result) (*Result, error) {
+	newResult, err := convert.Convert(result, ImplementedSpecVersion)
+	if err != nil {
+		return nil, err
+	}
+	return newResult.(*Result), nil
+}
+
+// Result is what gets returned from the plugin (via stdout) to the caller
+type Result struct {
+	CNIVersion string         `json:"cniVersion,omitempty"`
+	Interfaces []*Interface   `json:"interfaces,omitempty"`
+	IPs        []*IPConfig    `json:"ips,omitempty"`
+	Routes     []*types.Route `json:"routes,omitempty"`
+	DNS        types.DNS      `json:"dns,omitempty"`
+}
+
+func convertFrom02x(from types.Result, toVersion string) (types.Result, error) {
+	result040, err := convert.Convert(from, "0.4.0")
+	if err != nil {
+		return nil, err
+	}
+	result100, err := convertFrom04x(result040, ImplementedSpecVersion)
+	if err != nil {
+		return nil, err
+	}
+	return result100, nil
+}
+
+func convertIPConfigFrom040(from *types040.IPConfig) *IPConfig {
+	to := &IPConfig{
+		Address: from.Address,
+		Gateway: from.Gateway,
+	}
+	if from.Interface != nil {
+		intf := *from.Interface
+		to.Interface = &intf
+	}
+	return to
+}
+
+func convertInterfaceFrom040(from *types040.Interface) *Interface {
+	return &Interface{
+		Name:    from.Name,
+		Mac:     from.Mac,
+		Sandbox: from.Sandbox,
+	}
+}
+
+func convertFrom04x(from types.Result, toVersion string) (types.Result, error) {
+	fromResult := from.(*types040.Result)
+	toResult := &Result{
+		CNIVersion: toVersion,
+		DNS:        *fromResult.DNS.Copy(),
+		Routes:     []*types.Route{},
+	}
+	for _, fromIntf := range fromResult.Interfaces {
+		toResult.Interfaces = append(toResult.Interfaces, convertInterfaceFrom040(fromIntf))
+	}
+	for _, fromIPC := range fromResult.IPs {
+		toResult.IPs = append(toResult.IPs, convertIPConfigFrom040(fromIPC))
+	}
+	for _, fromRoute := range fromResult.Routes {
+		toResult.Routes = append(toResult.Routes, fromRoute.Copy())
+	}
+	return toResult, nil
+}
+
+func convertIPConfigTo040(from *IPConfig) *types040.IPConfig {
+	version := "6"
+	if from.Address.IP.To4() != nil {
+		version = "4"
+	}
+	to := &types040.IPConfig{
+		Version: version,
+		Address: from.Address,
+		Gateway: from.Gateway,
+	}
+	if from.Interface != nil {
+		intf := *from.Interface
+		to.Interface = &intf
+	}
+	return to
+}
+
+func convertInterfaceTo040(from *Interface) *types040.Interface {
+	return &types040.Interface{
+		Name:    from.Name,
+		Mac:     from.Mac,
+		Sandbox: from.Sandbox,
+	}
+}
+
+func convertTo04x(from types.Result, toVersion string) (types.Result, error) {
+	fromResult := from.(*Result)
+	toResult := &types040.Result{
+		CNIVersion: toVersion,
+		DNS:        *fromResult.DNS.Copy(),
+		Routes:     []*types.Route{},
+	}
+	for _, fromIntf := range fromResult.Interfaces {
+		toResult.Interfaces = append(toResult.Interfaces, convertInterfaceTo040(fromIntf))
+	}
+	for _, fromIPC := range fromResult.IPs {
+		toResult.IPs = append(toResult.IPs, convertIPConfigTo040(fromIPC))
+	}
+	for _, fromRoute := range fromResult.Routes {
+		toResult.Routes = append(toResult.Routes, fromRoute.Copy())
+	}
+	return toResult, nil
+}
+
+func convertTo02x(from types.Result, toVersion string) (types.Result, error) {
+	// First convert to 0.4.0
+	result040, err := convertTo04x(from, "0.4.0")
+	if err != nil {
+		return nil, err
+	}
+	result02x, err := convert.Convert(result040, toVersion)
+	if err != nil {
+		return nil, err
+	}
+	return result02x, nil
+}
+
+func (r *Result) Version() string {
+	return r.CNIVersion
+}
+
+func (r *Result) GetAsVersion(version string) (types.Result, error) {
+	// If the creator of the result did not set the CNIVersion, assume it
+	// should be the highest spec version implemented by this Result
+	if r.CNIVersion == "" {
+		r.CNIVersion = ImplementedSpecVersion
+	}
+	return convert.Convert(r, version)
+}
+
+func (r *Result) Print() error {
+	return r.PrintTo(os.Stdout)
+}
+
+func (r *Result) PrintTo(writer io.Writer) error {
+	data, err := json.MarshalIndent(r, "", "    ")
+	if err != nil {
+		return err
+	}
+	_, err = writer.Write(data)
+	return err
+}
+
+// Interface contains values about the created interfaces
+type Interface struct {
+	Name    string `json:"name"`
+	Mac     string `json:"mac,omitempty"`
+	Sandbox string `json:"sandbox,omitempty"`
+}
+
+func (i *Interface) String() string {
+	return fmt.Sprintf("%+v", *i)
+}
+
+func (i *Interface) Copy() *Interface {
+	if i == nil {
+		return nil
+	}
+	newIntf := *i
+	return &newIntf
+}
+
+// Int returns a pointer to the int value passed in.  Used to
+// set the IPConfig.Interface field.
+func Int(v int) *int {
+	return &v
+}
+
+// IPConfig contains values necessary to configure an IP address on an interface
+type IPConfig struct {
+	// Index into Result structs Interfaces list
+	Interface *int
+	Address   net.IPNet
+	Gateway   net.IP
+}
+
+func (i *IPConfig) String() string {
+	return fmt.Sprintf("%+v", *i)
+}
+
+func (i *IPConfig) Copy() *IPConfig {
+	if i == nil {
+		return nil
+	}
+
+	ipc := &IPConfig{
+		Address: i.Address,
+		Gateway: i.Gateway,
+	}
+	if i.Interface != nil {
+		intf := *i.Interface
+		ipc.Interface = &intf
+	}
+	return ipc
+}
+
+// JSON (un)marshallable types
+type ipConfig struct {
+	Interface *int        `json:"interface,omitempty"`
+	Address   types.IPNet `json:"address"`
+	Gateway   net.IP      `json:"gateway,omitempty"`
+}
+
+func (c *IPConfig) MarshalJSON() ([]byte, error) {
+	ipc := ipConfig{
+		Interface: c.Interface,
+		Address:   types.IPNet(c.Address),
+		Gateway:   c.Gateway,
+	}
+
+	return json.Marshal(ipc)
+}
+
+func (c *IPConfig) UnmarshalJSON(data []byte) error {
+	ipc := ipConfig{}
+	if err := json.Unmarshal(data, &ipc); err != nil {
+		return err
+	}
+
+	c.Interface = ipc.Interface
+	c.Address = net.IPNet(ipc.Address)
+	c.Gateway = ipc.Gateway
+	return nil
+}
diff --git a/pkg/types/current/types.go b/pkg/types/current/types.go
deleted file mode 100644
index 754cc6e..0000000
--- a/pkg/types/current/types.go
+++ /dev/null
@@ -1,276 +0,0 @@
-// Copyright 2016 CNI authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package current
-
-import (
-	"encoding/json"
-	"fmt"
-	"io"
-	"net"
-	"os"
-
-	"github.com/containernetworking/cni/pkg/types"
-	"github.com/containernetworking/cni/pkg/types/020"
-)
-
-const ImplementedSpecVersion string = "0.4.0"
-
-var SupportedVersions = []string{"0.3.0", "0.3.1", ImplementedSpecVersion}
-
-func NewResult(data []byte) (types.Result, error) {
-	result := &Result{}
-	if err := json.Unmarshal(data, result); err != nil {
-		return nil, err
-	}
-	return result, nil
-}
-
-func GetResult(r types.Result) (*Result, error) {
-	resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
-	if err != nil {
-		return nil, err
-	}
-	result, ok := resultCurrent.(*Result)
-	if !ok {
-		return nil, fmt.Errorf("failed to convert result")
-	}
-	return result, nil
-}
-
-var resultConverters = []struct {
-	versions []string
-	convert  func(types.Result) (*Result, error)
-}{
-	{types020.SupportedVersions, convertFrom020},
-	{SupportedVersions, convertFrom030},
-}
-
-func convertFrom020(result types.Result) (*Result, error) {
-	oldResult, err := types020.GetResult(result)
-	if err != nil {
-		return nil, err
-	}
-
-	newResult := &Result{
-		CNIVersion: ImplementedSpecVersion,
-		DNS:        oldResult.DNS,
-		Routes:     []*types.Route{},
-	}
-
-	if oldResult.IP4 != nil {
-		newResult.IPs = append(newResult.IPs, &IPConfig{
-			Version: "4",
-			Address: oldResult.IP4.IP,
-			Gateway: oldResult.IP4.Gateway,
-		})
-		for _, route := range oldResult.IP4.Routes {
-			newResult.Routes = append(newResult.Routes, &types.Route{
-				Dst: route.Dst,
-				GW:  route.GW,
-			})
-		}
-	}
-
-	if oldResult.IP6 != nil {
-		newResult.IPs = append(newResult.IPs, &IPConfig{
-			Version: "6",
-			Address: oldResult.IP6.IP,
-			Gateway: oldResult.IP6.Gateway,
-		})
-		for _, route := range oldResult.IP6.Routes {
-			newResult.Routes = append(newResult.Routes, &types.Route{
-				Dst: route.Dst,
-				GW:  route.GW,
-			})
-		}
-	}
-
-	return newResult, nil
-}
-
-func convertFrom030(result types.Result) (*Result, error) {
-	newResult, ok := result.(*Result)
-	if !ok {
-		return nil, fmt.Errorf("failed to convert result")
-	}
-	newResult.CNIVersion = ImplementedSpecVersion
-	return newResult, nil
-}
-
-func NewResultFromResult(result types.Result) (*Result, error) {
-	version := result.Version()
-	for _, converter := range resultConverters {
-		for _, supportedVersion := range converter.versions {
-			if version == supportedVersion {
-				return converter.convert(result)
-			}
-		}
-	}
-	return nil, fmt.Errorf("unsupported CNI result22 version %q", version)
-}
-
-// Result is what gets returned from the plugin (via stdout) to the caller
-type Result struct {
-	CNIVersion string         `json:"cniVersion,omitempty"`
-	Interfaces []*Interface   `json:"interfaces,omitempty"`
-	IPs        []*IPConfig    `json:"ips,omitempty"`
-	Routes     []*types.Route `json:"routes,omitempty"`
-	DNS        types.DNS      `json:"dns,omitempty"`
-}
-
-// Convert to the older 0.2.0 CNI spec Result type
-func (r *Result) convertTo020() (*types020.Result, error) {
-	oldResult := &types020.Result{
-		CNIVersion: types020.ImplementedSpecVersion,
-		DNS:        r.DNS,
-	}
-
-	for _, ip := range r.IPs {
-		// Only convert the first IP address of each version as 0.2.0
-		// and earlier cannot handle multiple IP addresses
-		if ip.Version == "4" && oldResult.IP4 == nil {
-			oldResult.IP4 = &types020.IPConfig{
-				IP:      ip.Address,
-				Gateway: ip.Gateway,
-			}
-		} else if ip.Version == "6" && oldResult.IP6 == nil {
-			oldResult.IP6 = &types020.IPConfig{
-				IP:      ip.Address,
-				Gateway: ip.Gateway,
-			}
-		}
-
-		if oldResult.IP4 != nil && oldResult.IP6 != nil {
-			break
-		}
-	}
-
-	for _, route := range r.Routes {
-		is4 := route.Dst.IP.To4() != nil
-		if is4 && oldResult.IP4 != nil {
-			oldResult.IP4.Routes = append(oldResult.IP4.Routes, types.Route{
-				Dst: route.Dst,
-				GW:  route.GW,
-			})
-		} else if !is4 && oldResult.IP6 != nil {
-			oldResult.IP6.Routes = append(oldResult.IP6.Routes, types.Route{
-				Dst: route.Dst,
-				GW:  route.GW,
-			})
-		}
-	}
-
-	if oldResult.IP4 == nil && oldResult.IP6 == nil {
-		return nil, fmt.Errorf("cannot convert: no valid IP addresses")
-	}
-
-	return oldResult, nil
-}
-
-func (r *Result) Version() string {
-	return ImplementedSpecVersion
-}
-
-func (r *Result) GetAsVersion(version string) (types.Result, error) {
-	switch version {
-	case "0.3.0", "0.3.1", ImplementedSpecVersion:
-		r.CNIVersion = version
-		return r, nil
-	case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
-		return r.convertTo020()
-	}
-	return nil, fmt.Errorf("cannot convert version 0.3.x to %q", version)
-}
-
-func (r *Result) Print() error {
-	return r.PrintTo(os.Stdout)
-}
-
-func (r *Result) PrintTo(writer io.Writer) error {
-	data, err := json.MarshalIndent(r, "", "    ")
-	if err != nil {
-		return err
-	}
-	_, err = writer.Write(data)
-	return err
-}
-
-// Convert this old version result to the current CNI version result
-func (r *Result) Convert() (*Result, error) {
-	return r, nil
-}
-
-// Interface contains values about the created interfaces
-type Interface struct {
-	Name    string `json:"name"`
-	Mac     string `json:"mac,omitempty"`
-	Sandbox string `json:"sandbox,omitempty"`
-}
-
-func (i *Interface) String() string {
-	return fmt.Sprintf("%+v", *i)
-}
-
-// Int returns a pointer to the int value passed in.  Used to
-// set the IPConfig.Interface field.
-func Int(v int) *int {
-	return &v
-}
-
-// IPConfig contains values necessary to configure an IP address on an interface
-type IPConfig struct {
-	// IP version, either "4" or "6"
-	Version string
-	// Index into Result structs Interfaces list
-	Interface *int
-	Address   net.IPNet
-	Gateway   net.IP
-}
-
-func (i *IPConfig) String() string {
-	return fmt.Sprintf("%+v", *i)
-}
-
-// JSON (un)marshallable types
-type ipConfig struct {
-	Version   string      `json:"version"`
-	Interface *int        `json:"interface,omitempty"`
-	Address   types.IPNet `json:"address"`
-	Gateway   net.IP      `json:"gateway,omitempty"`
-}
-
-func (c *IPConfig) MarshalJSON() ([]byte, error) {
-	ipc := ipConfig{
-		Version:   c.Version,
-		Interface: c.Interface,
-		Address:   types.IPNet(c.Address),
-		Gateway:   c.Gateway,
-	}
-
-	return json.Marshal(ipc)
-}
-
-func (c *IPConfig) UnmarshalJSON(data []byte) error {
-	ipc := ipConfig{}
-	if err := json.Unmarshal(data, &ipc); err != nil {
-		return err
-	}
-
-	c.Version = ipc.Version
-	c.Interface = ipc.Interface
-	c.Address = net.IPNet(ipc.Address)
-	c.Gateway = ipc.Gateway
-	return nil
-}
diff --git a/pkg/types/internal/convert.go b/pkg/types/internal/convert.go
new file mode 100644
index 0000000..442c11f
--- /dev/null
+++ b/pkg/types/internal/convert.go
@@ -0,0 +1,88 @@
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package convert
+
+import (
+	"fmt"
+
+	"github.com/containernetworking/cni/pkg/types"
+)
+
+// ConvertFn should convert from the given arbitrary Result type into a
+// Result implementing CNI specification version passed in toVersion.
+// The function is guaranteed to be passed a Result type matching the
+// fromVersion it was registered with, and is guaranteed to be
+// passed a toVersion matching one of the toVersions it was registered with.
+type ConvertFn func(from types.Result, toVersion string) (types.Result, error)
+
+type converter struct {
+	// fromVersion is the CNI Result spec version that convertFn accepts
+	fromVersion string
+	// toVersions is a list of versions that convertFn can convert to
+	toVersions []string
+	convertFn  ConvertFn
+}
+
+var converters []*converter
+
+func findConverter(fromVersion, toVersion string) *converter {
+	for _, c := range converters {
+		if c.fromVersion == fromVersion {
+			for _, v := range c.toVersions {
+				if v == toVersion {
+					return c
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// Convert converts a CNI Result to the requested CNI specification version,
+// or returns an error if the converstion could not be performed or failed
+func Convert(from types.Result, toVersion string) (types.Result, error) {
+	fromVersion := from.Version()
+
+	// Shortcut for same version
+	if fromVersion == toVersion {
+		return from, nil
+	}
+
+	// Otherwise find the right converter
+	c := findConverter(fromVersion, toVersion)
+	if c == nil {
+		return nil, fmt.Errorf("no converter for CNI result version %s to %s",
+			fromVersion, toVersion)
+	}
+	return c.convertFn(from, toVersion)
+}
+
+// Register registers a CNI Result converter. SHOULD NOT BE CALLED
+// EXCEPT FROM CNI ITSELF.
+func Register(fromVersion string, toVersions []string, convertFn ConvertFn) {
+	// Make sure there is no converter already registered for these
+	// from and to versions
+	for _, v := range toVersions {
+		if findConverter(fromVersion, v) != nil {
+			panic(fmt.Sprintf("converter already registered for %s to %s",
+				fromVersion, v))
+		}
+	}
+	converters = append(converters, &converter{
+		fromVersion: fromVersion,
+		toVersions:  toVersions,
+		convertFn:   convertFn,
+	})
+}
diff --git a/pkg/types/types.go b/pkg/types/types.go
index 3fa757a..56ec9ec 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -118,6 +118,24 @@ type DNS struct {
 	Options     []string `json:"options,omitempty"`
 }
 
+func (d *DNS) Copy() *DNS {
+	if d == nil {
+		return nil
+	}
+
+	to := &DNS{Domain: d.Domain}
+	for _, ns := range d.Nameservers {
+		to.Nameservers = append(to.Nameservers, ns)
+	}
+	for _, s := range d.Search {
+		to.Search = append(to.Search, s)
+	}
+	for _, o := range d.Options {
+		to.Options = append(to.Options, o)
+	}
+	return to
+}
+
 type Route struct {
 	Dst net.IPNet
 	GW  net.IP
@@ -127,6 +145,17 @@ func (r *Route) String() string {
 	return fmt.Sprintf("%+v", *r)
 }
 
+func (r *Route) Copy() *Route {
+	if r == nil {
+		return nil
+	}
+
+	return &Route{
+		Dst: r.Dst,
+		GW:  r.GW,
+	}
+}
+
 // Well known error codes
 // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
 const (
diff --git a/pkg/version/version.go b/pkg/version/version.go
index 8f3508e..88b37e7 100644
--- a/pkg/version/version.go
+++ b/pkg/version/version.go
@@ -20,12 +20,13 @@ import (
 
 	"github.com/containernetworking/cni/pkg/types"
 	"github.com/containernetworking/cni/pkg/types/020"
-	"github.com/containernetworking/cni/pkg/types/current"
+	"github.com/containernetworking/cni/pkg/types/040"
+	current "github.com/containernetworking/cni/pkg/types/100"
 )
 
 // Current reports the version of the CNI spec implemented by this library
 func Current() string {
-	return "0.4.0"
+	return current.ImplementedSpecVersion
 }
 
 // Legacy PluginInfo describes a plugin that is backwards compatible with the
@@ -36,13 +37,14 @@ func Current() string {
 // Any future CNI spec versions which meet this definition should be added to
 // this list.
 var Legacy = PluginSupports("0.1.0", "0.2.0")
-var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0")
+var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0")
 
 var resultFactories = []struct {
 	supportedVersions []string
 	newResult         types.ResultFactoryFunc
 }{
 	{current.SupportedVersions, current.NewResult},
+	{types040.SupportedVersions, types040.NewResult},
 	{types020.SupportedVersions, types020.NewResult},
 }
 
@@ -68,6 +70,13 @@ func ParsePrevResult(conf *types.NetConf) error {
 		return nil
 	}
 
+	// Prior to 1.0.0, Result types may not marshal a CNIVersion. Since the
+	// result version must match the config version, if the Result's version
+	// is empty, inject the config version.
+	if ver, ok := conf.RawPrevResult["CNIVersion"]; !ok || ver == "" {
+		conf.RawPrevResult["CNIVersion"] = conf.CNIVersion
+	}
+
 	resultBytes, err := json.Marshal(conf.RawPrevResult)
 	if err != nil {
 		return fmt.Errorf("could not serialize prevResult: %v", err)


Test patch:
diff --git a/libcni/api_test.go b/libcni/api_test.go
index 60025f2..7fbaa10 100644
--- a/libcni/api_test.go
+++ b/libcni/api_test.go
@@ -30,7 +30,7 @@ import (
 	"github.com/containernetworking/cni/libcni"
 	"github.com/containernetworking/cni/pkg/skel"
 	"github.com/containernetworking/cni/pkg/types"
-	"github.com/containernetworking/cni/pkg/types/current"
+	current "github.com/containernetworking/cni/pkg/types/100"
 	noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
 
 	. "github.com/onsi/ginkgo"
@@ -59,7 +59,7 @@ func stringInList(s string, list []string) bool {
 	return false
 }
 
-func newPluginInfo(configValue, prevResult string, injectDebugFilePath bool, result string, runtimeConfig map[string]interface{}, capabilities []string) pluginInfo {
+func newPluginInfo(cniVersion, configValue, prevResult string, injectDebugFilePath bool, result string, runtimeConfig map[string]interface{}, capabilities []string) pluginInfo {
 	debugFile, err := ioutil.TempFile("", "cni_debug")
 	Expect(err).NotTo(HaveOccurred())
 	Expect(debugFile.Close()).To(Succeed())
@@ -97,7 +97,7 @@ func newPluginInfo(configValue, prevResult string, injectDebugFilePath bool, res
 	err = json.Unmarshal([]byte(config), &newConfig)
 	Expect(err).NotTo(HaveOccurred())
 	newConfig["name"] = "some-list"
-	newConfig["cniVersion"] = current.ImplementedSpecVersion
+	newConfig["cniVersion"] = cniVersion
 
 	// Only include standard runtime config and capability args that this plugin advertises
 	newRuntimeConfig := make(map[string]interface{})
@@ -121,6 +121,27 @@ func newPluginInfo(configValue, prevResult string, injectDebugFilePath bool, res
 	}
 }
 
+func makePluginList(cniVersion, ipResult string, runtimeConfig map[string]interface{}) (*libcni.NetworkConfigList, []pluginInfo) {
+	plugins := make([]pluginInfo, 3)
+	plugins[0] = newPluginInfo(cniVersion, "some-value", "", true, ipResult, runtimeConfig, []string{"portMappings", "otherCapability"})
+	plugins[1] = newPluginInfo(cniVersion, "some-other-value", ipResult, true, "PASSTHROUGH", runtimeConfig, []string{"otherCapability"})
+	plugins[2] = newPluginInfo(cniVersion, "yet-another-value", ipResult, true, "INJECT-DNS", runtimeConfig, []string{})
+
+	configList := []byte(fmt.Sprintf(`{
+"name": "some-list",
+"cniVersion": "%s",
+"plugins": [
+%s,
+%s,
+%s
+]
+}`, cniVersion, plugins[0].config, plugins[1].config, plugins[2].config))
+
+	netConfigList, err := libcni.ConfListFromBytes(configList)
+	Expect(err).NotTo(HaveOccurred())
+	return netConfigList, plugins
+}
+
 func resultCacheFilePath(cacheDirPath, netName string, rt *libcni.RuntimeConf) string {
 	fName := fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)
 	return filepath.Join(cacheDirPath, "results", fName)
@@ -157,7 +178,11 @@ var _ = Describe("Invoking plugins", func() {
 			debugFilePath = debugFile.Name()
 
 			debug = &noop_debug.Debug{
-				ReportResult: fmt.Sprintf(` { "result": %q }`, noop_debug.EmptyReportResultMessage),
+				ReportResult: `{
+					"cniVersion": "1.0.0",
+					"ips": [{"address": "10.1.2.3/24"}],
+					"dns": {}
+				}`,
 			}
 			Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
 
@@ -260,7 +285,6 @@ var _ = Describe("Invoking plugins", func() {
 		var (
 			debugFilePath string
 			debug         *noop_debug.Debug
-			cniBinPath    string
 			pluginConfig  string
 			cniConfig     *libcni.CNIConfig
 			netConfig     *libcni.NetworkConfig
@@ -278,8 +302,8 @@ var _ = Describe("Invoking plugins", func() {
 
 			debug = &noop_debug.Debug{
 				ReportResult: `{
-					"cniVersion": "0.4.0",
-					"ips": [{"version": "4", "address": "10.1.2.3/24"}],
+					"cniVersion": "1.0.0",
+					"ips": [{"address": "10.1.2.3/24"}],
 					"dns": {}
 				}`,
 			}
@@ -289,7 +313,7 @@ var _ = Describe("Invoking plugins", func() {
 				{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
 			}
 
-			cniBinPath = filepath.Dir(pluginPaths["noop"])
+			cniBinPath := filepath.Dir(pluginPaths["noop"])
 			pluginConfig = fmt.Sprintf(`{
 				"type": "noop",
 				"name": "apitest",
@@ -347,7 +371,6 @@ var _ = Describe("Invoking plugins", func() {
 					CNIVersion: current.ImplementedSpecVersion,
 					IPs: []*current.IPConfig{
 						{
-							Version: "4",
 							Address: net.IPNet{
 								IP:   net.ParseIP("10.1.2.3"),
 								Mask: net.IPv4Mask(255, 255, 255, 0),
@@ -433,8 +456,8 @@ var _ = Describe("Invoking plugins", func() {
 				err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
 				Expect(err).NotTo(HaveOccurred())
 				cachedJson := `{
-					"cniVersion": "0.4.0",
-					"ips": [{"version": "4", "address": "10.1.2.3/24"}],
+					"cniVersion": "1.0.0",
+					"ips": [{"address": "10.1.2.3/24"}],
 					"dns": {}
 				}`
 				err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
@@ -499,13 +522,12 @@ var _ = Describe("Invoking plugins", func() {
 				Context("less than 0.4.0", func() {
 					It("fails as CHECK is not supported before 0.4.0", func() {
 						// Generate plugin config with older version
-						pluginConfig = `{
+						var err error
+						netConfig, err = libcni.ConfFromBytes([]byte(`{
 							"type": "noop",
 							"name": "apitest",
 							"cniVersion": "0.3.1"
-						}`
-						var err error
-						netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
+						}`))
 						Expect(err).NotTo(HaveOccurred())
 						err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
 						Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the CHECK command"))
@@ -519,8 +541,8 @@ var _ = Describe("Invoking plugins", func() {
 				Context("containing only a cached result", func() {
 					It("only passes a prevResult to the plugin", func() {
 						err := ioutil.WriteFile(cacheFile, []byte(`{
-							"cniVersion": "0.4.0",
-							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
+							"cniVersion": "1.0.0",
+							"ips": [{"address": "10.1.2.3/24"}],
 							"dns": {}
 						}`), 0600)
 						Expect(err).NotTo(HaveOccurred())
@@ -537,13 +559,31 @@ var _ = Describe("Invoking plugins", func() {
 
 				Context("equal to 0.4.0", func() {
 					It("passes a prevResult to the plugin", func() {
-						err := ioutil.WriteFile(cacheFile, []byte(`{
+						ipResult := `{
 							"cniVersion": "0.4.0",
 							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
 							"dns": {}
-						}`), 0600)
+						}`
+
+						var err error
+						netConfig, err = libcni.ConfFromBytes([]byte(`{
+							"type": "noop",
+							"name": "apitest",
+							"cniVersion": "0.4.0",
+							"capabilities": {
+								"portMappings": true,
+								"somethingElse": true,
+								"noCapability": false
+							}
+						}`))
 						Expect(err).NotTo(HaveOccurred())
 
+						err = ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
+						Expect(err).NotTo(HaveOccurred())
+
+						debug.ReportResult = ipResult
+						Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
+
 						err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
 						Expect(err).NotTo(HaveOccurred())
 						debug, err := noop_debug.ReadDebug(debugFilePath)
@@ -606,8 +646,8 @@ var _ = Describe("Invoking plugins", func() {
 				err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
 				Expect(err).NotTo(HaveOccurred())
 				cachedJson := `{
-					"cniVersion": "0.4.0",
-					"ips": [{"version": "4", "address": "10.1.2.3/24"}],
+					"cniVersion": "1.0.0",
+					"ips": [{"address": "10.1.2.3/24"}],
 					"dns": {}
 				}`
 				err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
@@ -717,12 +757,11 @@ var _ = Describe("Invoking plugins", func() {
 						Expect(err).NotTo(HaveOccurred())
 
 						// Generate plugin config with older version
-						pluginConfig = `{
+						netConfig, err = libcni.ConfFromBytes([]byte(`{
 							"type": "noop",
 							"name": "apitest",
 							"cniVersion": "0.3.1"
-						}`
-						netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
+						}`))
 						Expect(err).NotTo(HaveOccurred())
 						err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
 						Expect(err).NotTo(HaveOccurred())
@@ -808,7 +847,7 @@ var _ = Describe("Invoking plugins", func() {
 
 				Expect(versionInfo).NotTo(BeNil())
 				Expect(versionInfo.SupportedVersions()).To(Equal([]string{
-					"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0",
+					"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0",
 				}))
 			})
 
@@ -852,6 +891,7 @@ var _ = Describe("Invoking plugins", func() {
 			cniConfig     *libcni.CNIConfig
 			netConfigList *libcni.NetworkConfigList
 			runtimeConfig *libcni.RuntimeConf
+			rcMap         map[string]interface{}
 			ctx           context.Context
 			ipResult      string
 
@@ -859,8 +899,6 @@ var _ = Describe("Invoking plugins", func() {
 		)
 
 		BeforeEach(func() {
-			var err error
-
 			capabilityArgs := map[string]interface{}{
 				"portMappings": []portMapping{
 					{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
@@ -886,7 +924,7 @@ var _ = Describe("Invoking plugins", func() {
 				Path:        cniBinPath,
 			}
 
-			rc := map[string]interface{}{
+			rcMap = map[string]interface{}{
 				"containerId": runtimeConfig.ContainerID,
 				"netNs":       runtimeConfig.NetNS,
 				"ifName":      runtimeConfig.IfName,
@@ -897,24 +935,9 @@ var _ = Describe("Invoking plugins", func() {
 				"otherCapability": capabilityArgs["otherCapability"],
 			}
 
-			ipResult = fmt.Sprintf(`{"cniVersion": "%s", "dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`, current.ImplementedSpecVersion)
-			plugins = make([]pluginInfo, 3)
-			plugins[0] = newPluginInfo("some-value", "", true, ipResult, rc, []string{"portMappings", "otherCapability"})
-			plugins[1] = newPluginInfo("some-other-value", ipResult, true, "PASSTHROUGH", rc, []string{"otherCapability"})
-			plugins[2] = newPluginInfo("yet-another-value", ipResult, true, "INJECT-DNS", rc, []string{})
-
-			configList := []byte(fmt.Sprintf(`{
-  "name": "some-list",
-  "cniVersion": "%s",
-  "plugins": [
-    %s,
-    %s,
-    %s
-  ]
-}`, current.ImplementedSpecVersion, plugins[0].config, plugins[1].config, plugins[2].config))
+			ipResult = fmt.Sprintf(`{"cniVersion": "%s", "dns":{},"ips":[{"address": "10.1.2.3/24"}]}`, current.ImplementedSpecVersion)
+			netConfigList, plugins = makePluginList(current.ImplementedSpecVersion, ipResult, rcMap)
 
-			netConfigList, err = libcni.ConfListFromBytes(configList)
-			Expect(err).NotTo(HaveOccurred())
 			ctx = context.TODO()
 		})
 
@@ -937,7 +960,6 @@ var _ = Describe("Invoking plugins", func() {
 					// IP4 added by first plugin
 					IPs: []*current.IPConfig{
 						{
-							Version: "4",
 							Address: net.IPNet{
 								IP:   net.ParseIP("10.1.2.3"),
 								Mask: net.IPv4Mask(255, 255, 255, 0),
@@ -1205,14 +1227,20 @@ var _ = Describe("Invoking plugins", func() {
 
 				Context("is 0.4.0", func() {
 					It("passes a cached result to the first plugin", func() {
-						cachedJson := `{
+						ipResult = `{
 							"cniVersion": "0.4.0",
 							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
 							"dns": {}
 						}`
-						err := ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
+						err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
 						Expect(err).NotTo(HaveOccurred())
 
+						netConfigList, plugins = makePluginList("0.4.0", ipResult, rcMap)
+						for _, p := range plugins {
+							p.debug.ReportResult = ipResult
+							Expect(p.debug.WriteDebug(p.debugFilePath)).To(Succeed())
+						}
+
 						err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
 						Expect(err).NotTo(HaveOccurred())
 
@@ -1225,7 +1253,7 @@ var _ = Describe("Invoking plugins", func() {
 						Expect(err).NotTo(HaveOccurred())
 						stdinPrevResult, err := json.Marshal(data["prevResult"])
 						Expect(err).NotTo(HaveOccurred())
-						Expect(stdinPrevResult).To(MatchJSON(cachedJson))
+						Expect(stdinPrevResult).To(MatchJSON(ipResult))
 					})
 				})
 
@@ -1322,14 +1350,20 @@ var _ = Describe("Invoking plugins", func() {
 
 				Context("is 0.4.0", func() {
 					It("passes a cached result to the first plugin", func() {
-						cachedJson := `{
+						ipResult = `{
 							"cniVersion": "0.4.0",
 							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
 							"dns": {}
 						}`
-						err := ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
+						err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
 						Expect(err).NotTo(HaveOccurred())
 
+						netConfigList, plugins = makePluginList("0.4.0", ipResult, rcMap)
+						for _, p := range plugins {
+							p.debug.ReportResult = ipResult
+							Expect(p.debug.WriteDebug(p.debugFilePath)).To(Succeed())
+						}
+
 						err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
 						Expect(err).NotTo(HaveOccurred())
 
@@ -1342,29 +1376,26 @@ var _ = Describe("Invoking plugins", func() {
 						Expect(err).NotTo(HaveOccurred())
 						stdinPrevResult, err := json.Marshal(data["prevResult"])
 						Expect(err).NotTo(HaveOccurred())
-						Expect(stdinPrevResult).To(MatchJSON(cachedJson))
+						Expect(stdinPrevResult).To(MatchJSON(ipResult))
 					})
 				})
 
 				Context("is less than 0.4.0", func() {
 					It("does not pass a cached result to the first plugin", func() {
-						err := ioutil.WriteFile(cacheFile, []byte(`{
+						ipResult = `{
 							"cniVersion": "0.3.1",
 							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
 							"dns": {}
-						}`), 0600)
+						}`
+						err := ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
 						Expect(err).NotTo(HaveOccurred())
 
-						// Set an older CNI version
-						confList := make(map[string]interface{})
-						err = json.Unmarshal(netConfigList.Bytes, &confList)
-						Expect(err).NotTo(HaveOccurred())
-						confList["cniVersion"] = "0.3.1"
-						newBytes, err := json.Marshal(confList)
-						Expect(err).NotTo(HaveOccurred())
+						netConfigList, plugins = makePluginList("0.3.1", ipResult, rcMap)
+						for _, p := range plugins {
+							p.debug.ReportResult = ipResult
+							Expect(p.debug.WriteDebug(p.debugFilePath)).To(Succeed())
+						}
 
-						netConfigList, err = libcni.ConfListFromBytes(newBytes)
-						Expect(err).NotTo(HaveOccurred())
 						err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
 						Expect(err).NotTo(HaveOccurred())
 
diff --git a/pkg/invoke/delegate_test.go b/pkg/invoke/delegate_test.go
index fa428c1..75f92bc 100644
--- a/pkg/invoke/delegate_test.go
+++ b/pkg/invoke/delegate_test.go
@@ -23,7 +23,7 @@ import (
 	"path/filepath"
 
 	"github.com/containernetworking/cni/pkg/invoke"
-	"github.com/containernetworking/cni/pkg/types/current"
+	current "github.com/containernetworking/cni/pkg/types/100"
 	"github.com/containernetworking/cni/plugins/test/noop/debug"
 
 	. "github.com/onsi/ginkgo"
@@ -43,14 +43,13 @@ var _ = Describe("Delegate", func() {
 	BeforeEach(func() {
 		netConf, _ = json.Marshal(map[string]string{
 			"name":       "delegate-test",
-			"cniVersion": "0.4.0",
+			"cniVersion": current.ImplementedSpecVersion,
 		})
 
 		expectedResult = &current.Result{
-			CNIVersion: "0.4.0",
+			CNIVersion: current.ImplementedSpecVersion,
 			IPs: []*current.IPConfig{
 				{
-					Version: "4",
 					Address: net.IPNet{
 						IP:   net.ParseIP("10.1.2.3"),
 						Mask: net.CIDRMask(24, 32),
diff --git a/pkg/invoke/exec_test.go b/pkg/invoke/exec_test.go
index b9528e2..d89e3bb 100644
--- a/pkg/invoke/exec_test.go
+++ b/pkg/invoke/exec_test.go
@@ -21,7 +21,7 @@ import (
 
 	"github.com/containernetworking/cni/pkg/invoke"
 	"github.com/containernetworking/cni/pkg/invoke/fakes"
-	"github.com/containernetworking/cni/pkg/types/current"
+	current "github.com/containernetworking/cni/pkg/types/100"
 	"github.com/containernetworking/cni/pkg/version"
 
 	. "github.com/onsi/ginkgo"
@@ -42,7 +42,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
 
 	BeforeEach(func() {
 		rawExec = &fakes.RawExec{}
-		rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
+		rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "cniVersion": "0.3.1", "ips": [ { "version": "4", "address": "1.2.3.4/24" } ] }`)
 
 		versionDecoder = &fakes.VersionDecoder{}
 		versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0")
diff --git a/pkg/skel/skel_test.go b/pkg/skel/skel_test.go
index 2284dd0..a44486a 100644
--- a/pkg/skel/skel_test.go
+++ b/pkg/skel/skel_test.go
@@ -21,7 +21,7 @@ import (
 	"strings"
 
 	"github.com/containernetworking/cni/pkg/types"
-	"github.com/containernetworking/cni/pkg/types/current"
+	current "github.com/containernetworking/cni/pkg/types/100"
 	"github.com/containernetworking/cni/pkg/version"
 
 	. "github.com/onsi/ginkgo"
diff --git a/pkg/types/020/types_test.go b/pkg/types/020/types_test.go
index 97a8d7c..36aaadf 100644
--- a/pkg/types/020/types_test.go
+++ b/pkg/types/020/types_test.go
@@ -15,9 +15,9 @@
 package types020_test
 
 import (
-	"io/ioutil"
+	"encoding/json"
+	"fmt"
 	"net"
-	"os"
 
 	"github.com/containernetworking/cni/pkg/types"
 	"github.com/containernetworking/cni/pkg/types/020"
@@ -26,68 +26,52 @@ import (
 	. "github.com/onsi/gomega"
 )
 
-var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
-	It("correctly encodes a 0.1.0/0.2.0 Result", func() {
-		ipv4, err := types.ParseCIDR("1.2.3.30/24")
-		Expect(err).NotTo(HaveOccurred())
-		Expect(ipv4).NotTo(BeNil())
+func testResult(resultCNIVersion, jsonCNIVersion string) (*types020.Result, string) {
+	ipv4, err := types.ParseCIDR("1.2.3.30/24")
+	Expect(err).NotTo(HaveOccurred())
+	Expect(ipv4).NotTo(BeNil())
 
-		routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
-		Expect(err).NotTo(HaveOccurred())
-		Expect(routev4).NotTo(BeNil())
-		Expect(routegwv4).NotTo(BeNil())
+	routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
+	Expect(err).NotTo(HaveOccurred())
+	Expect(routev4).NotTo(BeNil())
+	Expect(routegwv4).NotTo(BeNil())
 
-		ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
-		Expect(err).NotTo(HaveOccurred())
-		Expect(ipv6).NotTo(BeNil())
+	ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
+	Expect(err).NotTo(HaveOccurred())
+	Expect(ipv6).NotTo(BeNil())
 
-		routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
-		Expect(err).NotTo(HaveOccurred())
-		Expect(routev6).NotTo(BeNil())
-		Expect(routegwv6).NotTo(BeNil())
+	routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
+	Expect(err).NotTo(HaveOccurred())
+	Expect(routev6).NotTo(BeNil())
+	Expect(routegwv6).NotTo(BeNil())
 
-		// Set every field of the struct to ensure source compatibility
-		res := types020.Result{
-			CNIVersion: types020.ImplementedSpecVersion,
-			IP4: &types020.IPConfig{
-				IP:      *ipv4,
-				Gateway: net.ParseIP("1.2.3.1"),
-				Routes: []types.Route{
-					{Dst: *routev4, GW: routegwv4},
-				},
-			},
-			IP6: &types020.IPConfig{
-				IP:      *ipv6,
-				Gateway: net.ParseIP("abcd:1234:ffff::1"),
-				Routes: []types.Route{
-					{Dst: *routev6, GW: routegwv6},
-				},
+	// Set every field of the struct to ensure source compatibility
+	res := &types020.Result{
+		CNIVersion: resultCNIVersion,
+		IP4: &types020.IPConfig{
+			IP:      *ipv4,
+			Gateway: net.ParseIP("1.2.3.1"),
+			Routes: []types.Route{
+				{Dst: *routev4, GW: routegwv4},
 			},
-			DNS: types.DNS{
-				Nameservers: []string{"1.2.3.4", "1::cafe"},
-				Domain:      "acompany.com",
-				Search:      []string{"somedomain.com", "otherdomain.net"},
-				Options:     []string{"foo", "bar"},
+		},
+		IP6: &types020.IPConfig{
+			IP:      *ipv6,
+			Gateway: net.ParseIP("abcd:1234:ffff::1"),
+			Routes: []types.Route{
+				{Dst: *routev6, GW: routegwv6},
 			},
-		}
-
-		// Redirect stdout to capture JSON result
-		oldStdout := os.Stdout
-		r, w, err := os.Pipe()
-		Expect(err).NotTo(HaveOccurred())
-
-		os.Stdout = w
-		err = res.Print()
-		w.Close()
-		Expect(err).NotTo(HaveOccurred())
+		},
+		DNS: types.DNS{
+			Nameservers: []string{"1.2.3.4", "1::cafe"},
+			Domain:      "acompany.com",
+			Search:      []string{"somedomain.com", "otherdomain.net"},
+			Options:     []string{"foo", "bar"},
+		},
+	}
 
-		// parse the result
-		out, err := ioutil.ReadAll(r)
-		os.Stdout = oldStdout
-		Expect(err).NotTo(HaveOccurred())
-
-		Expect(string(out)).To(Equal(`{
-    "cniVersion": "0.2.0",
+	json := fmt.Sprintf(`{
+    "cniVersion": "%s",
     "ip4": {
         "ip": "1.2.3.30/24",
         "gateway": "1.2.3.1",
@@ -123,6 +107,41 @@ var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
             "bar"
         ]
     }
-}`))
+}`, jsonCNIVersion)
+
+	return res, json
+}
+
+var _ = Describe("Ensures compatibility with the 0.1.0/0.2.0 spec", func() {
+	It("correctly encodes a 0.2.0 Result", func() {
+		res, expectedJSON := testResult(types020.ImplementedSpecVersion, types020.ImplementedSpecVersion)
+		out, err := json.Marshal(res)
+		Expect(err).NotTo(HaveOccurred())
+		Expect(out).To(MatchJSON(expectedJSON))
+	})
+
+	It("correctly encodes a 0.1.0 Result", func() {
+		res, expectedJSON := testResult("0.1.0", "0.1.0")
+		out, err := json.Marshal(res)
+		Expect(err).NotTo(HaveOccurred())
+		Expect(out).To(MatchJSON(expectedJSON))
+	})
+
+	It("converts a 0.2.0 result to 0.1.0", func() {
+		res, expectedJSON := testResult(types020.ImplementedSpecVersion, "0.1.0")
+		res010, err := res.GetAsVersion("0.1.0")
+		Expect(err).NotTo(HaveOccurred())
+		out, err := json.Marshal(res010)
+		Expect(err).NotTo(HaveOccurred())
+		Expect(out).To(MatchJSON(expectedJSON))
+	})
+
+	It("converts a 0.1.0 result to 0.2.0", func() {
+		res, expectedJSON := testResult("0.1.0", types020.ImplementedSpecVersion)
+		res020, err := res.GetAsVersion(types020.ImplementedSpecVersion)
+		Expect(err).NotTo(HaveOccurred())
+		out, err := json.Marshal(res020)
+		Expect(err).NotTo(HaveOccurred())
+		Expect(out).To(MatchJSON(expectedJSON))
 	})
 })
diff --git a/pkg/types/current/types_suite_test.go b/pkg/types/040/types_suite_test.go
similarity index 97%
rename from pkg/types/current/types_suite_test.go
rename to pkg/types/040/types_suite_test.go
index e05c1ff..4bcb0fa 100644
--- a/pkg/types/current/types_suite_test.go
+++ b/pkg/types/040/types_suite_test.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package current_test
+package types040_test
 
 import (
 	. "github.com/onsi/ginkgo"
diff --git a/pkg/types/current/types_test.go b/pkg/types/040/types_test.go
similarity index 92%
rename from pkg/types/current/types_test.go
rename to pkg/types/040/types_test.go
index 1fda430..badaa4a 100644
--- a/pkg/types/current/types_test.go
+++ b/pkg/types/040/types_test.go
@@ -12,23 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package current_test
+package types040_test
 
 import (
 	"encoding/json"
-	"github.com/containernetworking/cni/pkg/types/020"
 	"io/ioutil"
 	"net"
 	"os"
 
 	"github.com/containernetworking/cni/pkg/types"
-	"github.com/containernetworking/cni/pkg/types/current"
+	types020 "github.com/containernetworking/cni/pkg/types/020"
+	types040 "github.com/containernetworking/cni/pkg/types/040"
 
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 )
 
-func testResult() *current.Result {
+func testResult() *types040.Result {
 	ipv4, err := types.ParseCIDR("1.2.3.30/24")
 	Expect(err).NotTo(HaveOccurred())
 	Expect(ipv4).NotTo(BeNil())
@@ -48,25 +48,25 @@ func testResult() *current.Result {
 	Expect(routegwv6).NotTo(BeNil())
 
 	// Set every field of the struct to ensure source compatibility
-	return &current.Result{
+	return &types040.Result{
 		CNIVersion: "0.3.1",
-		Interfaces: []*current.Interface{
+		Interfaces: []*types040.Interface{
 			{
 				Name:    "eth0",
 				Mac:     "00:11:22:33:44:55",
 				Sandbox: "/proc/3553/ns/net",
 			},
 		},
-		IPs: []*current.IPConfig{
+		IPs: []*types040.IPConfig{
 			{
 				Version:   "4",
-				Interface: current.Int(0),
+				Interface: types040.Int(0),
 				Address:   *ipv4,
 				Gateway:   net.ParseIP("1.2.3.1"),
 			},
 			{
 				Version:   "6",
-				Interface: current.Int(0),
+				Interface: types040.Int(0),
 				Address:   *ipv6,
 				Gateway:   net.ParseIP("abcd:1234:ffff::1"),
 			},
@@ -84,7 +84,7 @@ func testResult() *current.Result {
 	}
 }
 
-var _ = Describe("Current types operations", func() {
+var _ = Describe("040 types operations", func() {
 	It("correctly encodes a 0.3.x Result", func() {
 		res := testResult()
 
@@ -174,7 +174,7 @@ var _ = Describe("Current types operations", func() {
 		Expect(err).NotTo(HaveOccurred())
 
 		Expect(string(out)).To(MatchJSON(`{
-    "cniVersion": "0.2.0",
+    "cniVersion": "0.1.0",
     "ip4": {
         "ip": "1.2.3.30/24",
         "gateway": "1.2.3.1",
@@ -257,8 +257,8 @@ var _ = Describe("Current types operations", func() {
 			},
 		}
 
-		// Convert to current
-		newRes, err := current.NewResultFromResult(res)
+		// Convert to 040
+		newRes, err := types040.NewResultFromResult(res)
 		Expect(err).NotTo(HaveOccurred())
 		// Convert back to 0.2.0
 		oldRes, err := newRes.GetAsVersion("0.2.0")
@@ -314,8 +314,8 @@ var _ = Describe("Current types operations", func() {
 			},
 		}
 
-		// Convert to current
-		newRes, err := current.NewResultFromResult(res)
+		// Convert to 040
+		newRes, err := types040.NewResultFromResult(res)
 		Expect(err).NotTo(HaveOccurred())
 		// Convert back to 0.2.0
 		oldRes, err := newRes.GetAsVersion("0.2.0")
@@ -330,9 +330,9 @@ var _ = Describe("Current types operations", func() {
 	})
 
 	It("correctly marshals and unmarshals interface index 0", func() {
-		ipc := &current.IPConfig{
+		ipc := &types040.IPConfig{
 			Version:   "4",
-			Interface: current.Int(0),
+			Interface: types040.Int(0),
 			Address: net.IPNet{
 				IP:   net.ParseIP("10.1.2.3"),
 				Mask: net.IPv4Mask(255, 255, 255, 0),
@@ -347,21 +347,21 @@ var _ = Describe("Current types operations", func() {
     "address": "10.1.2.3/24"
 }`))
 
-		recovered := &current.IPConfig{}
+		recovered := &types040.IPConfig{}
 		Expect(json.Unmarshal(jsonBytes, &recovered)).To(Succeed())
 		Expect(recovered).To(Equal(ipc))
 	})
 
 	Context("when unmarshalling json fails", func() {
 		It("returns an error", func() {
-			recovered := &current.IPConfig{}
+			recovered := &types040.IPConfig{}
 			err := json.Unmarshal([]byte(`{"address": 5}`), &recovered)
 			Expect(err).To(MatchError(HavePrefix("json: cannot unmarshal")))
 		})
 	})
 
 	It("correctly marshals a missing interface index", func() {
-		ipc := &current.IPConfig{
+		ipc := &types040.IPConfig{
 			Version: "4",
 			Address: net.IPNet{
 				IP:   net.ParseIP("10.1.2.3"),
diff --git a/pkg/types/100/types_suite_test.go b/pkg/types/100/types_suite_test.go
new file mode 100644
index 0000000..fd55a91
--- /dev/null
+++ b/pkg/types/100/types_suite_test.go
@@ -0,0 +1,27 @@
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package types100_test
+
+import (
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+
+	"testing"
+)
+
+func TestTypesCurrent(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Current Types Suite")
+}
diff --git a/pkg/types/100/types_test.go b/pkg/types/100/types_test.go
new file mode 100644
index 0000000..7687b9d
--- /dev/null
+++ b/pkg/types/100/types_test.go
@@ -0,0 +1,324 @@
+// Copyright 2016 CNI authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package types100_test
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"net"
+	"os"
+
+	"github.com/containernetworking/cni/pkg/types"
+	current "github.com/containernetworking/cni/pkg/types/100"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+func testResult() *current.Result {
+	ipv4, err := types.ParseCIDR("1.2.3.30/24")
+	Expect(err).NotTo(HaveOccurred())
+	Expect(ipv4).NotTo(BeNil())
+
+	routegwv4, routev4, err := net.ParseCIDR("15.5.6.8/24")
+	Expect(err).NotTo(HaveOccurred())
+	Expect(routev4).NotTo(BeNil())
+	Expect(routegwv4).NotTo(BeNil())
+
+	ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
+	Expect(err).NotTo(HaveOccurred())
+	Expect(ipv6).NotTo(BeNil())
+
+	routegwv6, routev6, err := net.ParseCIDR("1111:dddd::aaaa/80")
+	Expect(err).NotTo(HaveOccurred())
+	Expect(routev6).NotTo(BeNil())
+	Expect(routegwv6).NotTo(BeNil())
+
+	// Set every field of the struct to ensure source compatibility
+	return &current.Result{
+		CNIVersion: "1.0.0",
+		Interfaces: []*current.Interface{
+			{
+				Name:    "eth0",
+				Mac:     "00:11:22:33:44:55",
+				Sandbox: "/proc/3553/ns/net",
+			},
+		},
+		IPs: []*current.IPConfig{
+			{
+				Interface: current.Int(0),
+				Address:   *ipv4,
+				Gateway:   net.ParseIP("1.2.3.1"),
+			},
+			{
+				Interface: current.Int(0),
+				Address:   *ipv6,
+				Gateway:   net.ParseIP("abcd:1234:ffff::1"),
+			},
+		},
+		Routes: []*types.Route{
+			{Dst: *routev4, GW: routegwv4},
+			{Dst: *routev6, GW: routegwv6},
+		},
+		DNS: types.DNS{
+			Nameservers: []string{"1.2.3.4", "1::cafe"},
+			Domain:      "acompany.com",
+			Search:      []string{"somedomain.com", "otherdomain.net"},
+			Options:     []string{"foo", "bar"},
+		},
+	}
+}
+
+var _ = Describe("Current types operations", func() {
+	It("correctly encodes a 1.0.0 Result", func() {
+		res := testResult()
+
+		// Redirect stdout to capture JSON result
+		oldStdout := os.Stdout
+		r, w, err := os.Pipe()
+		Expect(err).NotTo(HaveOccurred())
+
+		os.Stdout = w
+		err = res.Print()
+		w.Close()
+		Expect(err).NotTo(HaveOccurred())
+
+		// parse the result
+		out, err := ioutil.ReadAll(r)
+		os.Stdout = oldStdout
+		Expect(err).NotTo(HaveOccurred())
+
+		Expect(string(out)).To(MatchJSON(`{
+    "cniVersion": "1.0.0",
+    "interfaces": [
+        {
+            "name": "eth0",
+            "mac": "00:11:22:33:44:55",
+            "sandbox": "/proc/3553/ns/net"
+        }
+    ],
+    "ips": [
+        {
+            "interface": 0,
+            "address": "1.2.3.30/24",
+            "gateway": "1.2.3.1"
+        },
+        {
+            "interface": 0,
+            "address": "abcd:1234:ffff::cdde/64",
+            "gateway": "abcd:1234:ffff::1"
+        }
+    ],
+    "routes": [
+        {
+            "dst": "15.5.6.0/24",
+            "gw": "15.5.6.8"
+        },
+        {
+            "dst": "1111:dddd::/80",
+            "gw": "1111:dddd::aaaa"
+        }
+    ],
+    "dns": {
+        "nameservers": [
+            "1.2.3.4",
+            "1::cafe"
+        ],
+        "domain": "acompany.com",
+        "search": [
+            "somedomain.com",
+            "otherdomain.net"
+        ],
+        "options": [
+            "foo",
+            "bar"
+        ]
+    }
+}`))
+	})
+
+	It("correctly encodes a 0.1.0 Result", func() {
+		res, err := testResult().GetAsVersion("0.1.0")
+		Expect(err).NotTo(HaveOccurred())
+
+		// Redirect stdout to capture JSON result
+		oldStdout := os.Stdout
+		r, w, err := os.Pipe()
+		Expect(err).NotTo(HaveOccurred())
+
+		os.Stdout = w
+		err = res.Print()
+		w.Close()
+		Expect(err).NotTo(HaveOccurred())
+
+		// parse the result
+		out, err := ioutil.ReadAll(r)
+		os.Stdout = oldStdout
+		Expect(err).NotTo(HaveOccurred())
+
+		Expect(string(out)).To(MatchJSON(`{
+    "cniVersion": "0.1.0",
+    "ip4": {
+        "ip": "1.2.3.30/24",
+        "gateway": "1.2.3.1",
+        "routes": [
+            {
+                "dst": "15.5.6.0/24",
+                "gw": "15.5.6.8"
+            }
+        ]
+    },
+    "ip6": {
+        "ip": "abcd:1234:ffff::cdde/64",
+        "gateway": "abcd:1234:ffff::1",
+        "routes": [
+            {
+                "dst": "1111:dddd::/80",
+                "gw": "1111:dddd::aaaa"
+            }
+        ]
+    },
+    "dns": {
+        "nameservers": [
+            "1.2.3.4",
+            "1::cafe"
+        ],
+        "domain": "acompany.com",
+        "search": [
+            "somedomain.com",
+            "otherdomain.net"
+        ],
+        "options": [
+            "foo",
+            "bar"
+        ]
+    }
+}`))
+	})
+
+	It("correctly encodes a 0.4.0 Result", func() {
+		res, err := testResult().GetAsVersion("0.4.0")
+		Expect(err).NotTo(HaveOccurred())
+
+		// Redirect stdout to capture JSON result
+		oldStdout := os.Stdout
+		r, w, err := os.Pipe()
+		Expect(err).NotTo(HaveOccurred())
+
+		os.Stdout = w
+		err = res.Print()
+		w.Close()
+		Expect(err).NotTo(HaveOccurred())
+
+		// parse the result
+		out, err := ioutil.ReadAll(r)
+		os.Stdout = oldStdout
+		Expect(err).NotTo(HaveOccurred())
+
+		Expect(string(out)).To(MatchJSON(`{
+    "cniVersion": "0.4.0",
+    "interfaces": [
+        {
+            "name": "eth0",
+            "mac": "00:11:22:33:44:55",
+            "sandbox": "/proc/3553/ns/net"
+        }
+    ],
+    "ips": [
+        {
+            "interface": 0,
+            "version": "4",
+            "address": "1.2.3.30/24",
+            "gateway": "1.2.3.1"
+        },
+        {
+            "interface": 0,
+            "version": "6",
+            "address": "abcd:1234:ffff::cdde/64",
+            "gateway": "abcd:1234:ffff::1"
+        }
+    ],
+    "routes": [
+        {
+            "dst": "15.5.6.0/24",
+            "gw": "15.5.6.8"
+        },
+        {
+            "dst": "1111:dddd::/80",
+            "gw": "1111:dddd::aaaa"
+        }
+    ],
+    "dns": {
+        "nameservers": [
+            "1.2.3.4",
+            "1::cafe"
+        ],
+        "domain": "acompany.com",
+        "search": [
+            "somedomain.com",
+            "otherdomain.net"
+        ],
+        "options": [
+            "foo",
+            "bar"
+        ]
+    }
+}`))
+	})
+
+	It("correctly marshals and unmarshals interface index 0", func() {
+		ipc := &current.IPConfig{
+			Interface: current.Int(0),
+			Address: net.IPNet{
+				IP:   net.ParseIP("10.1.2.3"),
+				Mask: net.IPv4Mask(255, 255, 255, 0),
+			},
+		}
+
+		jsonBytes, err := json.Marshal(ipc)
+		Expect(err).NotTo(HaveOccurred())
+		Expect(jsonBytes).To(MatchJSON(`{
+    "interface": 0,
+    "address": "10.1.2.3/24"
+}`))
+
+		recovered := &current.IPConfig{}
+		Expect(json.Unmarshal(jsonBytes, &recovered)).To(Succeed())
+		Expect(recovered).To(Equal(ipc))
+	})
+
+	Context("when unmarshalling json fails", func() {
+		It("returns an error", func() {
+			recovered := &current.IPConfig{}
+			err := json.Unmarshal([]byte(`{"address": 5}`), &recovered)
+			Expect(err).To(MatchError(HavePrefix("json: cannot unmarshal")))
+		})
+	})
+
+	It("correctly marshals a missing interface index", func() {
+		ipc := &current.IPConfig{
+			Address: net.IPNet{
+				IP:   net.ParseIP("10.1.2.3"),
+				Mask: net.IPv4Mask(255, 255, 255, 0),
+			},
+		}
+
+		json, err := json.Marshal(ipc)
+		Expect(err).NotTo(HaveOccurred())
+		Expect(json).To(MatchJSON(`{
+    "address": "10.1.2.3/24"
+}`))
+	})
+})
diff --git a/pkg/version/version_test.go b/pkg/version/version_test.go
index 1a34e9a..6d869c2 100644
--- a/pkg/version/version_test.go
+++ b/pkg/version/version_test.go
@@ -20,7 +20,7 @@ import (
 	"reflect"
 
 	"github.com/containernetworking/cni/pkg/types"
-	"github.com/containernetworking/cni/pkg/types/current"
+	current "github.com/containernetworking/cni/pkg/types/100"
 	"github.com/containernetworking/cni/pkg/version"
 
 	. "github.com/onsi/ginkgo"
@@ -31,7 +31,7 @@ var _ = Describe("Version operations", func() {
 	Context("when a prevResult is available", func() {
 		It("parses the prevResult", func() {
 			rawBytes := []byte(`{
-				"cniVersion": "0.3.0",
+				"cniVersion": "1.0.0",
 				"interfaces": [
 					{
 						"name": "eth0",
@@ -53,7 +53,7 @@ var _ = Describe("Version operations", func() {
 			Expect(err).NotTo(HaveOccurred())
 
 			conf := &types.NetConf{
-				CNIVersion:    "0.3.0",
+				CNIVersion:    "1.0.0",
 				Name:          "foobar",
 				Type:          "baz",
 				RawPrevResult: raw,
@@ -63,7 +63,7 @@ var _ = Describe("Version operations", func() {
 			Expect(err).NotTo(HaveOccurred())
 
 			expectedResult := &current.Result{
-				CNIVersion: "0.3.0",
+				CNIVersion: current.ImplementedSpecVersion,
 				Interfaces: []*current.Interface{
 					{
 						Name:    "eth0",
@@ -73,7 +73,6 @@ var _ = Describe("Version operations", func() {
 				},
 				IPs: []*current.IPConfig{
 					{
-						Version:   "4",
 						Interface: current.Int(0),
 						Address: net.IPNet{
 							IP:   net.ParseIP("1.2.3.30"),
@@ -88,7 +87,7 @@ var _ = Describe("Version operations", func() {
 
 		It("fails if the prevResult version is unknown", func() {
 			conf := &types.NetConf{
-				CNIVersion: "0.3.0",
+				CNIVersion: current.ImplementedSpecVersion,
 				Name:       "foobar",
 				Type:       "baz",
 				RawPrevResult: map[string]interface{}{
@@ -97,28 +96,32 @@ var _ = Describe("Version operations", func() {
 			}
 
 			err := version.ParsePrevResult(conf)
-			Expect(err).NotTo(HaveOccurred())
+			Expect(err).To(MatchError("could not parse prevResult: result type supports [1.0.0] but unmarshalled CNIVersion is \"5678.456\""))
 		})
 
-		It("fails if the prevResult is invalid", func() {
+		It("fails if the prevResult version does not match the prevResult version", func() {
 			conf := &types.NetConf{
-				CNIVersion: "0.3.0",
+				CNIVersion: current.ImplementedSpecVersion,
 				Name:       "foobar",
 				Type:       "baz",
 				RawPrevResult: map[string]interface{}{
-					"adsfasdfasdfasdfasdfaf": nil,
+					"cniVersion": "0.2.0",
+					"ip4": map[string]interface{}{
+						"ip":      "1.2.3.30/24",
+						"gateway": "1.2.3.1",
+					},
 				},
 			}
 
 			err := version.ParsePrevResult(conf)
-			Expect(err).NotTo(HaveOccurred())
+			Expect(err).To(MatchError("could not parse prevResult: result type supports [1.0.0] but unmarshalled CNIVersion is \"0.2.0\""))
 		})
 	})
 
 	Context("when a prevResult is not available", func() {
 		It("does not fail", func() {
 			conf := &types.NetConf{
-				CNIVersion: "0.3.0",
+				CNIVersion: current.ImplementedSpecVersion,
 				Name:       "foobar",
 				Type:       "baz",
 			}
diff --git a/plugins/test/noop/main.go b/plugins/test/noop/main.go
index 19c33c4..727bc84 100644
--- a/plugins/test/noop/main.go
+++ b/plugins/test/noop/main.go
@@ -31,15 +31,14 @@ import (
 
 	"github.com/containernetworking/cni/pkg/skel"
 	"github.com/containernetworking/cni/pkg/types"
-	"github.com/containernetworking/cni/pkg/types/current"
+	current "github.com/containernetworking/cni/pkg/types/100"
 	"github.com/containernetworking/cni/pkg/version"
 	noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
 )
 
 type NetConf struct {
 	types.NetConf
-	DebugFile  string          `json:"debugFile"`
-	PrevResult *current.Result `json:"prevResult,omitempty"`
+	DebugFile string `json:"debugFile"`
 }
 
 func loadConf(bytes []byte) (*NetConf, error) {
@@ -47,6 +46,9 @@ func loadConf(bytes []byte) (*NetConf, error) {
 	if err := json.Unmarshal(bytes, n); err != nil {
 		return nil, fmt.Errorf("failed to load netconf: %v %q", err, string(bytes))
 	}
+	if err := version.ParsePrevResult(&n.NetConf); err != nil {
+		return nil, err
+	}
 	return n, nil
 }
 
@@ -126,11 +128,12 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
 	} else if debug.ReportResult == "PASSTHROUGH" || debug.ReportResult == "INJECT-DNS" {
 		prevResult := netConf.PrevResult
 		if debug.ReportResult == "INJECT-DNS" {
-			prevResult, err = current.NewResultFromResult(netConf.PrevResult)
+			newResult, err := current.NewResultFromResult(netConf.PrevResult)
 			if err != nil {
 				return err
 			}
-			prevResult.DNS.Nameservers = []string{"1.2.3.4"}
+			newResult.DNS.Nameservers = []string{"1.2.3.4"}
+			prevResult = newResult
 		}
 
 		// Must print the prevResult as the CNIVersion of the config
@@ -160,7 +163,7 @@ func debugBehavior(args *skel.CmdArgs, command string) error {
 }
 
 func debugGetSupportedVersions(stdinData []byte) []string {
-	vers := []string{"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0"}
+	vers := []string{"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0"}
 	cniArgs := os.Getenv("CNI_ARGS")
 	if cniArgs == "" {
 		return vers
diff --git a/plugins/test/noop/noop_test.go b/plugins/test/noop/noop_test.go
index 07fda63..546f4d3 100644
--- a/plugins/test/noop/noop_test.go
+++ b/plugins/test/noop/noop_test.go
@@ -107,6 +107,7 @@ var _ = Describe("No-op plugin", func() {
 	"some":"stdin-json",
 	"cniVersion": "0.3.1",
 	"prevResult": {
+		"cniVersion": "0.3.1",
 		"ips": [{"version": "4", "address": "10.1.2.15/24"}]
 	}
 }`)
@@ -125,6 +126,7 @@ var _ = Describe("No-op plugin", func() {
 	"some":"stdin-json",
 	"cniVersion": "0.4.0",
 	"prevResult": {
+		"cniVersion": "0.4.0",
 		"ips": [{"version": "4", "address": "10.1.2.15/24"}]
 	}
 }`)
@@ -143,7 +145,7 @@ var _ = Describe("No-op plugin", func() {
 	"some":"stdin-json",
 	"cniVersion": "0.4.0",
 	"prevResult": {
-		"cniVersion": "0.3.1",
+		"cniVersion": "0.4.0",
 		"ips": [{"version": "4", "address": "10.1.2.3/24"}],
 		"dns": {}
 	}
@@ -153,7 +155,7 @@ var _ = Describe("No-op plugin", func() {
 		Expect(err).NotTo(HaveOccurred())
 		Eventually(session).Should(gexec.Exit(0))
 		Expect(session.Out.Contents()).To(MatchJSON(`{
-  "cniVersion": "0.4.0",
+	"cniVersion": "0.4.0",
 	"ips": [{"version": "4", "address": "10.1.2.3/24"}],
 	"dns": {"nameservers": ["1.2.3.4"]}
 }`))