Skip to content

Commit

Permalink
OCM-6528 | feat: add describe ingress cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
gdbranco committed Apr 30, 2024
1 parent 798fb30 commit 01c0241
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 8 deletions.
10 changes: 2 additions & 8 deletions cmd/ocm/describe/cluster/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ import (
"bytes"
"fmt"
"os"
"regexp"

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

c "github.com/openshift-online/ocm-cli/pkg/cluster"
clusterpkg "github.com/openshift-online/ocm-cli/pkg/cluster"
"github.com/openshift-online/ocm-cli/pkg/dump"
"github.com/openshift-online/ocm-cli/pkg/ocm"
)
Expand Down Expand Up @@ -75,7 +73,7 @@ func run(cmd *cobra.Command, argv []string) error {
// Check that the cluster key (name, identifier or external identifier) given by the user
// is reasonably safe so that there is no risk of SQL injection:
key := argv[0]
if !keyRE.MatchString(key) {
if !c.IsValidClusterKey(key) {
fmt.Fprintf(
os.Stderr,
"Cluster name, identifier or external identifier '%s' isn't valid: it "+
Expand Down Expand Up @@ -132,15 +130,11 @@ func run(cmd *cobra.Command, argv []string) error {
}

} else {
err = clusterpkg.PrintClusterDescription(connection, cluster)
err = c.PrintClusterDescription(connection, cluster)
if err != nil {
return err
}
}

return nil
}

// Regular expression to check that the cluster key (name, identifier or external identifier) given
// by the user is reasonably safe and that there is no risk of SQL injection.
var keyRE = regexp.MustCompile(`^(\w|-)+$`)
2 changes: 2 additions & 0 deletions cmd/ocm/describe/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package describe

import (
"github.com/openshift-online/ocm-cli/cmd/ocm/describe/cluster"
"github.com/openshift-online/ocm-cli/cmd/ocm/describe/ingress"
"github.com/spf13/cobra"
)

Expand All @@ -26,4 +27,5 @@ var Cmd = &cobra.Command{

func init() {
Cmd.AddCommand(cluster.Cmd)
Cmd.AddCommand(ingress.Cmd)
}
167 changes: 167 additions & 0 deletions cmd/ocm/describe/ingress/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package ingress

import (
"bytes"
"fmt"
"os"

c "github.com/openshift-online/ocm-cli/pkg/cluster"
"github.com/openshift-online/ocm-cli/pkg/dump"
i "github.com/openshift-online/ocm-cli/pkg/ingress"
"github.com/openshift-online/ocm-cli/pkg/ocm"
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
"github.com/spf13/cobra"
)

var args struct {
json bool
output bool
ingressKey string
}

var Cmd = &cobra.Command{
Use: "ingress [flags] {CLUSTER_NAME|CLUSTER_ID|CLUSTER_EXTERNAL_ID} -i ingress_key",
Short: "Show details of an ingress",
Long: "Show details of an ingress identified by name, or identifier",
RunE: run,
}

func init() {
// Add flags to rootCmd:
flags := Cmd.Flags()
flags.BoolVar(
&args.output,
"output",
false,
"Output result into JSON file.",
)
flags.BoolVar(
&args.json,
"json",
false,
"Output the entire JSON structure",
)
flags.StringVarP(
&args.ingressKey,
"ingress",
"i",
"",
"Ingress identifier",
)
}

func run(cmd *cobra.Command, argv []string) error {
// Check that there is exactly one cluster name, identifir or external identifier in the
// command line arguments:
if len(argv) != 1 {
fmt.Fprintf(
os.Stderr,
"Expected exactly one cluster name, identifier or external identifier "+
"is required\n",
)
os.Exit(1)
}

// Check that the cluster key (name, identifier or external identifier) given by the user
// is reasonably safe so that there is no risk of SQL injection:
key := argv[0]
if !c.IsValidClusterKey(key) {
fmt.Fprintf(
os.Stderr,
"Cluster name, identifier or external identifier '%s' isn't valid: it "+
"must contain only letters, digits, dashes and underscores\n",
key,
)
os.Exit(1)
}
ingressKey := args.ingressKey
if ingressKey == "" {
fmt.Fprintf(
os.Stderr,
"Ingress identifier must be supplied\n",
)
os.Exit(1)
}

// Create the client for the OCM API:
connection, err := ocm.NewConnection().Build()
if err != nil {
return fmt.Errorf("Failed to create OCM connection: %v", err)
}
defer connection.Close()

cluster, err := c.GetCluster(connection, key)
if err != nil {
return fmt.Errorf("Can't retrieve cluster for key '%s': %v", key, err)
}

clusterId := cluster.ID()
response, err := connection.ClustersMgmt().V1().
Clusters().Cluster(clusterId).
Ingresses().
List().Page(1).Size(-1).
Send()
if err != nil {
return err
}

ingresses := response.Items().Slice()
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 fmt.Errorf("Failed to get ingress '%s' for cluster '%s'", ingressKey, clusterId)
}

if args.output {
// Create a filename based on cluster name:
filename := fmt.Sprintf("ingress-%s-%s.json", cluster.ID(), ingress.ID())

// Attempt to create file:
myFile, err := os.Create(filename)
if err != nil {
return fmt.Errorf("Failed to create file: %v", err)
}

// Dump encoder content into file:
err = cmv1.MarshalIngress(ingress, myFile)
if err != nil {
return fmt.Errorf("Failed to Marshal ingress into file: %v", err)
}
}

// Get full API response (JSON):
if args.json {
// Buffer for pretty output:
buf := new(bytes.Buffer)
fmt.Println()

// Convert cluster to JSON and dump to encoder:
err = cmv1.MarshalIngress(ingress, buf)
if err != nil {
return fmt.Errorf("Failed to Marshal ingress into JSON encoder: %v", err)
}

err = dump.Pretty(os.Stdout, buf.Bytes())
if err != nil {
return fmt.Errorf("Can't print body: %v", err)
}

} else {
err = i.PrintIngressDescription(ingress, cluster)
if err != nil {
return err
}
}

return nil
}
100 changes: 100 additions & 0 deletions pkg/ingress/describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package ingress

import (
"fmt"
"sort"
"strconv"
"strings"

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

func PrintIngressDescription(ingress *cmv1.Ingress, cluster *cmv1.Cluster) error {
entries := generateEntriesOutput(cluster, ingress)
ingressOutput := ""
keys := utils.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)
return nil
}

// 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 := utils.SliceToSortedString(ingress.ExcludedNamespaces())
if excludedNamespaces != "" {
entries["Excluded Namespaces"] = excludedNamespaces
}
componentRoutes := ""
componentKeys := utils.MapKeys(ingress.ComponentRoutes())
sort.Strings(componentKeys)
for _, component := range componentKeys {
value := ingress.ComponentRoutes()[component]
keys := utils.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
paramKeys := utils.MapKeys(componentRouteEntries)
sort.Strings(paramKeys)
for _, param := range paramKeys {
componentRoutes += fmt.Sprintf(
"%s: %s\n",
strings.Repeat(" ", depth)+param,
strings.Repeat(" ", minWidth-len(param)-depth)+componentRouteEntries[param],
)
}
}
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
}
47 changes: 47 additions & 0 deletions pkg/ingress/describe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ingress

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
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 := cmv1.NewCluster().ID("123").Build()
Expect(err).To(BeNil())
ingress, err := cmv1.NewIngress().
ID("123").
Default(true).
Listening(cmv1.ListeningMethodExternal).
LoadBalancerType(cmv1.LoadBalancerFlavorNlb).
RouteWildcardPolicy(cmv1.WildcardPolicyWildcardsAllowed).
RouteNamespaceOwnershipPolicy(cmv1.NamespaceOwnershipPolicyStrict).
RouteSelectors(map[string]string{
"test-route": "test-selector",
}).
ExcludedNamespaces("test", "test2").
ComponentRoutes(map[string]*cmv1.ComponentRouteBuilder{
string(cmv1.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 pkg/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")
}
8 changes: 8 additions & 0 deletions pkg/utils/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ func SortStringRespectLength(s []string) {
return s[i] < s[j]
})
}

func MapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}

0 comments on commit 01c0241

Please sign in to comment.