-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OCM-6527 | feat: add describe ingress command
- Loading branch information
Showing
10 changed files
with
668 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
Oops, something went wrong.