Repository: containernetworking/cni
Pull request #1071 (2024-03-04 16:01:02)

Minor CNI v1.1 api fixes
A few small things I discovered I needed while implementing spec v1.1 for the plugins.

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

coverage: 70.083% (-0.05%) from 70.133%
when pulling **0bca329380a02f62f36e0b29465414da4aa13206 on squeed:minor-api-fixes**
into **b62753aa2bfa365c1ceaff6f25774a8047c896b5 on containernetworking:main**.

squeed: @henry118 @s1061123 fixed, PTAL

Patch:
diff --git a/libcni/api.go b/libcni/api.go
index d32c0f6..5c7f3b0 100644
--- a/libcni/api.go
+++ b/libcni/api.go
@@ -89,12 +89,8 @@ type NetworkAttachment struct {
 	CapabilityArgs map[string]interface{}
 }
 
-type GCAttachment struct {
-	ContainerID string `json:"containerID"`
-	IfName      string `json:"ifname"`
-}
 type GCArgs struct {
-	ValidAttachments []GCAttachment
+	ValidAttachments []types.GCAttachment
 }
 
 type CNI interface {
@@ -768,7 +764,7 @@ func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList,
 		return nil
 	}
 
-	validAttachments := make(map[GCAttachment]interface{}, len(args.ValidAttachments))
+	validAttachments := make(map[types.GCAttachment]interface{}, len(args.ValidAttachments))
 	for _, a := range args.ValidAttachments {
 		validAttachments[a] = nil
 	}
@@ -780,7 +776,7 @@ func (c *CNIConfig) GCNetworkList(ctx context.Context, list *NetworkConfigList,
 			continue
 		}
 		// we found this attachment
-		gca := GCAttachment{
+		gca := types.GCAttachment{
 			ContainerID: cachedAttachment.ContainerID,
 			IfName:      cachedAttachment.IfName,
 		}
diff --git a/pkg/invoke/delegate.go b/pkg/invoke/delegate.go
index 8defe4d..c8b548e 100644
--- a/pkg/invoke/delegate.go
+++ b/pkg/invoke/delegate.go
@@ -51,25 +51,34 @@ func DelegateAdd(ctx context.Context, delegatePlugin string, netconf []byte, exe
 // DelegateCheck calls the given delegate plugin with the CNI CHECK action and
 // JSON configuration
 func DelegateCheck(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
+	return delegateNoResult(ctx, delegatePlugin, netconf, exec, "CHECK")
+}
+
+func delegateNoResult(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec, verb string) error {
 	pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
 	if err != nil {
 		return err
 	}
 
-	// DelegateCheck will override the original CNI_COMMAND env from process with CHECK
-	return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("CHECK"), realExec)
+	return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs(verb), realExec)
 }
 
 // DelegateDel calls the given delegate plugin with the CNI DEL action and
 // JSON configuration
 func DelegateDel(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
-	pluginPath, realExec, err := delegateCommon(delegatePlugin, exec)
-	if err != nil {
-		return err
-	}
+	return delegateNoResult(ctx, delegatePlugin, netconf, exec, "DEL")
+}
 
-	// DelegateDel will override the original CNI_COMMAND env from process with DEL
-	return ExecPluginWithoutResult(ctx, pluginPath, netconf, delegateArgs("DEL"), realExec)
+// DelegateStatus calls the given delegate plugin with the CNI STATUS action and
+// JSON configuration
+func DelegateStatus(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
+	return delegateNoResult(ctx, delegatePlugin, netconf, exec, "STATUS")
+}
+
+// DelegateGC calls the given delegate plugin with the CNI GC action and
+// JSON configuration
+func DelegateGC(ctx context.Context, delegatePlugin string, netconf []byte, exec Exec) error {
+	return delegateNoResult(ctx, delegatePlugin, netconf, exec, "GC")
 }
 
 // return CNIArgs used by delegation
diff --git a/pkg/types/types.go b/pkg/types/types.go
index ed7a1bd..193ac46 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -68,6 +68,17 @@ type NetConf struct {
 
 	RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
 	PrevResult    Result                 `json:"-"`
+
+	// ValidAttachments is only supplied when executing a GC operation
+	ValidAttachments []GCAttachment `json:"cni.dev/valid-attachments,omitempty"`
+}
+
+// GCAttachment is the parameters to a GC call -- namely,
+// the container ID and ifname pair that represents a
+// still-valid attachment.
+type GCAttachment struct {
+	ContainerID string `json:"containerID"`
+	IfName      string `json:"ifname"`
 }
 
 // Note: DNS should be omit if DNS is empty but default Marshal function


Test patch:
diff --git a/libcni/api_test.go b/libcni/api_test.go
index c00424d..3325463 100644
--- a/libcni/api_test.go
+++ b/libcni/api_test.go
@@ -1513,7 +1513,7 @@ var _ = Describe("Invoking plugins", func() {
 
 				By("Issuing a GC with valid networks")
 				gcargs := &libcni.GCArgs{
-					ValidAttachments: []libcni.GCAttachment{{
+					ValidAttachments: []types.GCAttachment{{
 						ContainerID: runtimeConfig.ContainerID,
 						IfName:      runtimeConfig.IfName,
 					}},
diff --git a/pkg/invoke/delegate_test.go b/pkg/invoke/delegate_test.go
index d7869bc..023fa8e 100644
--- a/pkg/invoke/delegate_test.go
+++ b/pkg/invoke/delegate_test.go
@@ -223,4 +223,48 @@ var _ = Describe("Delegate", func() {
 			})
 		})
 	})
+
+	Describe("DelegateStatus", func() {
+		BeforeEach(func() {
+			os.Setenv("CNI_COMMAND", "STATUS")
+		})
+
+		It("finds and execs the named plugin", func() {
+			err := invoke.DelegateStatus(ctx, pluginName, netConf, nil)
+			Expect(err).NotTo(HaveOccurred())
+
+			pluginInvocation, err := debug.ReadDebug(debugFileName)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(pluginInvocation.Command).To(Equal("STATUS"))
+		})
+
+		Context("if the STATUS delegation runs on an existing non-STATUS command", func() {
+			BeforeEach(func() {
+				os.Setenv("CNI_COMMAND", "NOPE")
+			})
+
+			It("aborts and returns a useful error", func() {
+				err := invoke.DelegateStatus(ctx, pluginName, netConf, nil)
+				Expect(err).NotTo(HaveOccurred())
+
+				pluginInvocation, err := debug.ReadDebug(debugFileName)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(pluginInvocation.Command).To(Equal("STATUS"))
+
+				// check the original env
+				Expect(os.Getenv("CNI_COMMAND")).To(Equal("NOPE"))
+			})
+		})
+
+		Context("when the plugin cannot be found", func() {
+			BeforeEach(func() {
+				pluginName = "non-existent-plugin"
+			})
+
+			It("returns a useful error", func() {
+				err := invoke.DelegateStatus(ctx, pluginName, netConf, nil)
+				Expect(err).To(MatchError(HavePrefix("failed to find plugin")))
+			})
+		})
+	})
 })