"Fossies" - the Fresh Open Source Software Archive

Member "istio-1.6.5/istioctl/cmd/add-to-mesh.go" (8 Jul 2020, 19786 Bytes) of package /linux/misc/istio-1.6.5.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Go source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "add-to-mesh.go": 1.5.4_vs_1.6.0.

    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 cmd
   16 
   17 import (
   18     "context"
   19     "fmt"
   20     "io"
   21     "io/ioutil"
   22     "strconv"
   23     "strings"
   24 
   25     "github.com/ghodss/yaml"
   26     "github.com/hashicorp/go-multierror"
   27     "github.com/spf13/cobra"
   28 
   29     appsv1 "k8s.io/api/apps/v1"
   30     corev1 "k8s.io/api/core/v1"
   31     metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
   32     "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
   33     k8s_labels "k8s.io/apimachinery/pkg/labels"
   34     "k8s.io/apimachinery/pkg/runtime/schema"
   35     "k8s.io/client-go/dynamic"
   36     "k8s.io/client-go/kubernetes"
   37 
   38     meshconfig "istio.io/api/mesh/v1alpha1"
   39     "istio.io/api/networking/v1alpha3"
   40     "istio.io/pkg/log"
   41 
   42     "istio.io/istio/istioctl/pkg/util/handlers"
   43     "istio.io/istio/pilot/pkg/model"
   44     kube_registry "istio.io/istio/pilot/pkg/serviceregistry/kube"
   45     "istio.io/istio/pkg/config/mesh"
   46     istioProtocol "istio.io/istio/pkg/config/protocol"
   47     "istio.io/istio/pkg/config/schema/collections"
   48     "istio.io/istio/pkg/kube"
   49     "istio.io/istio/pkg/kube/inject"
   50 )
   51 
   52 var (
   53     crdFactory = createDynamicInterface
   54 )
   55 
   56 // vmServiceOpts contains the options of a mesh expansion service running on VM.
   57 type vmServiceOpts struct {
   58     Name           string
   59     Namespace      string
   60     ServiceAccount string
   61     IP             []string
   62     PortList       model.PortList
   63     Labels         map[string]string
   64     Annotations    map[string]string
   65 }
   66 
   67 func addToMeshCmd() *cobra.Command {
   68     addToMeshCmd := &cobra.Command{
   69         Use:     "add-to-mesh",
   70         Aliases: []string{"add"},
   71         Short:   "Add workloads into Istio service mesh",
   72         RunE: func(cmd *cobra.Command, args []string) error {
   73             cmd.HelpFunc()(cmd, args)
   74             if len(args) != 0 {
   75                 return fmt.Errorf("unknown resource type %q", args[0])
   76             }
   77             return nil
   78         },
   79     }
   80     addToMeshCmd.AddCommand(svcMeshifyCmd())
   81     addToMeshCmd.AddCommand(deploymentMeshifyCmd())
   82     externalSvcMeshifyCmd := externalSvcMeshifyCmd()
   83     hideInheritedFlags(externalSvcMeshifyCmd, "meshConfigFile", "meshConfigMapName", "injectConfigFile",
   84         "injectConfigMapName", "valuesFile")
   85     addToMeshCmd.AddCommand(externalSvcMeshifyCmd)
   86     addToMeshCmd.PersistentFlags().StringVar(&meshConfigFile, "meshConfigFile", "",
   87         "mesh configuration filename. Takes precedence over --meshConfigMapName if set")
   88     addToMeshCmd.PersistentFlags().StringVar(&injectConfigFile, "injectConfigFile", "",
   89         "injection configuration filename. Cannot be used with --injectConfigMapName")
   90     addToMeshCmd.PersistentFlags().StringVar(&valuesFile, "valuesFile", "",
   91         "injection values configuration filename.")
   92 
   93     addToMeshCmd.PersistentFlags().StringVar(&meshConfigMapName, "meshConfigMapName", defaultMeshConfigMapName,
   94         fmt.Sprintf("ConfigMap name for Istio mesh configuration, key should be %q", configMapKey))
   95     addToMeshCmd.PersistentFlags().StringVar(&injectConfigMapName, "injectConfigMapName", defaultInjectConfigMapName,
   96         fmt.Sprintf("ConfigMap name for Istio sidecar injection, key should be %q.", injectConfigMapKey))
   97     return addToMeshCmd
   98 }
   99 
  100 func deploymentMeshifyCmd() *cobra.Command {
  101     var revision string
  102 
  103     cmd := &cobra.Command{
  104         Use:   "deployment",
  105         Short: "Add deployment to Istio service mesh",
  106         Long: `istioctl experimental add-to-mesh deployment restarts pods with the Istio sidecar.  Use 'add-to-mesh'
  107 to test deployments for compatibility with Istio.  If your deployment does not function after
  108 using 'add-to-mesh' you must re-deploy it and troubleshoot it for Istio compatibility.
  109 See https://istio.io/docs/setup/kubernetes/additional-setup/requirements/
  110 THIS COMMAND IS STILL UNDER ACTIVE DEVELOPMENT AND NOT READY FOR PRODUCTION USE.
  111 `,
  112         Example: `istioctl experimental add-to-mesh deployment productpage-v1`,
  113         RunE: func(cmd *cobra.Command, args []string) error {
  114             if len(args) != 1 {
  115                 return fmt.Errorf("expecting deployment name")
  116             }
  117             client, err := interfaceFactory(kubeconfig)
  118             if err != nil {
  119                 return err
  120             }
  121             var sidecarTemplate, valuesConfig string
  122             ns := handlers.HandleNamespace(namespace, defaultNamespace)
  123             writer := cmd.OutOrStdout()
  124 
  125             meshConfig, err := setupParameters(&sidecarTemplate, &valuesConfig)
  126             if err != nil {
  127                 return err
  128             }
  129             dep, err := client.AppsV1().Deployments(ns).Get(context.TODO(), args[0], metav1.GetOptions{})
  130             if err != nil {
  131                 return fmt.Errorf("deployment %q does not exist", args[0])
  132             }
  133             deps := make([]appsv1.Deployment, 0)
  134             deps = append(deps, *dep)
  135             return injectSideCarIntoDeployment(client, deps, sidecarTemplate, valuesConfig,
  136                 args[0], ns, revision, meshConfig, writer)
  137         },
  138     }
  139 
  140     cmd.PersistentFlags().StringVar(&revision, "revision", "",
  141         "control plane revision")
  142 
  143     return cmd
  144 }
  145 
  146 func svcMeshifyCmd() *cobra.Command {
  147     var revision string
  148 
  149     cmd := &cobra.Command{
  150         Use:   "service",
  151         Short: "Add Service to Istio service mesh",
  152         Long: `istioctl experimental add-to-mesh service restarts pods with the Istio sidecar.  Use 'add-to-mesh'
  153 to test deployments for compatibility with Istio.  If your service does not function after
  154 using 'add-to-mesh' you must re-deploy it and troubleshoot it for Istio compatibility.
  155 See https://istio.io/docs/setup/kubernetes/additional-setup/requirements/
  156 THIS COMMAND IS STILL UNDER ACTIVE DEVELOPMENT AND NOT READY FOR PRODUCTION USE.
  157 `,
  158         Example: `istioctl experimental add-to-mesh service productpage`,
  159         RunE: func(cmd *cobra.Command, args []string) error {
  160             if len(args) != 1 {
  161                 return fmt.Errorf("expecting service name")
  162             }
  163             client, err := interfaceFactory(kubeconfig)
  164             if err != nil {
  165                 return err
  166             }
  167             var sidecarTemplate, valuesConfig string
  168             ns := handlers.HandleNamespace(namespace, defaultNamespace)
  169             writer := cmd.OutOrStdout()
  170 
  171             meshConfig, err := setupParameters(&sidecarTemplate, &valuesConfig)
  172             if err != nil {
  173                 return err
  174             }
  175             matchingDeployments, err := findDeploymentsForSvc(client, ns, args[0])
  176             if err != nil {
  177                 return err
  178             }
  179             if len(matchingDeployments) == 0 {
  180                 _, _ = fmt.Fprintf(writer, "No deployments found for service %s.%s\n", args[0], ns)
  181                 return nil
  182             }
  183             return injectSideCarIntoDeployment(client, matchingDeployments, sidecarTemplate, valuesConfig,
  184                 args[0], ns, revision, meshConfig, writer)
  185         },
  186     }
  187 
  188     cmd.PersistentFlags().StringVar(&revision, "revision", "",
  189         "control plane revision")
  190 
  191     return cmd
  192 }
  193 
  194 func externalSvcMeshifyCmd() *cobra.Command {
  195     cmd := &cobra.Command{
  196         Use:   "external-service <svcname> <ip>... [name1:]port1 [name2:]port2 ...",
  197         Short: "Add external service(eg:services running on VM) to Istio service mesh",
  198         Long: `istioctl experimental add-to-mesh external-service create a ServiceEntry and\ 
  199 a Service without selector for the specified external service in Istio service mesh.
  200 The typical usage scenario is Mesh Expansion on VMs.
  201 THIS COMMAND IS STILL UNDER ACTIVE DEVELOPMENT AND NOT READY FOR PRODUCTION USE.
  202 `,
  203         Example: `istioctl experimental add-to-mesh external-service vmhttp 172.12.23.125,172.12.23.126\
  204 http:9080 tcp:8888 -l app=test,version=v1 -a env=stage -s stageAdmin`,
  205         RunE: func(cmd *cobra.Command, args []string) error {
  206             if len(args) < 3 {
  207                 return fmt.Errorf("provide service name, IP and Port List")
  208             }
  209             client, err := interfaceFactory(kubeconfig)
  210             if err != nil {
  211                 return err
  212             }
  213             seClient, err := crdFactory(kubeconfig)
  214             if err != nil {
  215                 return err
  216             }
  217             writer := cmd.OutOrStdout()
  218             ns := handlers.HandleNamespace(namespace, defaultNamespace)
  219             _, err = client.CoreV1().Services(ns).Get(context.TODO(), args[0], metav1.GetOptions{})
  220             if err != nil {
  221                 return addServiceOnVMToMesh(seClient, client, ns, args, labels, annotations, svcAcctAnn, writer)
  222             }
  223             return fmt.Errorf("service %q already exists, skip", args[0])
  224         },
  225     }
  226     cmd.PersistentFlags().StringSliceVarP(&labels, "labels", "l",
  227         nil, "List of labels to apply if creating a service/endpoint; e.g. -l env=prod,vers=2")
  228     cmd.PersistentFlags().StringSliceVarP(&annotations, "annotations", "a",
  229         nil, "List of string annotations to apply if creating a service/endpoint; e.g. -a foo=bar,x=y")
  230     cmd.PersistentFlags().StringVarP(&svcAcctAnn, "serviceaccount", "s",
  231         "default", "Service account to link to the service")
  232     return cmd
  233 }
  234 
  235 func setupParameters(sidecarTemplate, valuesConfig *string) (*meshconfig.MeshConfig, error) {
  236     var meshConfig *meshconfig.MeshConfig
  237     var err error
  238     if meshConfigFile != "" {
  239         if meshConfig, err = mesh.ReadMeshConfig(meshConfigFile); err != nil {
  240             return nil, err
  241         }
  242     } else {
  243         if meshConfig, err = getMeshConfigFromConfigMap(kubeconfig, "add-to-mesh"); err != nil {
  244             return nil, err
  245         }
  246     }
  247     if injectConfigFile != "" {
  248         injectionConfig, err := ioutil.ReadFile(injectConfigFile) // nolint: vetshadow
  249         if err != nil {
  250             return nil, err
  251         }
  252         var injectConfig inject.Config
  253         if err := yaml.Unmarshal(injectionConfig, &injectConfig); err != nil {
  254             return nil, multierror.Append(err, fmt.Errorf("loading --injectConfigFile"))
  255         }
  256         *sidecarTemplate = injectConfig.Template
  257     } else if *sidecarTemplate, err = getInjectConfigFromConfigMap(kubeconfig); err != nil {
  258         return nil, err
  259     }
  260     if valuesFile != "" {
  261         valuesConfigBytes, err := ioutil.ReadFile(valuesFile) // nolint: vetshadow
  262         if err != nil {
  263             return nil, err
  264         }
  265         *valuesConfig = string(valuesConfigBytes)
  266     } else if *valuesConfig, err = getValuesFromConfigMap(kubeconfig); err != nil {
  267         return nil, err
  268     }
  269     return meshConfig, err
  270 }
  271 
  272 func injectSideCarIntoDeployment(client kubernetes.Interface, deps []appsv1.Deployment, sidecarTemplate, valuesConfig,
  273     svcName, svcNamespace string, revision string, meshConfig *meshconfig.MeshConfig, writer io.Writer) error {
  274     var errs error
  275     for _, dep := range deps {
  276         log.Debugf("updating deployment %s.%s with Istio sidecar injected",
  277             dep.Name, dep.Namespace)
  278         newDep, err := inject.IntoObject(sidecarTemplate, valuesConfig, revision, meshConfig, &dep)
  279         if err != nil {
  280             errs = multierror.Append(errs, fmt.Errorf("failed to inject sidecar to deployment resource %s.%s for service %s.%s due to %v",
  281                 dep.Name, dep.Namespace, svcName, svcNamespace, err))
  282             continue
  283         }
  284         res, b := newDep.(*appsv1.Deployment)
  285         if !b {
  286             errs = multierror.Append(errs, fmt.Errorf("failed to create new deployment resource %s.%s for service %s.%s due to %v",
  287                 dep.Name, dep.Namespace, svcName, svcNamespace, err))
  288             continue
  289         }
  290         if _, err =
  291             client.AppsV1().Deployments(svcNamespace).Update(context.TODO(), res, metav1.UpdateOptions{}); err != nil {
  292             errs = multierror.Append(errs, fmt.Errorf("failed to update deployment %s.%s for service %s.%s due to %v",
  293                 dep.Name, dep.Namespace, svcName, svcNamespace, err))
  294             continue
  295 
  296         }
  297         d := &appsv1.Deployment{
  298             ObjectMeta: metav1.ObjectMeta{
  299                 Name:      dep.Name,
  300                 Namespace: dep.Namespace,
  301                 UID:       dep.UID,
  302             },
  303         }
  304         if _, err = client.AppsV1().Deployments(svcNamespace).UpdateStatus(context.TODO(), d, metav1.UpdateOptions{}); err != nil {
  305             errs = multierror.Append(errs, fmt.Errorf("failed to update deployment status %s.%s for service %s.%s due to %v",
  306                 dep.Name, dep.Namespace, svcName, svcNamespace, err))
  307             continue
  308         }
  309         _, _ = fmt.Fprintf(writer, "deployment %s.%s updated successfully with Istio sidecar injected.\n"+
  310             "Next Step: Add related labels to the deployment to align with Istio's requirement: "+
  311             "https://istio.io/docs/setup/kubernetes/additional-setup/requirements/\n",
  312             dep.Name, dep.Namespace)
  313     }
  314     return errs
  315 }
  316 
  317 func findDeploymentsForSvc(client kubernetes.Interface, ns, name string) ([]appsv1.Deployment, error) {
  318     deps := make([]appsv1.Deployment, 0)
  319     svc, err := client.CoreV1().Services(ns).Get(context.TODO(), name, metav1.GetOptions{})
  320     if err != nil {
  321         return nil, err
  322     }
  323     svcSelector := k8s_labels.SelectorFromSet(svc.Spec.Selector)
  324     if svcSelector.Empty() {
  325         return nil, nil
  326     }
  327     deployments, err := client.AppsV1().Deployments(ns).List(context.TODO(), metav1.ListOptions{})
  328     if err != nil {
  329         return nil, err
  330     }
  331     for _, dep := range deployments.Items {
  332         depLabels := k8s_labels.Set(dep.Spec.Selector.MatchLabels)
  333         if svcSelector.Matches(depLabels) {
  334             deps = append(deps, dep)
  335         }
  336     }
  337     return deps, nil
  338 }
  339 
  340 func createDynamicInterface(kubeconfig string) (dynamic.Interface, error) {
  341     restConfig, err := kube.BuildClientConfig(kubeconfig, configContext)
  342 
  343     if err != nil {
  344         return nil, err
  345     }
  346     dynamicClient, err := dynamic.NewForConfig(restConfig)
  347     if err != nil {
  348         return nil, err
  349     }
  350     return dynamicClient, nil
  351 }
  352 
  353 func convertPortList(ports []string) (model.PortList, error) {
  354     portList := model.PortList{}
  355     for _, p := range ports {
  356         np, err := kube_registry.Str2NamedPort(p)
  357         if err != nil {
  358             return nil, fmt.Errorf("invalid port format %v", p)
  359         }
  360         protocol := istioProtocol.Parse(np.Name)
  361         if protocol == istioProtocol.Unsupported {
  362             return nil, fmt.Errorf("protocol %s is not supported by Istio", np.Name)
  363         }
  364         portList = append(portList, &model.Port{
  365             Port:     int(np.Port),
  366             Protocol: protocol,
  367             Name:     np.Name + "-" + strconv.Itoa(int(np.Port)),
  368         })
  369     }
  370     return portList, nil
  371 }
  372 
  373 // addServiceOnVMToMesh adds a service running on VM into Istio service mesh
  374 func addServiceOnVMToMesh(dynamicClient dynamic.Interface, client kubernetes.Interface, ns string,
  375     args, l, a []string, svcAcctAnn string, writer io.Writer) error {
  376     svcName := args[0]
  377     ips := strings.Split(args[1], ",")
  378     portsListStr := args[2:]
  379     ports, err := convertPortList(portsListStr)
  380     if err != nil {
  381         return err
  382     }
  383     labels := convertToMap(l)
  384     annotations := convertToMap(a)
  385     opts := &vmServiceOpts{
  386         Name:           svcName,
  387         Namespace:      ns,
  388         PortList:       ports,
  389         IP:             ips,
  390         ServiceAccount: svcAcctAnn,
  391         Labels:         labels,
  392         Annotations:    annotations,
  393     }
  394 
  395     u := &unstructured.Unstructured{
  396         Object: map[string]interface{}{
  397             "apiVersion": collections.IstioNetworkingV1Alpha3Serviceentries.Resource().APIVersion(),
  398             "kind":       collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Kind(),
  399             "metadata": map[string]interface{}{
  400                 "namespace": opts.Namespace,
  401                 "name":      resourceName(opts.Name),
  402             },
  403         },
  404     }
  405     annotations[corev1.ServiceAccountNameKey] = opts.ServiceAccount
  406     s := &corev1.Service{
  407         ObjectMeta: metav1.ObjectMeta{
  408             Name:        opts.Name,
  409             Namespace:   opts.Namespace,
  410             Annotations: annotations,
  411             Labels:      labels,
  412         },
  413     }
  414 
  415     // Pre-check Kubernetes service and service entry does not exist.
  416     _, err = client.CoreV1().Services(ns).Get(context.TODO(), opts.Name, metav1.GetOptions{})
  417     if err == nil {
  418         return fmt.Errorf("service %q already exists, skip", opts.Name)
  419     }
  420     serviceEntryGVR := schema.GroupVersionResource{
  421         Group:    collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Group(),
  422         Version:  collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Version(),
  423         Resource: collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Plural(),
  424     }
  425     _, err = dynamicClient.Resource(serviceEntryGVR).Namespace(ns).Get(context.TODO(), resourceName(opts.Name), metav1.GetOptions{})
  426     if err == nil {
  427         return fmt.Errorf("service entry %q already exists, skip", resourceName(opts.Name))
  428     }
  429 
  430     if err = generateServiceEntry(u, opts); err != nil {
  431         return err
  432     }
  433     generateK8sService(s, opts)
  434     if err = createServiceEntry(dynamicClient, ns, u, opts.Name, writer); err != nil {
  435         return err
  436     }
  437     return createK8sService(client, ns, s, writer)
  438 }
  439 
  440 func generateServiceEntry(u *unstructured.Unstructured, o *vmServiceOpts) error {
  441     if o == nil {
  442         return fmt.Errorf("empty vm service options")
  443     }
  444     ports := make([]*v1alpha3.Port, 0)
  445     for _, p := range o.PortList {
  446         ports = append(ports, &v1alpha3.Port{
  447             Number:   uint32(p.Port),
  448             Protocol: string(p.Protocol),
  449             Name:     p.Name,
  450         })
  451     }
  452     eps := make([]*v1alpha3.WorkloadEntry, 0)
  453     for _, ip := range o.IP {
  454         eps = append(eps, &v1alpha3.WorkloadEntry{
  455             Address: ip,
  456             Labels:  o.Labels,
  457         })
  458     }
  459     host := fmt.Sprintf("%v.%v.svc.cluster.local", o.Name, o.Namespace)
  460     spec := &v1alpha3.ServiceEntry{
  461         Hosts:      []string{host},
  462         Ports:      ports,
  463         Endpoints:  eps,
  464         Resolution: v1alpha3.ServiceEntry_STATIC,
  465         Location:   v1alpha3.ServiceEntry_MESH_INTERNAL,
  466     }
  467 
  468     // Because we are placing into an Unstructured, place as a map instead
  469     // of structured Istio types.  (The go-client can handle the structured data, but the
  470     // fake go-client used for mocking cannot.)
  471     b, err := yaml.Marshal(spec)
  472     if err != nil {
  473         return err
  474     }
  475     iSpec := map[string]interface{}{}
  476     err = yaml.Unmarshal(b, &iSpec)
  477     if err != nil {
  478         return err
  479     }
  480 
  481     u.Object["spec"] = iSpec
  482 
  483     return nil
  484 }
  485 
  486 func resourceName(hostShortName string) string {
  487     return fmt.Sprintf("mesh-expansion-%v", hostShortName)
  488 }
  489 
  490 func generateK8sService(s *corev1.Service, o *vmServiceOpts) {
  491     ports := make([]corev1.ServicePort, 0)
  492     for _, p := range o.PortList {
  493         ports = append(ports, corev1.ServicePort{
  494             Name: strings.ToLower(p.Name),
  495             Port: int32(p.Port),
  496         })
  497     }
  498 
  499     spec := corev1.ServiceSpec{
  500         Ports: ports,
  501     }
  502     s.Spec = spec
  503 }
  504 
  505 func convertToMap(s []string) map[string]string {
  506     out := make(map[string]string, len(s))
  507     for _, l := range s {
  508         k, v := splitEqual(l)
  509         out[k] = v
  510     }
  511     return out
  512 }
  513 
  514 // splitEqual splits key=value string into key,value. if no = is found
  515 // the whole string is the key and value is empty.
  516 func splitEqual(str string) (string, string) {
  517     idx := strings.Index(str, "=")
  518     var k string
  519     var v string
  520     if idx >= 0 {
  521         k = str[:idx]
  522         v = str[idx+1:]
  523     } else {
  524         k = str
  525     }
  526     return k, v
  527 }
  528 
  529 // createK8sService creates k8s service object for external services in order for DNS query and cluster VIP.
  530 func createK8sService(client kubernetes.Interface, ns string, svc *corev1.Service, writer io.Writer) error {
  531     if svc == nil {
  532         return fmt.Errorf("failed to create vm service")
  533     }
  534     if _, err := client.CoreV1().Services(ns).Create(context.TODO(), svc, metav1.CreateOptions{}); err != nil {
  535         return fmt.Errorf("failed to create kuberenetes service %v", err)
  536     }
  537     if _, err := client.CoreV1().Services(ns).UpdateStatus(context.TODO(), svc, metav1.UpdateOptions{}); err != nil {
  538         return fmt.Errorf("failed to create kuberenetes service %v", err)
  539     }
  540     sName := strings.Join([]string{svc.Name, svc.Namespace}, ".")
  541     _, _ = fmt.Fprintf(writer, "Kubernetes Service %q has been created in the Istio service mesh"+
  542         " for the external service %q\n", sName, svc.Name)
  543     return nil
  544 }
  545 
  546 // createServiceEntry creates an Istio ServiceEntry object in order to register vm service.
  547 func createServiceEntry(dynamicClient dynamic.Interface, ns string,
  548     u *unstructured.Unstructured, name string, writer io.Writer) error {
  549     if u == nil {
  550         return fmt.Errorf("failed to create vm service")
  551     }
  552     serviceEntryGVR := schema.GroupVersionResource{
  553         Group:    collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Group(),
  554         Version:  collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Version(),
  555         Resource: collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Plural(),
  556     }
  557     _, err := dynamicClient.Resource(serviceEntryGVR).Namespace(ns).Create(context.TODO(), u, metav1.CreateOptions{})
  558     if err != nil {
  559         return fmt.Errorf("failed to create service entry %v", err)
  560     }
  561     seName := strings.Join([]string{u.GetName(), u.GetNamespace()}, ".")
  562     _, _ = fmt.Fprintf(writer, "ServiceEntry %q has been created in the Istio service mesh"+
  563         " for the external service %q\n", seName, name)
  564     return nil
  565 }