...

Source file src/github.com/linkerd/linkerd2/cli/cmd/install-cni-plugin.go

Documentation: github.com/linkerd/linkerd2/cli/cmd

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/linkerd/linkerd2/pkg/charts"
    10  	cnicharts "github.com/linkerd/linkerd2/pkg/charts/cni"
    11  	"github.com/linkerd/linkerd2/pkg/charts/static"
    12  	"github.com/linkerd/linkerd2/pkg/cmd"
    13  	"github.com/linkerd/linkerd2/pkg/flags"
    14  	"github.com/linkerd/linkerd2/pkg/version"
    15  	log "github.com/sirupsen/logrus"
    16  	"github.com/spf13/cobra"
    17  	"helm.sh/helm/v3/pkg/chart"
    18  	"helm.sh/helm/v3/pkg/chart/loader"
    19  	"helm.sh/helm/v3/pkg/chartutil"
    20  	"helm.sh/helm/v3/pkg/cli/values"
    21  )
    22  
    23  const (
    24  	helmCNIDefaultChartName = "linkerd2-cni"
    25  	helmCNIDefaultChartDir  = "linkerd2-cni"
    26  )
    27  
    28  type cniPluginImage struct {
    29  	name       string
    30  	version    string
    31  	pullPolicy interface{}
    32  }
    33  
    34  type cniPluginOptions struct {
    35  	linkerdVersion      string
    36  	dockerRegistry      string
    37  	proxyControlPort    uint
    38  	proxyAdminPort      uint
    39  	inboundPort         uint
    40  	outboundPort        uint
    41  	ignoreInboundPorts  []string
    42  	ignoreOutboundPorts []string
    43  	portsToRedirect     []uint
    44  	proxyUID            int64
    45  	proxyGID            int64
    46  	image               cniPluginImage
    47  	logLevel            string
    48  	destCNINetDir       string
    49  	destCNIBinDir       string
    50  	useWaitFlag         bool
    51  	priorityClassName   string
    52  }
    53  
    54  func (options *cniPluginOptions) validate() error {
    55  	if !alphaNumDashDot.MatchString(options.linkerdVersion) {
    56  		return fmt.Errorf("%s is not a valid version", options.linkerdVersion)
    57  	}
    58  
    59  	if !alphaNumDashDotSlashColon.MatchString(options.dockerRegistry) {
    60  		return fmt.Errorf("%s is not a valid Docker registry. The url can contain only letters, numbers, dash, dot, slash and colon", options.dockerRegistry)
    61  	}
    62  
    63  	if _, err := log.ParseLevel(options.logLevel); err != nil {
    64  		return fmt.Errorf("--cni-log-level must be one of: panic, fatal, error, warn, info, debug, trace")
    65  	}
    66  
    67  	if err := validateRangeSlice(options.ignoreInboundPorts); err != nil {
    68  		return err
    69  	}
    70  
    71  	if err := validateRangeSlice(options.ignoreOutboundPorts); err != nil {
    72  		return err
    73  	}
    74  	return nil
    75  }
    76  
    77  func (options *cniPluginOptions) pluginImage() cnicharts.Image {
    78  	image := cnicharts.Image{
    79  		Name:       options.image.name,
    80  		Version:    options.image.version,
    81  		PullPolicy: options.image.pullPolicy,
    82  	}
    83  	// env var overrides CLI flag
    84  	if override := os.Getenv(flags.EnvOverrideDockerRegistry); override != "" {
    85  		image.Name = cmd.RegistryOverride(options.image.name, override)
    86  		return image
    87  	}
    88  	if options.dockerRegistry != cmd.DefaultDockerRegistry {
    89  		image.Name = cmd.RegistryOverride(options.image.name, options.dockerRegistry)
    90  		return image
    91  	}
    92  	return image
    93  }
    94  
    95  func newCmdInstallCNIPlugin() *cobra.Command {
    96  	options, err := newCNIInstallOptionsWithDefaults()
    97  	if err != nil {
    98  		fmt.Fprintln(os.Stderr, err)
    99  		os.Exit(1)
   100  	}
   101  	var valOpts values.Options
   102  
   103  	cmd := &cobra.Command{
   104  		Use:   "install-cni [flags]",
   105  		Short: "Output Kubernetes configs to install Linkerd CNI",
   106  		Long: `Output Kubernetes configs to install Linkerd CNI.
   107  
   108  This command installs a DaemonSet into the Linkerd control plane. The DaemonSet
   109  copies the necessary linkerd-cni plugin binaries and configs onto the host. It
   110  assumes that the 'linkerd install' command will be executed with the
   111  '--linkerd-cni-enabled' flag. This command needs to be executed before the
   112  'linkerd install --linkerd-cni-enabled' command.
   113  
   114  The installation can be configured by using the --set, --values, --set-string and --set-file flags. A full list of configurable values can be found at https://artifacthub.io/packages/helm/linkerd2/linkerd2-cni#values`,
   115  		RunE: func(cmd *cobra.Command, args []string) error {
   116  			return renderCNIPlugin(os.Stdout, valOpts, options)
   117  		},
   118  	}
   119  
   120  	cmd.PersistentFlags().StringVarP(&options.linkerdVersion, "linkerd-version", "v", options.linkerdVersion, "Tag to be used for Linkerd images")
   121  	cmd.PersistentFlags().StringVar(&options.dockerRegistry, "registry", options.dockerRegistry,
   122  		fmt.Sprintf("Docker registry to pull images from ($%s)", flags.EnvOverrideDockerRegistry))
   123  	cmd.PersistentFlags().Int64Var(&options.proxyUID, "proxy-uid", options.proxyUID, "Run the proxy under this user ID")
   124  	cmd.PersistentFlags().Int64Var(&options.proxyGID, "proxy-gid", options.proxyGID, "Run the proxy under this group ID")
   125  	cmd.PersistentFlags().UintVar(&options.inboundPort, "inbound-port", options.inboundPort, "Proxy port to use for inbound traffic")
   126  	cmd.PersistentFlags().UintVar(&options.outboundPort, "outbound-port", options.outboundPort, "Proxy port to use for outbound traffic")
   127  	cmd.PersistentFlags().UintVar(&options.proxyControlPort, "control-port", options.proxyControlPort, "Proxy port to use for control")
   128  	cmd.PersistentFlags().UintVar(&options.proxyAdminPort, "admin-port", options.proxyAdminPort, "Proxy port to serve metrics on")
   129  	cmd.PersistentFlags().StringSliceVar(&options.ignoreInboundPorts, "skip-inbound-ports", options.ignoreInboundPorts, "Ports and/or port ranges (inclusive) that should skip the proxy and send directly to the application")
   130  	cmd.PersistentFlags().StringSliceVar(&options.ignoreOutboundPorts, "skip-outbound-ports", options.ignoreOutboundPorts, "Outbound ports and/or port ranges (inclusive) that should skip the proxy")
   131  	cmd.PersistentFlags().UintSliceVar(&options.portsToRedirect, "redirect-ports", options.portsToRedirect, "Ports to redirect to proxy, if no port is specified then ALL ports are redirected")
   132  	cmd.PersistentFlags().StringVar(&options.image.name, "cni-image", options.image.name, "Image for the cni-plugin")
   133  	cmd.PersistentFlags().StringVar(&options.image.version, "cni-image-version", options.image.version, "Image Version for the cni-plugin")
   134  	cmd.PersistentFlags().StringVar(&options.logLevel, "cni-log-level", options.logLevel, "Log level for the cni-plugin")
   135  	cmd.PersistentFlags().StringVar(&options.destCNINetDir, "dest-cni-net-dir", options.destCNINetDir, "Directory on the host where the CNI configuration will be placed")
   136  	cmd.PersistentFlags().StringVar(&options.destCNIBinDir, "dest-cni-bin-dir", options.destCNIBinDir, "Directory on the host where the CNI binary will be placed")
   137  	cmd.PersistentFlags().StringVar(&options.priorityClassName, "priority-class-name", options.priorityClassName, "Pod priorityClassName for CNI daemonset's pods")
   138  	cmd.PersistentFlags().BoolVar(
   139  		&options.useWaitFlag,
   140  		"use-wait-flag",
   141  		options.useWaitFlag,
   142  		"Configures the CNI plugin to use the \"-w\" flag for the iptables command. (default false)")
   143  
   144  	flags.AddValueOptionsFlags(cmd.Flags(), &valOpts)
   145  
   146  	return cmd
   147  }
   148  
   149  func newCNIInstallOptionsWithDefaults() (*cniPluginOptions, error) {
   150  	defaults, err := cnicharts.NewValues()
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	cniPluginImage := cniPluginImage{
   156  		name:    cmd.DefaultDockerRegistry + "/cni-plugin",
   157  		version: version.LinkerdCNIVersion,
   158  	}
   159  
   160  	cniOptions := cniPluginOptions{
   161  		linkerdVersion:      version.Version,
   162  		dockerRegistry:      cmd.DefaultDockerRegistry,
   163  		proxyControlPort:    4190,
   164  		proxyAdminPort:      4191,
   165  		inboundPort:         defaults.InboundProxyPort,
   166  		outboundPort:        defaults.OutboundProxyPort,
   167  		ignoreInboundPorts:  nil,
   168  		ignoreOutboundPorts: nil,
   169  		proxyUID:            defaults.ProxyUID,
   170  		proxyGID:            defaults.ProxyGID,
   171  		image:               cniPluginImage,
   172  		logLevel:            "info",
   173  		destCNINetDir:       defaults.DestCNINetDir,
   174  		destCNIBinDir:       defaults.DestCNIBinDir,
   175  		useWaitFlag:         defaults.UseWaitFlag,
   176  		priorityClassName:   defaults.PriorityClassName,
   177  	}
   178  
   179  	if defaults.IgnoreInboundPorts != "" {
   180  		cniOptions.ignoreInboundPorts = strings.Split(defaults.IgnoreInboundPorts, ",")
   181  
   182  	}
   183  	if defaults.IgnoreOutboundPorts != "" {
   184  		cniOptions.ignoreOutboundPorts = strings.Split(defaults.IgnoreOutboundPorts, ",")
   185  	}
   186  
   187  	return &cniOptions, nil
   188  }
   189  
   190  func (options *cniPluginOptions) buildValues() (*cnicharts.Values, error) {
   191  	installValues, err := cnicharts.NewValues()
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	portsToRedirect := []string{}
   197  	for _, p := range options.portsToRedirect {
   198  		portsToRedirect = append(portsToRedirect, fmt.Sprintf("%d", p))
   199  	}
   200  
   201  	installValues.Image = options.pluginImage()
   202  	installValues.LogLevel = options.logLevel
   203  	installValues.InboundProxyPort = options.inboundPort
   204  	installValues.OutboundProxyPort = options.outboundPort
   205  	installValues.IgnoreInboundPorts = strings.Join(options.ignoreInboundPorts, ",")
   206  	installValues.IgnoreOutboundPorts = strings.Join(options.ignoreOutboundPorts, ",")
   207  	installValues.PortsToRedirect = strings.Join(portsToRedirect, ",")
   208  	installValues.ProxyUID = options.proxyUID
   209  	installValues.ProxyGID = options.proxyGID
   210  	installValues.DestCNINetDir = options.destCNINetDir
   211  	installValues.DestCNIBinDir = options.destCNIBinDir
   212  	installValues.UseWaitFlag = options.useWaitFlag
   213  	installValues.PriorityClassName = options.priorityClassName
   214  	return installValues, nil
   215  }
   216  
   217  func renderCNIPlugin(w io.Writer, valOpts values.Options, config *cniPluginOptions) error {
   218  
   219  	if err := config.validate(); err != nil {
   220  		return err
   221  	}
   222  
   223  	valuesOverrides, err := valOpts.MergeValues(nil)
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	values, err := config.buildValues()
   229  	if err != nil {
   230  		return err
   231  	}
   232  
   233  	mapValues, err := values.ToMap()
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	valuesWrapper := &chart.Chart{
   239  		Metadata: &chart.Metadata{Name: ""},
   240  		Values:   mapValues,
   241  	}
   242  	mergedValues, err := chartutil.CoalesceValues(valuesWrapper, valuesOverrides)
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	files := []*loader.BufferedFile{
   248  		{Name: chartutil.ChartfileName},
   249  		{Name: "templates/cni-plugin.yaml"},
   250  	}
   251  
   252  	ch := &charts.Chart{
   253  		Name:      helmCNIDefaultChartName,
   254  		Dir:       helmCNIDefaultChartDir,
   255  		Namespace: defaultCNINamespace,
   256  		Values:    mergedValues,
   257  		Files:     files,
   258  		Fs:        static.Templates,
   259  	}
   260  
   261  	buf, err := ch.RenderCNI()
   262  	if err != nil {
   263  		return err
   264  	}
   265  	w.Write(buf.Bytes())
   266  	w.Write([]byte("---\n"))
   267  
   268  	return nil
   269  }
   270  

View as plain text