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 }