|
| 1 | +// Copyright 2019 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 | + "io/ioutil" |
| 20 | + "sort" |
| 21 | + "strings" |
| 22 | + "time" |
| 23 | + |
| 24 | + "github.com/spf13/cobra" |
| 25 | + "k8s.io/utils/pointer" |
| 26 | + |
| 27 | + "istio.io/operator/pkg/helm" |
| 28 | + "istio.io/operator/pkg/kubectlcmd" |
| 29 | + "istio.io/operator/pkg/manifest" |
| 30 | + "istio.io/operator/pkg/name" |
| 31 | + "istio.io/operator/pkg/object" |
| 32 | + "istio.io/operator/pkg/util" |
| 33 | + "istio.io/operator/version" |
| 34 | + "istio.io/pkg/log" |
| 35 | + buildversion "istio.io/pkg/version" |
| 36 | +) |
| 37 | + |
| 38 | +type operatorInitArgs struct { |
| 39 | + // hub is the hub for the operator image. |
| 40 | + hub string |
| 41 | + // tag is the tag for the operator image. |
| 42 | + tag string |
| 43 | + // operatorNamespace is the namespace the operator controller is installed into. |
| 44 | + operatorNamespace string |
| 45 | + // istioNamespace is the namespace Istio is installed into. |
| 46 | + istioNamespace string |
| 47 | + // inFilename is the path to the input IstioControlPlane CR. |
| 48 | + inFilename string |
| 49 | + |
| 50 | + // kubeConfigPath is the path to kube config file. |
| 51 | + kubeConfigPath string |
| 52 | + // context is the cluster context in the kube config. |
| 53 | + context string |
| 54 | + // readinessTimeout is maximum time to wait for all Istio resources to be ready. |
| 55 | + readinessTimeout time.Duration |
| 56 | + // wait is flag that indicates whether to wait resources ready before exiting. |
| 57 | + wait bool |
| 58 | +} |
| 59 | + |
| 60 | +const ( |
| 61 | + istioControllerComponentName = "Operator" |
| 62 | + istioNamespaceComponentName = "IstioNamespace" |
| 63 | + istioOperatorCRComponentName = "OperatorCustomResource" |
| 64 | +) |
| 65 | + |
| 66 | +// manifestApplier is used for test dependency injection. |
| 67 | +type manifestApplier func(manifestStr, componentName string, opts *kubectlcmd.Options, verbose bool, l *Logger) bool |
| 68 | + |
| 69 | +var ( |
| 70 | + defaultManifestApplier = applyManifest |
| 71 | +) |
| 72 | + |
| 73 | +func addOperatorInitFlags(cmd *cobra.Command, args *operatorInitArgs) { |
| 74 | + hub, tag := buildversion.DockerInfo.Hub, buildversion.DockerInfo.Tag |
| 75 | + if hub == "" { |
| 76 | + hub = "gcr.io/istio-testing" |
| 77 | + } |
| 78 | + if tag == "" { |
| 79 | + tag = "latest" |
| 80 | + } |
| 81 | + cmd.PersistentFlags().StringVarP(&args.inFilename, "filename", "f", "", filenameFlagHelpStr) |
| 82 | + cmd.PersistentFlags().StringVarP(&args.kubeConfigPath, "kubeconfig", "c", "", "Path to kube config") |
| 83 | + cmd.PersistentFlags().StringVar(&args.context, "context", "", "The name of the kubeconfig context to use") |
| 84 | + cmd.PersistentFlags().DurationVar(&args.readinessTimeout, "readiness-timeout", 300*time.Second, "Maximum seconds to wait for all Istio resources to be ready."+ |
| 85 | + " The --wait flag must be set for this flag to apply") |
| 86 | + cmd.PersistentFlags().BoolVarP(&args.wait, "wait", "w", false, "Wait, if set will wait until all Pods, Services, and minimum number of Pods "+ |
| 87 | + "of a Deployment are in a ready state before the command exits. It will wait for a maximum duration of --readiness-timeout seconds") |
| 88 | + |
| 89 | + cmd.PersistentFlags().StringVar(&args.hub, "hub", hub, "The hub for the operator controller image") |
| 90 | + cmd.PersistentFlags().StringVar(&args.tag, "tag", tag, "The tag for the operator controller image") |
| 91 | + cmd.PersistentFlags().StringVar(&args.operatorNamespace, "operatorNamespace", "istio-operator", |
| 92 | + "The namespace the operator controller is installed into") |
| 93 | + cmd.PersistentFlags().StringVar(&args.istioNamespace, "istioNamespace", "istio-system", |
| 94 | + "The namespace Istio is installed into") |
| 95 | +} |
| 96 | + |
| 97 | +func operatorInitCmd(rootArgs *rootArgs, oiArgs *operatorInitArgs) *cobra.Command { |
| 98 | + return &cobra.Command{ |
| 99 | + Use: "init", |
| 100 | + Short: "Installs the Istio operator controller in the cluster.", |
| 101 | + Long: "The init subcommand installs the Istio operator controller in the cluster.", |
| 102 | + Args: cobra.ExactArgs(0), |
| 103 | + Run: func(cmd *cobra.Command, args []string) { |
| 104 | + l := NewLogger(rootArgs.logToStdErr, cmd.OutOrStdout(), cmd.OutOrStderr()) |
| 105 | + operatorInit(rootArgs, oiArgs, l, defaultManifestApplier) |
| 106 | + }} |
| 107 | +} |
| 108 | + |
| 109 | +// operatorInit installs the Istio operator controller into the cluster. |
| 110 | +func operatorInit(args *rootArgs, oiArgs *operatorInitArgs, l *Logger, apply manifestApplier) { |
| 111 | + initLogsOrExit(args) |
| 112 | + |
| 113 | + // Error here likely indicates Deployment is missing. If some other K8s error, we will hit it again later. |
| 114 | + already, _ := isControllerInstalled(oiArgs.kubeConfigPath, oiArgs.context, oiArgs.operatorNamespace) |
| 115 | + if already { |
| 116 | + l.logAndPrintf("Operator controller is already installed in %s namespace, updating.", oiArgs.operatorNamespace) |
| 117 | + } |
| 118 | + |
| 119 | + l.logAndPrintf("Using operator Deployment image: %s/operator:%s", oiArgs.hub, oiArgs.tag) |
| 120 | + |
| 121 | + mstr, err := renderOperatorManifest(args, oiArgs, l) |
| 122 | + if err != nil { |
| 123 | + l.logAndFatal(err) |
| 124 | + } |
| 125 | + |
| 126 | + log.Infof("Using the following manifest to install operator:\n%s\n", mstr) |
| 127 | + |
| 128 | + // If CR was passed, we must create a namespace for it and install CR into it. |
| 129 | + customResource, istioNamespace, err := getCRAndNamespaceFromFile(oiArgs.inFilename, l) |
| 130 | + if err != nil { |
| 131 | + l.logAndFatal(err) |
| 132 | + } |
| 133 | + |
| 134 | + opts := &kubectlcmd.Options{ |
| 135 | + DryRun: args.dryRun, |
| 136 | + Verbose: args.verbose, |
| 137 | + WaitTimeout: 1 * time.Minute, |
| 138 | + Kubeconfig: oiArgs.kubeConfigPath, |
| 139 | + Context: oiArgs.context, |
| 140 | + } |
| 141 | + |
| 142 | + if err := manifest.InitK8SRestClient(opts.Kubeconfig, opts.Context); err != nil { |
| 143 | + l.logAndFatal(err) |
| 144 | + } |
| 145 | + |
| 146 | + success := apply(mstr, istioControllerComponentName, opts, args.verbose, l) |
| 147 | + |
| 148 | + if customResource != "" { |
| 149 | + success = success && apply(genNamespaceResource(istioNamespace), istioNamespaceComponentName, opts, args.verbose, l) |
| 150 | + success = success && apply(customResource, istioOperatorCRComponentName, opts, args.verbose, l) |
| 151 | + } |
| 152 | + |
| 153 | + if !success { |
| 154 | + l.logAndPrint("\n*** Errors were logged during apply operation. Please check component installation logs above. ***\n") |
| 155 | + return |
| 156 | + } |
| 157 | + |
| 158 | + l.logAndPrint("\n*** Success. ***\n") |
| 159 | +} |
| 160 | + |
| 161 | +func applyManifest(manifestStr, componentName string, opts *kubectlcmd.Options, verbose bool, l *Logger) bool { |
| 162 | + l.logAndPrint("") |
| 163 | + // Specifically don't prune operator installation since it leads to a lot of resources being reapplied. |
| 164 | + opts.Prune = pointer.BoolPtr(false) |
| 165 | + out, objs := manifest.ApplyManifest(name.ComponentName(componentName), manifestStr, version.OperatorBinaryVersion.String(), opts) |
| 166 | + |
| 167 | + success := true |
| 168 | + if out.Err != nil { |
| 169 | + cs := fmt.Sprintf("Component %s install returned the following errors:", componentName) |
| 170 | + l.logAndPrintf("\n%s\n%s", cs, strings.Repeat("=", len(cs))) |
| 171 | + l.logAndPrint("Error: ", out.Err, "\n") |
| 172 | + success = false |
| 173 | + } else { |
| 174 | + l.logAndPrintf("Component %s installed successfully.", componentName) |
| 175 | + if opts.Verbose { |
| 176 | + l.logAndPrintf("The following objects were installed:\n%s", k8sObjectsString(objs)) |
| 177 | + } |
| 178 | + } |
| 179 | + |
| 180 | + if !ignoreError(out.Stderr) { |
| 181 | + l.logAndPrint("Error detail:\n", out.Stderr, "\n") |
| 182 | + success = false |
| 183 | + } |
| 184 | + if !ignoreError(out.Stderr) { |
| 185 | + l.logAndPrint(out.Stdout, "\n") |
| 186 | + } |
| 187 | + return success |
| 188 | +} |
| 189 | + |
| 190 | +func getCRAndNamespaceFromFile(filePath string, l *Logger) (customResource string, istioNamespace string, err error) { |
| 191 | + if filePath == "" { |
| 192 | + return "", "", nil |
| 193 | + } |
| 194 | + |
| 195 | + mergedYAML, err := genProfile(false, filePath, "", "", "", true, l) |
| 196 | + if err != nil { |
| 197 | + return "", "", err |
| 198 | + } |
| 199 | + mergedICPS, err := unmarshalAndValidateICPS(mergedYAML, true, l) |
| 200 | + if err != nil { |
| 201 | + return "", "", err |
| 202 | + } |
| 203 | + |
| 204 | + b, err := ioutil.ReadFile(filePath) |
| 205 | + if err != nil { |
| 206 | + return "", "", fmt.Errorf("could not read values from file %s: %s", filePath, err) |
| 207 | + } |
| 208 | + customResource = string(b) |
| 209 | + istioNamespace = mergedICPS.DefaultNamespace |
| 210 | + return |
| 211 | +} |
| 212 | + |
| 213 | +// chartsRootDir, helmBaseDir, componentName, namespace string) (TemplateRenderer, error) { |
| 214 | +func renderOperatorManifest(_ *rootArgs, oiArgs *operatorInitArgs, _ *Logger) (string, error) { |
| 215 | + r, err := helm.NewHelmRenderer("", "../operator", istioControllerComponentName, oiArgs.operatorNamespace) |
| 216 | + if err != nil { |
| 217 | + return "", err |
| 218 | + } |
| 219 | + |
| 220 | + if err := r.Run(); err != nil { |
| 221 | + return "", err |
| 222 | + } |
| 223 | + |
| 224 | + tmpl := ` |
| 225 | +operatorNamespace: {{.OperatorNamespace}} |
| 226 | +istioNamespace: {{.IstioNamespace}} |
| 227 | +hub: {{.Hub}} |
| 228 | +tag: {{.Tag}} |
| 229 | +` |
| 230 | + |
| 231 | + tv := struct { |
| 232 | + OperatorNamespace string |
| 233 | + IstioNamespace string |
| 234 | + Hub string |
| 235 | + Tag string |
| 236 | + }{ |
| 237 | + OperatorNamespace: oiArgs.operatorNamespace, |
| 238 | + IstioNamespace: oiArgs.istioNamespace, |
| 239 | + Hub: oiArgs.hub, |
| 240 | + Tag: oiArgs.tag, |
| 241 | + } |
| 242 | + vals, err := util.RenderTemplate(tmpl, tv) |
| 243 | + if err != nil { |
| 244 | + return "", err |
| 245 | + } |
| 246 | + log.Infof("Installing operator charts with the following values:\n%s", vals) |
| 247 | + return r.RenderManifest(vals) |
| 248 | +} |
| 249 | + |
| 250 | +func genNamespaceResource(namespace string) string { |
| 251 | + tmpl := ` |
| 252 | +apiVersion: v1 |
| 253 | +kind: Namespace |
| 254 | +metadata: |
| 255 | + labels: |
| 256 | + istio-injection: disabled |
| 257 | + name: {{.Namespace}} |
| 258 | +` |
| 259 | + |
| 260 | + tv := struct { |
| 261 | + Namespace string |
| 262 | + }{ |
| 263 | + Namespace: namespace, |
| 264 | + } |
| 265 | + vals, err := util.RenderTemplate(tmpl, tv) |
| 266 | + if err != nil { |
| 267 | + return "" |
| 268 | + } |
| 269 | + return vals |
| 270 | +} |
| 271 | + |
| 272 | +func k8sObjectsString(objs object.K8sObjects) string { |
| 273 | + var out []string |
| 274 | + for _, o := range objs { |
| 275 | + out = append(out, fmt.Sprintf("- %s/%s/%s", o.Kind, o.Namespace, o.Name)) |
| 276 | + } |
| 277 | + sort.Strings(out) |
| 278 | + return strings.Join(out, "\n") |
| 279 | +} |
0 commit comments