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 7fd6c58
Show file tree
Hide file tree
Showing 10 changed files with 668 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)
}
73 changes: 73 additions & 0 deletions cmd/describe/ingress/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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: cobra.NoArgs,
}

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
}
clusterKey := runtime.GetClusterKey()
cluster := runtime.FetchCluster()
if cluster.State() != cmv1.ClusterStateReady {
return fmt.Errorf("cluster '%s' is not yet ready", clusterKey)
}
service := ingress.NewIngressService()
return service.DescribeIngress(runtime, cluster, options.args.ingress)
}
}
247 changes: 247 additions & 0 deletions cmd/describe/ingress/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
package ingress

import (
"bytes"
"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
`
privateIngressOutput = `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: true
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})
mockNotReadyCluster := test.MockCluster(func(c *cmv1.ClusterBuilder) {
c.ID("123")
c.Region(cmv1.NewCloudRegion().ID(aws.DefaultRegion))
c.State(cmv1.ClusterStateInstalling)
})
classicClusterNotReady := test.FormatClusterList([]*cmv1.Cluster{mockNotReadyCluster})
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})
privateIngress, err := cmv1.NewIngress().
ID("a1b1").
Default(true).
Listening(cmv1.ListeningMethodInternal).
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())
privateIngressResponse := test.FormatIngressList([]*cmv1.Ingress{privateIngress})
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("Fails if ingress ID/alias is invalid", func() {
args := NewDescribeIngressUserOptions()
args.ingress = "A1b2"
runner := DescribeIngressRunner(args)
err := t.StdOutReader.Record()
Expect(err).ToNot(HaveOccurred())
err = runner(context.Background(), t.RosaRuntime, NewDescribeIngressCommand(), []string{})
Expect(err).ToNot(BeNil())
Expect(
err.Error(),
).To(Equal("ingress identifier 'A1b2' isn't valid: it must contain only letters or digits"))
})

It("Cluster not ready", func() {
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, classicClusterNotReady))
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("cluster '123' is not yet ready"))
})

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))
})

It("Ingress found through argv", func() {
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, classicClusterReady))
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, ingressResponse))
args := NewDescribeIngressUserOptions()
runner := DescribeIngressRunner(args)
cmd := NewDescribeIngressCommand()
cmd.Flag("cluster").Value.Set("123")
err := t.StdOutReader.Record()
Expect(err).ToNot(HaveOccurred())
err = runner(context.Background(), t.RosaRuntime, cmd,
[]string{
"apps",
})
Expect(err).ToNot(HaveOccurred())
stdout, err := t.StdOutReader.Read()
Expect(err).ToNot(HaveOccurred())
Expect(stdout).To(Equal(ingressOutput))
})

It("Ingress found json output", func() {
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, classicClusterReady))
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, ingressResponse))
args := NewDescribeIngressUserOptions()
args.ingress = "apps"
runner := DescribeIngressRunner(args)
cmd := NewDescribeIngressCommand()
cmd.Flag("output").Value.Set("json")
err := t.StdOutReader.Record()
Expect(err).ToNot(HaveOccurred())
err = runner(context.Background(), t.RosaRuntime, cmd, []string{
"-c", mockReadyCluster.ID(),
})
Expect(err).ToNot(HaveOccurred())
stdout, err := t.StdOutReader.Read()
Expect(err).ToNot(HaveOccurred())
var ingressJson bytes.Buffer
cmv1.MarshalIngress(ingress, &ingressJson)
Expect(stdout).To(Equal(ingressJson.String() + "\n"))
})

It("Private Ingress found", func() {
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, classicClusterReady))
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, privateIngressResponse))
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(privateIngressOutput))
})
})
})
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")
}
Loading

0 comments on commit 7fd6c58

Please sign in to comment.