Skip to content

Commit

Permalink
OCM-6527 | feat: add describe ingress command
Browse files Browse the repository at this point in the history
  • Loading branch information
gdbranco committed Apr 22, 2024
1 parent 0c02dc0 commit 91fa4cb
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 39 deletions.
25 changes: 12 additions & 13 deletions cmd/describe/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/openshift/rosa/cmd/describe/breakglasscredential"
"github.com/openshift/rosa/cmd/describe/cluster"
"github.com/openshift/rosa/cmd/describe/externalauthprovider"
"github.com/openshift/rosa/cmd/describe/ingress"
"github.com/openshift/rosa/cmd/describe/installation"
"github.com/openshift/rosa/cmd/describe/kubeletconfig"
"github.com/openshift/rosa/cmd/describe/machinepool"
Expand All @@ -42,19 +43,17 @@ var Cmd = &cobra.Command{
}

func init() {
Cmd.AddCommand(addon.Cmd)
Cmd.AddCommand(admin.Cmd)
Cmd.AddCommand(cluster.Cmd)
Cmd.AddCommand(service.Cmd)
Cmd.AddCommand(installation.Cmd)
Cmd.AddCommand(upgrade.Cmd)
Cmd.AddCommand(tuningconfigs.Cmd)
machinePoolCommand := machinepool.NewDescribeMachinePoolCommand()
Cmd.AddCommand(machinePoolCommand)
Cmd.AddCommand(kubeletconfig.Cmd)
Cmd.AddCommand(autoscaler.NewDescribeAutoscalerCommand())
Cmd.AddCommand(externalauthprovider.Cmd)
Cmd.AddCommand(breakglasscredential.Cmd)
cmds := []*cobra.Command{
addon.Cmd, admin.Cmd, cluster.Cmd, service.Cmd,
installation.Cmd, upgrade.Cmd, tuningconfigs.Cmd,
machinePoolCommand, kubeletconfig.Cmd,
autoscaler.NewDescribeAutoscalerCommand(), ingress.Cmd,
externalauthprovider.Cmd, breakglasscredential.Cmd,
}
for _, cmd := range cmds {
Cmd.AddCommand(cmd)
}

flags := Cmd.PersistentFlags()
arguments.AddProfileFlag(flags)
Expand All @@ -65,7 +64,7 @@ func init() {
machinePoolCommand, addon.Cmd, upgrade.Cmd,
admin.Cmd, breakglasscredential.Cmd,
externalauthprovider.Cmd, installation.Cmd,
kubeletconfig.Cmd, upgrade.Cmd,
kubeletconfig.Cmd, upgrade.Cmd, ingress.Cmd,
}
arguments.MarkRegionDeprecated(Cmd, globallyAvailableCommands)
}
166 changes: 166 additions & 0 deletions cmd/describe/ingress/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package ingress

import (
"bytes"
"encoding/json"
"fmt"
"os"
"regexp"
"sort"
"strconv"
"strings"

cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
"github.com/spf13/cobra"

"github.com/openshift/rosa/pkg/helper"
"github.com/openshift/rosa/pkg/ocm"
"github.com/openshift/rosa/pkg/output"
"github.com/openshift/rosa/pkg/rosa"
)

// Regular expression to used to make sure that the identifier given by the
// user is safe and that it there is no risk of SQL injection:
var ingressKeyRE = regexp.MustCompile(`^[a-z0-9]{3,5}$`)

var Cmd = &cobra.Command{
Use: "ingress",
Short: "Show details of the specified ingress within cluster",
Example: `rosa describe ingress <ingress_id> -c mycluster`,
Run: run,
Args: func(_ *cobra.Command, argv []string) error {
if len(argv) != 1 {
return fmt.Errorf(
"Expected exactly one command line parameter containing the id of the ingress",
)
}
return nil
},
}

func init() {
ocm.AddClusterFlag(Cmd)
output.AddFlag(Cmd)
}

func run(_ *cobra.Command, argv []string) {
r := rosa.NewRuntime().WithOCM()
defer r.Cleanup()

ingressKey := argv[0]
if !ingressKeyRE.MatchString(ingressKey) {
r.Reporter.Errorf(
"Ingress identifier '%s' isn't valid: it must contain only letters or digits",
ingressKey,
)
os.Exit(1)
}

cluster := r.FetchCluster()

ingress, err := r.OCMClient.GetIngress(cluster.ID(), ingressKey)
if err != nil {
r.Reporter.Errorf("Failed to fetch ingress: %v", err)
os.Exit(1)
}
if output.HasFlag() {
var b bytes.Buffer
err := cmv1.MarshalIngress(ingress, &b)
if err != nil {
r.Reporter.Errorf("Failed to generate output for ingress '%s': %v", ingress.ID(), err)
os.Exit(1)
}
ret := make(map[string]interface{})
err = json.Unmarshal(b.Bytes(), &ret)
if err != nil {
r.Reporter.Errorf("Failed to generate output for ingress '%s': %v", ingress.ID(), err)
os.Exit(1)
}
err = output.Print(ret)
if err != nil {
r.Reporter.Errorf("Failed to output ingress '%s': %v", ingress.ID(), err)
os.Exit(1)
}
return
}
entries := generateEntriesOutput(cluster, ingress)
ingressOutput := ""
keys := helper.MapKeys(entries)
sort.Strings(keys)
minWidth := getMinWidth(keys)
for _, key := range keys {
ingressOutput += fmt.Sprintf("%s: %s\n", key, strings.Repeat(" ", minWidth-len(key))+entries[key])
}
fmt.Print(ingressOutput)
}

// Min width is defined as the length of the longest string
func getMinWidth(keys []string) int {
minWidth := 0
for _, key := range keys {
if len(key) > minWidth {
minWidth = len(key)
}
}
return minWidth
}

func generateEntriesOutput(cluster *cmv1.Cluster, ingress *cmv1.Ingress) map[string]string {
private := false
if ingress.Listening() == cmv1.ListeningMethodInternal {
private = true
}
entries := map[string]string{
"ID": ingress.ID(),
"Cluster ID": cluster.ID(),
"Default": strconv.FormatBool(ingress.Default()),
"Private": strconv.FormatBool(private),
"LB-Type": string(ingress.LoadBalancerType()),
}
// These are only available for ingress v2
wildcardPolicy := string(ingress.RouteWildcardPolicy())
if wildcardPolicy != "" {
entries["Wildcard Policy"] = string(ingress.RouteWildcardPolicy())
}
namespaceOwnershipPolicy := string(ingress.RouteNamespaceOwnershipPolicy())
if namespaceOwnershipPolicy != "" {
entries["Namespace Ownership Policy"] = namespaceOwnershipPolicy
}
routeSelectors := ""
if len(ingress.RouteSelectors()) > 0 {
routeSelectors = fmt.Sprintf("%v", ingress.RouteSelectors())
}
if routeSelectors != "" {
entries["Route Selectors"] = routeSelectors
}
excludedNamespaces := helper.SliceToSortedString(ingress.ExcludedNamespaces())
if excludedNamespaces != "" {
entries["Excluded Namespaces"] = excludedNamespaces
}
componentRoutes := ""
for component, value := range ingress.ComponentRoutes() {
keys := helper.MapKeys(entries)
minWidth := getMinWidth(keys)
depth := 4
componentRouteEntries := map[string]string{
"Hostname": value.Hostname(),
"TLS Secret Ref": value.TlsSecretRef(),
}
componentRoutes += fmt.Sprintf("%s: \n", strings.Repeat(" ", depth)+component)
depth *= 2
for key, entry := range componentRouteEntries {
componentRoutes += fmt.Sprintf(
"%s: %s\n",
strings.Repeat(" ", depth)+key,
strings.Repeat(" ", minWidth-len(key)-depth)+entry,
)
}
}
if componentRoutes != "" {
componentRoutes = fmt.Sprintf("\n%s", componentRoutes)
//remove extra \n at the end
componentRoutes = componentRoutes[:len(componentRoutes)-1]
entries["Component Routes"] = componentRoutes
}
return entries
}
62 changes: 62 additions & 0 deletions cmd/describe/ingress/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
Copyright (c) 2024 Red Hat, Inc.
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 ingress

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
v1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
)

var _ = Describe("Get min width for output", func() {
It("retrieves the min width", func() {
minWidth := getMinWidth([]string{"a", "ab", "abc", "def"})
Expect(minWidth).To(Equal(3))
})
When("empty slice", func() {
It("retrieves the min width as 0", func() {
minWidth := getMinWidth([]string{})
Expect(minWidth).To(Equal(0))
})
})
})

var _ = Describe("Retrieve map of entries for output", func() {
It("retrieves map", func() {
cluster, err := v1.NewCluster().ID("123").Build()
Expect(err).To(BeNil())
ingress, err := v1.NewIngress().
ID("123").
Default(true).
Listening(v1.ListeningMethodExternal).
LoadBalancerType(v1.LoadBalancerFlavorNlb).
RouteWildcardPolicy(v1.WildcardPolicyWildcardsAllowed).
RouteNamespaceOwnershipPolicy(v1.NamespaceOwnershipPolicyStrict).
RouteSelectors(map[string]string{
"test-route": "test-selector",
}).
ExcludedNamespaces("test", "test2").
ComponentRoutes(map[string]*v1.ComponentRouteBuilder{
string(v1.ComponentRouteTypeOauth): v1.NewComponentRoute().
Hostname("oauth-hostname").TlsSecretRef("oauth-secret"),
}).
Build()
Expect(err).To(BeNil())
mapOutput := generateEntriesOutput(cluster, ingress)
Expect(mapOutput).To(HaveLen(10))
})
})
13 changes: 13 additions & 0 deletions cmd/describe/ingress/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ingress

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestEditCluster(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Describe ingress suite")
}
33 changes: 7 additions & 26 deletions cmd/edit/ingress/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ func run(cmd *cobra.Command, argv []string) {
r := rosa.NewRuntime().WithAWS().WithOCM()
defer r.Cleanup()

ingressID := argv[0]
if !ingressKeyRE.MatchString(ingressID) {
ingressKey := argv[0]
if !ingressKeyRE.MatchString(ingressKey) {
r.Reporter.Errorf(
"Ingress identifier '%s' isn't valid: it must contain only letters or digits",
ingressID,
ingressKey,
)
os.Exit(1)
}
Expand Down Expand Up @@ -223,7 +223,7 @@ func run(cmd *cobra.Command, argv []string) {
private = &privArg
}
// Edit API endpoint instead of ingresses
if ingressID == "api" {
if ingressKey == "api" {
clusterConfig := ocm.Spec{
Private: private,
}
Expand All @@ -233,32 +233,13 @@ func run(cmd *cobra.Command, argv []string) {
r.Reporter.Errorf("Failed to update cluster API on cluster '%s': %v", clusterKey, err)
os.Exit(1)
}
r.Reporter.Infof("Updated ingress '%s' on cluster '%s'", ingressID, clusterKey)
r.Reporter.Infof("Updated ingress '%s' on cluster '%s'", ingressKey, clusterKey)
os.Exit(0)
}

// Try to find the ingress:
r.Reporter.Debugf("Loading ingresses for cluster '%s'", clusterKey)
ingresses, err := r.OCMClient.GetIngresses(cluster.ID())
ingress, err := r.OCMClient.GetIngress(cluster.ID(), ingressKey)
if err != nil {
r.Reporter.Errorf("Failed to get ingresses for cluster '%s': %v", clusterKey, err)
os.Exit(1)
}

var ingress *cmv1.Ingress
for _, item := range ingresses {
if ingressID == "apps" && item.Default() {
ingress = item
}
if ingressID == "apps2" && !item.Default() {
ingress = item
}
if item.ID() == ingressID {
ingress = item
}
}
if ingress == nil {
r.Reporter.Errorf("Failed to get ingress '%s' for cluster '%s'", ingressID, clusterKey)
r.Reporter.Errorf("Failed to fetch ingress: %v", err)
os.Exit(1)
}

Expand Down
26 changes: 26 additions & 0 deletions pkg/ocm/ingresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,35 @@ limitations under the License.
package ocm

import (
"fmt"

cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
)

func (c *Client) GetIngress(clusterId string, ingressKey string) (*cmv1.Ingress, error) {
ingresses, err := c.GetIngresses(clusterId)
if err != nil {
return nil, err
}

var ingress *cmv1.Ingress
for _, item := range ingresses {
if ingressKey == "apps" && item.Default() {
ingress = item
}
if ingressKey == "apps2" && !item.Default() {
ingress = item
}
if item.ID() == ingressKey {
ingress = item
}
}
if ingress == nil {
return nil, fmt.Errorf("Failed to get ingress '%s' for cluster '%s'", ingressKey, clusterId)
}
return ingress, nil
}

func (c *Client) GetIngresses(clusterID string) ([]*cmv1.Ingress, error) {
response, err := c.ocm.ClustersMgmt().V1().
Clusters().Cluster(clusterID).
Expand Down

0 comments on commit 91fa4cb

Please sign in to comment.