istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/cmd/mesh/manifest-generate.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package mesh
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  
    23  	"github.com/spf13/cobra"
    24  
    25  	"istio.io/istio/istioctl/pkg/cli"
    26  	"istio.io/istio/operator/pkg/helm"
    27  	"istio.io/istio/operator/pkg/helmreconciler"
    28  	"istio.io/istio/operator/pkg/manifest"
    29  	"istio.io/istio/operator/pkg/name"
    30  	"istio.io/istio/operator/pkg/object"
    31  	"istio.io/istio/operator/pkg/util/clog"
    32  	"istio.io/istio/pkg/kube"
    33  )
    34  
    35  type ManifestGenerateArgs struct {
    36  	// InFilenames is an array of paths to the input IstioOperator CR files.
    37  	InFilenames []string
    38  	// OutFilename is the path to the generated output directory.
    39  	OutFilename string
    40  
    41  	// EnableClusterSpecific determines if the current Kubernetes cluster will be used to autodetect values.
    42  	// If false, generic defaults will be used. This is useful when generating once and then applying later.
    43  	EnableClusterSpecific bool
    44  
    45  	// Set is a string with element format "path=value" where path is an IstioOperator path and the value is a
    46  	// value to set the node at that path to.
    47  	Set []string
    48  	// Force proceeds even if there are validation errors
    49  	Force bool
    50  	// ManifestsPath is a path to a charts and profiles directory in the local filesystem with a release tgz.
    51  	ManifestsPath string
    52  	// Revision is the Istio control plane revision the command targets.
    53  	Revision string
    54  	// Components is a list of strings specifying which component's manifests to be generated.
    55  	Components []string
    56  	// Filter is the list of components to render
    57  	Filter []string
    58  }
    59  
    60  var kubeClientFunc func() (kube.CLIClient, error)
    61  
    62  func (a *ManifestGenerateArgs) String() string {
    63  	var b strings.Builder
    64  	b.WriteString("InFilenames:   " + fmt.Sprint(a.InFilenames) + "\n")
    65  	b.WriteString("OutFilename:   " + a.OutFilename + "\n")
    66  	b.WriteString("Set:           " + fmt.Sprint(a.Set) + "\n")
    67  	b.WriteString("Force:         " + fmt.Sprint(a.Force) + "\n")
    68  	b.WriteString("ManifestsPath: " + a.ManifestsPath + "\n")
    69  	b.WriteString("Revision:      " + a.Revision + "\n")
    70  	b.WriteString("Components:    " + fmt.Sprint(a.Components) + "\n")
    71  	return b.String()
    72  }
    73  
    74  func addManifestGenerateFlags(cmd *cobra.Command, args *ManifestGenerateArgs) {
    75  	cmd.PersistentFlags().StringSliceVarP(&args.InFilenames, "filename", "f", nil, filenameFlagHelpStr)
    76  	cmd.PersistentFlags().StringVarP(&args.OutFilename, "output", "o", "", "Manifest output directory path.")
    77  	cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
    78  	cmd.PersistentFlags().BoolVar(&args.Force, "force", false, ForceFlagHelpStr)
    79  	cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "charts", "", "", ChartsDeprecatedStr)
    80  	cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", ManifestsFlagHelpStr)
    81  	cmd.PersistentFlags().StringVarP(&args.Revision, "revision", "r", "", revisionFlagHelpStr)
    82  	cmd.PersistentFlags().StringSliceVar(&args.Components, "component", nil, ComponentFlagHelpStr)
    83  	cmd.PersistentFlags().StringSliceVar(&args.Filter, "filter", nil, "")
    84  	_ = cmd.PersistentFlags().MarkHidden("filter")
    85  
    86  	cmd.PersistentFlags().BoolVar(&args.EnableClusterSpecific, "cluster-specific", false,
    87  		"If enabled, the current cluster will be checked for cluster-specific setting detection.")
    88  }
    89  
    90  func ManifestGenerateCmd(ctx cli.Context, rootArgs *RootArgs, mgArgs *ManifestGenerateArgs) *cobra.Command {
    91  	return &cobra.Command{
    92  		Use:   "generate",
    93  		Short: "Generates an Istio install manifest",
    94  		Long:  "The generate subcommand generates an Istio install manifest and outputs to the console by default.",
    95  		// nolint: lll
    96  		Example: `  # Generate a default Istio installation
    97    istioctl manifest generate
    98  
    99    # Enable Tracing
   100    istioctl manifest generate --set meshConfig.enableTracing=true
   101  
   102    # Generate the demo profile
   103    istioctl manifest generate --set profile=demo
   104  
   105    # To override a setting that includes dots, escape them with a backslash (\).  Your shell may require enclosing quotes.
   106    istioctl manifest generate --set "values.sidecarInjectorWebhook.injectedAnnotations.container\.apparmor\.security\.beta\.kubernetes\.io/istio-proxy=runtime/default"
   107  `,
   108  		Args: func(cmd *cobra.Command, args []string) error {
   109  			if len(args) != 0 {
   110  				return fmt.Errorf("generate accepts no positional arguments, got %#v", args)
   111  			}
   112  			return nil
   113  		},
   114  		RunE: func(cmd *cobra.Command, args []string) error {
   115  			if kubeClientFunc == nil {
   116  				kubeClientFunc = ctx.CLIClient
   117  			}
   118  			var kubeClient kube.CLIClient
   119  			if mgArgs.EnableClusterSpecific {
   120  				kc, err := kubeClientFunc()
   121  				if err != nil {
   122  					return err
   123  				}
   124  				kubeClient = kc
   125  			}
   126  			l := clog.NewConsoleLogger(cmd.OutOrStdout(), cmd.ErrOrStderr(), installerScope)
   127  			return ManifestGenerate(kubeClient, rootArgs, mgArgs, l)
   128  		},
   129  	}
   130  }
   131  
   132  func ManifestGenerate(kubeClient kube.CLIClient, args *RootArgs, mgArgs *ManifestGenerateArgs, l clog.Logger) error {
   133  	manifests, _, err := manifest.GenManifests(mgArgs.InFilenames, applyFlagAliases(mgArgs.Set, mgArgs.ManifestsPath, mgArgs.Revision),
   134  		mgArgs.Force, mgArgs.Filter, kubeClient, l)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	if len(mgArgs.Components) != 0 {
   140  		filteredManifests := name.ManifestMap{}
   141  		for _, cArg := range mgArgs.Components {
   142  			componentName := name.ComponentName(cArg)
   143  			if cManifests, ok := manifests[componentName]; ok {
   144  				filteredManifests[componentName] = cManifests
   145  			} else {
   146  				return fmt.Errorf("incorrect component name: %s. Valid options: %v", cArg, name.AllComponentNames)
   147  			}
   148  		}
   149  		manifests = filteredManifests
   150  	}
   151  
   152  	if mgArgs.OutFilename == "" {
   153  		ordered, err := orderedManifests(manifests)
   154  		if err != nil {
   155  			return fmt.Errorf("failed to order manifests: %v", err)
   156  		}
   157  		for _, m := range ordered {
   158  			l.Print(m + object.YAMLSeparator)
   159  		}
   160  	} else {
   161  		if err := os.MkdirAll(mgArgs.OutFilename, os.ModePerm); err != nil {
   162  			return err
   163  		}
   164  		if err := RenderToDir(manifests, mgArgs.OutFilename, args.DryRun, l); err != nil {
   165  			return err
   166  		}
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  // orderedManifests generates a list of manifests from the given map sorted by the default object order
   173  // This allows
   174  func orderedManifests(mm name.ManifestMap) ([]string, error) {
   175  	var rawOutput []string
   176  	var output []string
   177  	for _, mfs := range mm {
   178  		rawOutput = append(rawOutput, mfs...)
   179  	}
   180  	objects, err := object.ParseK8sObjectsFromYAMLManifest(strings.Join(rawOutput, helm.YAMLSeparator))
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	// For a given group of objects, sort in order to avoid missing dependencies, such as creating CRDs first
   185  	objects.Sort(object.DefaultObjectOrder())
   186  	for _, obj := range objects {
   187  		yml, err := obj.YAML()
   188  		if err != nil {
   189  			return nil, err
   190  		}
   191  		output = append(output, string(yml))
   192  	}
   193  
   194  	return output, nil
   195  }
   196  
   197  // RenderToDir writes manifests to a local filesystem directory tree.
   198  func RenderToDir(manifests name.ManifestMap, outputDir string, dryRun bool, l clog.Logger) error {
   199  	l.LogAndPrintf("Component dependencies tree: \n%s", helmreconciler.InstallTreeString())
   200  	l.LogAndPrintf("Rendering manifests to output dir %s", outputDir)
   201  	return renderRecursive(manifests, helmreconciler.InstallTree, outputDir, dryRun, l)
   202  }
   203  
   204  func renderRecursive(manifests name.ManifestMap, installTree helmreconciler.ComponentTree, outputDir string, dryRun bool, l clog.Logger) error {
   205  	for k, v := range installTree {
   206  		componentName := string(k)
   207  		// In cases (like gateways) where multiple instances can exist, concatenate the manifests and apply as one.
   208  		ym := strings.Join(manifests[k], helm.YAMLSeparator)
   209  		l.LogAndPrintf("Rendering: %s", componentName)
   210  		dirName := filepath.Join(outputDir, componentName)
   211  		if !dryRun {
   212  			if err := os.MkdirAll(dirName, os.ModePerm); err != nil {
   213  				return fmt.Errorf("could not create directory %s; %s", outputDir, err)
   214  			}
   215  		}
   216  		fname := filepath.Join(dirName, componentName) + ".yaml"
   217  		l.LogAndPrintf("Writing manifest to %s", fname)
   218  		if !dryRun {
   219  			if err := os.WriteFile(fname, []byte(ym), 0o644); err != nil {
   220  				return fmt.Errorf("could not write manifest config; %s", err)
   221  			}
   222  		}
   223  
   224  		kt, ok := v.(helmreconciler.ComponentTree)
   225  		if !ok {
   226  			// Leaf
   227  			return nil
   228  		}
   229  		if err := renderRecursive(manifests, kt, dirName, dryRun, l); err != nil {
   230  			return err
   231  		}
   232  	}
   233  	return nil
   234  }