Skip to content

Commit

Permalink
cnitool: deduplicate environment variable definitions
Browse files Browse the repository at this point in the history
create a reusable package for environment variable to be used
by `cnitool` and the other library code.

Signed-off-by: Gordon Bleux <33967640+UiP9AV6Y@users.noreply.github.com>
  • Loading branch information
UiP9AV6Y committed Nov 14, 2022
1 parent 76aaefb commit fb89176
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 109 deletions.
41 changes: 10 additions & 31 deletions cnitool/cnitool.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,7 @@ import (
"strings"

"github.com/containernetworking/cni/libcni"
)

// Protocol parameters are passed to the plugins via OS environment variables.
const (
EnvCNIPath = "CNI_PATH"
EnvNetDir = "NETCONFPATH"
EnvCapabilityArgs = "CAP_ARGS"
EnvCNIArgs = "CNI_ARGS"
EnvCNIIfname = "CNI_IFNAME"

DefaultNetDir = "/etc/cni/net.d"

CmdAdd = "add"
CmdCheck = "check"
CmdDel = "del"
"github.com/containernetworking/cni/pkg/env"
)

func parseArgs(args string) ([][2]string, error) {
Expand All @@ -62,37 +48,30 @@ func main() {
usage()
}

netdir := os.Getenv(EnvNetDir)
if netdir == "" {
netdir = DefaultNetDir
}
netdir := env.GetNetDir()
netconf, err := libcni.LoadConfList(netdir, os.Args[2])
if err != nil {
exit(err)
}

var capabilityArgs map[string]interface{}
capabilityArgsValue := os.Getenv(EnvCapabilityArgs)
capabilityArgsValue := env.GetCapabilityArgs()
if len(capabilityArgsValue) > 0 {
if err = json.Unmarshal([]byte(capabilityArgsValue), &capabilityArgs); err != nil {
exit(err)
}
}

var cniArgs [][2]string
args := os.Getenv(EnvCNIArgs)
args := env.GetCNIArgs()
if len(args) > 0 {
cniArgs, err = parseArgs(args)
if err != nil {
exit(err)
}
}

ifName, ok := os.LookupEnv(EnvCNIIfname)
if !ok {
ifName = "eth0"
}

ifName := env.GetCNIIfname()
netns := os.Args[3]
netns, err = filepath.Abs(netns)
if err != nil {
Expand All @@ -103,7 +82,7 @@ func main() {
s := sha512.Sum512([]byte(netns))
containerID := fmt.Sprintf("cnitool-%x", s[:10])

cninet := libcni.NewCNIConfig(filepath.SplitList(os.Getenv(EnvCNIPath)), nil)
cninet := libcni.NewCNIConfig(env.ParseCNIPath(), nil)

rt := &libcni.RuntimeConf{
ContainerID: containerID,
Expand All @@ -113,17 +92,17 @@ func main() {
CapabilityArgs: capabilityArgs,
}

switch os.Args[1] {
case CmdAdd:
switch strings.ToUpper(os.Args[1]) {
case env.CmdAdd:
result, err := cninet.AddNetworkList(context.TODO(), netconf, rt)
if result != nil {
_ = result.Print()
}
exit(err)
case CmdCheck:
case env.CmdCheck:
err := cninet.CheckNetworkList(context.TODO(), netconf, rt)
exit(err)
case CmdDel:
case env.CmdDel:
exit(cninet.DelNetworkList(context.TODO(), netconf, rt))
}
}
Expand Down
95 changes: 95 additions & 0 deletions pkg/env/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2015 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 env

import (
"os"
"path/filepath"
)

// Protocol parameters are passed to the plugins via OS environment variables.
const (
VarCapabilityArgs = "CAP_ARGS"
VarCNIArgs = "CNI_ARGS"
VarCNICommand = "CNI_COMMAND"
VarCNIContainerId = "CNI_CONTAINERID"
VarCNIIfname = "CNI_IFNAME"
VarCNINetNs = "CNI_NETNS"
VarCNINetNsOverride = "CNI_NETNS_OVERRIDE"
VarCNIPath = "CNI_PATH"
VarNetDir = "NETCONFPATH"

DefaultCapabilityArgs = ""
DefaultCNIArgs = ""
DefaultCNIIfname = "eth0"
DefaultNetDir = "/etc/cni/net.d"
DefaultCNIPath = ""

// supported CNI_COMMAND values

CmdAdd = "ADD"
CmdCheck = "CHECK"
CmdDel = "DEL"
CmdVersion = "VERSION"
)

// GetValue is a wrapper around os.GetEnv, which
// returns the given fallback value in case the
// environment variable is not set or an empty
// string.
func GetValue(key, fallbackValue string) string {
val := os.Getenv(key)
if val == "" {
return fallbackValue
}

return val
}

// GetNetDir gets the network configuration
// directory setting from the environment
func GetNetDir() string {
return GetValue(VarNetDir, DefaultNetDir)
}

// GetCapabilityArgs gets the capability
// settings from the environment
func GetCapabilityArgs() string {
return GetValue(VarCapabilityArgs, DefaultCapabilityArgs)
}

// GetCNIArgs gets the plugin arguments
// from the environment
func GetCNIArgs() string {
return GetValue(VarCNIArgs, DefaultCNIArgs)
}

// GetCNIIfname gets the network interface
// setting from the environment
func GetCNIIfname() string {
return GetValue(VarCNIIfname, DefaultCNIIfname)
}

// GetCNIPath gets the plugin lookup path
// from the environment
func GetCNIPath() string {
return GetValue(VarCNIPath, DefaultCNIPath)
}

// ParseCNIPath returns a list of directories to
// search for executables
func ParseCNIPath() []string {
return filepath.SplitList(GetCNIPath())
}
77 changes: 77 additions & 0 deletions pkg/env/env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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 env_test

import (
"os"
"strings"

"github.com/containernetworking/cni/pkg/env"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("GetValue", func() {
BeforeEach(func() {
os.Setenv("FOO", "bar")
os.Unsetenv("BAC")
})

Context("when environment has variable", func() {
It("returns the variable value", func() {
val := env.GetValue("FOO", "gaff")
Expect(val).To(Equal("bar"))
})
})

Context("when environment does not have variable", func() {
It("returns the fallback value", func() {
val := env.GetValue("BAC", "gaff")
Expect(val).To(Equal("gaff"))
})
})
})

var _ = Describe("ParseCNIPath", func() {
BeforeEach(func() {
os.Unsetenv("CNI_PATH")
})

AfterEach(func() {
os.Unsetenv("CNI_PATH")
})

Context("when no directories are specified", func() {
It("returns an empty list", func() {
val := env.ParseCNIPath()
Expect(val).To(BeEmpty())
})
})

Context("when multiple directories are specified", func() {
It("returns the directories as a list", func() {
mockCNIPath("/test/bin", "/test/libexec")

val := env.ParseCNIPath()
Expect(val).To(Equal([]string{"/test/bin", "/test/libexec"}))
})
})
})

func mockCNIPath(dir ...string) {
mock := strings.Join(dir, string(os.PathListSeparator))

os.Setenv(env.VarCNIPath, mock)
}
34 changes: 18 additions & 16 deletions pkg/invoke/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"fmt"
"os"
"strings"

"github.com/containernetworking/cni/pkg/env"
)

type CNIArgs interface {
Expand Down Expand Up @@ -54,23 +56,23 @@ type Args struct {
var _ CNIArgs = &Args{}

func (args *Args) AsEnv() []string {
env := os.Environ()
environ := os.Environ()
pluginArgsStr := args.PluginArgsStr
if pluginArgsStr == "" {
pluginArgsStr = stringify(args.PluginArgs)
}

// Duplicated values which come first will be overridden, so we must put the
// custom values in the end to avoid being overridden by the process environments.
env = append(env,
"CNI_COMMAND="+args.Command,
"CNI_CONTAINERID="+args.ContainerID,
"CNI_NETNS="+args.NetNS,
"CNI_ARGS="+pluginArgsStr,
"CNI_IFNAME="+args.IfName,
"CNI_PATH="+args.Path,
environ = append(environ,
env.VarCNICommand+"="+args.Command,
env.VarCNIContainerId+"="+args.ContainerID,
env.VarCNINetNs+"="+args.NetNS,
env.VarCNIArgs+"="+pluginArgsStr,
env.VarCNIIfname+"="+args.IfName,
env.VarCNIPath+"="+args.Path,
)
return dedupEnv(env)
return dedupEnv(environ)
}

// taken from rkt/networking/net_plugin.go
Expand All @@ -94,23 +96,23 @@ type DelegateArgs struct {
}

func (d *DelegateArgs) AsEnv() []string {
env := os.Environ()
environ := os.Environ()

// The custom values should come in the end to override the existing
// process environment of the same key.
env = append(env,
"CNI_COMMAND="+d.Command,
environ = append(environ,
env.VarCNICommand+"="+d.Command,
)
return dedupEnv(env)
return dedupEnv(environ)
}

// dedupEnv returns a copy of env with any duplicates removed, in favor of later values.
// Items not of the normal environment "key=value" form are preserved unchanged.
func dedupEnv(env []string) []string {
out := make([]string, 0, len(env))
func dedupEnv(environ []string) []string {
out := make([]string, 0, len(environ))
envMap := map[string]string{}

for _, kv := range env {
for _, kv := range environ {
// find the first "=" in environment, if not, just keep it
eq := strings.Index(kv, "=")
if eq < 0 {
Expand Down
5 changes: 2 additions & 3 deletions pkg/invoke/delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ package invoke

import (
"context"
"os"
"path/filepath"

"github.com/containernetworking/cni/pkg/env"
"github.com/containernetworking/cni/pkg/types"
)

Expand All @@ -27,7 +26,7 @@ func delegateCommon(delegatePlugin string, exec Exec) (string, Exec, error) {
exec = defaultExec
}

paths := filepath.SplitList(os.Getenv("CNI_PATH"))
paths := env.ParseCNIPath()
pluginPath, err := exec.FindInPath(delegatePlugin, paths)
if err != nil {
return "", nil, err
Expand Down
Loading

0 comments on commit fb89176

Please sign in to comment.