istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/version/version.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 version 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "os" 22 23 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 24 discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 25 statusv3 "github.com/envoyproxy/go-control-plane/envoy/service/status/v3" 26 "github.com/spf13/cobra" 27 "github.com/spf13/pflag" 28 "google.golang.org/protobuf/types/known/structpb" 29 30 "istio.io/istio/istioctl/pkg/cli" 31 "istio.io/istio/istioctl/pkg/clioptions" 32 "istio.io/istio/istioctl/pkg/multixds" 33 "istio.io/istio/operator/cmd/mesh" 34 "istio.io/istio/pilot/pkg/model" 35 "istio.io/istio/pilot/pkg/xds" 36 "istio.io/istio/pkg/proxy" 37 istioVersion "istio.io/istio/pkg/version" 38 ) 39 40 func NewVersionCommand(ctx cli.Context) *cobra.Command { 41 profileCmd := mesh.ProfileCmd(ctx) 42 var opts clioptions.ControlPlaneOptions 43 versionCmd := istioVersion.CobraCommandWithOptions(istioVersion.CobraOptions{ 44 GetRemoteVersion: getRemoteInfoWrapper(ctx, &profileCmd, &opts), 45 GetProxyVersions: getProxyInfoWrapper(ctx, &opts), 46 }) 47 opts.AttachControlPlaneFlags(versionCmd) 48 49 versionCmd.Flags().VisitAll(func(flag *pflag.Flag) { 50 if flag.Name == "short" { 51 err := flag.Value.Set("true") 52 if err != nil { 53 fmt.Fprintf(os.Stdout, "set flag %q as true failed due to error %v", flag.Name, err) 54 } 55 } 56 if flag.Name == "remote" { 57 err := flag.Value.Set("true") 58 if err != nil { 59 fmt.Fprintf(os.Stdout, "set flag %q as true failed due to error %v", flag.Name, err) 60 } 61 } 62 }) 63 return versionCmd 64 } 65 66 func getRemoteInfo(ctx cli.Context, opts clioptions.ControlPlaneOptions) (*istioVersion.MeshInfo, error) { 67 kubeClient, err := ctx.CLIClientWithRevision(opts.Revision) 68 if err != nil { 69 return nil, err 70 } 71 72 return kubeClient.GetIstioVersions(context.TODO(), ctx.IstioNamespace()) 73 } 74 75 func getRemoteInfoWrapper(ctx cli.Context, pc **cobra.Command, opts *clioptions.ControlPlaneOptions) func() (*istioVersion.MeshInfo, error) { 76 return func() (*istioVersion.MeshInfo, error) { 77 remInfo, err := getRemoteInfo(ctx, *opts) 78 if err != nil { 79 fmt.Fprintf((*pc).OutOrStderr(), "%v\n", err) 80 // Return nil so that the client version is printed 81 return nil, nil 82 } 83 if remInfo == nil { 84 fmt.Fprintf((*pc).OutOrStderr(), "Istio is not present in the cluster with namespace %q\n", ctx.IstioNamespace()) 85 } 86 return remInfo, err 87 } 88 } 89 90 func getProxyInfoWrapper(ctx cli.Context, opts *clioptions.ControlPlaneOptions) func() (*[]istioVersion.ProxyInfo, error) { 91 return func() (*[]istioVersion.ProxyInfo, error) { 92 client, err := ctx.CLIClientWithRevision(opts.Revision) 93 if err != nil { 94 return nil, err 95 } 96 return proxy.GetProxyInfo(client, ctx.IstioNamespace()) 97 } 98 } 99 100 // XdsVersionCommand gets the Control Plane and Sidecar versions via XDS 101 func XdsVersionCommand(ctx cli.Context) *cobra.Command { 102 var opts clioptions.ControlPlaneOptions 103 var centralOpts clioptions.CentralControlPlaneOptions 104 var xdsResponses *discovery.DiscoveryResponse 105 versionCmd := istioVersion.CobraCommandWithOptions(istioVersion.CobraOptions{ 106 GetRemoteVersion: xdsRemoteVersionWrapper(ctx, &opts, ¢ralOpts, &xdsResponses), 107 GetProxyVersions: xdsProxyVersionWrapper(&xdsResponses), 108 }) 109 opts.AttachControlPlaneFlags(versionCmd) 110 centralOpts.AttachControlPlaneFlags(versionCmd) 111 versionCmd.Args = func(c *cobra.Command, args []string) error { 112 if err := cobra.NoArgs(c, args); err != nil { 113 return err 114 } 115 if err := centralOpts.ValidateControlPlaneFlags(); err != nil { 116 return err 117 } 118 return nil 119 } 120 versionCmd.Example = ` # Retrieve version information directly from the control plane, using token security 121 # (This is the usual way to get the control plane version with an out-of-cluster control plane.) 122 istioctl x version --xds-address istio.cloudprovider.example.com:15012 123 124 # Retrieve version information via Kubernetes config, using token security 125 # (This is the usual way to get the control plane version with an in-cluster control plane.) 126 istioctl x version 127 128 # Retrieve version information directly from the control plane, using RSA certificate security 129 # (Certificates must be obtained before this step. The --cert-dir flag lets istioctl bypass the Kubernetes API server.) 130 istioctl x version --xds-address istio.example.com:15012 --cert-dir ~/.istio-certs 131 132 # Retrieve version information via XDS from specific control plane in multi-control plane in-cluster configuration 133 # (Select a specific control plane in an in-cluster canary Istio configuration.) 134 istioctl x version --xds-label istio.io/rev=default 135 ` 136 137 versionCmd.Flags().VisitAll(func(flag *pflag.Flag) { 138 if flag.Name == "short" { 139 err := flag.Value.Set("true") 140 if err != nil { 141 fmt.Fprintf(os.Stdout, "set flag %q as true failed due to error %v", flag.Name, err) 142 } 143 } 144 if flag.Name == "remote" { 145 err := flag.Value.Set("true") 146 if err != nil { 147 fmt.Fprintf(os.Stdout, "set flag %q as true failed due to error %v", flag.Name, err) 148 } 149 } 150 }) 151 return versionCmd 152 } 153 154 // xdsRemoteVersionWrapper uses outXDS to share the XDS response with xdsProxyVersionWrapper. 155 // (Screwy API on istioVersion.CobraCommandWithOptions) 156 // nolint: lll 157 func xdsRemoteVersionWrapper(ctx cli.Context, opts *clioptions.ControlPlaneOptions, centralOpts *clioptions.CentralControlPlaneOptions, outXDS **discovery.DiscoveryResponse) func() (*istioVersion.MeshInfo, error) { 158 return func() (*istioVersion.MeshInfo, error) { 159 xdsRequest := discovery.DiscoveryRequest{ 160 TypeUrl: xds.TypeDebugSyncronization, 161 } 162 kubeClient, err := ctx.CLIClientWithRevision(opts.Revision) 163 if err != nil { 164 return nil, err 165 } 166 xdsResponse, err := multixds.RequestAndProcessXds(&xdsRequest, *centralOpts, ctx.IstioNamespace(), kubeClient) 167 if err != nil { 168 return nil, err 169 } 170 *outXDS = xdsResponse 171 if xdsResponse.ControlPlane == nil { 172 return &istioVersion.MeshInfo{ 173 istioVersion.ServerInfo{ 174 Component: "MISSING CP ID", 175 Info: istioVersion.BuildInfo{ 176 Version: "MISSING CP ID", 177 }, 178 Revision: "MISSING CP ID", 179 }, 180 }, nil 181 } 182 cpID := xds.IstioControlPlaneInstance{} 183 err = json.Unmarshal([]byte(xdsResponse.ControlPlane.Identifier), &cpID) 184 if err != nil { 185 return nil, fmt.Errorf("could not parse CP Identifier: %w", err) 186 } 187 return &istioVersion.MeshInfo{ 188 istioVersion.ServerInfo{ 189 Component: cpID.Component, 190 Info: cpID.Info, 191 Revision: opts.Revision, 192 }, 193 }, nil 194 } 195 } 196 197 func xdsProxyVersionWrapper(xdsResponse **discovery.DiscoveryResponse) func() (*[]istioVersion.ProxyInfo, error) { 198 return func() (*[]istioVersion.ProxyInfo, error) { 199 pi := []istioVersion.ProxyInfo{} 200 for _, resource := range (*xdsResponse).Resources { 201 switch resource.TypeUrl { 202 case "type.googleapis.com/envoy.config.core.v3.Node": 203 node := core.Node{} 204 err := resource.UnmarshalTo(&node) 205 if err != nil { 206 return nil, fmt.Errorf("could not unmarshal Node: %w", err) 207 } 208 meta, err := model.ParseMetadata(node.Metadata) 209 if err != nil || meta.ProxyConfig == nil { 210 // Skip non-sidecars (e.g. istioctl queries) 211 continue 212 } 213 pi = append(pi, istioVersion.ProxyInfo{ 214 ID: node.Id, 215 IstioVersion: getIstioVersionFromXdsMetadata(node.Metadata), 216 }) 217 case "type.googleapis.com/envoy.service.status.v3.ClientConfig": 218 cc := statusv3.ClientConfig{} 219 err := resource.UnmarshalTo(&cc) 220 if err != nil { 221 return nil, fmt.Errorf("could not unmarshal Node: %w", err) 222 } 223 node := cc.Node 224 pi = append(pi, istioVersion.ProxyInfo{ 225 ID: node.Id, 226 IstioVersion: getIstioVersionFromXdsMetadata(node.Metadata), 227 }) 228 default: 229 return nil, fmt.Errorf("unexpected resource type %q", resource.TypeUrl) 230 } 231 } 232 return &pi, nil 233 } 234 } 235 236 func getIstioVersionFromXdsMetadata(metadata *structpb.Struct) string { 237 meta, err := model.ParseMetadata(metadata) 238 if err != nil { 239 return "unknown sidecar version" 240 } 241 return meta.IstioVersion 242 }