...

Source file src/github.com/linkerd/linkerd2/multicluster/cmd/install.go

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

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path"
    11  	"time"
    12  
    13  	"github.com/linkerd/linkerd2/multicluster/static"
    14  	multicluster "github.com/linkerd/linkerd2/multicluster/values"
    15  	"github.com/linkerd/linkerd2/pkg/charts"
    16  	partials "github.com/linkerd/linkerd2/pkg/charts/static"
    17  	"github.com/linkerd/linkerd2/pkg/flags"
    18  	"github.com/linkerd/linkerd2/pkg/healthcheck"
    19  	"github.com/linkerd/linkerd2/pkg/version"
    20  	log "github.com/sirupsen/logrus"
    21  	"github.com/spf13/cobra"
    22  	"helm.sh/helm/v3/pkg/chart/loader"
    23  	"helm.sh/helm/v3/pkg/chartutil"
    24  	valuespkg "helm.sh/helm/v3/pkg/cli/values"
    25  	"helm.sh/helm/v3/pkg/engine"
    26  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    27  	"sigs.k8s.io/yaml"
    28  )
    29  
    30  type (
    31  	multiclusterInstallOptions struct {
    32  		gateway                 multicluster.Gateway
    33  		remoteMirrorCredentials bool
    34  	}
    35  )
    36  
    37  var TemplatesMulticluster = []string{
    38  	chartutil.ChartfileName,
    39  	chartutil.ValuesfileName,
    40  	"templates/namespace.yaml",
    41  	"templates/gateway.yaml",
    42  	"templates/gateway-policy.yaml",
    43  	"templates/psp.yaml",
    44  	"templates/remote-access-service-mirror-rbac.yaml",
    45  	"templates/link-crd.yaml",
    46  	"templates/service-mirror-policy.yaml",
    47  }
    48  
    49  func newMulticlusterInstallCommand() *cobra.Command {
    50  	options, err := newMulticlusterInstallOptionsWithDefault()
    51  	var ha bool
    52  	var wait time.Duration
    53  	var valuesOptions valuespkg.Options
    54  	var ignoreCluster bool
    55  	var cniEnabled bool
    56  
    57  	if err != nil {
    58  		fmt.Fprintln(os.Stderr, err)
    59  		os.Exit(1)
    60  	}
    61  
    62  	cmd := &cobra.Command{
    63  		Use:   "install",
    64  		Short: "Output Kubernetes configs to install the Linkerd multicluster add-on",
    65  		Args:  cobra.NoArgs,
    66  		Example: `  # Default install.
    67    linkerd multicluster install | kubectl apply -f -
    68  
    69  The installation can be configured by using the --set, --values, --set-string and --set-file flags.
    70  A full list of configurable values can be found at https://github.com/linkerd/linkerd2/blob/main/multicluster/charts/linkerd-multicluster/README.md
    71    `,
    72  		RunE: func(cmd *cobra.Command, _ []string) error {
    73  			if !ignoreCluster {
    74  				// Wait for the core control-plane to be up and running
    75  				hc := healthcheck.NewWithCoreChecks(&healthcheck.Options{
    76  					ControlPlaneNamespace: controlPlaneNamespace,
    77  					KubeConfig:            kubeconfigPath,
    78  					KubeContext:           kubeContext,
    79  					Impersonate:           impersonate,
    80  					ImpersonateGroup:      impersonateGroup,
    81  					APIAddr:               apiAddr,
    82  					RetryDeadline:         time.Now().Add(wait),
    83  				})
    84  				hc.RunWithExitOnError()
    85  				cniEnabled = hc.CNIEnabled
    86  			}
    87  			return install(cmd.Context(), stdout, options, valuesOptions, ha, ignoreCluster, cniEnabled)
    88  		},
    89  	}
    90  
    91  	flags.AddValueOptionsFlags(cmd.Flags(), &valuesOptions)
    92  	cmd.Flags().BoolVar(&options.gateway.Enabled, "gateway", options.gateway.Enabled, "If the gateway component should be installed")
    93  	cmd.Flags().Uint32Var(&options.gateway.Port, "gateway-port", options.gateway.Port, "The port on the gateway used for all incoming traffic")
    94  	cmd.Flags().Uint32Var(&options.gateway.Probe.Seconds, "gateway-probe-seconds", options.gateway.Probe.Seconds, "The interval at which the gateway will be checked for being alive in seconds")
    95  	cmd.Flags().Uint32Var(&options.gateway.Probe.Port, "gateway-probe-port", options.gateway.Probe.Port, "The liveness check port of the gateway")
    96  	cmd.Flags().BoolVar(&options.remoteMirrorCredentials, "service-mirror-credentials", options.remoteMirrorCredentials, "Whether to install the service account which can be used by service mirror components in source clusters to discover exported services")
    97  	cmd.Flags().StringVar(&options.gateway.ServiceType, "gateway-service-type", options.gateway.ServiceType, "Overwrite Service type for gateway service")
    98  	cmd.Flags().BoolVar(&ha, "ha", false, `Install multicluster extension in High Availability mode.`)
    99  	cmd.Flags().DurationVar(&wait, "wait", 300*time.Second, "Wait for core control-plane components to be available")
   100  	cmd.Flags().BoolVar(&ignoreCluster, "ignore-cluster", false,
   101  		"Ignore the current Kubernetes cluster when checking for existing cluster configuration (default false)")
   102  
   103  	// Hide developer focused flags in release builds.
   104  	release, err := version.IsReleaseChannel(version.Version)
   105  	if err != nil {
   106  		log.Errorf("Unable to parse version: %s", version.Version)
   107  	}
   108  	if release {
   109  		cmd.Flags().MarkHidden("control-plane-version")
   110  		cmd.Flags().MarkHidden("gateway-nginx-image")
   111  		cmd.Flags().MarkHidden("gateway-nginx-image-version")
   112  	}
   113  
   114  	return cmd
   115  }
   116  
   117  func install(ctx context.Context, w io.Writer, options *multiclusterInstallOptions, valuesOptions valuespkg.Options, ha, ignoreCluster, cniEnabled bool) error {
   118  	values, err := buildMulticlusterInstallValues(ctx, options, ignoreCluster)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	// Create values override
   124  	valuesOverrides, err := valuesOptions.MergeValues(nil)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	if ha {
   130  		valuesOverrides, err = charts.OverrideFromFile(valuesOverrides, static.Templates, helmMulticlusterDefaultChartName, "values-ha.yaml")
   131  		if err != nil {
   132  			return err
   133  		}
   134  	}
   135  
   136  	if cniEnabled {
   137  		valuesOverrides["cniEnabled"] = true
   138  	}
   139  
   140  	return render(w, values, valuesOverrides)
   141  }
   142  
   143  func render(w io.Writer, values *multicluster.Values, valuesOverrides map[string]interface{}) error {
   144  	var files []*loader.BufferedFile
   145  	for _, template := range TemplatesMulticluster {
   146  		files = append(files, &loader.BufferedFile{Name: template})
   147  	}
   148  
   149  	var partialFiles []*loader.BufferedFile
   150  	for _, template := range charts.L5dPartials {
   151  		partialFiles = append(partialFiles,
   152  			&loader.BufferedFile{Name: template},
   153  		)
   154  	}
   155  
   156  	// Load all multicluster install chart files into buffer
   157  	if err := charts.FilesReader(static.Templates, helmMulticlusterDefaultChartName+"/", files); err != nil {
   158  		return err
   159  	}
   160  
   161  	// Load all partial chart files into buffer
   162  	if err := charts.FilesReader(partials.Templates, "", partialFiles); err != nil {
   163  		return err
   164  	}
   165  
   166  	// Create a Chart obj from the files
   167  	chart, err := loader.LoadFiles(append(files, partialFiles...))
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	// Render raw values and create chart config
   173  	rawValues, err := yaml.Marshal(values)
   174  	if err != nil {
   175  		return err
   176  	}
   177  	// Store final Values generated from values.yaml and CLI flags
   178  	err = yaml.Unmarshal(rawValues, &chart.Values)
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	vals, err := chartutil.CoalesceValues(chart, valuesOverrides)
   184  	if err != nil {
   185  		return err
   186  	}
   187  
   188  	fullValues := map[string]interface{}{
   189  		"Values": vals,
   190  		"Release": map[string]interface{}{
   191  			"Namespace": defaultMulticlusterNamespace,
   192  			"Service":   "CLI",
   193  		},
   194  	}
   195  
   196  	// Attach the final values into the `Values` field for rendering to work
   197  	renderedTemplates, err := engine.Render(chart, fullValues)
   198  	if err != nil {
   199  		return err
   200  	}
   201  
   202  	// Merge templates and inject
   203  	var buf bytes.Buffer
   204  	for _, tmpl := range chart.Templates {
   205  		t := path.Join(chart.Metadata.Name, tmpl.Name)
   206  		if _, err := buf.WriteString(renderedTemplates[t]); err != nil {
   207  			return err
   208  		}
   209  	}
   210  	w.Write(buf.Bytes())
   211  	w.Write([]byte("---\n"))
   212  
   213  	return nil
   214  }
   215  
   216  func newMulticlusterInstallOptionsWithDefault() (*multiclusterInstallOptions, error) {
   217  	defaults, err := multicluster.NewInstallValues()
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	return &multiclusterInstallOptions{
   223  		gateway:                 *defaults.Gateway,
   224  		remoteMirrorCredentials: true,
   225  	}, nil
   226  }
   227  
   228  func buildMulticlusterInstallValues(ctx context.Context, opts *multiclusterInstallOptions, ignoreCluster bool) (*multicluster.Values, error) {
   229  	defaults, err := multicluster.NewInstallValues()
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	defaults.Gateway.Enabled = opts.gateway.Enabled
   235  	defaults.Gateway.Port = opts.gateway.Port
   236  	defaults.Gateway.Probe.Seconds = opts.gateway.Probe.Seconds
   237  	defaults.Gateway.Probe.Port = opts.gateway.Probe.Port
   238  	defaults.LinkerdNamespace = controlPlaneNamespace
   239  	defaults.LinkerdVersion = version.Version
   240  	defaults.RemoteMirrorServiceAccount = opts.remoteMirrorCredentials
   241  	defaults.Gateway.ServiceType = opts.gateway.ServiceType
   242  
   243  	if ignoreCluster {
   244  		return defaults, nil
   245  	}
   246  
   247  	values, err := getLinkerdConfigMap(ctx)
   248  	if err != nil {
   249  		if kerrors.IsNotFound(err) {
   250  			return nil, errors.New("you need Linkerd to be installed in order to install multicluster addons")
   251  		}
   252  		return nil, err
   253  	}
   254  	defaults.ProxyOutboundPort = uint32(values.Proxy.Ports.Outbound)
   255  	defaults.IdentityTrustDomain = values.IdentityTrustDomain
   256  
   257  	return defaults, nil
   258  }
   259  

View as plain text