istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/kubeinject/kubeinject.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 kubeinject
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"crypto/tls"
    21  	"crypto/x509"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"net/http"
    27  	"os"
    28  	"sort"
    29  	"strings"
    30  	"time"
    31  
    32  	"github.com/hashicorp/go-multierror"
    33  	"github.com/spf13/cobra"
    34  	admission "k8s.io/api/admission/v1"
    35  	admissionv1beta1 "k8s.io/api/admission/v1beta1"
    36  	admissionregistration "k8s.io/api/admissionregistration/v1"
    37  	corev1 "k8s.io/api/core/v1"
    38  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    39  	"k8s.io/apimachinery/pkg/runtime"
    40  	"k8s.io/apimachinery/pkg/runtime/serializer"
    41  	v1 "k8s.io/client-go/kubernetes/typed/core/v1"
    42  	"k8s.io/kubectl/pkg/polymorphichelpers"
    43  	"k8s.io/kubectl/pkg/util/podutils"
    44  	"sigs.k8s.io/yaml"
    45  
    46  	"istio.io/api/label"
    47  	meshconfig "istio.io/api/mesh/v1alpha1"
    48  	"istio.io/istio/istioctl/pkg/cli"
    49  	"istio.io/istio/istioctl/pkg/clioptions"
    50  	"istio.io/istio/istioctl/pkg/util"
    51  	iopv1alpha1 "istio.io/istio/operator/pkg/apis/istio/v1alpha1"
    52  	"istio.io/istio/operator/pkg/manifest"
    53  	"istio.io/istio/operator/pkg/validate"
    54  	"istio.io/istio/pkg/config/mesh"
    55  	"istio.io/istio/pkg/kube"
    56  	"istio.io/istio/pkg/kube/inject"
    57  	"istio.io/istio/pkg/log"
    58  	"istio.io/istio/pkg/util/protomarshal"
    59  	"istio.io/istio/pkg/version"
    60  )
    61  
    62  const (
    63  	configMapKey       = "mesh"
    64  	injectConfigMapKey = "config"
    65  	valuesConfigMapKey = "values"
    66  )
    67  
    68  type ExternalInjector struct {
    69  	client          kube.CLIClient
    70  	clientConfig    *admissionregistration.WebhookClientConfig
    71  	injectorAddress string
    72  }
    73  
    74  func (e ExternalInjector) Inject(pod *corev1.Pod, deploymentNS string) ([]byte, error) {
    75  	cc := e.clientConfig
    76  	if cc == nil {
    77  		return nil, nil
    78  	}
    79  	var address string
    80  	if cc.URL != nil {
    81  		address = *cc.URL
    82  	}
    83  	var certPool *x509.CertPool
    84  	if len(cc.CABundle) > 0 {
    85  		certPool = x509.NewCertPool()
    86  		certPool.AppendCertsFromPEM(cc.CABundle)
    87  	} else {
    88  		var err error
    89  		certPool, err = x509.SystemCertPool()
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  	}
    94  	tlsClientConfig := &tls.Config{RootCAs: certPool, MinVersion: tls.VersionTLS12}
    95  	client := http.Client{
    96  		Timeout: time.Second * 5,
    97  		Transport: &http.Transport{
    98  			TLSClientConfig: tlsClientConfig,
    99  		},
   100  	}
   101  	if cc.Service != nil {
   102  		svc, err := e.client.Kube().CoreV1().Services(cc.Service.Namespace).Get(context.Background(), cc.Service.Name, metav1.GetOptions{})
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  		namespace, selector, err := polymorphichelpers.SelectorsForObject(svc)
   107  		if err != nil {
   108  			if e.injectorAddress == "" {
   109  				return nil, fmt.Errorf("cannot attach to %T: %v", svc, err)
   110  			}
   111  			address = fmt.Sprintf("https://%s:%d%s", e.injectorAddress, *cc.Service.Port, *cc.Service.Path)
   112  		} else {
   113  			pod, err := GetFirstPod(e.client.Kube().CoreV1(), namespace, selector.String())
   114  			if err != nil {
   115  				return nil, err
   116  			}
   117  			webhookPort := cc.Service.Port
   118  			podPort := 15017
   119  			for _, v := range svc.Spec.Ports {
   120  				if v.Port == *webhookPort {
   121  					podPort = v.TargetPort.IntValue()
   122  					break
   123  				}
   124  			}
   125  			f, err := e.client.NewPortForwarder(pod.Name, pod.Namespace, "", 0, podPort)
   126  			if err != nil {
   127  				return nil, err
   128  			}
   129  			if err := f.Start(); err != nil {
   130  				return nil, err
   131  			}
   132  			address = fmt.Sprintf("https://%s%s", f.Address(), *cc.Service.Path)
   133  			defer func() {
   134  				f.Close()
   135  				f.WaitForStop()
   136  			}()
   137  		}
   138  		tlsClientConfig.ServerName = fmt.Sprintf("%s.%s.%s", cc.Service.Name, cc.Service.Namespace, "svc")
   139  	} else if isMCPAddr(address) {
   140  		var err error
   141  		client.Transport, err = mcpTransport(context.TODO(), client.Transport)
   142  		if err != nil {
   143  			return nil, err
   144  		}
   145  	}
   146  	podBytes, err := json.Marshal(pod)
   147  	if pod.Namespace != "" {
   148  		deploymentNS = pod.Namespace
   149  	}
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	rev := &admission.AdmissionReview{
   154  		TypeMeta: metav1.TypeMeta{
   155  			APIVersion: admission.SchemeGroupVersion.String(),
   156  			Kind:       "AdmissionReview",
   157  		},
   158  		Request: &admission.AdmissionRequest{
   159  			Object: runtime.RawExtension{Raw: podBytes},
   160  			Kind: metav1.GroupVersionKind{
   161  				Group:   admission.GroupName,
   162  				Version: admission.SchemeGroupVersion.Version,
   163  				Kind:    "AdmissionRequest",
   164  			},
   165  			Resource:           metav1.GroupVersionResource{},
   166  			SubResource:        "",
   167  			RequestKind:        nil,
   168  			RequestResource:    nil,
   169  			RequestSubResource: "",
   170  			Name:               pod.Name,
   171  			Namespace:          deploymentNS,
   172  		},
   173  		Response: nil,
   174  	}
   175  	revBytes, err := json.Marshal(rev)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	resp, err := client.Post(address, "application/json", bytes.NewBuffer(revBytes))
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	defer resp.Body.Close()
   184  	body, err := io.ReadAll(resp.Body)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	var obj runtime.Object
   189  	var ar *kube.AdmissionReview
   190  	out, _, err := deserializer.Decode(body, nil, obj)
   191  	if err != nil {
   192  		return nil, fmt.Errorf("could not decode body: %v", err)
   193  	}
   194  	ar, err = kube.AdmissionReviewKubeToAdapter(out)
   195  	if err != nil {
   196  		return nil, fmt.Errorf("could not decode object: %v", err)
   197  	}
   198  
   199  	return ar.Response.Patch, nil
   200  }
   201  
   202  var (
   203  	runtimeScheme = func() *runtime.Scheme {
   204  		r := runtime.NewScheme()
   205  		r.AddKnownTypes(admissionv1beta1.SchemeGroupVersion, &admissionv1beta1.AdmissionReview{})
   206  		r.AddKnownTypes(admission.SchemeGroupVersion, &admission.AdmissionReview{})
   207  		return r
   208  	}()
   209  	codecs       = serializer.NewCodecFactory(runtimeScheme)
   210  	deserializer = codecs.UniversalDeserializer()
   211  )
   212  
   213  // GetFirstPod returns a pod matching the namespace and label selector
   214  // and the number of all pods that match the label selector.
   215  // This is forked from  polymorphichelpers.GetFirstPod to not watch and instead return an error if no pods are found
   216  func GetFirstPod(client v1.CoreV1Interface, namespace string, selector string) (*corev1.Pod, error) {
   217  	options := metav1.ListOptions{LabelSelector: selector}
   218  
   219  	sortBy := func(pods []*corev1.Pod) sort.Interface { return sort.Reverse(podutils.ActivePods(pods)) }
   220  	podList, err := client.Pods(namespace).List(context.TODO(), options)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	pods := make([]*corev1.Pod, 0, len(podList.Items))
   225  	for i := range podList.Items {
   226  		pod := podList.Items[i]
   227  		pods = append(pods, &pod)
   228  	}
   229  	if len(pods) > 0 {
   230  		sort.Sort(sortBy(pods))
   231  		return pods[0], nil
   232  	}
   233  	return nil, fmt.Errorf("no pods matching selector %q found in namespace %q", selector, namespace)
   234  }
   235  
   236  func getMeshConfigFromConfigMap(ctx cli.Context, command, revision string) (*meshconfig.MeshConfig, error) {
   237  	client, err := ctx.CLIClient()
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	if meshConfigMapName == defaultMeshConfigMapName && revision != "" {
   243  		meshConfigMapName = fmt.Sprintf("%s-%s", defaultMeshConfigMapName, revision)
   244  	}
   245  	meshConfigMap, err := client.Kube().CoreV1().ConfigMaps(ctx.IstioNamespace()).Get(context.TODO(), meshConfigMapName, metav1.GetOptions{})
   246  	if err != nil {
   247  		return nil, fmt.Errorf("could not read valid configmap %q from namespace %q: %v - "+
   248  			"Use --meshConfigFile or re-run "+command+" with `-i <istioSystemNamespace> and ensure valid MeshConfig exists",
   249  			meshConfigMapName, ctx.IstioNamespace(), err)
   250  	}
   251  	// values in the data are strings, while proto might use a
   252  	// different data type.  therefore, we have to get a value by a
   253  	// key
   254  	configYaml, exists := meshConfigMap.Data[configMapKey]
   255  	if !exists {
   256  		return nil, fmt.Errorf("missing configuration map key %q", configMapKey)
   257  	}
   258  	cfg, err := mesh.ApplyMeshConfigDefaults(configYaml)
   259  	if err != nil {
   260  		err = multierror.Append(err, fmt.Errorf("istioctl version %s cannot parse mesh config.  Install istioctl from the latest Istio release",
   261  			version.Info.Version))
   262  	}
   263  	return cfg, err
   264  }
   265  
   266  // grabs the raw values from the ConfigMap. These are encoded as JSON.
   267  func GetValuesFromConfigMap(ctx cli.Context, revision string) (string, error) {
   268  	client, err := ctx.CLIClient()
   269  	if err != nil {
   270  		return "", err
   271  	}
   272  
   273  	if revision != "" {
   274  		injectConfigMapName = fmt.Sprintf("%s-%s", defaultInjectConfigMapName, revision)
   275  	}
   276  	meshConfigMap, err := client.Kube().CoreV1().ConfigMaps(ctx.IstioNamespace()).Get(context.TODO(), injectConfigMapName, metav1.GetOptions{})
   277  	if err != nil {
   278  		return "", fmt.Errorf("could not find valid configmap %q from namespace  %q: %v - "+
   279  			"Use --valuesFile or re-run kube-inject with `-i <istioSystemNamespace> and ensure istio-sidecar-injector configmap exists",
   280  			injectConfigMapName, ctx.IstioNamespace(), err)
   281  	}
   282  
   283  	valuesData, exists := meshConfigMap.Data[valuesConfigMapKey]
   284  	if !exists {
   285  		return "", fmt.Errorf("missing configuration map key %q in %q",
   286  			valuesConfigMapKey, injectConfigMapName)
   287  	}
   288  
   289  	return valuesData, nil
   290  }
   291  
   292  func readInjectConfigFile(f []byte) (inject.RawTemplates, error) {
   293  	var injectConfig inject.Config
   294  	err := yaml.Unmarshal(f, &injectConfig)
   295  	if err != nil || len(injectConfig.RawTemplates) == 0 {
   296  		// This must be a direct template, instead of an inject.Config. We support both formats
   297  		return map[string]string{inject.SidecarTemplateName: string(f)}, nil
   298  	}
   299  	cfg, err := inject.UnmarshalConfig(f)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  	return cfg.RawTemplates, err
   304  }
   305  
   306  func getInjectConfigFromConfigMap(ctx cli.Context, revision string) (inject.RawTemplates, error) {
   307  	client, err := ctx.CLIClient()
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	if injectConfigMapName == defaultInjectConfigMapName && revision != "" {
   313  		injectConfigMapName = fmt.Sprintf("%s-%s", defaultInjectConfigMapName, revision)
   314  	}
   315  	meshConfigMap, err := client.Kube().CoreV1().ConfigMaps(ctx.IstioNamespace()).Get(context.TODO(), injectConfigMapName, metav1.GetOptions{})
   316  	if err != nil {
   317  		return nil, fmt.Errorf("could not find valid configmap %q from namespace  %q: %v - "+
   318  			"Use --injectConfigFile or re-run kube-inject with `-i <istioSystemNamespace>` and ensure istio-sidecar-injector configmap exists",
   319  			injectConfigMapName, ctx.IstioNamespace(), err)
   320  	}
   321  	// values in the data are strings, while proto might use a
   322  	// different data type.  therefore, we have to get a value by a
   323  	// key
   324  	injectData, exists := meshConfigMap.Data[injectConfigMapKey]
   325  	if !exists {
   326  		return nil, fmt.Errorf("missing configuration map key %q in %q",
   327  			injectConfigMapKey, injectConfigMapName)
   328  	}
   329  	injectConfig, err := inject.UnmarshalConfig([]byte(injectData))
   330  	if err != nil {
   331  		return nil, fmt.Errorf("unable to convert data from configmap %q: %v",
   332  			injectConfigMapName, err)
   333  	}
   334  	log.Debugf("using inject template from configmap %q", injectConfigMapName)
   335  	return injectConfig.RawTemplates, nil
   336  }
   337  
   338  func setUpExternalInjector(ctx cli.Context, revision, injectorAddress string) (*ExternalInjector, error) {
   339  	e := &ExternalInjector{}
   340  	client, err := ctx.CLIClient()
   341  	if err != nil {
   342  		return e, err
   343  	}
   344  	if revision == "" {
   345  		revision = "default"
   346  	}
   347  	whcList, err := client.Kube().AdmissionregistrationV1().MutatingWebhookConfigurations().List(context.TODO(),
   348  		metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", label.IoIstioRev.Name, revision)})
   349  	if err != nil {
   350  		return e, fmt.Errorf("could not find valid mutatingWebhookConfiguration %q from cluster %v",
   351  			whcName, err)
   352  	}
   353  	if whcList != nil && len(whcList.Items) != 0 {
   354  		for _, wh := range whcList.Items[0].Webhooks {
   355  			if strings.HasSuffix(wh.Name, defaultWebhookName) {
   356  				return &ExternalInjector{client, &wh.ClientConfig, injectorAddress}, nil
   357  			}
   358  		}
   359  	}
   360  	return e, fmt.Errorf("could not find valid mutatingWebhookConfiguration %q from cluster", defaultWebhookName)
   361  }
   362  
   363  func validateFlags() error {
   364  	var err error
   365  	if inFilename == "" {
   366  		err = multierror.Append(err, errors.New("filename not specified (see --filename or -f)"))
   367  	}
   368  	if meshConfigFile == "" && meshConfigMapName == "" && iopFilename == "" {
   369  		err = multierror.Append(err,
   370  			errors.New("--meshConfigFile or --meshConfigMapName or --operatorFileName must be set"))
   371  	}
   372  	return err
   373  }
   374  
   375  func setupKubeInjectParameters(cliContext cli.Context, sidecarTemplate *inject.RawTemplates, valuesConfig *string,
   376  	revision, injectorAddress string,
   377  ) (*ExternalInjector, *meshconfig.MeshConfig, error) {
   378  	var err error
   379  	// Get configs from IOP files firstly, and if not exists, get configs from files and configmaps.
   380  	values, meshConfig, err := getIOPConfigs()
   381  	if err != nil {
   382  		return nil, nil, err
   383  	}
   384  	if meshConfig == nil {
   385  		if meshConfigFile != "" {
   386  			if meshConfig, err = mesh.ReadMeshConfig(meshConfigFile); err != nil {
   387  				return nil, nil, err
   388  			}
   389  		} else {
   390  			if meshConfig, err = getMeshConfigFromConfigMap(cliContext, "kube-inject", revision); err != nil {
   391  				return nil, nil, err
   392  			}
   393  		}
   394  	}
   395  
   396  	injector := &ExternalInjector{}
   397  	if injectConfigFile != "" {
   398  		injectionConfig, err := os.ReadFile(injectConfigFile) // nolint: vetshadow
   399  		if err != nil {
   400  			return nil, nil, err
   401  		}
   402  		injectConfig, err := readInjectConfigFile(injectionConfig)
   403  		if err != nil {
   404  			return nil, nil, multierror.Append(err, fmt.Errorf("loading --injectConfigFile"))
   405  		}
   406  		*sidecarTemplate = injectConfig
   407  	} else {
   408  		injector, err = setUpExternalInjector(cliContext, revision, injectorAddress)
   409  		if err != nil || injector.clientConfig == nil {
   410  			log.Warnf("failed to get injection config from mutatingWebhookConfigurations %q, will fall back to "+
   411  				"get injection from the injection configmap %q : %v", whcName, defaultInjectWebhookConfigName, err)
   412  			if *sidecarTemplate, err = getInjectConfigFromConfigMap(cliContext, revision); err != nil {
   413  				return nil, nil, err
   414  			}
   415  		}
   416  		return injector, meshConfig, nil
   417  	}
   418  
   419  	if values != "" {
   420  		*valuesConfig = values
   421  	}
   422  	if valuesConfig == nil || *valuesConfig == "" {
   423  		if valuesFile != "" {
   424  			valuesConfigBytes, err := os.ReadFile(valuesFile) // nolint: vetshadow
   425  			if err != nil {
   426  				return nil, nil, err
   427  			}
   428  			*valuesConfig = string(valuesConfigBytes)
   429  		} else if *valuesConfig, err = GetValuesFromConfigMap(cliContext, revision); err != nil {
   430  			return nil, nil, err
   431  		}
   432  	}
   433  	return injector, meshConfig, err
   434  }
   435  
   436  // getIOPConfigs gets the configs in IOPs.
   437  func getIOPConfigs() (string, *meshconfig.MeshConfig, error) {
   438  	var meshConfig *meshconfig.MeshConfig
   439  	var valuesConfig string
   440  	if iopFilename != "" {
   441  		var iop *iopv1alpha1.IstioOperator
   442  		y, err := manifest.ReadLayeredYAMLs([]string{iopFilename})
   443  		if err != nil {
   444  			return "", nil, err
   445  		}
   446  		iop, err = validate.UnmarshalIOP(y)
   447  		if err != nil {
   448  			return "", nil, err
   449  		}
   450  		if err := validate.ValidIOP(iop); err != nil {
   451  			return "", nil, fmt.Errorf("validation errors: \n%s", err)
   452  		}
   453  		if err != nil {
   454  			return "", nil, err
   455  		}
   456  		if iop.Spec.Values != nil {
   457  			values, err := protomarshal.ToJSON(iop.Spec.Values)
   458  			if err != nil {
   459  				return "", nil, err
   460  			}
   461  			valuesConfig = values
   462  		}
   463  		if iop.Spec.MeshConfig != nil {
   464  			meshConfigYaml, err := protomarshal.ToYAML(iop.Spec.MeshConfig)
   465  			if err != nil {
   466  				return "", nil, err
   467  			}
   468  			meshConfig, err = mesh.ApplyMeshConfigDefaults(meshConfigYaml)
   469  			if err != nil {
   470  				return "", nil, err
   471  			}
   472  		}
   473  	}
   474  	return valuesConfig, meshConfig, nil
   475  }
   476  
   477  var (
   478  	inFilename          string
   479  	outFilename         string
   480  	meshConfigFile      string
   481  	meshConfigMapName   string
   482  	valuesFile          string
   483  	injectConfigFile    string
   484  	injectConfigMapName string
   485  	whcName             string
   486  	iopFilename         string
   487  )
   488  
   489  const (
   490  	defaultMeshConfigMapName       = "istio"
   491  	defaultInjectConfigMapName     = "istio-sidecar-injector"
   492  	defaultInjectWebhookConfigName = "istio-sidecar-injector"
   493  	defaultWebhookName             = "sidecar-injector.istio.io"
   494  )
   495  
   496  func InjectCommand(cliContext cli.Context) *cobra.Command {
   497  	var opts clioptions.ControlPlaneOptions
   498  	var centralOpts clioptions.CentralControlPlaneOptions
   499  
   500  	injectCmd := &cobra.Command{
   501  		Use:   "kube-inject",
   502  		Short: "Inject Istio sidecar into Kubernetes pod resources",
   503  		Long: `
   504  kube-inject manually injects the Istio sidecar into Kubernetes
   505  workloads. Unsupported resources are left unmodified so it is safe to
   506  run kube-inject over a single file that contains multiple Service,
   507  ConfigMap, Deployment, etc. definitions for a complex application. When in
   508  doubt re-run istioctl kube-inject on deployments to get the most up-to-date changes.
   509  
   510  It's best to do kube-inject when the resource is initially created.
   511  `,
   512  		Example: `  # Update resources on the fly before applying.
   513    kubectl apply -f <(istioctl kube-inject -f <resource.yaml>)
   514  
   515    # Create a persistent version of the deployment with Istio sidecar injected.
   516    istioctl kube-inject -f deployment.yaml -o deployment-injected.yaml
   517  
   518    # Update an existing deployment.
   519    kubectl get deployment -o yaml | istioctl kube-inject -f - | kubectl apply -f -
   520  
   521    # Capture cluster configuration for later use with kube-inject
   522    kubectl -n istio-system get cm istio-sidecar-injector  -o jsonpath="{.data.config}" > /tmp/inj-template.tmpl
   523    kubectl -n istio-system get cm istio -o jsonpath="{.data.mesh}" > /tmp/mesh.yaml
   524    kubectl -n istio-system get cm istio-sidecar-injector -o jsonpath="{.data.values}" > /tmp/values.json
   525  
   526    # Use kube-inject based on captured configuration
   527    istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml \
   528      --injectConfigFile /tmp/inj-template.tmpl \
   529      --meshConfigFile /tmp/mesh.yaml \
   530      --valuesFile /tmp/values.json
   531  `,
   532  		RunE: func(c *cobra.Command, _ []string) (err error) {
   533  			if err = validateFlags(); err != nil {
   534  				return err
   535  			}
   536  			var reader io.Reader
   537  
   538  			if inFilename == "-" {
   539  				reader = os.Stdin
   540  			} else {
   541  				var in *os.File
   542  				if in, err = os.Open(inFilename); err != nil {
   543  					return err
   544  				}
   545  				reader = in
   546  				defer func() {
   547  					if errClose := in.Close(); errClose != nil {
   548  						log.Errorf("Error: close file from %s, %s", inFilename, errClose)
   549  
   550  						// don't overwrite the previous error
   551  						if err == nil {
   552  							err = errClose
   553  						}
   554  					}
   555  				}()
   556  			}
   557  
   558  			var writer io.Writer
   559  			if outFilename == "" {
   560  				writer = c.OutOrStdout()
   561  			} else {
   562  				var out *os.File
   563  				if out, err = os.Create(outFilename); err != nil {
   564  					return err
   565  				}
   566  				writer = out
   567  				defer func() {
   568  					if errClose := out.Close(); errClose != nil {
   569  						log.Errorf("Error: close file from %s, %s", outFilename, errClose)
   570  
   571  						// don't overwrite the previous error
   572  						if err == nil {
   573  							err = errClose
   574  						}
   575  					}
   576  				}()
   577  			}
   578  			var valuesConfig string
   579  			var sidecarTemplate inject.RawTemplates
   580  			var meshConfig *meshconfig.MeshConfig
   581  			rev := opts.Revision
   582  			// if the revision is "default", render templates with an empty revision
   583  			if rev == util.DefaultRevisionName {
   584  				rev = ""
   585  			}
   586  			injectorAddress := centralOpts.Xds
   587  			index := strings.IndexByte(injectorAddress, ':')
   588  			if index != -1 {
   589  				injectorAddress = injectorAddress[:index]
   590  			}
   591  			injector, meshConfig, err := setupKubeInjectParameters(cliContext, &sidecarTemplate, &valuesConfig, rev, injectorAddress)
   592  			if err != nil {
   593  				return err
   594  			}
   595  			if injector.client == nil && meshConfig == nil {
   596  				return fmt.Errorf(
   597  					"failed to get injection config from mutatingWebhookConfigurations and injection configmap - " +
   598  						"check injection configmap or pass --revision flag",
   599  				)
   600  			}
   601  			var warnings []string
   602  			templs, err := inject.ParseTemplates(sidecarTemplate)
   603  			if err != nil {
   604  				return err
   605  			}
   606  			vc, err := inject.NewValuesConfig(valuesConfig)
   607  			if err != nil {
   608  				return err
   609  			}
   610  			retval := inject.IntoResourceFile(injector, templs, vc, rev, meshConfig,
   611  				reader, writer, func(warning string) {
   612  					warnings = append(warnings, warning)
   613  				})
   614  			if len(warnings) > 0 {
   615  				fmt.Fprintln(c.ErrOrStderr())
   616  			}
   617  			for _, warning := range warnings {
   618  				fmt.Fprintln(c.ErrOrStderr(), warning)
   619  			}
   620  			return retval
   621  		},
   622  		PersistentPreRunE: func(c *cobra.Command, args []string) error {
   623  			// istioctl kube-inject is typically redirected to a .yaml file;
   624  			// the default for log messages should be stderr, not stdout
   625  			root := c.Root()
   626  			if root != nil {
   627  				_ = c.Root().PersistentFlags().Set("log_target", "stderr")
   628  			}
   629  			if c.Parent() != nil && c.Parent().PersistentPreRunE != nil {
   630  				return c.Parent().PersistentPreRunE(c, args)
   631  			}
   632  
   633  			return nil
   634  		},
   635  	}
   636  
   637  	injectCmd.PersistentFlags().StringVar(&meshConfigFile, "meshConfigFile", "",
   638  		"Mesh configuration filename. Takes precedence over --meshConfigMapName if set")
   639  	injectCmd.PersistentFlags().StringVar(&injectConfigFile, "injectConfigFile", "",
   640  		"Injection configuration filename. Cannot be used with --injectConfigMapName")
   641  	injectCmd.PersistentFlags().StringVar(&valuesFile, "valuesFile", "",
   642  		"Injection values configuration filename.")
   643  
   644  	injectCmd.PersistentFlags().StringVarP(&inFilename, "filename", "f",
   645  		"", "Input Kubernetes resource filename")
   646  	injectCmd.PersistentFlags().StringVarP(&outFilename, "output", "o",
   647  		"", "Modified output Kubernetes resource filename")
   648  	injectCmd.PersistentFlags().StringVar(&iopFilename, "operatorFileName", "",
   649  		"Path to file containing IstioOperator custom resources. If configs from files like "+
   650  			"meshConfigFile, valuesFile are provided, they will be overridden by iop config values.")
   651  
   652  	injectCmd.PersistentFlags().StringVar(&meshConfigMapName, "meshConfigMapName", defaultMeshConfigMapName,
   653  		fmt.Sprintf("ConfigMap name for Istio mesh configuration, key should be %q", configMapKey))
   654  	injectCmd.PersistentFlags().StringVar(&injectConfigMapName, "injectConfigMapName", defaultInjectConfigMapName,
   655  		fmt.Sprintf("ConfigMap name for Istio sidecar injection, key should be %q.", injectConfigMapKey))
   656  	_ = injectCmd.PersistentFlags().MarkHidden("injectConfigMapName")
   657  	injectCmd.PersistentFlags().StringVar(&whcName, "webhookConfig", defaultInjectWebhookConfigName,
   658  		"MutatingWebhookConfiguration name for Istio")
   659  	opts.AttachControlPlaneFlags(injectCmd)
   660  	centralOpts.AttachControlPlaneFlags(injectCmd)
   661  	return injectCmd
   662  }