// Copyright (c) 2023 Multus 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 entrypoint for thin (stand-alone) images.
package main

import (
	"bytes"
	"crypto/sha256"
	b64 "encoding/base64"
	"encoding/json"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/containernetworking/cni/libcni"
	cniversion "github.com/containernetworking/cni/pkg/version"
	"github.com/spf13/pflag"

	"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/cmdutils"
	"gopkg.in/k8snetworkplumbingwg/multus-cni.v4/pkg/signals"
)

// Options stores command line options
type Options struct {
	CNIBinDir                string
	CNIConfDir               string
	CNIVersion               string
	MultusConfFile           string
	MultusBinFile            string // may be hidden or remove?
	MultusCNIConfDir         string
	SkipMultusBinaryCopy     bool
	MultusKubeConfigFileHost string
	MultusMasterCNIFileName  string
	NamespaceIsolation       bool
	GlobalNamespaces         string
	MultusAutoconfigDir      string
	MultusLogToStderr        bool
	MultusLogLevel           string
	MultusLogFile            string
	OverrideNetworkName      bool
	CleanupConfigOnExit      bool
	RenameConfFile           bool
	ReadinessIndicatorFile   string
	AdditionalBinDir         string
	ForceCNIVersion          bool
	SkipTLSVerify            bool
	SkipMultusConfWatch      bool
}

const (
	serviceAccountTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
	serviceAccountCAFile    = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
)

func (o *Options) addFlags() {
	pflag.ErrHelp = nil // suppress error message for help
	fs := pflag.CommandLine
	fs.StringVar(&o.CNIBinDir, "cni-bin-dir", "/host/opt/cni/bin", "CNI binary directory")
	fs.StringVar(&o.CNIConfDir, "cni-conf-dir", "/host/etc/cni/net.d", "CNI config directory")
	fs.StringVar(&o.CNIVersion, "cni-version", "", "CNI version for multus CNI config (e.g. '0.3.1')")
	fs.StringVar(&o.MultusConfFile, "multus-conf-file", "auto", "multus CNI config file")
	fs.StringVar(&o.MultusBinFile, "multus-bin-file", "/usr/src/multus-cni/bin/multus", "multus binary file path")
	fs.StringVar(&o.MultusCNIConfDir, "multus-cni-conf-dir", "/host/etc/cni/multus/net.d", "multus specific CNI config directory")
	fs.BoolVar(&o.SkipMultusBinaryCopy, "skip-multus-binary-copy", false, "skip multus binary file copy")

	fs.StringVar(&o.MultusKubeConfigFileHost, "multus-kubeconfig-file-host", "/etc/cni/net.d/multus.d/multus.kubeconfig", "kubeconfig for multus (used only with --multus-conf-file=auto)")
	fs.StringVar(&o.MultusMasterCNIFileName, "multus-master-cni-file-name", "", "master CNI file in multus-autoconfig-dir")
	fs.BoolVar(&o.NamespaceIsolation, "namespace-isolation", false, "namespace isolation")
	fs.StringVar(&o.GlobalNamespaces, "global-namespaces", "", "global namespaces, comma separated (used only with --namespace-isolation=true)")
	fs.StringVar(&o.MultusAutoconfigDir, "multus-autoconfig-dir", "/host/etc/cni/net.d", "multus autoconfig dir (used only with --multus-conf-file=auto)")
	fs.BoolVar(&o.MultusLogToStderr, "multus-log-to-stderr", true, "log to stderr")
	fs.StringVar(&o.MultusLogLevel, "multus-log-level", "", "multus log level")
	fs.StringVar(&o.MultusLogFile, "multus-log-file", "", "multus log file")
	fs.BoolVar(&o.OverrideNetworkName, "override-network-name", false, "override network name from master cni file (used only with --multus-conf-file=auto)")
	fs.BoolVar(&o.CleanupConfigOnExit, "cleanup-config-on-exit", false, "cleanup config file on exit")
	fs.BoolVar(&o.SkipMultusConfWatch, "skip-config-watch", false, "dont watch for config (master cni and kubeconfig) changes (used only with --multus-conf-file=auto)")
	fs.BoolVar(&o.RenameConfFile, "rename-conf-file", false, "rename master config file to invalidate (used only with --multus-conf-file=auto)")
	fs.StringVar(&o.ReadinessIndicatorFile, "readiness-indicator-file", "", "readiness indicator file (used only with --multus-conf-file=auto)")
	fs.StringVar(&o.AdditionalBinDir, "additional-bin-dir", "", "adds binDir option to configuration (used only with --multus-conf-file=auto)")
	fs.BoolVar(&o.SkipTLSVerify, "skip-tls-verify", false, "skip TLS verify")
	fs.BoolVar(&o.ForceCNIVersion, "force-cni-version", false, "force cni version to '--cni-version' (only for e2e-kind testing)")
	fs.MarkHidden("force-cni-version")
	fs.MarkHidden("skip-tls-verify")
}

func (o *Options) verifyFileExists() error {
	// CNIConfDir
	if _, err := os.Stat(o.CNIConfDir); err != nil {
		return fmt.Errorf("cni-conf-dir is not found: %v", err)
	}

	// CNIBinDir
	if _, err := os.Stat(o.CNIBinDir); err != nil {
		return fmt.Errorf("cni-bin-dir is not found: %v", err)
	}

	// MultusBinFile
	if _, err := os.Stat(o.MultusBinFile); err != nil {
		return fmt.Errorf("multus-bin-file is not found: %v", err)
	}

	if o.MultusConfFile != "auto" {
		// MultusConfFile
		if _, err := os.Stat(o.MultusConfFile); err != nil {
			return fmt.Errorf("multus-conf-file is not found: %v", err)
		}
	}
	return nil
}

const kubeConfigTemplate = `# Kubeconfig file for Multus CNI plugin.
apiVersion: v1
kind: Config
clusters:
- name: local
  cluster:
    server: {{ .KubeConfigHost }}
    {{ .KubeServerTLS }}
users:
- name: multus
  user:
    token: "{{ .KubeServiceAccountToken }}"
contexts:
- name: multus-context
  context:
    cluster: local
    user: multus
current-context: multus-context
`

func getFileAndHash(filepath string) ([]byte, []byte, error) {
	if _, err := os.Stat(filepath); err != nil {
		return nil, nil, fmt.Errorf("file %s not found: %v", filepath, err)
	}
	content, err := os.ReadFile(filepath)
	if err != nil {
		return nil, nil, fmt.Errorf("cannot read %s file: %v", filepath, err)
	}

	hash := sha256.New()
	hash.Write(content)
	return content, hash.Sum(nil), nil
}

func (o *Options) createKubeConfig(prevCAHash, prevSATokenHash []byte) ([]byte, []byte, error) {
	caFileByte, caHash, err := getFileAndHash(serviceAccountCAFile)
	if err != nil {
		return nil, nil, err
	}
	saTokenByte, saTokenHash, err := getFileAndHash(serviceAccountTokenFile)
	if err != nil {
		return nil, nil, err
	}

	caUnchanged := prevCAHash != nil && bytes.Equal(prevCAHash, caHash)
	saUnchanged := prevSATokenHash != nil && bytes.Equal(prevSATokenHash, saTokenHash)

	if o.SkipTLSVerify {
		if saUnchanged {
			return caHash, saTokenHash, nil
		}
	} else {
		if caUnchanged && saUnchanged {
			return caHash, saTokenHash, nil
		}
	}

	if prevSATokenHash != nil {
		// don't log "recreating" on first function execution
		fmt.Printf("CA (%v) or SA token (%v) changed - recreating kubeconfig\n", !caUnchanged, !saUnchanged)
	}

	// create multus.d directory
	if err := os.MkdirAll(fmt.Sprintf("%s/multus.d", o.CNIConfDir), 0755); err != nil {
		return nil, nil, fmt.Errorf("cannot create multus.d directory: %v", err)
	}

	// create multus cni conf directory
	if err := os.MkdirAll(o.MultusCNIConfDir, 0755); err != nil {
		return nil, nil, fmt.Errorf("cannot create multus-cni-conf-dir(%s) directory: %v", o.MultusCNIConfDir, err)
	}

	// get Kubernetes service protocol/host/port
	kubeProtocol := os.Getenv("KUBERNETES_SERVICE_PROTOCOL")
	if kubeProtocol == "" {
		kubeProtocol = "https"
	}
	kubeHost := os.Getenv("KUBERNETES_SERVICE_HOST")
	kubePort := os.Getenv("KUBERNETES_SERVICE_PORT")

	// check tlsConfig
	tlsConfig := ""
	if o.SkipTLSVerify {
		tlsConfig = "insecure-skip-tls-verify: true"
	} else {
		// create tlsConfig by service account CA file
		caFileB64 := bytes.ReplaceAll([]byte(b64.StdEncoding.EncodeToString(caFileByte)), []byte("\n"), []byte(""))
		tlsConfig = fmt.Sprintf("certificate-authority-data: %s", string(caFileB64))
	}

	// create kubeconfig by template and replace it by atomic
	tempKubeConfigFile := fmt.Sprintf("%s/multus.d/multus.kubeconfig.new", o.CNIConfDir)
	multusKubeConfig := fmt.Sprintf("%s/multus.d/multus.kubeconfig", o.CNIConfDir)
	fp, err := os.OpenFile(tempKubeConfigFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return nil, nil, fmt.Errorf("cannot create kubeconfig temp file: %v", err)
	}

	templateKubeconfig, err := template.New("kubeconfig").Parse(kubeConfigTemplate)
	if err != nil {
		return nil, nil, fmt.Errorf("template parse error: %v", err)
	}
	templateData := map[string]string{
		"KubeConfigHost":          fmt.Sprintf("%s://%s", kubeProtocol, net.JoinHostPort(kubeHost, kubePort)),
		"KubeServerTLS":           tlsConfig,
		"KubeServiceAccountToken": string(saTokenByte),
	}

	// generate kubeconfig from template
	if err = templateKubeconfig.Execute(fp, templateData); err != nil {
		return nil, nil, fmt.Errorf("cannot create kubeconfig: %v", err)
	}

	if err := fp.Sync(); err != nil {
		os.Remove(fp.Name())
		return nil, nil, fmt.Errorf("cannot flush kubeconfig temp file: %v", err)
	}
	if err := fp.Close(); err != nil {
		os.Remove(fp.Name())
		return nil, nil, fmt.Errorf("cannot close kubeconfig temp file: %v", err)
	}

	// replace file with tempfile
	if err := os.Rename(tempKubeConfigFile, multusKubeConfig); err != nil {
		return nil, nil, fmt.Errorf("cannot replace %q with temp file %q: %v", multusKubeConfig, tempKubeConfigFile, err)
	}

	fmt.Printf("kubeconfig is created in %s\n", multusKubeConfig)
	return caHash, saTokenHash, nil
}

const multusConflistTemplate = `{
    "cniVersion": "{{ .CNIVersion }}",
    "name": "{{ .MasterPluginNetworkName }}",
    "plugins": [ {
        "type": "multus",{{
            .NestedCapabilities
        }}{{
            .NamespaceIsolationConfig
        }}{{
            .GlobalNamespacesConfig
        }}{{
            .LogToStderrConfig
        }}{{
            .LogLevelConfig
        }}{{
            .LogFileConfig
        }}{{
            .AdditionalBinDirConfig
        }}{{
            .MultusCNIConfDirConfig
        }}{{
            .ReadinessIndicatorFileConfig
        }}
        "kubeconfig": "{{ .MultusKubeConfigFileHost }}",
        "delegates": [
            {{ .MasterPluginJSON }}
        ]
    }]
}
`

const multusConfTemplate = `{
        "cniVersion": "{{ .CNIVersion }}",
        "name": "{{ .MasterPluginNetworkName }}",
        "type": "multus",{{
            .NestedCapabilities
        }}{{
            .NamespaceIsolationConfig
        }}{{
            .GlobalNamespacesConfig
        }}{{
            .LogToStderrConfig
        }}{{
            .LogLevelConfig
        }}{{
            .LogFileConfig
        }}{{
            .AdditionalBinDirConfig
        }}{{
            .MultusCNIConfDirConfig
        }}{{
            .ReadinessIndicatorFileConfig
        }}
        "kubeconfig": "{{ .MultusKubeConfigFileHost }}",
        "delegates": [
                {{ .MasterPluginJSON }}
        ]
}
`

func (o *Options) getMasterConfigPath() (string, error) {
	// Master config file is specified
	if o.MultusMasterCNIFileName != "" {
		return filepath.Join(o.MultusAutoconfigDir, o.MultusMasterCNIFileName), nil
	}

	// Pick the alphabetically first config file from MultusAutoconfigDir
	files, err := libcni.ConfFiles(o.MultusAutoconfigDir, []string{".conf", ".conflist"})
	if err != nil {
		return "", fmt.Errorf("cannot find master CNI config in %q: %v", o.MultusAutoconfigDir, err)
	}

	for _, filename := range files {
		if !strings.HasPrefix(filepath.Base(filename), "00-multus.conf") {
			return filename, nil
		}
	}

	// No config file found
	return "", fmt.Errorf("cannot find valid master CNI config in %q", o.MultusAutoconfigDir)
}

func (o *Options) createMultusConfig(prevMasterConfigFileHash []byte) (string, []byte, error) {
	masterConfigPath, err := o.getMasterConfigPath()
	if err != nil {
		return "", nil, err
	}

	masterConfigBytes, masterConfigFileHash, err := getFileAndHash(masterConfigPath)
	if err != nil {
		return "", nil, err
	}

	if prevMasterConfigFileHash != nil && bytes.Equal(prevMasterConfigFileHash, masterConfigFileHash) {
		return masterConfigPath, masterConfigFileHash, nil
	}

	if prevMasterConfigFileHash != nil {
		// don't log "recreating" on first function execution
		fmt.Printf("master config changed - recreating multus config\n")
	}

	masterConfig := map[string]interface{}{}
	if err = json.Unmarshal(masterConfigBytes, &masterConfig); err != nil {
		return "", nil, fmt.Errorf("cannot read master CNI config json: %v", err)
	}

	// check CNIVersion
	masterCNIVersionElem, ok := masterConfig["cniVersion"]
	if !ok {
		return "", nil, fmt.Errorf("cannot get cniVersion in master CNI config file %q: %v", masterConfigPath, err)
	}

	if o.ForceCNIVersion {
		masterConfig["cniVersion"] = o.CNIVersion
		fmt.Printf("force CNI version to %q\n", o.CNIVersion)
	} else {
		masterCNIVersion := masterCNIVersionElem.(string)
		if o.CNIVersion != "" && masterCNIVersion != o.CNIVersion {
			return "", nil, fmt.Errorf("Multus cni version is %q while master plugin cni version is %q", o.CNIVersion, masterCNIVersion)
		}
		o.CNIVersion = masterCNIVersion
	}
	cniVersionConfig := o.CNIVersion

	// check OverrideNetworkName (if true, get master plugin name, otherwise 'multus-cni-network'
	masterPluginNetworkName := "multus-cni-network"
	if o.OverrideNetworkName {
		masterPluginNetworkElem, ok := masterConfig["name"]
		if !ok {
			return "", nil, fmt.Errorf("cannot get name in master CNI config file %q: %v", masterConfigPath, err)
		}

		masterPluginNetworkName = masterPluginNetworkElem.(string)
		fmt.Printf("master plugin name is overrided to %q\n", masterPluginNetworkName)
	}

	// check capabilities (from master conf, top and 'plugins')
	masterCapabilities := map[string]bool{}
	_, isMasterConfList := masterConfig["plugins"]

	if isMasterConfList {
		masterPluginsElem, ok := masterConfig["plugins"]
		if !ok {
			return "", nil, fmt.Errorf("cannot get 'plugins' field in master CNI config file %q: %v", masterConfigPath, err)
		}
		masterPlugins := masterPluginsElem.([]interface{})
		for _, v := range masterPlugins {
			pluginFields := v.(map[string]interface{})
			capabilitiesElem, ok := pluginFields["capabilities"]
			if ok {
				capabilities := capabilitiesElem.(map[string]interface{})
				for k, v := range capabilities {
					masterCapabilities[k] = v.(bool)
				}
			}
		}
		fmt.Printf("master capabilities is get from conflist\n")
	} else {
		masterCapabilitiesElem, ok := masterConfig["capabilities"]
		if ok {
			for k, v := range masterCapabilitiesElem.(map[string]interface{}) {
				masterCapabilities[k] = v.(bool)
			}
		}
		fmt.Printf("master capabilities is get from conffile\n")
	}
	nestedCapabilitiesConf := ""
	if len(masterCapabilities) != 0 {
		capabilitiesByte, err := json.Marshal(masterCapabilities)
		if err != nil {
			return "", nil, fmt.Errorf("cannot get capabilities map: %v", err)
		}
		nestedCapabilitiesConf = fmt.Sprintf("\n        \"capabilities\": %s,", string(capabilitiesByte))
	}

	// check NamespaceIsolation
	namespaceIsolationConfig := ""
	if o.NamespaceIsolation {
		namespaceIsolationConfig = "\n        \"namespaceIsolation\": true,"
	}

	// check GlobalNamespaces
	globalNamespaceConfig := ""
	if o.GlobalNamespaces != "" {
		globalNamespaceConfig = fmt.Sprintf("\n        \"globalNamespaces\": %q,", o.GlobalNamespaces)
	}

	// check MultusLogToStderr
	logToStderrConfig := ""
	if !o.MultusLogToStderr {
		logToStderrConfig = "\n        \"logToStderr\": false,"
	}

	// check MultusLogLevel (debug/error/panic/verbose) and reject others
	logLevelConfig := ""
	logLevelStr := strings.ToLower(o.MultusLogLevel)
	switch logLevelStr {
	case "debug", "error", "panic", "verbose":
		logLevelConfig = fmt.Sprintf("\n        \"logLevel\": %q,", logLevelStr)
	case "":
		// no logLevel config, skipped
	default:
		return "", nil, fmt.Errorf("Log levels should be one of: debug/verbose/error/panic, did not understand: %q", o.MultusLogLevel)
	}

	// check MultusLogFile
	logFileConfig := ""
	if o.MultusLogFile != "" {
		logFileConfig = fmt.Sprintf("\n        \"logFile\": %q,", o.MultusLogFile)
	}

	// check AdditionalBinDir
	additionalBinDirConfig := ""
	if o.AdditionalBinDir != "" {
		additionalBinDirConfig = fmt.Sprintf("\n        \"binDir\": %q,", o.AdditionalBinDir)
	}

	// check MultusCNIConfDir
	multusCNIConfDirConfig := ""
	if o.MultusCNIConfDir != "" {
		multusCNIConfDirConfig = fmt.Sprintf("\n        \"cniConf\": %q,", o.MultusCNIConfDir)
	}

	// check ReadinessIndicatorFile
	readinessIndicatorFileConfig := ""
	if o.ReadinessIndicatorFile != "" {
		readinessIndicatorFileConfig = fmt.Sprintf("\n        \"readinessindicatorfile\": %q,", o.ReadinessIndicatorFile)
	}

	// fill .MasterPluginJSON
	masterPluginByte, err := json.Marshal(masterConfig)
	if err != nil {
		return "", nil, fmt.Errorf("cannot encode master CNI config: %v", err)
	}

	// generate multus config
	tempFileName := fmt.Sprintf("%s/00-multus.conf.new", o.CNIConfDir)
	fp, err := os.OpenFile(tempFileName, os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		return "", nil, fmt.Errorf("cannot create multus cni temp file: %v", err)
	}

	// use conflist template if cniVersionConfig >= "1.0.0"
	multusConfFilePath := fmt.Sprintf("%s/00-multus.conf", o.CNIConfDir)
	templateMultusConfig, err := template.New("multusCNIConfig").Parse(multusConfTemplate)
	if err != nil {
		return "", nil, fmt.Errorf("template parse error: %v", err)
	}

	if gt, err := cniversion.GreaterThanOrEqualTo(o.CNIVersion, "1.0.0"); err == nil && gt {
		multusConfFilePath = fmt.Sprintf("%s/00-multus.conflist", o.CNIConfDir)
		templateMultusConfig, err = template.New("multusCNIConfig").Parse(multusConflistTemplate)
		if err != nil {
			return "", nil, fmt.Errorf("template parse error: %v", err)
		}
	}

	templateData := map[string]string{
		"CNIVersion":                   cniVersionConfig,
		"MasterPluginNetworkName":      masterPluginNetworkName,
		"NestedCapabilities":           nestedCapabilitiesConf,
		"NamespaceIsolationConfig":     namespaceIsolationConfig,
		"GlobalNamespacesConfig":       globalNamespaceConfig,
		"LogToStderrConfig":            logToStderrConfig,
		"LogLevelConfig":               logLevelConfig,
		"LogFileConfig":                logFileConfig,
		"AdditionalBinDirConfig":       additionalBinDirConfig,
		"MultusCNIConfDirConfig":       multusCNIConfDirConfig,
		"ReadinessIndicatorFileConfig": readinessIndicatorFileConfig,
		"MultusKubeConfigFileHost":     o.MultusKubeConfigFileHost, // be fixed?
		"MasterPluginJSON":             string(masterPluginByte),
	}
	if err = templateMultusConfig.Execute(fp, templateData); err != nil {
		return "", nil, fmt.Errorf("cannot create multus cni config: %v", err)
	}

	if err := fp.Sync(); err != nil {
		os.Remove(tempFileName)
		return "", nil, fmt.Errorf("cannot flush multus cni config: %v", err)
	}
	if err := fp.Close(); err != nil {
		os.Remove(tempFileName)
		return "", nil, fmt.Errorf("cannot close multus cni config: %v", err)
	}

	if err := os.Rename(tempFileName, multusConfFilePath); err != nil {
		return "", nil, fmt.Errorf("cannot replace %q with temp file %q: %v", multusConfFilePath, tempFileName, err)
	}

	if o.RenameConfFile {
		//masterConfigPath
		renamedMasterConfigPath := fmt.Sprintf("%s.old", masterConfigPath)
		if err := os.Rename(masterConfigPath, renamedMasterConfigPath); err != nil {
			return "", nil, fmt.Errorf("cannot move original master file to %q", renamedMasterConfigPath)
		}
		fmt.Printf("Original master file moved to %q\n", renamedMasterConfigPath)
	}

	return masterConfigPath, masterConfigFileHash, nil
}

func main() {
	opt := Options{}
	opt.addFlags()
	helpFlag := pflag.BoolP("help", "h", false, "show help message and quit")

	pflag.Parse()
	if *helpFlag {
		pflag.PrintDefaults()
		os.Exit(1)
	}

	err := opt.verifyFileExists()
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		return
	}

	// copy multus binary
	if !opt.SkipMultusBinaryCopy {
		// Copy
		if err = cmdutils.CopyFileAtomic(opt.MultusBinFile, opt.CNIBinDir, "_multus", "multus"); err != nil {
			fmt.Fprintf(os.Stderr, "failed at multus copy: %v\n", err)
			return
		}
	}

	var masterConfigHash, caHash, saTokenHash []byte
	var masterConfigFilePath string
	// copy user specified multus conf to CNI conf directory
	if opt.MultusConfFile != "auto" {
		caHash, saTokenHash, err = opt.createKubeConfig(nil, nil)
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to create multus kubeconfig: %v\n", err)
			return
		}
		confFileName := filepath.Base(opt.MultusConfFile)
		tempConfFileName := fmt.Sprintf("%s.temp", confFileName)
		if err = cmdutils.CopyFileAtomic(opt.MultusConfFile, opt.CNIConfDir, tempConfFileName, confFileName); err != nil {
			fmt.Fprintf(os.Stderr, "failed at copy multus conf file: %v\n", err)
			return
		}
		fmt.Printf("multus config file %s is copied.\n", opt.MultusConfFile)
	} else { // auto generate multus config
		caHash, saTokenHash, err = opt.createKubeConfig(nil, nil)
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to create multus kubeconfig: %v\n", err)
			return
		}
		fmt.Printf("kubeconfig file is created.\n")
		masterConfigFilePath, masterConfigHash, err = opt.createMultusConfig(nil)
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to create multus config: %v\n", err)
			return
		}
		fmt.Printf("multus config file is created.\n")
	}

	ctx := signals.SetupSignalHandler()

	if opt.CleanupConfigOnExit {
		defer cleanupMultusConf(&opt)
	}

	watchChanges := opt.CleanupConfigOnExit && opt.MultusConfFile == "auto" && !opt.SkipMultusConfWatch
	if watchChanges {
		fmt.Printf("Entering watch loop...\n")
		masterConfigExists := true

	outer:
		for range time.Tick(1 * time.Second) {
			select {
			case <-ctx.Done():
				// signal received break from loop
				break outer
			default:
				// Check kubeconfig and update if different (i.e. service account updated)
				caHash, saTokenHash, err = opt.createKubeConfig(caHash, saTokenHash)
				if err != nil {
					fmt.Fprintf(os.Stderr, "failed to update multus kubeconfig: %v\n", err)
					return
				}

				// TODO: should we watch master CNI config (by fsnotify? https://github.com/fsnotify/fsnotify)
				_, err = os.Stat(masterConfigFilePath)

				// if masterConfigFilePath is no longer exists
				if os.IsNotExist(err) {
					if masterConfigExists {
						fmt.Printf("Master plugin @ %q has been deleted. waiting for its restoration...\n", masterConfigFilePath)
					}
					masterConfigExists = false
					continue
				}

				if !masterConfigExists {
					fmt.Printf("Master plugin @ %q was restored. Regenerating given configuration.\n", masterConfigFilePath)
					masterConfigExists = true
				}

				masterConfigFilePath, masterConfigHash, err = opt.createMultusConfig(masterConfigHash)
				if err != nil {
					fmt.Fprintf(os.Stderr, "failed to create multus config: %v\n", err)
					return
				}
			}
		}
	} else {
		// wait until signal received
		<-ctx.Done()
	}
}

func cleanupMultusConf(opt *Options) {
	// try remove multus conf
	if opt.MultusConfFile == "auto" {
		multusConfFilePath := fmt.Sprintf("%s/00-multus.conf", opt.CNIConfDir)
		_ = os.Remove(multusConfFilePath)

		multusConfFilePath = fmt.Sprintf("%s/00-multus.conflist", opt.CNIConfDir)
		_ = os.Remove(multusConfFilePath)
	} else {
		confFileName := filepath.Base(opt.MultusConfFile)
		_ = os.Remove(filepath.Join(opt.CNIConfDir, confFileName))
	}

}
