Skip to content

Commit

Permalink
OCM-6030 | feat: allow to edit component routes of ingress
Browse files Browse the repository at this point in the history
  • Loading branch information
gdbranco committed Mar 7, 2024
1 parent 5aa159f commit 90c6e8a
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 0 deletions.
98 changes: 98 additions & 0 deletions cmd/ocm/edit/ingress/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

c "github.com/openshift-online/ocm-cli/pkg/cluster"
"github.com/openshift-online/ocm-cli/pkg/ocm"
"github.com/openshift-online/ocm-cli/pkg/utils"
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
"github.com/spf13/cobra"
)
Expand All @@ -31,6 +32,15 @@ var ValidWildcardPolicies = []string{string(cmv1.WildcardPolicyWildcardsDisallow
string(cmv1.WildcardPolicyWildcardsAllowed)}
var ValidNamespaceOwnershipPolicies = []string{string(cmv1.NamespaceOwnershipPolicyStrict),
string(cmv1.NamespaceOwnershipPolicyInterNamespaceAllowed)}
var expectedComponentRoutes = []string{
string(cmv1.ComponentRouteTypeOauth),
string(cmv1.ComponentRouteTypeConsole),
string(cmv1.ComponentRouteTypeDownloads),
}
var expectedParameters = []string{
hostnameParameter,
tlsSecretRefParameter,
}

var args struct {
clusterKey string
Expand All @@ -43,6 +53,8 @@ var args struct {
namespaceOwnershipPolicy string
clusterRoutesHostname string
clusterRoutesTlsSecretRef string

componentRoutes string
}

const (
Expand All @@ -55,6 +67,12 @@ const (
namespaceOwnershipPolicyFlag = "namespace-ownership-policy"
clusterRoutesHostnameFlag = "cluster-routes-hostname"
clusterRoutesTlsSecretRefFlag = "cluster-routes-tls-secret-ref"
componentRoutesFlag = "component-routes"

amountOfParametersPerComponentRoute = 2
hostnameParameter = "hostname"
//nolint:gosec
tlsSecretRefParameter = "tlsSecretRef"
)

var Cmd = &cobra.Command{
Expand Down Expand Up @@ -148,6 +166,15 @@ func init() {
"",
"Components route TLS secret reference for oauth, console, download.",
)

flags.StringVar(
&args.componentRoutes,
componentRoutesFlag,
"",
//nolint:lll
"Component routes settings. Available keys [oauth, console, download]. For each key a pair of hostname and tlsSecretRef is expected to be supplied. "+
"Format should be a comma separate list 'oauth: hostname=example-hostname;tlsSecretRef=example-secret-ref,downloads:...",
)
}

func run(cmd *cobra.Command, argv []string) error {
Expand Down Expand Up @@ -284,6 +311,17 @@ func run(cmd *cobra.Command, argv []string) error {
ingressBuilder = ingressBuilder.ClusterRoutesTlsSecretRef(args.clusterRoutesTlsSecretRef)
}

if cmd.Flags().Changed(componentRoutesFlag) {
if cluster.Hypershift().Enabled() {
return fmt.Errorf("Can't edit `%s` for Hosted Control Plane clusters", componentRoutesFlag)
}
componentRoutes, err := parseComponentRoutes(args.componentRoutes)
if err != nil {
return fmt.Errorf("An error occurred whilst parsing the supplied component routes: %s", err)
}
ingressBuilder = ingressBuilder.ComponentRoutes(componentRoutes)
}

ingress, err = ingressBuilder.Build()
if err != nil {
return fmt.Errorf("Failed to edit ingress for cluster '%s': %v", clusterKey, err)
Expand All @@ -302,6 +340,66 @@ func run(cmd *cobra.Command, argv []string) error {
return nil
}

func parseComponentRoutes(input string) (map[string]*cmv1.ComponentRouteBuilder, error) {
result := map[string]*cmv1.ComponentRouteBuilder{}
input = strings.TrimSpace(input)
components := strings.Split(input, ",")
if len(components) != len(expectedComponentRoutes) {
return nil, fmt.Errorf(
"the expected amount of component routes is %d, but %d have been supplied",
len(expectedComponentRoutes),
len(components),
)
}
for _, component := range components {
component = strings.TrimSpace(component)
parsedComponent := strings.Split(component, ":")
if len(parsedComponent) != amountOfParametersPerComponentRoute {
return nil, fmt.Errorf(
"only the name of the component should be followed by ':'",
)
}
componentName := strings.TrimSpace(parsedComponent[0])
if !utils.Contains(expectedComponentRoutes, componentName) {
return nil, fmt.Errorf(
"'%s' is not a valid component name. Expected include %s",
componentName,
utils.SliceToSortedString(expectedComponentRoutes),
)
}
parameters := strings.TrimSpace(parsedComponent[1])
componentRouteBuilder := new(cmv1.ComponentRouteBuilder)
parsedParameter := strings.Split(parameters, ";")
if len(parsedParameter) != amountOfParametersPerComponentRoute {
return nil, fmt.Errorf(
"only %d parameters are expected for each component",
amountOfParametersPerComponentRoute,
)
}
for _, values := range parsedParameter {
values = strings.TrimSpace(values)
parsedValues := strings.Split(values, "=")
parameterName := strings.TrimSpace(parsedValues[0])
if !utils.Contains(expectedParameters, parameterName) {
return nil, fmt.Errorf(
"'%s' is not a valid parameter for a component route. Expected include %s",
parameterName,
utils.SliceToSortedString(expectedParameters),
)
}
parameterValue := strings.TrimSpace(parsedValues[1])
// TODO: use reflection, couldn't get it to work
if parameterName == hostnameParameter {
componentRouteBuilder.Hostname(parameterValue)
} else if parameterName == tlsSecretRefParameter {
componentRouteBuilder.TlsSecretRef(parameterValue)
}
}
result[componentName] = componentRouteBuilder
}
return result, nil
}

func GetExcludedNamespaces(excludedNamespaces string) []string {
if excludedNamespaces == "" {
return []string{}
Expand Down
78 changes: 78 additions & 0 deletions cmd/ocm/edit/ingress/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package ingress

import (
"fmt"

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

var _ = Describe("Parse component routes", func() {
It("Parses input string for component routes", func() {
componentRouteBuilder, err := parseComponentRoutes(
//nolint:lll
"oauth: hostname=oauth-host;tlsSecretRef=oauth-secret,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret,console: hostname=console-host;tlsSecretRef=console-secret",
)
Expect(err).To(BeNil())
for key, builder := range componentRouteBuilder {
expectedHostname := fmt.Sprintf("%s-host", key)
expectedTlsRef := fmt.Sprintf("%s-secret", key)
componentRoute, err := builder.Build()
Expect(err).To(BeNil())
Expect(componentRoute.Hostname()).To(Equal(expectedHostname))
Expect(componentRoute.TlsSecretRef()).To(Equal(expectedTlsRef))
}
})
Context("Fails to parse input string for component routes", func() {
It("fails due to invalid component route", func() {
_, err := parseComponentRoutes(
//nolint:lll
"unknown: hostname=oauth-host;tlsSecretRef=oauth-secret,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret,console: hostname=console-host;tlsSecretRef=console-secret",
)
Expect(err).ToNot(BeNil())
Expect(
err.Error(),
).To(Equal("'unknown' is not a valid component name. Expected include [oauth, console, downloads]"))
})
It("fails due to wrong amount of component routes", func() {
_, err := parseComponentRoutes(
//nolint:lll
"oauth: hostname=oauth-host;tlsSecretRef=oauth-secret,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret",
)
Expect(err).ToNot(BeNil())
Expect(
err.Error(),
).To(Equal("the expected amount of component routes is 3, but 2 have been supplied"))
})
It("fails if it can split ':' in more than they key separation", func() {
_, err := parseComponentRoutes(
//nolint:lll
"oauth: hostname=oauth:-host;tlsSecretRef=oauth-secret,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret,",
)
Expect(err).ToNot(BeNil())
Expect(
err.Error(),
).To(Equal("only the name of the component should be followed by ':'"))
})
It("fails due to invalid parameter", func() {
_, err := parseComponentRoutes(
//nolint:lll
"oauth: unknown=oauth-host;tlsSecretRef=oauth-secret,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret,console: hostname=console-host;tlsSecretRef=console-secret",
)
Expect(err).ToNot(BeNil())
Expect(
err.Error(),
).To(Equal("'unknown' is not a valid parameter for a component route. Expected include [hostname, tlsSecretRef]"))
})
It("fails due to wrong amount of parameters", func() {
_, err := parseComponentRoutes(
//nolint:lll
"oauth: hostname=oauth-host,downloads: hostname=downloads-host;tlsSecretRef=downloads-secret,console: hostname=console-host;tlsSecretRef=console-secret",
)
Expect(err).ToNot(BeNil())
Expect(
err.Error(),
).To(Equal("only 2 parameters are expected for each component"))
})
})
})
13 changes: 13 additions & 0 deletions cmd/ocm/edit/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, "Edit ingress suite")
}
13 changes: 13 additions & 0 deletions pkg/utils/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package utils

import (
"testing"

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

func TestEditCluster(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Utils suite")
}
13 changes: 13 additions & 0 deletions pkg/utils/slices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package utils

import "reflect"

func Contains[T comparable](slice []T, element T) bool {
for _, sliceElement := range slice {
if reflect.DeepEqual(sliceElement, element) {
return true
}
}

return false
}
22 changes: 22 additions & 0 deletions pkg/utils/slices_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package utils

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

var _ = Describe("Slices", func() {
Context("Validates Contains", func() {
It("Return false when input is empty", func() {
Expect(false).To(Equal(Contains([]string{}, "any")))
})

It("Return true when input is populated and present", func() {
Expect(true).To(Equal(Contains([]string{"test", "any"}, "any")))
})

It("Return false when input is populated and not present", func() {
Expect(false).To(Equal(Contains([]string{"test", "any"}, "none")))
})
})
})
24 changes: 24 additions & 0 deletions pkg/utils/strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package utils

import (
"sort"
"strings"
)

func SliceToSortedString(s []string) string {
if len(s) == 0 {
return ""
}
SortStringRespectLength(s)
return "[" + strings.Join(s, ", ") + "]"
}

func SortStringRespectLength(s []string) {
sort.Slice(s, func(i, j int) bool {
l1, l2 := len(s[i]), len(s[j])
if l1 != l2 {
return l1 < l2
}
return s[i] < s[j]
})
}
18 changes: 18 additions & 0 deletions pkg/utils/strings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package utils

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

var _ = Describe("Validates SliceToSortedString", func() {
It("Empty when slice is empty", func() {
s := SliceToSortedString([]string{})
Expect("").To(Equal(s))
})

It("Sorted when slice is filled", func() {
s := SliceToSortedString([]string{"b", "a", "c", "a10", "a1", "a20", "a2", "1", "2", "10", "20"})
Expect("[1, 2, a, b, c, 10, 20, a1, a2, a10, a20]").To(Equal(s))
})
})

0 comments on commit 90c6e8a

Please sign in to comment.