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
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