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 7cb5c92
Show file tree
Hide file tree
Showing 9 changed files with 481 additions and 39 deletions.
26 changes: 13 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,18 @@ 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)
ingressCommand := ingress.NewDescribeIngressCommand()
cmds := []*cobra.Command{
addon.Cmd, admin.Cmd, cluster.Cmd, service.Cmd,
installation.Cmd, upgrade.Cmd, tuningconfigs.Cmd,
machinePoolCommand, kubeletconfig.Cmd,
autoscaler.NewDescribeAutoscalerCommand(), ingressCommand,
externalauthprovider.Cmd, breakglasscredential.Cmd,
}
for _, cmd := range cmds {
Cmd.AddCommand(cmd)
}

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

import (
"context"
"fmt"
"regexp"

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

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

const (
use = "ingress"
short = "Show details of the specified ingress within cluster"
example = `rosa describe ingress <ingress_id> -c mycluster`
)

// 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}$`)

func NewDescribeIngressCommand() *cobra.Command {
options := NewDescribeIngressUserOptions()
cmd := &cobra.Command{
Use: use,
Short: short,
Example: example,
Run: rosa.DefaultRunner(rosa.RuntimeWithOCM(), DescribeIngressRunner(options)),
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
},
}

flags := cmd.Flags()
flags.StringVar(
&options.ingress,
"ingress",
"",
"Ingress of the cluster to target",
)

ocm.AddClusterFlag(cmd)
output.AddFlag(cmd)
return cmd
}

func DescribeIngressRunner(userOptions DescribeIngressUserOptions) rosa.CommandRunner {
return func(_ context.Context, runtime *rosa.Runtime, cmd *cobra.Command, argv []string) error {
options := NewDescribeIngressOptions()
if len(argv) == 1 && !cmd.Flag("ingress").Changed {
userOptions.ingress = argv[0]
} else {
err := cmd.ParseFlags(argv)
if err != nil {
return fmt.Errorf("unable to parse flags: %v", err)
}
}
err := options.Bind(userOptions)
if err != nil {
return err
}
ingressKey := options.args.ingress
if !ingressKeyRE.MatchString(ingressKey) {
return fmt.Errorf(
"ingress identifier %q isn't valid: it must contain only letters or digits",
ingressKey,
)
}
clusterKey := runtime.GetClusterKey()
cluster := runtime.FetchCluster()
if cluster.State() != cmv1.ClusterStateReady {
return fmt.Errorf("cluster %q is not yet ready", clusterKey)
}
service := ingress.NewIngressService()
return service.DescribeIngress(runtime, cluster, ingressKey)
}
}
117 changes: 117 additions & 0 deletions cmd/describe/ingress/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package ingress

import (
"context"
"net/http"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
. "github.com/openshift-online/ocm-sdk-go/testing"

"github.com/openshift/rosa/pkg/aws"
. "github.com/openshift/rosa/pkg/output"
"github.com/openshift/rosa/pkg/test"
)

var _ = Describe("Describe ingress", func() {
const (
ingressOutput = `Cluster ID: 123
Component Routes:
console:
Hostname: console-hostname
TLS Secret Ref: console-secret
downloads:
Hostname: downloads-hostname
TLS Secret Ref: downloads-secret
oauth:
Hostname: oauth-hostname
TLS Secret Ref: oauth-secret
Default: true
Excluded Namespaces: [excluded-ns-1, excluded-ns-2]
ID: a1b1
LB-Type: nlb
Namespace Ownership Policy: Strict
Private: false
Route Selectors: map[route-1:selector-1 route-2:selector-2]
Wildcard Policy: WildcardsAllowed
`
)
Context("describe", func() {
// Full diff for long string to help debugging
format.TruncatedDiff = false

mockReadyCluster := test.MockCluster(func(c *cmv1.ClusterBuilder) {
c.ID("123")
c.Region(cmv1.NewCloudRegion().ID(aws.DefaultRegion))
c.State(cmv1.ClusterStateReady)
})
classicClusterReady := test.FormatClusterList([]*cmv1.Cluster{mockReadyCluster})
ingress, err := cmv1.NewIngress().
ID("a1b1").
Default(true).
Listening(cmv1.ListeningMethodExternal).
LoadBalancerType(cmv1.LoadBalancerFlavorNlb).
RouteWildcardPolicy(cmv1.WildcardPolicyWildcardsAllowed).
RouteNamespaceOwnershipPolicy(cmv1.NamespaceOwnershipPolicyStrict).
RouteSelectors(map[string]string{
"route-1": "selector-1",
"route-2": "selector-2",
}).
ExcludedNamespaces("excluded-ns-1", "excluded-ns-2").
ComponentRoutes(map[string]*cmv1.ComponentRouteBuilder{
"oauth": cmv1.NewComponentRoute().Hostname("oauth-hostname").TlsSecretRef("oauth-secret"),
"downloads": cmv1.NewComponentRoute().Hostname("downloads-hostname").TlsSecretRef("downloads-secret"),
"console": cmv1.NewComponentRoute().Hostname("console-hostname").TlsSecretRef("console-secret"),
}).Build()
Expect(err).To(BeNil())
ingressResponse := test.FormatIngressList([]*cmv1.Ingress{ingress})
var t *test.TestingRuntime
BeforeEach(func() {
t = test.NewTestRuntime()
SetOutput("")
})

It("Fails if ingress ID/alias has not been specified", func() {
runner := DescribeIngressRunner(NewDescribeIngressUserOptions())
err := t.StdOutReader.Record()
Expect(err).ToNot(HaveOccurred())
err = runner(context.Background(), t.RosaRuntime, NewDescribeIngressCommand(), []string{})
Expect(err).ToNot(BeNil())
Expect(err.Error()).To(ContainSubstring("you need to specify an ingress ID/alias"))
})

It("Ingress not found", func() {
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, classicClusterReady))
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusNotFound, ""))
args := NewDescribeIngressUserOptions()
args.ingress = "apps"
runner := DescribeIngressRunner(args)
err := t.StdOutReader.Record()
Expect(err).ToNot(HaveOccurred())
err = runner(context.Background(), t.RosaRuntime, NewDescribeIngressCommand(), []string{
"-c", mockReadyCluster.ID(),
})
Expect(err).ToNot(BeNil())
Expect(err.Error()).To(Equal("Failed to get ingress 'apps' for cluster '123'"))
})

It("Ingress found", func() {
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, classicClusterReady))
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, ingressResponse))
args := NewDescribeIngressUserOptions()
args.ingress = "apps"
runner := DescribeIngressRunner(args)
err := t.StdOutReader.Record()
Expect(err).ToNot(HaveOccurred())
err = runner(context.Background(), t.RosaRuntime, NewDescribeIngressCommand(), []string{
"-c", mockReadyCluster.ID(),
})
Expect(err).ToNot(HaveOccurred())
stdout, err := t.StdOutReader.Read()
Expect(err).ToNot(HaveOccurred())
Expect(stdout).To(Equal(ingressOutput))
})
})
})
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")
}
39 changes: 39 additions & 0 deletions cmd/describe/ingress/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ingress

import (
"fmt"

"github.com/openshift/rosa/pkg/reporter"
)

type DescribeIngressUserOptions struct {
ingress string
}

type DescribeIngressOptions struct {
reporter *reporter.Object
args DescribeIngressUserOptions
}

func NewDescribeIngressUserOptions() DescribeIngressUserOptions {
return DescribeIngressUserOptions{ingress: ""}
}

func NewDescribeIngressOptions() *DescribeIngressOptions {
return &DescribeIngressOptions{
reporter: reporter.CreateReporter(),
args: NewDescribeIngressUserOptions(),
}
}

func (i *DescribeIngressOptions) Ingress() string {
return i.args.ingress
}

func (i *DescribeIngressOptions) Bind(args DescribeIngressUserOptions) error {
if args.ingress == "" {
return fmt.Errorf("you need to specify an ingress ID/alias")
}
i.args.ingress = args.ingress
return nil
}
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
Loading

0 comments on commit 7cb5c92

Please sign in to comment.