Skip to content

Commit

Permalink
Show actual running versions
Browse files Browse the repository at this point in the history
Modify show versions to show actual running version on the pod,
not just configured one.

Fixes #798

Signed-off-by: Vishal Thapar <[email protected]>
  • Loading branch information
vthapar committed Aug 22, 2023
1 parent dba4072 commit 239f51f
Showing 1 changed file with 118 additions and 8 deletions.
126 changes: 118 additions & 8 deletions internal/show/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,43 @@ limitations under the License.
package show

import (
"bufio"
"context"
"fmt"
"strings"

"github.com/pkg/errors"
"github.com/submariner-io/admiral/pkg/names"
"github.com/submariner-io/admiral/pkg/reporter"
"github.com/submariner-io/subctl/internal/constants"
"github.com/submariner-io/subctl/internal/pods"
"github.com/submariner-io/subctl/internal/show/table"
"github.com/submariner-io/subctl/pkg/cluster"
"github.com/submariner-io/submariner-operator/pkg/images"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
)

const maxLogLinesToScan = 20

var componentCmd = map[string][]string{
names.RouteAgentComponent: {"submariner-route-agent", "--version"},
names.GatewayComponent: {"submariner-gateway", "--version"},
names.GlobalnetComponent: {"submariner-globalnet", "--version"},
names.ServiceDiscoveryComponent: {"lighthouse-agent", "--version"},
names.LighthouseCoreDNSComponent: {"lighthouse-coredns", "--subm-version"},
names.OperatorComponent: {"submariner-operator", "--version"},
names.MetricsProxyComponent: {"cat", "version"},
}

func printDaemonSetVersions(clusterInfo *cluster.Info, printer *table.Printer, components ...string) error {
daemonSets := clusterInfo.ClientProducer.ForKubernetes().AppsV1().DaemonSets(constants.OperatorNamespace)

for _, component := range components {
daemonSet, err := daemonSets.Get(context.TODO(), component, v1.GetOptions{})
daemonSet, err := daemonSets.Get(context.TODO(), component, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
continue
Expand All @@ -47,7 +66,14 @@ func printDaemonSetVersions(clusterInfo *cluster.Info, printer *table.Printer, c

// The name of the function is confusing, it just parses any image repo & version
version, repository := images.ParseOperatorImage(daemonSet.Spec.Template.Spec.Containers[0].Image)
printer.Add(component, repository, version)

runningVersion, err := getVersionForComponent(clusterInfo, component,
labels.SelectorFromSet(daemonSet.Spec.Selector.MatchLabels))
if err != nil {
return errors.Wrapf(err, "error retrieving running version for %s", component)
}

printer.Add(component, repository, version, runningVersion)
}

return nil
Expand All @@ -57,7 +83,7 @@ func printDeploymentVersions(clusterInfo *cluster.Info, printer *table.Printer,
deployments := clusterInfo.ClientProducer.ForKubernetes().AppsV1().Deployments(constants.OperatorNamespace)

for _, component := range components {
deployment, err := deployments.Get(context.TODO(), component, v1.GetOptions{})
deployment, err := deployments.Get(context.TODO(), component, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
continue
Expand All @@ -67,19 +93,104 @@ func printDeploymentVersions(clusterInfo *cluster.Info, printer *table.Printer,
}

version, repository := images.ParseOperatorImage(deployment.Spec.Template.Spec.Containers[0].Image)
printer.Add(component, repository, version)

runningVersion, err := getVersionForComponent(clusterInfo, component,
labels.SelectorFromSet(deployment.Spec.Selector.MatchLabels))
if err != nil {
return err
}

printer.Add(component, repository, version, runningVersion)
}

return nil
}

func getVersionForComponent(clusterInfo *cluster.Info, component string, labelSelector labels.Selector) (string, error) {
podsClient := clusterInfo.ClientProducer.ForKubernetes().CoreV1().Pods(constants.OperatorNamespace)
podList, err := podsClient.List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()})

if err != nil || len(podList.Items) < 1 {
return "", errors.Wrapf(err, "failed to find pods for component %s", component)
}

// Try all pods
for i := range podList.Items {
pod := &podList.Items[i]
podVersion := getVersionFromPodBinary(pod, clusterInfo, component)

if podVersion != "" {
return podVersion, nil
}

podVersion = getVersionFromPodLogs(pod, podsClient, component)

if podVersion != "" {
return podVersion, nil
}
}

return "Unavailable", nil
}

func getVersionFromPodBinary(pod *corev1.Pod, clusterInfo *cluster.Info, component string) string {
execOptions := pods.ExecOptionsFromPod(pod)
execConfig := pods.ExecConfig{
RestConfig: clusterInfo.RestConfig,
ClientSet: clusterInfo.ClientProducer.ForKubernetes(),
}

execOptions.Command = componentCmd[component]

outStr, errStr, err := pods.ExecWithOptions(context.TODO(), execConfig, &execOptions)
if err != nil {
return ""
}

if component == names.MetricsProxyComponent {
return outStr
}

result, found := strings.CutPrefix(errStr, fmt.Sprintf("%s version: ", component))

if !found {
return ""
}

return result
}

func getVersionFromPodLogs(pod *corev1.Pod, podClient v1.PodInterface, component string) string {
podLogOptions := corev1.PodLogOptions{
Container: pod.Spec.Containers[0].Name,
}
logRequest := podClient.GetLogs(pod.Name, &podLogOptions)
logStream, _ := logRequest.Stream(context.TODO())

if logStream != nil {
logScanner := bufio.NewScanner(logStream)
logScanner.Split(bufio.ScanLines)

for line := 1; logScanner.Scan() && line < maxLogLinesToScan; line++ {
result, found := strings.CutPrefix(logScanner.Text(), fmt.Sprintf("%s version: ", component))

if found {
return result
}
}
}

return ""
}

func Versions(clusterInfo *cluster.Info, _ string, status reporter.Interface) error {
status.Start("Showing versions")

printer := table.Printer{Columns: []table.Column{
{Name: "COMPONENT"},
{Name: "REPOSITORY"},
{Name: "VERSION"},
{Name: "CONFIGURED"},
{Name: "RUNNING"},
}}

err := printDaemonSetVersions(clusterInfo, &printer, names.GatewayComponent, names.RouteAgentComponent, names.GlobalnetComponent,
Expand All @@ -89,8 +200,7 @@ func Versions(clusterInfo *cluster.Info, _ string, status reporter.Interface) er
}

err = printDeploymentVersions(
clusterInfo, &printer, names.OperatorComponent, names.ServiceDiscoveryComponent, names.LighthouseCoreDNSComponent,
names.NetworkPluginSyncerComponent)
clusterInfo, &printer, names.OperatorComponent, names.ServiceDiscoveryComponent, names.LighthouseCoreDNSComponent)
if err != nil {
return status.Error(err, "Error retrieving Deployment versions")
}
Expand Down

0 comments on commit 239f51f

Please sign in to comment.