mirror of
https://github.com/k8snetworkplumbingwg/multus-cni.git
synced 2025-10-22 07:52:12 +00:00
adding conflist support
This commit is contained in:
committed by
Kuralamudhan Ramakrishnan
parent
42e6894962
commit
7372922617
30
examples/npwg-demo-1/07_ptp_tuning_conflist.yml
Normal file
30
examples/npwg-demo-1/07_ptp_tuning_conflist.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
apiVersion: "k8s.cni.cncf.io/v1"
|
||||
kind: NetworkAttachmentDefinition
|
||||
metadata:
|
||||
name: ptp-tuning-conflist
|
||||
spec:
|
||||
config: '{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "ptp-tuning-conflist",
|
||||
"plugins": [{
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 512,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "172.16.0.0/24"
|
||||
},
|
||||
"dns": {
|
||||
"nameservers": ["172.16.1.1"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "mytuning",
|
||||
"type": "tuning",
|
||||
"sysctl": {
|
||||
"net.core.somaxconn": "500"
|
||||
}
|
||||
}
|
||||
]
|
||||
}'
|
15
examples/npwg-demo-1/15_pod_case5.yml
Normal file
15
examples/npwg-demo-1/15_pod_case5.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod-case-05
|
||||
annotations:
|
||||
k8s.v1.cni.cncf.io/networks: '[
|
||||
{ "name": "ptp-tuning-conflist" }
|
||||
]'
|
||||
spec:
|
||||
containers:
|
||||
- name: pod-case-05
|
||||
image: docker.io/centos/tools:latest
|
||||
command:
|
||||
- /sbin/init
|
@@ -55,7 +55,7 @@ func (d *defaultKubeClient) GetPod(namespace, name string) (*v1.Pod, error) {
|
||||
return d.client.Core().Pods(namespace).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
func getPodNetworkAnnotation(client KubeClient, k8sArgs types.K8sArgs) (string, string, error) {
|
||||
func getPodNetworkAnnotation(client KubeClient, k8sArgs *types.K8sArgs) (string, string, error) {
|
||||
var err error
|
||||
|
||||
pod, err := client.GetPod(string(k8sArgs.K8S_POD_NAMESPACE), string(k8sArgs.K8S_POD_NAME))
|
||||
@@ -152,10 +152,8 @@ func getCNIConfigFromFile(name string, confdir string) ([]byte, error) {
|
||||
// or .config (in that order) file on-disk whose JSON
|
||||
// “name” key matches this Network object’s name.
|
||||
|
||||
//Todo
|
||||
// support conflist for chaining mechanism
|
||||
// In part, adapted from K8s pkg/kubelet/dockershim/network/cni/cni.go#getDefaultCNINetwork
|
||||
files, err := libcni.ConfFiles(confdir, []string{".conf", ".json"})
|
||||
files, err := libcni.ConfFiles(confdir, []string{".conf", ".json", ".conflist"})
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("No networks found in %s", confdir)
|
||||
@@ -164,18 +162,31 @@ func getCNIConfigFromFile(name string, confdir string) ([]byte, error) {
|
||||
}
|
||||
|
||||
for _, confFile := range files {
|
||||
conf, err := libcni.ConfFromFile(confFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading CNI config file %s: %v", confFile, err)
|
||||
}
|
||||
|
||||
if conf.Network.Name == name {
|
||||
// Ensure the config has a "type" so we know what plugin to run.
|
||||
// Also catches the case where somebody put a conflist into a conf file.
|
||||
if conf.Network.Type == "" {
|
||||
return nil, fmt.Errorf("Error loading CNI config file %s: no 'type'; perhaps this is a .conflist?", confFile)
|
||||
var confList *libcni.NetworkConfigList
|
||||
if strings.HasSuffix(confFile, ".conflist") {
|
||||
confList, err = libcni.ConfListFromFile(confFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading CNI conflist file %s: %v", confFile, err)
|
||||
}
|
||||
|
||||
if confList.Name == name {
|
||||
return confList.Bytes, nil
|
||||
}
|
||||
|
||||
} else {
|
||||
conf, err := libcni.ConfFromFile(confFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading CNI config file %s: %v", confFile, err)
|
||||
}
|
||||
|
||||
if conf.Network.Name == name {
|
||||
// Ensure the config has a "type" so we know what plugin to run.
|
||||
// Also catches the case where somebody put a conflist into a conf file.
|
||||
if conf.Network.Type == "" {
|
||||
return nil, fmt.Errorf("Error loading CNI config file %s: no 'type'; perhaps this is a .conflist?", confFile)
|
||||
}
|
||||
return conf.Bytes, nil
|
||||
}
|
||||
return conf.Bytes, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +222,7 @@ func cniConfigFromNetworkResource(customResource *types.NetworkAttachmentDefinit
|
||||
var err error
|
||||
|
||||
emptySpec := types.NetworkAttachmentDefinitionSpec{}
|
||||
if (customResource.Spec == emptySpec) {
|
||||
if customResource.Spec == emptySpec {
|
||||
// Network Spec empty; generate delegate from CNI JSON config
|
||||
// from the configuration directory that has the same network
|
||||
// name as the custom resource
|
||||
@@ -262,6 +273,17 @@ type KubeClient interface {
|
||||
GetPod(namespace, name string) (*v1.Pod, error)
|
||||
}
|
||||
|
||||
func GetK8sArgs(args *skel.CmdArgs) (*types.K8sArgs, error) {
|
||||
k8sArgs := &types.K8sArgs{}
|
||||
|
||||
err := cnitypes.LoadArgs(args.Args, k8sArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return k8sArgs, nil
|
||||
}
|
||||
|
||||
func GetK8sClient(kubeconfig string, kubeClient KubeClient) (KubeClient, error) {
|
||||
// If we get a valid kubeClient (eg from testcases) just return that
|
||||
// one.
|
||||
@@ -299,13 +321,7 @@ func GetK8sClient(kubeconfig string, kubeClient KubeClient) (KubeClient, error)
|
||||
return &defaultKubeClient{client: client}, nil
|
||||
}
|
||||
|
||||
func GetK8sNetwork(k8sclient KubeClient, args *skel.CmdArgs, confdir string) ([]*types.DelegateNetConf, error) {
|
||||
k8sArgs := types.K8sArgs{}
|
||||
|
||||
err := cnitypes.LoadArgs(args.Args, &k8sArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func GetK8sNetwork(k8sclient KubeClient, k8sArgs *types.K8sArgs, confdir string) ([]*types.DelegateNetConf, error) {
|
||||
|
||||
netAnnot, defaultNamespace, err := getPodNetworkAnnotation(k8sclient, k8sArgs)
|
||||
if err != nil {
|
||||
|
113
multus/multus.go
113
multus/multus.go
@@ -25,6 +25,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
cnitypes "github.com/containernetworking/cni/pkg/types"
|
||||
@@ -104,43 +105,98 @@ func validateIfName(nsname string, ifname string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func delegateAdd(exec invoke.Exec, ifName string, delegate *types.DelegateNetConf) (cnitypes.Result, error) {
|
||||
if os.Setenv("CNI_IFNAME", ifName) != nil {
|
||||
return nil, fmt.Errorf("Multus: error in setting CNI_IFNAME")
|
||||
}
|
||||
func conflistAdd(rt *libcni.RuntimeConf, rawnetconflist []byte, binDir string) (cnitypes.Result, error) {
|
||||
// In part, adapted from K8s pkg/kubelet/dockershim/network/cni/cni.go
|
||||
binDirs := []string{binDir}
|
||||
cniNet := libcni.CNIConfig{Path: binDirs}
|
||||
|
||||
if err := validateIfName(os.Getenv("CNI_NETNS"), ifName); err != nil {
|
||||
return nil, fmt.Errorf("cannot set %q ifname to %q: %v", delegate.Type, ifName, err)
|
||||
}
|
||||
|
||||
result, err := invoke.DelegateAdd(delegate.Type, delegate.Bytes, exec)
|
||||
confList, err := libcni.ConfListFromBytes(rawnetconflist)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Multus: error in invoke Delegate add - %q: %v", delegate.Type, err)
|
||||
return nil, fmt.Errorf("error in converting the raw bytes to conflist: %v", err)
|
||||
}
|
||||
|
||||
result, err := cniNet.AddNetworkList(confList, rt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in getting result from AddNetworkList: %v", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func delegateDel(exec invoke.Exec, ifName string, delegateConf *types.DelegateNetConf) error {
|
||||
func conflistDel(rt *libcni.RuntimeConf, rawnetconflist []byte, binDir string) error {
|
||||
// In part, adapted from K8s pkg/kubelet/dockershim/network/cni/cni.go
|
||||
binDirs := []string{binDir}
|
||||
cniNet := libcni.CNIConfig{Path: binDirs}
|
||||
|
||||
confList, err := libcni.ConfListFromBytes(rawnetconflist)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in converting the raw bytes to conflist: %v", err)
|
||||
}
|
||||
|
||||
err = cniNet.DelNetworkList(confList, rt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in getting result from DelNetworkList: %v", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func delegateAdd(exec invoke.Exec, ifName string, delegate *types.DelegateNetConf, rt *libcni.RuntimeConf, binDir string) (cnitypes.Result, error) {
|
||||
if os.Setenv("CNI_IFNAME", ifName) != nil {
|
||||
return nil, fmt.Errorf("Multus: error in setting CNI_IFNAME")
|
||||
}
|
||||
|
||||
if err := validateIfName(os.Getenv("CNI_NETNS"), ifName); err != nil {
|
||||
return nil, fmt.Errorf("cannot set %q ifname to %q: %v", delegate.Conf.Type, ifName, err)
|
||||
}
|
||||
|
||||
if delegate.ConfListPlugin != false {
|
||||
result, err := conflistAdd(rt, delegate.Bytes, binDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Multus: error in invoke Conflist add - %q: %v", delegate.ConfList.Name, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
result, err := invoke.DelegateAdd(delegate.Conf.Type, delegate.Bytes, exec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Multus: error in invoke Delegate add - %q: %v", delegate.Conf.Type, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func delegateDel(exec invoke.Exec, ifName string, delegateConf *types.DelegateNetConf, rt *libcni.RuntimeConf, binDir string) error {
|
||||
if os.Setenv("CNI_IFNAME", ifName) != nil {
|
||||
return fmt.Errorf("Multus: error in setting CNI_IFNAME")
|
||||
}
|
||||
|
||||
if err := invoke.DelegateDel(delegateConf.Type, delegateConf.Bytes, exec); err != nil {
|
||||
return fmt.Errorf("Multus: error in invoke Delegate del - %q: %v", delegateConf.Type, err)
|
||||
if delegateConf.ConfListPlugin != false {
|
||||
err := conflistDel(rt, delegateConf.Bytes, binDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Multus: error in invoke Conflist Del - %q: %v", delegateConf.ConfList.Name, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := invoke.DelegateDel(delegateConf.Conf.Type, delegateConf.Bytes, exec); err != nil {
|
||||
return fmt.Errorf("Multus: error in invoke Delegate del - %q: %v", delegateConf.Conf.Type, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func delPlugins(exec invoke.Exec, argIfname string, delegates []*types.DelegateNetConf, lastIdx int) error {
|
||||
func delPlugins(exec invoke.Exec, argIfname string, delegates []*types.DelegateNetConf, lastIdx int, rt *libcni.RuntimeConf, binDir string) error {
|
||||
if os.Setenv("CNI_COMMAND", "DEL") != nil {
|
||||
return fmt.Errorf("Multus: error in setting CNI_COMMAND to DEL")
|
||||
}
|
||||
|
||||
for idx := lastIdx; idx >= 0; idx-- {
|
||||
ifName := getIfname(delegates[idx], argIfname, idx)
|
||||
if err := delegateDel(exec, ifName, delegates[idx]); err != nil {
|
||||
rt.IfName = ifName
|
||||
if err := delegateDel(exec, ifName, delegates[idx], rt, binDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -150,7 +206,7 @@ func delPlugins(exec invoke.Exec, argIfname string, delegates []*types.DelegateN
|
||||
|
||||
// Attempts to load Kubernetes-defined delegates and add them to the Multus config.
|
||||
// Returns the number of Kubernetes-defined delegates added or an error.
|
||||
func tryLoadK8sDelegates(args *skel.CmdArgs, conf *types.NetConf, kubeClient k8s.KubeClient) (int, error) {
|
||||
func tryLoadK8sDelegates(k8sArgs *types.K8sArgs, conf *types.NetConf, kubeClient k8s.KubeClient) (int, error) {
|
||||
var err error
|
||||
|
||||
kubeClient, err = k8s.GetK8sClient(conf.Kubeconfig, kubeClient)
|
||||
@@ -166,7 +222,7 @@ func tryLoadK8sDelegates(args *skel.CmdArgs, conf *types.NetConf, kubeClient k8s
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
delegates, err := k8s.GetK8sNetwork(kubeClient, args, conf.ConfDir)
|
||||
delegates, err := k8s.GetK8sNetwork(kubeClient, k8sArgs, conf.ConfDir)
|
||||
if err != nil {
|
||||
if _, ok := err.(*k8s.NoK8sNetworkError); ok {
|
||||
return 0, nil
|
||||
@@ -187,7 +243,12 @@ func cmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) (cn
|
||||
return nil, fmt.Errorf("err in loading netconf: %v", err)
|
||||
}
|
||||
|
||||
numK8sDelegates, err := tryLoadK8sDelegates(args, n, kubeClient)
|
||||
k8sArgs, err := k8s.GetK8sArgs(args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Multus: Err in getting k8s args: %v", err)
|
||||
}
|
||||
|
||||
numK8sDelegates, err := tryLoadK8sDelegates(k8sArgs, n, kubeClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -200,11 +261,13 @@ func cmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) (cn
|
||||
}
|
||||
|
||||
var result, tmpResult cnitypes.Result
|
||||
var rt *libcni.RuntimeConf
|
||||
lastIdx := 0
|
||||
for idx, delegate := range n.Delegates {
|
||||
lastIdx = idx
|
||||
ifName := getIfname(delegate, args.IfName, idx)
|
||||
tmpResult, err = delegateAdd(exec, ifName, delegate)
|
||||
rt, _ = types.LoadCNIRuntimeConf(args, k8sArgs, ifName)
|
||||
tmpResult, err = delegateAdd(exec, ifName, delegate, rt, n.BinDir)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -217,7 +280,7 @@ func cmdAdd(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) (cn
|
||||
|
||||
if err != nil {
|
||||
// Ignore errors; DEL must be idempotent anyway
|
||||
_ = delPlugins(exec, args.IfName, n.Delegates, lastIdx)
|
||||
_ = delPlugins(exec, args.IfName, n.Delegates, lastIdx, rt, n.BinDir)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -241,7 +304,12 @@ func cmdDel(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) err
|
||||
return err
|
||||
}
|
||||
|
||||
numK8sDelegates, err := tryLoadK8sDelegates(args, in, kubeClient)
|
||||
k8sArgs, err := k8s.GetK8sArgs(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Multus: Err in getting k8s args: %v", err)
|
||||
}
|
||||
|
||||
numK8sDelegates, err := tryLoadK8sDelegates(k8sArgs, in, kubeClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -262,7 +330,8 @@ func cmdDel(args *skel.CmdArgs, exec invoke.Exec, kubeClient k8s.KubeClient) err
|
||||
}
|
||||
}
|
||||
|
||||
return delPlugins(exec, args.IfName, in.Delegates, len(in.Delegates)-1)
|
||||
rt, _ := types.LoadCNIRuntimeConf(args, k8sArgs, "")
|
||||
return delPlugins(exec, args.IfName, in.Delegates, len(in.Delegates)-1, rt, in.BinDir)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@@ -19,33 +19,74 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/intel/multus-cni/logging"
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
"github.com/intel/multus-cni/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCNIDir = "/var/lib/cni/multus"
|
||||
defaultConfDir = "/etc/cni/multus/net.d"
|
||||
defaultBinDir = "/opt/cni/bin"
|
||||
)
|
||||
|
||||
func LoadDelegateNetConfList(bytes []byte, delegateConf *DelegateNetConf) error {
|
||||
|
||||
if err := json.Unmarshal(bytes, &delegateConf.ConfList); err != nil {
|
||||
return fmt.Errorf("err in unmarshalling delegate conflist: %v", err)
|
||||
}
|
||||
|
||||
if delegateConf.ConfList.Plugins == nil {
|
||||
return fmt.Errorf("delegate must have the 'type'or 'Plugin' field")
|
||||
}
|
||||
if delegateConf.ConfList.Plugins[0].Type == "" {
|
||||
return fmt.Errorf("a plugin delegate must have the 'type' field")
|
||||
}
|
||||
delegateConf.ConfListPlugin = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadCNIRuntimeConf(args *skel.CmdArgs, k8sArgs *K8sArgs, ifName string) (*libcni.RuntimeConf, error) {
|
||||
|
||||
// In part, adapted from K8s pkg/kubelet/dockershim/network/cni/cni.go#buildCNIRuntimeConf
|
||||
// Todo
|
||||
// ingress, egress and bandwidth capability features as same as kubelet.
|
||||
rt := &libcni.RuntimeConf{
|
||||
ContainerID: args.ContainerID,
|
||||
NetNS: args.Netns,
|
||||
IfName: ifName,
|
||||
Args: [][2]string{
|
||||
{"IgnoreUnknown", "1"},
|
||||
{"K8S_POD_NAMESPACE", string(k8sArgs.K8S_POD_NAMESPACE)},
|
||||
{"K8S_POD_NAME", string(k8sArgs.K8S_POD_NAME)},
|
||||
{"K8S_POD_INFRA_CONTAINER_ID", string(k8sArgs.K8S_POD_INFRA_CONTAINER_ID)},
|
||||
},
|
||||
}
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
// Convert raw CNI JSON into a DelegateNetConf structure
|
||||
func LoadDelegateNetConf(bytes []byte, ifnameRequest string) (*DelegateNetConf, error) {
|
||||
delegateConf := &DelegateNetConf{}
|
||||
if err := json.Unmarshal(bytes, delegateConf); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling delegate config: %v", err)
|
||||
if err := json.Unmarshal(bytes, &delegateConf.Conf); err != nil {
|
||||
return nil, fmt.Errorf("error in LoadDelegateNetConf - unmarshalling delegate config: %v", err)
|
||||
}
|
||||
delegateConf.Bytes = bytes
|
||||
|
||||
// Do some minimal validation
|
||||
if delegateConf.Type == "" {
|
||||
return nil, fmt.Errorf("delegate must have the 'type' field")
|
||||
if delegateConf.Conf.Type == "" {
|
||||
if err := LoadDelegateNetConfList(bytes, delegateConf); err != nil {
|
||||
return nil, fmt.Errorf("error in LoadDelegateNetConf: %v")
|
||||
}
|
||||
}
|
||||
|
||||
if ifnameRequest != "" {
|
||||
delegateConf.IfnameRequest = ifnameRequest
|
||||
}
|
||||
|
||||
delegateConf.Bytes = bytes
|
||||
|
||||
return delegateConf, nil
|
||||
}
|
||||
|
||||
@@ -93,10 +134,15 @@ func LoadNetConf(bytes []byte) (*NetConf, error) {
|
||||
if netconf.CNIDir == "" {
|
||||
netconf.CNIDir = defaultCNIDir
|
||||
}
|
||||
|
||||
if netconf.ConfDir == "" {
|
||||
netconf.ConfDir = defaultConfDir
|
||||
}
|
||||
|
||||
if netconf.BinDir == "" {
|
||||
netconf.BinDir = defaultBinDir
|
||||
}
|
||||
|
||||
for idx, rawConf := range netconf.RawDelegates {
|
||||
bytes, err := json.Marshal(rawConf)
|
||||
if err != nil {
|
||||
|
@@ -34,6 +34,7 @@ type NetConf struct {
|
||||
|
||||
ConfDir string `json:"confDir"`
|
||||
CNIDir string `json:"cniDir"`
|
||||
BinDir string `json:"binDir"`
|
||||
// RawDelegates is private to the NetConf class; use Delegates instead
|
||||
RawDelegates []map[string]interface{} `json:"delegates"`
|
||||
Delegates []*DelegateNetConf `json:"-"`
|
||||
@@ -43,10 +44,13 @@ type NetConf struct {
|
||||
}
|
||||
|
||||
type DelegateNetConf struct {
|
||||
types.NetConf
|
||||
Conf types.NetConf
|
||||
ConfList types.NetConfList
|
||||
IfnameRequest string `json:"ifnameRequest,omitempty"`
|
||||
// MasterPlugin is only used internal housekeeping
|
||||
MasterPlugin bool `json:"-"`
|
||||
// Conflist plugin is only used internal housekeeping
|
||||
ConfListPlugin bool `json:"-"`
|
||||
|
||||
// Raw JSON
|
||||
Bytes []byte
|
||||
|
Reference in New Issue
Block a user