diff --git a/pkg/testutils/cmd.go b/pkg/testutils/cmd.go index 6f65d6ddd..276f9e5a6 100644 --- a/pkg/testutils/cmd.go +++ b/pkg/testutils/cmd.go @@ -114,3 +114,12 @@ func CmdDel(cniNetns, cniContainerID, cniIfname string, f func() error) error { func CmdDelWithArgs(args *skel.CmdArgs, f func() error) error { return CmdDel(args.Netns, args.ContainerID, args.IfName, f) } + +func CmdStatus(f func() error) error { + os.Setenv("CNI_COMMAND", "STATUS") + os.Setenv("CNI_PATH", os.Getenv("PATH")) + os.Setenv("CNI_NETNS_OVERRIDE", "1") + defer envCleanup() + + return f() +} diff --git a/pkg/testutils/testing.go b/pkg/testutils/testing.go index 9444a8b2d..9f5140fc6 100644 --- a/pkg/testutils/testing.go +++ b/pkg/testutils/testing.go @@ -19,7 +19,7 @@ import ( ) // AllSpecVersions contains all CNI spec version numbers -var AllSpecVersions = [...]string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0"} +var AllSpecVersions = [...]string{"0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0", "1.1.0"} // SpecVersionHasIPVersion returns true if the given CNI specification version // includes the "version" field in the IP address elements @@ -39,6 +39,13 @@ func SpecVersionHasCHECK(ver string) bool { return ok } +// SpecVersionHasSTATUS returns true if the given CNI specification version +// supports the STATUS command +func SpecVersionHasSTATUS(ver string) bool { + ok, _ := version.GreaterThanOrEqualTo(ver, "1.1.0") + return ok +} + // SpecVersionHasChaining returns true if the given CNI specification version // supports plugin chaining func SpecVersionHasChaining(ver string) bool { diff --git a/plugins/main/bridge/bridge.go b/plugins/main/bridge/bridge.go index 4eea27e90..ef4d46476 100644 --- a/plugins/main/bridge/bridge.go +++ b/plugins/main/bridge/bridge.go @@ -828,9 +828,10 @@ func cmdDel(args *skel.CmdArgs) error { func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + Status: cmdStatus, }, version.All, bv.BuildString("bridge")) } @@ -1092,3 +1093,18 @@ func cmdCheck(args *skel.CmdArgs) error { func uniqueID(containerID, cniIface string) string { return containerID + "-" + cniIface } + +func cmdStatus(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %w", err) + } + + if conf.IPAM.Type != "" { + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + } + + return nil +} diff --git a/plugins/main/bridge/bridge_test.go b/plugins/main/bridge/bridge_test.go index 42c70ae29..022d858eb 100644 --- a/plugins/main/bridge/bridge_test.go +++ b/plugins/main/bridge/bridge_test.go @@ -494,6 +494,11 @@ type ( func newTesterByVersion(version string, testNS, targetNS ns.NetNS) cmdAddDelTester { switch { + case strings.HasPrefix(version, "1.1."): + return &testerV10x{ + testNS: testNS, + targetNS: targetNS, + } case strings.HasPrefix(version, "1.0."): return &testerV10x{ testNS: testNS, @@ -1469,6 +1474,14 @@ func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) (types.Re err := tester.testNS.Do(func(ns.NetNS) error { defer GinkgoRecover() + // check that STATUS is + if testutils.SpecVersionHasSTATUS(tc.cniVersion) { + err := testutils.CmdStatus(func() error { + return cmdStatus(&skel.CmdArgs{StdinData: []byte(tc.netConfJSON(dataDir))}) + }) + Expect(err).NotTo(HaveOccurred()) + } + r, raw, err := testutils.CmdAddWithArgs(tester.args, func() error { return cmdAdd(tester.args) }) diff --git a/plugins/main/dummy/dummy.go b/plugins/main/dummy/dummy.go index 161cfc18f..c03da094c 100644 --- a/plugins/main/dummy/dummy.go +++ b/plugins/main/dummy/dummy.go @@ -180,9 +180,10 @@ func cmdDel(args *skel.CmdArgs) error { func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + Status: cmdStatus, }, version.All, bv.BuildString("dummy")) } @@ -292,3 +293,16 @@ func validateCniContainerInterface(intf current.Interface) error { return nil } + +func cmdStatus(args *skel.CmdArgs) error { + conf := types.NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %w", err) + } + + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + + return nil +} diff --git a/plugins/main/dummy/dummy_test.go b/plugins/main/dummy/dummy_test.go index 6d7c0d0a2..7adaf5689 100644 --- a/plugins/main/dummy/dummy_test.go +++ b/plugins/main/dummy/dummy_test.go @@ -106,7 +106,7 @@ type ( func newTesterByVersion(version string) tester { switch { - case strings.HasPrefix(version, "1.0."): + case strings.HasPrefix(version, "1."): return &testerV10x{} case strings.HasPrefix(version, "0.4."): return &testerV04x{} @@ -261,6 +261,13 @@ var _ = Describe("dummy Operations", func() { defer GinkgoRecover() var err error + if testutils.SpecVersionHasSTATUS(ver) { + err = testutils.CmdStatus(func() error { + return cmdStatus(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + result, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) diff --git a/plugins/main/host-device/host-device.go b/plugins/main/host-device/host-device.go index 187eac3ae..622280f0f 100644 --- a/plugins/main/host-device/host-device.go +++ b/plugins/main/host-device/host-device.go @@ -518,9 +518,10 @@ func getLink(devname, hwaddr, kernelpath, pciaddr string, auxDev string) (netlin func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + Status: cmdStatus, }, version.All, bv.BuildString("host-device")) } @@ -628,3 +629,20 @@ func validateCniContainerInterface(intf current.Interface) error { return nil } + +func cmdStatus(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %w", err) + } + + if conf.IPAM.Type != "" { + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + } + + // TODO: Check if hode device exists. + + return nil +} diff --git a/plugins/main/host-device/host-device_test.go b/plugins/main/host-device/host-device_test.go index 2c26d03cb..eaa72eef7 100644 --- a/plugins/main/host-device/host-device_test.go +++ b/plugins/main/host-device/host-device_test.go @@ -231,7 +231,7 @@ type ( func newTesterByVersion(version string) tester { switch { - case strings.HasPrefix(version, "1.0."): + case strings.HasPrefix(version, "1."): return &testerV10x{} case strings.HasPrefix(version, "0.4."): return &testerV04x{} @@ -362,6 +362,15 @@ var _ = Describe("base functionality", func() { "type": "host-device", "device": %q }`, ver, ifname) + + // if v1.1 or greater, call CmdStatus + if testutils.SpecVersionHasSTATUS(ver) { + err := testutils.CmdStatus(func() error { + return cmdStatus(&skel.CmdArgs{StdinData: []byte(conf)}) + }) + Expect(err).NotTo(HaveOccurred()) + } + args := &skel.CmdArgs{ ContainerID: "dummy", Netns: targetNS.Path(), diff --git a/plugins/main/ipvlan/ipvlan.go b/plugins/main/ipvlan/ipvlan.go index 8b9a3c12f..c1251c27f 100644 --- a/plugins/main/ipvlan/ipvlan.go +++ b/plugins/main/ipvlan/ipvlan.go @@ -350,9 +350,10 @@ func cmdDel(args *skel.CmdArgs) error { func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + Status: cmdStatus, }, version.All, bv.BuildString("ipvlan")) } @@ -488,3 +489,19 @@ func validateCniContainerInterface(intf current.Interface, modeExpected string) return nil } + +func cmdStatus(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %w", err) + } + if conf.IPAM.Type != "" { + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + } + + // TODO: Check if master interface exists. + + return nil +} diff --git a/plugins/main/ipvlan/ipvlan_test.go b/plugins/main/ipvlan/ipvlan_test.go index 3c787fd7a..bebd388d6 100644 --- a/plugins/main/ipvlan/ipvlan_test.go +++ b/plugins/main/ipvlan/ipvlan_test.go @@ -114,6 +114,13 @@ func ipvlanAddCheckDelTest(conf, masterName string, originalNS, targetNS ns.NetN err = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() + if testutils.SpecVersionHasSTATUS(cniVersion) { + err = testutils.CmdStatus(func() error { + return cmdStatus(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + result, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) @@ -214,7 +221,7 @@ type ( func newTesterByVersion(version string) tester { switch { - case strings.HasPrefix(version, "1.0."): + case strings.HasPrefix(version, "1."): return &testerV10x{} case strings.HasPrefix(version, "0.4.") || strings.HasPrefix(version, "0.3."): return &testerV04x{} diff --git a/plugins/main/macvlan/macvlan.go b/plugins/main/macvlan/macvlan.go index 9c9240c69..c7f4cbed2 100644 --- a/plugins/main/macvlan/macvlan.go +++ b/plugins/main/macvlan/macvlan.go @@ -427,9 +427,10 @@ func cmdDel(args *skel.CmdArgs) error { func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + Status: cmdStatus, }, version.All, bv.BuildString("macvlan")) } @@ -566,3 +567,20 @@ func validateCniContainerInterface(intf current.Interface, modeExpected string) return nil } + +func cmdStatus(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %w", err) + } + + if conf.IPAM.Type != "" { + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + } + + // TODO: Check if master interface exists. + + return nil +} diff --git a/plugins/main/macvlan/macvlan_test.go b/plugins/main/macvlan/macvlan_test.go index ae73f99f2..0d47f47df 100644 --- a/plugins/main/macvlan/macvlan_test.go +++ b/plugins/main/macvlan/macvlan_test.go @@ -117,7 +117,7 @@ type ( func newTesterByVersion(version string) tester { switch { - case strings.HasPrefix(version, "1.0."): + case strings.HasPrefix(version, "1."): return &testerV10x{} case strings.HasPrefix(version, "0.4."): return &testerV04x{} @@ -322,6 +322,13 @@ var _ = Describe("macvlan Operations", func() { err := originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() + if testutils.SpecVersionHasSTATUS(ver) { + err := testutils.CmdStatus(func() error { + return cmdStatus(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + result, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) @@ -434,6 +441,13 @@ var _ = Describe("macvlan Operations", func() { err := originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() + if testutils.SpecVersionHasSTATUS(ver) { + err := testutils.CmdStatus(func() error { + return cmdStatus(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + result, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) @@ -520,6 +534,13 @@ var _ = Describe("macvlan Operations", func() { defer GinkgoRecover() var err error + if testutils.SpecVersionHasSTATUS(ver) { + err := testutils.CmdStatus(func() error { + return cmdStatus(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + result, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) @@ -660,6 +681,13 @@ var _ = Describe("macvlan Operations", func() { err = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() + if testutils.SpecVersionHasSTATUS(ver) { + err := testutils.CmdStatus(func() error { + return cmdStatus(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + result, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) diff --git a/plugins/main/ptp/ptp.go b/plugins/main/ptp/ptp.go index c35e66900..a9e7b502b 100644 --- a/plugins/main/ptp/ptp.go +++ b/plugins/main/ptp/ptp.go @@ -305,9 +305,10 @@ func cmdDel(args *skel.CmdArgs) error { func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + Status: cmdStatus, }, version.All, bv.BuildString("ptp")) } @@ -411,3 +412,16 @@ func validateCniContainerInterface(intf current.Interface) error { return nil } + +func cmdStatus(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %w", err) + } + + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + + return nil +} diff --git a/plugins/main/ptp/ptp_test.go b/plugins/main/ptp/ptp_test.go index 0ede4409e..f356b21db 100644 --- a/plugins/main/ptp/ptp_test.go +++ b/plugins/main/ptp/ptp_test.go @@ -104,7 +104,7 @@ type ( func newTesterByVersion(version string) tester { switch { - case strings.HasPrefix(version, "1.0."): + case strings.HasPrefix(version, "1."): return &testerV10x{} case strings.HasPrefix(version, "0.4."): return &testerV04x{} @@ -249,6 +249,14 @@ var _ = Describe("ptp Operations", func() { defer GinkgoRecover() var err error + if testutils.SpecVersionHasSTATUS(cniVersion) { + By("Doing a cni STATUS") + err = testutils.CmdStatus(func() error { + return cmdStatus(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + result, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) diff --git a/plugins/main/tap/tap.go b/plugins/main/tap/tap.go index 51a6e7019..1f995b989 100644 --- a/plugins/main/tap/tap.go +++ b/plugins/main/tap/tap.go @@ -387,9 +387,10 @@ func cmdDel(args *skel.CmdArgs) error { func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + Status: cmdStatus, }, version.All, bv.BuildString("tap")) } @@ -459,3 +460,18 @@ func cmdCheck(args *skel.CmdArgs) error { return nil }) } + +func cmdStatus(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %w", err) + } + + if conf.IPAM.Type != "" { + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + } + + return nil +} diff --git a/plugins/main/tap/tap_test.go b/plugins/main/tap/tap_test.go index c39a25ee6..8c0b31107 100644 --- a/plugins/main/tap/tap_test.go +++ b/plugins/main/tap/tap_test.go @@ -108,7 +108,7 @@ type ( func newTesterByVersion(version string) tester { switch { - case strings.HasPrefix(version, "1.0."): + case strings.HasPrefix(version, "1."): return &testerV10x{} case strings.HasPrefix(version, "0.4."): return &testerV04x{} @@ -223,6 +223,13 @@ var _ = Describe("Add, check, remove tap plugin", func() { err = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() + if testutils.SpecVersionHasSTATUS(ver) { + err := testutils.CmdStatus(func() error { + return cmdStatus(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + result, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) @@ -364,6 +371,13 @@ var _ = Describe("Add, check, remove tap plugin", func() { err = originalNS.Do(func(ns.NetNS) error { defer GinkgoRecover() + if testutils.SpecVersionHasSTATUS(ver) { + err := testutils.CmdStatus(func() error { + return cmdStatus(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + result, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) diff --git a/plugins/main/vlan/vlan.go b/plugins/main/vlan/vlan.go index 331a07539..975948f67 100644 --- a/plugins/main/vlan/vlan.go +++ b/plugins/main/vlan/vlan.go @@ -260,9 +260,10 @@ func cmdDel(args *skel.CmdArgs) error { func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + Status: cmdStatus, }, version.All, bv.BuildString("vlan")) } @@ -396,3 +397,18 @@ func validateCniContainerInterface(intf current.Interface, vlanID int, mtu int) return nil } + +func cmdStatus(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %w", err) + } + + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + + // TODO: Check if master interface exists. + + return nil +} diff --git a/plugins/main/vlan/vlan_test.go b/plugins/main/vlan/vlan_test.go index c0cd3ed30..1f0f90192 100644 --- a/plugins/main/vlan/vlan_test.go +++ b/plugins/main/vlan/vlan_test.go @@ -113,7 +113,7 @@ type ( func newTesterByVersion(version string) tester { switch { - case strings.HasPrefix(version, "1.0."): + case strings.HasPrefix(version, "1."): return &testerV10x{} case strings.HasPrefix(version, "0.4."): return &testerV04x{} @@ -354,6 +354,13 @@ var _ = Describe("vlan Operations", func() { defer GinkgoRecover() var err error + if testutils.SpecVersionHasSTATUS(ver) { + err := testutils.CmdStatus(func() error { + return cmdStatus(args) + }) + Expect(err).NotTo(HaveOccurred()) + } + result, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) }) diff --git a/plugins/main/windows/win-bridge/win-bridge_windows.go b/plugins/main/windows/win-bridge/win-bridge_windows.go index 7c390a224..c8060d039 100644 --- a/plugins/main/windows/win-bridge/win-bridge_windows.go +++ b/plugins/main/windows/win-bridge/win-bridge_windows.go @@ -216,8 +216,24 @@ func cmdCheck(_ *skel.CmdArgs) error { func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + Status: cmdStatus, }, version.All, bv.BuildString("win-bridge")) } + +func cmdStatus(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %w", err) + } + + if conf.IPAM.Type != "" { + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + } + + return nil +} diff --git a/plugins/main/windows/win-overlay/win-overlay_windows.go b/plugins/main/windows/win-overlay/win-overlay_windows.go index 9b5a6bd15..b8d6e723a 100644 --- a/plugins/main/windows/win-overlay/win-overlay_windows.go +++ b/plugins/main/windows/win-overlay/win-overlay_windows.go @@ -288,8 +288,22 @@ func cmdCheck(_ *skel.CmdArgs) error { func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Check: cmdCheck, - Del: cmdDel, + Add: cmdAdd, + Check: cmdCheck, + Del: cmdDel, + Status: cmdStatus, }, version.All, bv.BuildString("win-overlay")) } + +func cmdStatus(args *skel.CmdArgs) error { + conf := NetConf{} + if err := json.Unmarshal(args.StdinData, &conf); err != nil { + return fmt.Errorf("failed to load netconf: %w", err) + } + + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + + return nil +} diff --git a/plugins/sample/main.go b/plugins/sample/main.go index 6678fdbd7..f2a992ce2 100644 --- a/plugins/sample/main.go +++ b/plugins/sample/main.go @@ -24,6 +24,7 @@ import ( "github.com/containernetworking/cni/pkg/types" current "github.com/containernetworking/cni/pkg/types/100" "github.com/containernetworking/cni/pkg/version" + "github.com/containernetworking/plugins/pkg/ipam" bv "github.com/containernetworking/plugins/pkg/utils/buildversion" ) @@ -150,9 +151,10 @@ func cmdDel(args *skel.CmdArgs) error { func main() { skel.PluginMainFuncs(skel.CNIFuncs{ - Add: cmdAdd, - Del: cmdDel, - Check: cmdCheck, + Add: cmdAdd, + Del: cmdDel, + Check: cmdCheck, + Status: cmdStatus, }, version.All, bv.BuildString("TODO")) } @@ -160,3 +162,27 @@ func cmdCheck(_ *skel.CmdArgs) error { // TODO: implement return fmt.Errorf("not implemented") } + +// cmdStatus implements the STATUS command, which indicates whether or not +// this plugin is able to accept ADD requests. +// +// If the plugin has external dependencies, such as a daemon +// or chained ipam plugin, it should determine their status. If all is well, +// and an ADD can be successfully processed, return nil +func cmdStatus(args *skel.CmdArgs) error { + conf, err := parseConfig(args.StdinData) + if err != nil { + return err + } + _ = conf + + // If this plugins delegates IPAM, ensure that IPAM is also running + if err := ipam.ExecStatus(conf.IPAM.Type, args.StdinData); err != nil { + return err + } + + // TODO: implement STATUS here + // e.g. querying an external deamon, or delegating STATUS to an IPAM plugin + + return nil +}