229 lines
5.2 KiB
Go

// 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.
// This is a "meta-plugin". It reads in its own netconf, combines it with
// the data from flannel generated subnet file and then invokes a plugin
// like bridge or ipvlan to do the real work.
package main
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
)
const (
defaultSubnetFile = "/run/flannel/subnet.env"
defaultDataDir = "/var/lib/cni/flannel"
)
type NetConf struct {
types.NetConf
SubnetFile string `json:"subnetFile"`
DataDir string `json:"dataDir"`
Delegate map[string]interface{} `json:"delegate"`
}
type subnetEnv struct {
nw *net.IPNet
sn *net.IPNet
mtu *uint
ipmasq *bool
}
func (se *subnetEnv) missing() string {
m := []string{}
if se.nw == nil {
m = append(m, "FLANNEL_NETWORK")
}
if se.sn == nil {
m = append(m, "FLANNEL_SUBNET")
}
if se.mtu == nil {
m = append(m, "FLANNEL_MTU")
}
if se.ipmasq == nil {
m = append(m, "FLANNEL_IPMASQ")
}
return strings.Join(m, ", ")
}
func loadFlannelNetConf(bytes []byte) (*NetConf, error) {
n := &NetConf{
SubnetFile: defaultSubnetFile,
DataDir: defaultDataDir,
}
if err := json.Unmarshal(bytes, n); err != nil {
return nil, fmt.Errorf("failed to load netconf: %v", err)
}
return n, nil
}
func loadFlannelSubnetEnv(fn string) (*subnetEnv, error) {
f, err := os.Open(fn)
if err != nil {
return nil, err
}
defer f.Close()
se := &subnetEnv{}
s := bufio.NewScanner(f)
for s.Scan() {
parts := strings.SplitN(s.Text(), "=", 2)
switch parts[0] {
case "FLANNEL_NETWORK":
_, se.nw, err = net.ParseCIDR(parts[1])
if err != nil {
return nil, err
}
case "FLANNEL_SUBNET":
_, se.sn, err = net.ParseCIDR(parts[1])
if err != nil {
return nil, err
}
case "FLANNEL_MTU":
mtu, err := strconv.ParseUint(parts[1], 10, 32)
if err != nil {
return nil, err
}
se.mtu = new(uint)
*se.mtu = uint(mtu)
case "FLANNEL_IPMASQ":
ipmasq := parts[1] == "true"
se.ipmasq = &ipmasq
}
}
if err := s.Err(); err != nil {
return nil, err
}
if m := se.missing(); m != "" {
return nil, fmt.Errorf("%v is missing %v", fn, m)
}
return se, nil
}
func saveScratchNetConf(containerID, dataDir string, netconf []byte) error {
if err := os.MkdirAll(dataDir, 0700); err != nil {
return err
}
path := filepath.Join(dataDir, containerID)
return ioutil.WriteFile(path, netconf, 0600)
}
func consumeScratchNetConf(containerID, dataDir string) ([]byte, error) {
path := filepath.Join(dataDir, containerID)
// Ignore errors when removing - Per spec safe to continue during DEL
defer os.Remove(path)
return ioutil.ReadFile(path)
}
func delegateAdd(cid, dataDir string, netconf map[string]interface{}) error {
netconfBytes, err := json.Marshal(netconf)
if err != nil {
return fmt.Errorf("error serializing delegate netconf: %v", err)
}
// save the rendered netconf for cmdDel
if err = saveScratchNetConf(cid, dataDir, netconfBytes); err != nil {
return err
}
result, err := invoke.DelegateAdd(context.TODO(), netconf["type"].(string), netconfBytes, nil)
if err != nil {
return err
}
return result.Print()
}
func hasKey(m map[string]interface{}, k string) bool {
_, ok := m[k]
return ok
}
func isString(i interface{}) bool {
_, ok := i.(string)
return ok
}
func cmdAdd(args *skel.CmdArgs) error {
n, err := loadFlannelNetConf(args.StdinData)
if err != nil {
return err
}
fenv, err := loadFlannelSubnetEnv(n.SubnetFile)
if err != nil {
return err
}
if n.Delegate == nil {
n.Delegate = make(map[string]interface{})
} else {
if hasKey(n.Delegate, "type") && !isString(n.Delegate["type"]) {
return fmt.Errorf("'delegate' dictionary, if present, must have (string) 'type' field")
}
if hasKey(n.Delegate, "name") {
return fmt.Errorf("'delegate' dictionary must not have 'name' field, it'll be set by flannel")
}
if hasKey(n.Delegate, "ipam") {
return fmt.Errorf("'delegate' dictionary must not have 'ipam' field, it'll be set by flannel")
}
}
return doCmdAdd(args, n, fenv)
}
func cmdDel(args *skel.CmdArgs) error {
nc, err := loadFlannelNetConf(args.StdinData)
if err != nil {
return err
}
return doCmdDel(args, nc)
}
func main() {
skel.PluginMain(cmdAdd, cmdCheck, cmdDel, version.All, bv.BuildString("flannel"))
}
func cmdCheck(args *skel.CmdArgs) error {
// TODO: implement
return nil
}