Skip to content

Commit

Permalink
feat(cli): add mesh check-upgrade command
Browse files Browse the repository at this point in the history
Resolves openservicemesh#1720.

Signed-off-by: Johnson Shi <Johnson.Shi@microsoft.com>
  • Loading branch information
johnsonshi committed Jun 3, 2021
1 parent 83c2a38 commit eae0eca
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/cli/mesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func newMeshCmd(config *action.Configuration, in io.Reader, out io.Writer) *cobr
Long: meshDescription,
Args: cobra.NoArgs,
}
cmd.AddCommand(newMeshCheckUpgrade(out))
cmd.AddCommand(newMeshList(out))
cmd.AddCommand(newMeshUpgradeCmd(config, out))

Expand Down
118 changes: 118 additions & 0 deletions cmd/cli/mesh_check_upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package main

import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"io"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"net/http"

"github.com/openservicemesh/osm/pkg/constants"
)

const (
osmReleaseRepoOwner = "openservicemesh"
osmReleaseRepoName = "osm"
)

const meshCheckUpgradeDesc = `
This command checks if there are upgrades available for a given OSM mesh and namespace.
The following information is printed:
(1) the OSM version installed in the mesh name and namespace,
(2) the latest OSM release version available.
The mesh to check for updates is identified by its mesh name and namespace.
If either were overridden from the default for the "osm install" command,
the --mesh-name and --osm-namespace flags need to be specified.
`

const meshCheckUpgradeExample = `
# Check if upgrades are available for a mesh and print version information.
osm mesh check-upgrade --mesh-name osm --osm-namespace osm-system
`

type meshCheckUpgradeCmd struct {
meshName string
out io.Writer
config *rest.Config
clientSet kubernetes.Interface
}

func newMeshCheckUpgrade(out io.Writer) *cobra.Command {
checkUpgradeCmd := &meshCheckUpgradeCmd{
out: out,
}

cmd := &cobra.Command{
Use: "check-upgrade",
Short: "check if upgrades are available for an OSM mesh and print version information",
Long: meshCheckUpgradeDesc,
Example: meshCheckUpgradeExample,
Args: cobra.ExactArgs(0),
RunE: func(_ *cobra.Command, args []string) error {
config, err := settings.RESTClientGetter().ToRESTConfig()
if err != nil {
return errors.Errorf("Error fetching kubeconfig: %s", err)
}
checkUpgradeCmd.config = config
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return errors.Errorf("Could not access Kubernetes cluster, check kubeconfig: %s", err)
}
checkUpgradeCmd.clientSet = clientset
return checkUpgradeCmd.run()
},
}

f := cmd.Flags()
f.StringVar(&checkUpgradeCmd.meshName, "mesh-name", defaultMeshName, "Name of the mesh to check for upgrades")

return cmd
}

func (c *meshCheckUpgradeCmd) run() error {
meshNamespace := settings.Namespace()
osmControllerDeployment, err := getMeshControllerDeployment(c.clientSet, c.meshName, meshNamespace)
if err != nil {
return err
}

// get mesh version currently installed
meshVersion := osmControllerDeployment.ObjectMeta.Labels[constants.OSMAppVersionLabelKey]
if meshVersion == "" {
meshVersion = "Unknown"
}
_, _ = fmt.Fprintf(c.out, "Mesh Name: [%s]. Mesh Namespace: [%s]. Mesh Version: [%s].\n", c.meshName, meshNamespace, meshVersion)

// get latest available release version from the osm repo release API endpoint
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", osmReleaseRepoOwner, osmReleaseRepoName)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return fmt.Errorf("error fetching latest available release version from %s", url)
}

req.Header.Add("Accept", "application/vnd.github.v3+json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error fetching latest available release version from %s", url)
}
defer resp.Body.Close() //nolint: errcheck,gosec

latestReleaseVersionInfo := map[string]interface{}{}
if err := json.NewDecoder(resp.Body).Decode(&latestReleaseVersionInfo); err != nil {
return fmt.Errorf("error json decoding latest release version info from %s", url)
}

if latestVersion := latestReleaseVersionInfo["tag_name"]; latestVersion != "" {
_, _ = fmt.Fprintf(c.out, "Latest Available Version: [%s].\n", latestVersion)
} else {
return fmt.Errorf("error fetching latest available release version from %s", url)
}

return nil
}
24 changes: 24 additions & 0 deletions cmd/cli/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,30 @@ func getNamespacePods(clientSet kubernetes.Interface, m string, ns string) map[s
return x
}

// getMeshControllerDeployment returns the Deployment corresponding to osm-controller within
func getMeshControllerDeployment(clientSet kubernetes.Interface, meshName, meshNamespace string) (*v1.Deployment, error) {
deploymentsClient := clientSet.AppsV1().Deployments(meshNamespace) // Get deployments from namespace
labelSelector := metav1.LabelSelector{
MatchLabels: map[string]string{
"app": constants.OSMControllerName,
"meshName": meshName,
},
}
listOptions := metav1.ListOptions{
LabelSelector: labels.Set(labelSelector.MatchLabels).String(),
}

deploymentList, err := deploymentsClient.List(context.TODO(), listOptions)
if err != nil {
return nil, err
}
if len(deploymentList.Items) < 1 {
return nil, fmt.Errorf("No osm mesh installation found with mesh name [%s] and namespace [%s]", meshName, meshNamespace)
}

return &deploymentList.Items[0], nil
}

// getControllerDeployments returns a list of Deployments corresponding to osm-controller
func getControllerDeployments(clientSet kubernetes.Interface) (*v1.DeploymentList, error) {
deploymentsClient := clientSet.AppsV1().Deployments("") // Get deployments from all namespaces
Expand Down
23 changes: 23 additions & 0 deletions cmd/cli/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
v1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"

"github.com/openservicemesh/osm/pkg/constants"
)
Expand Down Expand Up @@ -125,6 +126,28 @@ var _ = Describe("Test getting pretty printed output of a list of meshes", func(
})
})

var _ = Describe("Test getting mesh controller metadata", func() {
meshName := "testMesh"
meshNamespace := "testNamespace"

Context("mesh controller metadata in clientSet", func() {
fakeClientSet := fake.NewSimpleClientset()
_, err := addDeployment(fakeClientSet, "osm-controller", meshName, meshNamespace, "testVersion0.1.2", true)
Expect(err).NotTo(HaveOccurred())

It("asd", func() {
deployment, err := getMeshControllerDeployment(fakeClientSet, meshName, meshNamespace)
Expect(err).NotTo(HaveOccurred())

Expect(deployment.ObjectMeta.Namespace).To(Equal(meshNamespace))
Expect(deployment.ObjectMeta.Labels["meshName"]).To(Equal(meshName))
Expect(deployment.ObjectMeta.Labels[constants.OSMAppVersionLabelKey]).To(Equal("testVersion0.1.2"))
Expect(deployment.ObjectMeta.Name).To(Equal("osm-controller"))

})
})
})

// helper function for tests that adds deployment to the clientset
func addDeployment(fakeClientSet kubernetes.Interface, depName string, meshName string, namespace string, osmVersion string, isMesh bool) (*v1.Deployment, error) {
dep := createDeployment(depName, meshName, osmVersion, isMesh)
Expand Down

0 comments on commit eae0eca

Please sign in to comment.