blob: 7288f1457a83e1f4fb9e1c9f4545c691ed8a7364 [file] [log] [blame]
// Copyright Istio Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"fmt"
"io"
"os"
"strconv"
"strings"
)
import (
"github.com/hashicorp/go-multierror"
"github.com/spf13/cobra"
meshconfig "istio.io/api/mesh/v1alpha1"
"istio.io/api/networking/v1alpha3"
"istio.io/pkg/log"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
k8s_labels "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/yaml"
)
import (
"github.com/apache/dubbo-go-pixiu/istioctl/pkg/clioptions"
"github.com/apache/dubbo-go-pixiu/istioctl/pkg/util/handlers"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pkg/config/constants"
"github.com/apache/dubbo-go-pixiu/pkg/config/mesh"
istioProtocol "github.com/apache/dubbo-go-pixiu/pkg/config/protocol"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collections"
"github.com/apache/dubbo-go-pixiu/pkg/kube"
"github.com/apache/dubbo-go-pixiu/pkg/kube/inject"
"github.com/apache/dubbo-go-pixiu/pkg/url"
)
var crdFactory = createDynamicInterface
// For most common ports allow the protocol to be guessed, this isn't meant
// to replace /etc/services. Fully qualified proto[-extra]:port is the
// recommended usage.
var portsToName = map[int32]string{
80: "http",
443: "https",
3306: "mysql",
8080: "http",
}
// vmServiceOpts contains the options of a mesh expansion service running on VM.
type vmServiceOpts struct {
Name string
Namespace string
ServiceAccount string
IP []string
PortList model.PortList
Labels map[string]string
Annotations map[string]string
}
func addToMeshCmd() *cobra.Command {
addToMeshCmd := &cobra.Command{
Use: "add-to-mesh",
Aliases: []string{"add"},
Short: "Add workloads into Istio service mesh",
Long: `'istioctl experimental add-to-mesh' restarts pods with an Istio sidecar or configures meshed pod access to external services.
Use 'add-to-mesh' as an alternate to namespace-wide auto injection for troubleshooting compatibility.
The 'remove-from-mesh' command can be used to restart with the sidecar removed.`,
Example: ` # Restart all productpage pods with an Istio sidecar
istioctl experimental add-to-mesh service productpage
# Restart just pods from the productpage-v1 deployment
istioctl experimental add-to-mesh deployment productpage-v1
# Restart just pods from the details-v1 deployment
istioctl x add deployment details-v1
# Control how meshed pods see an external service
istioctl experimental add-to-mesh external-service vmhttp 172.12.23.125,172.12.23.126 \
http:9080 tcp:8888 --labels app=test,version=v1 --annotations env=stage --serviceaccount stageAdmin`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("unknown resource type %q", args[0])
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
cmd.HelpFunc()(cmd, args)
return nil
},
}
addToMeshCmd.AddCommand(svcMeshifyCmd())
addToMeshCmd.AddCommand(deploymentMeshifyCmd())
externalSvcMeshifyCmd := externalSvcMeshifyCmd()
hideInheritedFlags(externalSvcMeshifyCmd, "meshConfigFile", "meshConfigMapName", "injectConfigFile",
"injectConfigMapName", "valuesFile")
addToMeshCmd.AddCommand(externalSvcMeshifyCmd)
addToMeshCmd.PersistentFlags().StringVar(&meshConfigFile, "meshConfigFile", "",
"Mesh configuration filename. Takes precedence over --meshConfigMapName if set")
addToMeshCmd.PersistentFlags().StringVar(&injectConfigFile, "injectConfigFile", "",
"Injection configuration filename. Cannot be used with --injectConfigMapName")
addToMeshCmd.PersistentFlags().StringVar(&valuesFile, "valuesFile", "",
"Injection values configuration filename.")
addToMeshCmd.PersistentFlags().StringVar(&meshConfigMapName, "meshConfigMapName", defaultMeshConfigMapName,
fmt.Sprintf("ConfigMap name for Istio mesh configuration, key should be %q", configMapKey))
addToMeshCmd.PersistentFlags().StringVar(&injectConfigMapName, "injectConfigMapName", defaultInjectConfigMapName,
fmt.Sprintf("ConfigMap name for Istio sidecar injection, key should be %q.", injectConfigMapKey))
addToMeshCmd.Long += "\n\n" + ExperimentalMsg
return addToMeshCmd
}
func deploymentMeshifyCmd() *cobra.Command {
var opts clioptions.ControlPlaneOptions
cmd := &cobra.Command{
Use: "deployment <deployment>",
Aliases: []string{"deploy", "dep"},
Short: "Add deployment to Istio service mesh",
// nolint: lll
Long: `'istioctl experimental add-to-mesh deployment' restarts pods with the Istio sidecar. Use 'add-to-mesh'
to test deployments for compatibility with Istio. It can be used instead of namespace-wide auto-injection of sidecars and is especially helpful for compatibility testing.
If your deployment does not function after using 'add-to-mesh' you must re-deploy it and troubleshoot it for Istio compatibility.
See ` + url.DeploymentRequirements + `
See also 'istioctl experimental remove-from-mesh deployment' which does the reverse.`,
Example: ` # Restart pods from the productpage-v1 deployment with Istio sidecar
istioctl experimental add-to-mesh deployment productpage-v1
# Restart pods from the details-v1 deployment with Istio sidecar
istioctl x add-to-mesh deploy details-v1
# Restart pods from the ratings-v1 deployment with Istio sidecar
istioctl x add dep ratings-v1`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("expecting deployment name")
}
client, err := interfaceFactory(kubeconfig)
if err != nil {
return err
}
ns := handlers.HandleNamespace(namespace, defaultNamespace)
writer := cmd.OutOrStdout()
var valuesConfig string
var sidecarTemplate inject.RawTemplates
meshConfig, err := setupParameters(&sidecarTemplate, &valuesConfig, opts.Revision)
if err != nil {
return err
}
dep, err := client.AppsV1().Deployments(ns).Get(context.TODO(), args[0], metav1.GetOptions{})
if err != nil {
return fmt.Errorf("deployment %q does not exist", args[0])
}
return injectSideCarIntoDeployment(client, dep, sidecarTemplate, valuesConfig,
args[0], ns, opts.Revision, meshConfig, writer, func(warning string) {
fmt.Fprintln(cmd.ErrOrStderr(), warning)
})
},
}
cmd.Long += "\n\n" + ExperimentalMsg
opts.AttachControlPlaneFlags(cmd)
return cmd
}
func svcMeshifyCmd() *cobra.Command {
var opts clioptions.ControlPlaneOptions
cmd := &cobra.Command{
Use: "service <service>",
Aliases: []string{"svc"},
Short: "Add Service to Istio service mesh",
// nolint: lll
Long: `istioctl experimental add-to-mesh service restarts pods with the Istio sidecar. Use 'add-to-mesh'
to test deployments for compatibility with Istio. It can be used instead of namespace-wide auto-injection of sidecars and is especially helpful for compatibility testing.
If your service does not function after using 'add-to-mesh' you must re-deploy it and troubleshoot it for Istio compatibility.
See ` + url.DeploymentRequirements + `
See also 'istioctl experimental remove-from-mesh service' which does the reverse.`,
Example: ` # Restart all productpage pods with an Istio sidecar
istioctl experimental add-to-mesh service productpage
# Restart all details-v1 pods with an Istio sidecar
istioctl x add-to-mesh svc details-v1
# Restart all ratings-v1 pods with an Istio sidecar
istioctl x add svc ratings-v1`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("expecting service name")
}
client, err := interfaceFactory(kubeconfig)
if err != nil {
return err
}
ns := handlers.HandleNamespace(namespace, defaultNamespace)
writer := cmd.OutOrStdout()
var valuesConfig string
var sidecarTemplate inject.RawTemplates
meshConfig, err := setupParameters(&sidecarTemplate, &valuesConfig, opts.Revision)
if err != nil {
return err
}
matchingDeployments, err := findDeploymentsForSvc(client, ns, args[0])
if err != nil {
return err
}
if len(matchingDeployments) == 0 {
_, _ = fmt.Fprintf(writer, "No deployments found for service %s.%s\n", args[0], ns)
return nil
}
return injectSideCarIntoDeployments(client, matchingDeployments, sidecarTemplate, valuesConfig,
args[0], ns, opts.Revision, meshConfig, writer, func(warning string) {
fmt.Fprintln(cmd.ErrOrStderr(), warning)
})
},
}
cmd.Long += "\n\n" + ExperimentalMsg
opts.AttachControlPlaneFlags(cmd)
return cmd
}
func injectSideCarIntoDeployments(client kubernetes.Interface, deps []appsv1.Deployment, sidecarTemplate inject.RawTemplates, valuesConfig,
name, namespace string, revision string, meshConfig *meshconfig.MeshConfig, writer io.Writer, warningHandler func(string)) error {
var errs error
for _, dep := range deps {
err := injectSideCarIntoDeployment(client, &dep, sidecarTemplate, valuesConfig,
name, namespace, revision, meshConfig, writer, warningHandler)
if err != nil {
errs = multierror.Append(errs, err)
}
}
return errs
}
func externalSvcMeshifyCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "external-service <svcname> <ip> [name1:]port1 [[name2:]port2] ...",
Aliases: []string{"es"},
Short: "Add external service (e.g. services running on a VM) to Istio service mesh",
Long: `istioctl experimental add-to-mesh external-service create a ServiceEntry and
a Service without selector for the specified external service in Istio service mesh.
The typical usage scenario is Mesh Expansion on VMs.
See also 'istioctl experimental remove-from-mesh external-service' which does the reverse.`,
Example: ` # Control how meshed pods contact 172.12.23.125 and .126
istioctl experimental add-to-mesh external-service vmhttp 172.12.23.125,172.12.23.126 \
http:9080 tcp:8888 --labels app=test,version=v1 --annotations env=stage --serviceaccount stageAdmin`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 3 {
return fmt.Errorf("provide service name, IP and Port List")
}
client, err := interfaceFactory(kubeconfig)
if err != nil {
return err
}
seClient, err := crdFactory(kubeconfig)
if err != nil {
return err
}
writer := cmd.OutOrStdout()
ns := handlers.HandleNamespace(namespace, defaultNamespace)
_, err = client.CoreV1().Services(ns).Get(context.TODO(), args[0], metav1.GetOptions{})
if err != nil {
return addServiceOnVMToMesh(seClient, client, ns, args, resourceLabels, annotations, svcAcctAnn, writer)
}
return fmt.Errorf("service %q already exists, skip", args[0])
},
}
cmd.PersistentFlags().StringSliceVarP(&resourceLabels, "labels", "l",
nil, "List of labels to apply if creating a service/endpoint; e.g. -l env=prod,vers=2")
cmd.PersistentFlags().StringSliceVarP(&annotations, "annotations", "a",
nil, "List of string annotations to apply if creating a service/endpoint; e.g. -a foo=bar,x=y")
cmd.PersistentFlags().StringVarP(&svcAcctAnn, "serviceaccount", "s",
"default", "Service account to link to the service")
cmd.Long += "\n\n" + ExperimentalMsg
return cmd
}
func setupParameters(sidecarTemplate *inject.RawTemplates, valuesConfig *string, revision string) (*meshconfig.MeshConfig, error) {
var meshConfig *meshconfig.MeshConfig
var err error
injectConfigMapName = defaultInjectWebhookConfigName
if meshConfigFile != "" {
if meshConfig, err = mesh.ReadMeshConfig(meshConfigFile); err != nil {
return nil, err
}
} else {
if meshConfig, err = getMeshConfigFromConfigMap(kubeconfig, "add-to-mesh", revision); err != nil {
return nil, err
}
}
if injectConfigFile != "" {
injectionConfig, err := os.ReadFile(injectConfigFile) // nolint: vetshadow
if err != nil {
return nil, err
}
injectConfig, err := readInjectConfigFile(injectionConfig)
if err != nil {
return nil, multierror.Append(err, fmt.Errorf("loading --injectConfigFile"))
}
*sidecarTemplate = injectConfig
} else if *sidecarTemplate, err = getInjectConfigFromConfigMap(kubeconfig, revision); err != nil {
return nil, err
}
if valuesFile != "" {
valuesConfigBytes, err := os.ReadFile(valuesFile) // nolint: vetshadow
if err != nil {
return nil, err
}
*valuesConfig = string(valuesConfigBytes)
} else if *valuesConfig, err = getValuesFromConfigMap(kubeconfig, revision); err != nil {
return nil, err
}
return meshConfig, err
}
func injectSideCarIntoDeployment(client kubernetes.Interface, dep *appsv1.Deployment, sidecarTemplate inject.RawTemplates, valuesConfig,
svcName, svcNamespace string, revision string, meshConfig *meshconfig.MeshConfig, writer io.Writer, warningHandler func(string)) error {
var errs error
log.Debugf("updating deployment %s.%s with Istio sidecar injected",
dep.Name, dep.Namespace)
templs, err := inject.ParseTemplates(sidecarTemplate)
if err != nil {
return err
}
vc, err := inject.NewValuesConfig(valuesConfig)
if err != nil {
return err
}
newDep, err := inject.IntoObject(nil, templs, vc, revision, meshConfig, dep, warningHandler)
if err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to inject sidecar to deployment resource %s.%s for service %s.%s due to %v",
dep.Name, dep.Namespace, svcName, svcNamespace, err))
return errs
}
res, b := newDep.(*appsv1.Deployment)
if !b {
errs = multierror.Append(errs, fmt.Errorf("failed to create new deployment resource %s.%s for service %s.%s due to %v",
dep.Name, dep.Namespace, svcName, svcNamespace, err))
return errs
}
if _, err = client.AppsV1().Deployments(svcNamespace).Update(context.TODO(), res, metav1.UpdateOptions{}); err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to update deployment %s.%s for service %s.%s due to %v",
dep.Name, dep.Namespace, svcName, svcNamespace, err))
return errs
}
d := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: dep.Name,
Namespace: dep.Namespace,
UID: dep.UID,
OwnerReferences: dep.OwnerReferences,
},
}
if _, err = client.AppsV1().Deployments(svcNamespace).UpdateStatus(context.TODO(), d, metav1.UpdateOptions{}); err != nil {
errs = multierror.Append(errs, fmt.Errorf("failed to update deployment status %s.%s for service %s.%s due to %v",
dep.Name, dep.Namespace, svcName, svcNamespace, err))
return errs
}
_, _ = fmt.Fprintf(writer, "deployment %s.%s updated successfully with Istio sidecar injected.\n"+
"Next Step: Add related labels to the deployment to align with Istio's requirement: %s\n",
dep.Name, dep.Namespace, url.DeploymentRequirements)
return errs
}
func findDeploymentsForSvc(client kubernetes.Interface, ns, name string) ([]appsv1.Deployment, error) {
svc, err := client.CoreV1().Services(ns).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return nil, err
}
svcSelector := k8s_labels.SelectorFromSet(svc.Spec.Selector)
if svcSelector.Empty() {
return nil, nil
}
deployments, err := client.AppsV1().Deployments(ns).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return nil, err
}
deps := make([]appsv1.Deployment, 0, len(deployments.Items))
for _, dep := range deployments.Items {
depLabels := k8s_labels.Set(dep.Spec.Selector.MatchLabels)
if svcSelector.Matches(depLabels) {
deps = append(deps, dep)
}
}
return deps, nil
}
func createDynamicInterface(kubeconfig string) (dynamic.Interface, error) {
restConfig, err := kube.BuildClientConfig(kubeconfig, configContext)
if err != nil {
return nil, err
}
dynamicClient, err := dynamic.NewForConfig(restConfig)
if err != nil {
return nil, err
}
return dynamicClient, nil
}
func convertPortList(ports []string) (model.PortList, error) {
portList := model.PortList{}
for _, p := range ports {
np, err := str2NamedPort(p)
if err != nil {
return nil, fmt.Errorf("invalid port format %v", p)
}
protocol := istioProtocol.Parse(np.name)
if protocol == istioProtocol.Unsupported {
return nil, fmt.Errorf("protocol %s is not supported by Istio", np.name)
}
portList = append(portList, &model.Port{
Port: int(np.port),
Protocol: protocol,
Name: np.name + "-" + strconv.Itoa(int(np.port)),
})
}
return portList, nil
}
// namedPort defines the Port and Name tuple needed for services and endpoints.
type namedPort struct {
port int32
name string
}
// str2NamedPort parses a proto:port string into a namePort struct.
func str2NamedPort(str string) (namedPort, error) {
var r namedPort
idx := strings.Index(str, ":")
if idx >= 0 {
r.name = str[:idx]
str = str[idx+1:]
}
p, err := strconv.Atoi(str)
if err != nil {
return r, err
}
r.port = int32(p)
if len(r.name) == 0 {
name, found := portsToName[r.port]
r.name = name
if !found {
r.name = str
}
}
return r, nil
}
// addServiceOnVMToMesh adds a service running on VM into Istio service mesh
func addServiceOnVMToMesh(dynamicClient dynamic.Interface, client kubernetes.Interface, ns string,
args, l, a []string, svcAcctAnn string, writer io.Writer) error {
svcName := args[0]
ips := strings.Split(args[1], ",")
portsListStr := args[2:]
ports, err := convertPortList(portsListStr)
if err != nil {
return err
}
labels := convertToStringMap(l)
annotations := convertToStringMap(a)
opts := &vmServiceOpts{
Name: svcName,
Namespace: ns,
PortList: ports,
IP: ips,
ServiceAccount: svcAcctAnn,
Labels: labels,
Annotations: annotations,
}
u := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": collections.IstioNetworkingV1Alpha3Serviceentries.Resource().APIVersion(),
"kind": collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Kind(),
"metadata": map[string]interface{}{
"namespace": opts.Namespace,
"name": resourceName(opts.Name),
},
},
}
annotations[corev1.ServiceAccountNameKey] = opts.ServiceAccount
s := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: opts.Name,
Namespace: opts.Namespace,
Annotations: annotations,
Labels: labels,
},
}
// Pre-check Kubernetes service and service entry does not exist.
_, err = client.CoreV1().Services(ns).Get(context.TODO(), opts.Name, metav1.GetOptions{})
if err == nil {
return fmt.Errorf("service %q already exists, skip", opts.Name)
}
serviceEntryGVR := schema.GroupVersionResource{
Group: collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Group(),
Version: collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Version(),
Resource: collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Plural(),
}
_, err = dynamicClient.Resource(serviceEntryGVR).Namespace(ns).Get(context.TODO(), resourceName(opts.Name), metav1.GetOptions{})
if err == nil {
return fmt.Errorf("service entry %q already exists, skip", resourceName(opts.Name))
}
if err = generateServiceEntry(u, opts); err != nil {
return err
}
generateK8sService(s, opts)
if err = createServiceEntry(dynamicClient, ns, u, opts.Name, writer); err != nil {
return err
}
return createK8sService(client, ns, s, writer)
}
func generateServiceEntry(u *unstructured.Unstructured, o *vmServiceOpts) error {
if o == nil {
return fmt.Errorf("empty vm service options")
}
ports := make([]*v1alpha3.Port, 0, len(o.PortList))
for _, p := range o.PortList {
ports = append(ports, &v1alpha3.Port{
Number: uint32(p.Port),
Protocol: string(p.Protocol),
Name: p.Name,
})
}
eps := make([]*v1alpha3.WorkloadEntry, 0, len(o.IP))
for _, ip := range o.IP {
eps = append(eps, &v1alpha3.WorkloadEntry{
Address: ip,
Labels: o.Labels,
})
}
host := fmt.Sprintf("%v.%v.svc.%s", o.Name, o.Namespace, constants.DefaultKubernetesDomain)
spec := &v1alpha3.ServiceEntry{
Hosts: []string{host},
Ports: ports,
Endpoints: eps,
Resolution: v1alpha3.ServiceEntry_STATIC,
Location: v1alpha3.ServiceEntry_MESH_INTERNAL,
}
iSpec, err := unstructureIstioType(spec)
if err != nil {
return err
}
u.Object["spec"] = iSpec
return nil
}
// Because we are placing into an Unstructured, place as a map instead
// of structured Istio types. (The go-client can handle the structured data, but the
// fake go-client used for mocking cannot.)
func unstructureIstioType(spec interface{}) (map[string]interface{}, error) {
b, err := yaml.Marshal(spec)
if err != nil {
return nil, err
}
iSpec := map[string]interface{}{}
err = yaml.Unmarshal(b, &iSpec)
if err != nil {
return nil, err
}
return iSpec, nil
}
func resourceName(hostShortName string) string {
return fmt.Sprintf("mesh-expansion-%v", hostShortName)
}
func generateK8sService(s *corev1.Service, o *vmServiceOpts) {
ports := make([]corev1.ServicePort, 0, len(o.PortList))
for _, p := range o.PortList {
ports = append(ports, corev1.ServicePort{
Name: strings.ToLower(p.Name),
Port: int32(p.Port),
})
}
spec := corev1.ServiceSpec{
Ports: ports,
}
s.Spec = spec
}
func convertToUnsignedInt32Map(s []string) map[string]uint32 {
out := make(map[string]uint32, len(s))
for _, l := range s {
k, v := splitEqual(l)
u64, err := strconv.ParseUint(v, 10, 32)
if err != nil {
log.Errorf("failed to convert to uint32: %v", err)
}
out[k] = uint32(u64)
}
return out
}
func convertToStringMap(s []string) map[string]string {
out := make(map[string]string, len(s))
for _, l := range s {
k, v := splitEqual(l)
out[k] = v
}
return out
}
// splitEqual splits key=value string into key,value. if no = is found
// the whole string is the key and value is empty.
func splitEqual(str string) (string, string) {
idx := strings.Index(str, "=")
var k string
var v string
if idx >= 0 {
k = str[:idx]
v = str[idx+1:]
} else {
k = str
}
return k, v
}
// createK8sService creates k8s service object for external services in order for DNS query and cluster VIP.
func createK8sService(client kubernetes.Interface, ns string, svc *corev1.Service, writer io.Writer) error {
if svc == nil {
return fmt.Errorf("failed to create vm service")
}
if _, err := client.CoreV1().Services(ns).Create(context.TODO(), svc, metav1.CreateOptions{}); err != nil {
return fmt.Errorf("failed to create kubernetes service %v", err)
}
if _, err := client.CoreV1().Services(ns).UpdateStatus(context.TODO(), svc, metav1.UpdateOptions{}); err != nil {
return fmt.Errorf("failed to create kubernetes service %v", err)
}
sName := strings.Join([]string{svc.Name, svc.Namespace}, ".")
_, _ = fmt.Fprintf(writer, "Kubernetes Service %q has been created in the Istio service mesh"+
" for the external service %q\n", sName, svc.Name)
return nil
}
// createServiceEntry creates an Istio ServiceEntry object in order to register vm service.
func createServiceEntry(dynamicClient dynamic.Interface, ns string,
u *unstructured.Unstructured, name string, writer io.Writer) error {
if u == nil {
return fmt.Errorf("failed to create vm service")
}
serviceEntryGVR := schema.GroupVersionResource{
Group: collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Group(),
Version: collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Version(),
Resource: collections.IstioNetworkingV1Alpha3Serviceentries.Resource().Plural(),
}
_, err := dynamicClient.Resource(serviceEntryGVR).Namespace(ns).Create(context.TODO(), u, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("failed to create service entry %v", err)
}
seName := strings.Join([]string{u.GetName(), u.GetNamespace()}, ".")
_, _ = fmt.Fprintf(writer, "ServiceEntry %q has been created in the Istio service mesh"+
" for the external service %q\n", seName, name)
return nil
}