Skip to content

Commit

Permalink
Merge branch 'main' into ocm-5736
Browse files Browse the repository at this point in the history
  • Loading branch information
tirthct committed Mar 25, 2024
2 parents b450cda + 82033bd commit fe74426
Show file tree
Hide file tree
Showing 16 changed files with 352 additions and 18 deletions.
25 changes: 23 additions & 2 deletions cmd/ocm/create/cluster/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ const (

var args struct {
// positional args
clusterName string
clusterName string
domainPrefix string

// flags
interactive bool
Expand Down Expand Up @@ -99,7 +100,8 @@ var args struct {
defaultIngressNamespaceOwnershipPolicy string
}

const clusterNameHelp = "will be used when generating a sub-domain for your cluster on openshiftapps.com."
const clusterNameHelp = "The name can be used as the identifier of the cluster." +
" The maximum length is 54 characters. Once set, the cluster name cannot be changed"

const subnetTemplate = "%s (%s)"

Expand Down Expand Up @@ -174,6 +176,19 @@ func init() {
arguments.SetQuestion(fs, "version", "OpenShift version:")
Cmd.RegisterFlagCompletionFunc("version", arguments.MakeCompleteFunc(getVersionOptions))

fs.StringVar(
&args.domainPrefix,
"domain-prefix",
"",
"An optional unique domain prefix of the cluster. If not provided, the cluster name will be "+
"used if it contains at most 15 characters, otherwise a generated value will be used. This "+
"will be used when generating a sub-domain for your cluster. It must be unique and consist "+
"of lowercase alphanumeric,characters or '-', start with an alphabetic character, and end with "+
"an alphanumeric character. The maximum length is 15 characters. Once set, the cluster domain "+
"prefix cannot be changed",
)
arguments.SetQuestion(fs, "domain-prefix", "Domain Prefix:")

fs.StringVar(
&args.channelGroup,
"channel-group",
Expand Down Expand Up @@ -680,6 +695,11 @@ func preRun(cmd *cobra.Command, argv []string) error {
return err
}

err = arguments.PromptString(fs, "domain-prefix")
if err != nil {
return err
}

return nil
}

Expand Down Expand Up @@ -710,6 +730,7 @@ func run(cmd *cobra.Command, argv []string) error {

clusterConfig := c.Spec{
Name: args.clusterName,
DomainPrefix: args.domainPrefix,
Region: args.region,
Provider: args.provider,
CCS: args.ccs,
Expand Down
33 changes: 26 additions & 7 deletions cmd/ocm/edit/cluster/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package cluster
import (
"fmt"
"os"
"reflect"
"strings"
"time"

Expand All @@ -29,9 +30,9 @@ import (

var args struct {
// Basic options
expirationTime string
expirationDuration time.Duration

expirationTime string
expirationDuration time.Duration
enableDeleteProtection bool
// Networking options
private bool

Expand All @@ -47,6 +48,7 @@ var Cmd = &cobra.Command{
Example: ` # Edit a cluster named "mycluster" to make it private
ocm edit cluster mycluster --private`,
RunE: run,
Args: cobra.MinimumNArgs(1),
}

func init() {
Expand Down Expand Up @@ -119,6 +121,12 @@ func init() {
"A file contains a PEM-encoded X.509 certificate bundle that will be "+
"added to the nodes' trusted certificate store.")

flags.BoolVar(
&args.enableDeleteProtection,
"enable-delete-protection",
false,
"Enable cluster delete protection against accidental cluster deletion.",
)
}

func isGCPNetworkEmpty(network *cmv1.GCPNetwork) bool {
Expand All @@ -132,7 +140,7 @@ func wasClusterWideProxyReceived(httpProxy, httpsProxy, noProxy, additionalTrust
}

func run(cmd *cobra.Command, argv []string) error {
// Check that there is exactly one cluster name, identifir or external identifier in the
// Check that there is exactly one cluster name, identifier or external identifier in the
// command line arguments:
if len(argv) != 1 {
return fmt.Errorf(
Expand Down Expand Up @@ -222,6 +230,15 @@ func run(cmd *cobra.Command, argv []string) error {
noProxy = args.clusterWideProxy.NoProxy
}

var enableDeleteProtection bool
if cmd.Flags().Changed("enable-delete-protection") {
enableDeleteProtection = args.enableDeleteProtection
err := c.UpdateDeleteProtection(clusterCollection, cluster.ID(), enableDeleteProtection)
if err != nil {
return err
}
}

var additionalTrustBundleFile *string
var additionalTrustBundleFileValue string
if cmd.Flags().Changed("additional-trust-bundle-file") {
Expand Down Expand Up @@ -268,9 +285,11 @@ func run(cmd *cobra.Command, argv []string) error {
}
clusterConfig.ClusterWideProxy = clusterWideProxy

err = c.UpdateCluster(clusterCollection, cluster.ID(), clusterConfig)
if err != nil {
return fmt.Errorf("Failed to update cluster: %v", err)
if !reflect.ValueOf(clusterConfig).IsZero() {
err = c.UpdateCluster(clusterCollection, cluster.ID(), clusterConfig)
if err != nil {
return fmt.Errorf("Failed to update cluster: %v", err)
}
}

return nil
Expand Down
102 changes: 100 additions & 2 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"

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

var Cmd = &cobra.Command{
Expand Down Expand Up @@ -139,14 +157,23 @@ func init() {
&args.clusterRoutesHostname,
clusterRoutesHostnameFlag,
"",
"Components route hostname for oauth, console, download.",
"Components route hostname for oauth, console, downloads.",
)

flags.StringVar(
&args.clusterRoutesTlsSecretRef,
clusterRoutesTlsSecretRefFlag,
"",
"Components route TLS secret reference for oauth, console, download.",
"Components route TLS secret reference for oauth, console, downloads.",
)

flags.StringVar(
&args.componentRoutes,
componentRoutesFlag,
"",
//nolint:lll
"Component routes settings. Available keys [oauth, console, downloads]. 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:...",
)
}

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) != expectedLengthOfParsedComponent {
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) != len(expectedParameters) {
return nil, fmt.Errorf(
"only %d parameters are expected for each component",
len(expectedParameters),
)
}
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")
}
Loading

0 comments on commit fe74426

Please sign in to comment.