Skip to content

Commit

Permalink
Merge pull request #146 from igoihman/edit-cluster
Browse files Browse the repository at this point in the history
Implement Edit cluster command
  • Loading branch information
Irit Goihman authored Sep 17, 2020
2 parents 28fb228 + 034a475 commit 8fc6114
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 27 deletions.
31 changes: 4 additions & 27 deletions cmd/ocm/create/cluster/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,6 @@ func init() {

func run(cmd *cobra.Command, argv []string) error {
var err error
var expiration time.Time

// Validate options
if len(args.expirationTime) > 0 && args.expirationSeconds != 0 {
return fmt.Errorf("at most one of `expiration-time` or `expiration` may be specified")
}
if args.region == "us-east-1" && args.provider != "aws" {
return fmt.Errorf("if specifying a non-aws cloud provider, region must be set to a valid region")
}
Expand All @@ -217,20 +211,11 @@ func run(cmd *cobra.Command, argv []string) error {
}
clusterName := argv[0]

// Parse the expiration options
if len(args.expirationTime) > 0 {
t, err := parseRFC3339(args.expirationTime)
if err != nil {
return fmt.Errorf("unable to parse expiration time: %s", err)
}

expiration = t
}
if args.expirationSeconds != 0 {
// round up to the nearest second
expiration = time.Now().Add(args.expirationSeconds).Round(time.Second)
// Validate flags:
expiration, err := c.ValidateClusterExpiration(args.expirationTime, args.expirationSeconds)
if err != nil {
return fmt.Errorf(fmt.Sprintf("%s", err))
}

// Retrieve valid/default versions
versionList := sets.NewString()
var defaultVersion string
Expand Down Expand Up @@ -381,14 +366,6 @@ func fetchFlavours(client *cmv1.Client) (flavours []*cmv1.Flavour, err error) {
return
}

// parseRFC3339 parses an RFC3339 date in either RFC3339Nano or RFC3339 format.
func parseRFC3339(s string) (time.Time, error) {
if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil {
return t, nil
}
return time.Parse(time.RFC3339, s)
}

func validateMachineType(client *cmv1.Client, provider string, machineType string) (string, error) {
machineTypeList, err := getMachineTypeList(client, provider)
if err != nil {
Expand Down
156 changes: 156 additions & 0 deletions cmd/ocm/edit/cluster/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
Copyright (c) 2020 Red Hat, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cluster

import (
"fmt"
"time"

"github.com/spf13/cobra"

c "github.com/openshift-online/ocm-cli/pkg/cluster"
"github.com/openshift-online/ocm-cli/pkg/ocm"
)

var args struct {
clusterKey string

// Basic options
expirationTime string
expirationDuration time.Duration

// Scaling options
computeNodes int

// Networking options
private bool
}

var Cmd = &cobra.Command{
Use: "cluster",
Short: "Edit cluster",
Long: "Edit cluster.",
Example: ` # Edit a cluster named "mycluster" to make it private
ocm edit cluster mycluster --private`,
RunE: run,
}

func init() {
flags := Cmd.Flags()

flags.StringVarP(
&args.clusterKey,
"cluster",
"c",
"",
"Name or ID of the cluster.",
)
//nolint:gosec
Cmd.MarkFlagRequired("cluster")

// Basic options
flags.StringVar(
&args.expirationTime,
"expiration-time",
"",
"Specific time when cluster should expire (RFC3339). Only one of expiration-time / expiration may be used.",
)
flags.DurationVar(
&args.expirationDuration,
"expiration",
0,
"Expire cluster after a relative duration like 2h, 8h, 72h. Only one of expiration-time / expiration may be used.",
)
// Cluster expiration is not supported in production
//nolint:gosec
flags.MarkHidden("expiration-time")
//nolint:gosec
flags.MarkHidden("expiration")

// Scaling options
flags.IntVar(
&args.computeNodes,
"compute-nodes",
0,
"Number of worker nodes to provision per zone. Single zone clusters need at least 4 nodes, "+
"while multizone clusters need at least 9 nodes (3 per zone) for resiliency.",
)

// Networking options
flags.BoolVar(
&args.private,
"private",
false,
"Restrict master API endpoint to direct, private connectivity.",
)

}

func run(cmd *cobra.Command, argv []string) error {

// Check that the cluster key (name, identifier or external identifier) given by the user
// is reasonably safe so that there is no risk of SQL injection:
clusterKey := args.clusterKey
if !c.IsValidClusterKey(clusterKey) {
return fmt.Errorf(
"Cluster name, identifier or external identifier '%s' isn't valid: it "+
"must contain only letters, digits, dashes and underscores",
clusterKey,
)
}

// Create the client for the OCM API:
connection, err := ocm.NewConnection().Build()
if err != nil {
return fmt.Errorf("Failed to create OCM connection: %v", err)
}
defer connection.Close()

// Get the client for the cluster management api
clusterCollection := connection.ClustersMgmt().V1().Clusters()

cluster, err := c.GetCluster(clusterCollection, clusterKey)
if err != nil {
return fmt.Errorf("Failed to get cluster '%s': %v", clusterKey, err)
}

// Validate flags:
expiration, err := c.ValidateClusterExpiration(args.expirationTime, args.expirationDuration)
if err != nil {
return fmt.Errorf(fmt.Sprintf("%s", err))
}

var private *bool
if cmd.Flags().Changed("private") {
private = &args.private
}

var computeNodes int
if cmd.Flags().Changed("compute-nodes") {
computeNodes = args.computeNodes
}

clusterConfig := c.Spec{
Expiration: expiration,
ComputeNodes: computeNodes,
Private: private,
}
err = c.UpdateCluster(clusterCollection, cluster.ID(), clusterConfig)
if err != nil {
return fmt.Errorf("Failed to update cluster: %v", err)
}

return nil

}
2 changes: 2 additions & 0 deletions cmd/ocm/edit/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ limitations under the License.
package edit

import (
"github.com/openshift-online/ocm-cli/cmd/ocm/edit/cluster"
"github.com/openshift-online/ocm-cli/cmd/ocm/edit/ingress"
"github.com/spf13/cobra"
)
Expand All @@ -27,4 +28,5 @@ var Cmd = &cobra.Command{

func init() {
Cmd.AddCommand(ingress.Cmd)
Cmd.AddCommand(cluster.Cmd)
}
82 changes: 82 additions & 0 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package cluster

import (
"errors"
"fmt"
"net"
"regexp"
Expand Down Expand Up @@ -216,6 +217,51 @@ func CreateCluster(cmv1Client *cmv1.Client, config Spec) (*cmv1.Cluster, error)
return response.Body(), nil
}

func UpdateCluster(client *cmv1.ClustersClient, clusterID string, config Spec) error {

clusterBuilder := cmv1.NewCluster()

// Update expiration timestamp
if !config.Expiration.IsZero() {
clusterBuilder = clusterBuilder.ExpirationTimestamp(config.Expiration)
}

// Scale cluster
if config.ComputeNodes != 0 {
clusterBuilder = clusterBuilder.Nodes(
cmv1.NewClusterNodes().
Compute(config.ComputeNodes),
)
}

// Toggle private mode
if config.Private != nil {
if *config.Private {
clusterBuilder = clusterBuilder.API(
cmv1.NewClusterAPI().
Listening(cmv1.ListeningMethodInternal),
)
} else {
clusterBuilder = clusterBuilder.API(
cmv1.NewClusterAPI().
Listening(cmv1.ListeningMethodExternal),
)
}
}

clusterSpec, err := clusterBuilder.Build()
if err != nil {
return err
}

_, err = client.Cluster(clusterID).Update().Body(clusterSpec).Send()
if err != nil {
return err
}

return nil
}

func GetClusterOauthURL(cluster *cmv1.Cluster) string {
var oauthURL string
consoleURL := cluster.Console().URL()
Expand Down Expand Up @@ -385,3 +431,39 @@ func GetMachineTypes(client *cmv1.Client, provider string) (machineTypes []*cmv1
}
return
}

func ValidateClusterExpiration(
expirationTime string,
expirationDuration time.Duration,
) (expiration time.Time, err error) {
// Validate options
if len(expirationTime) > 0 && expirationDuration != 0 {
err = errors.New("At most one of 'expiration-time' or 'expiration' may be specified")
return
}

// Parse the expiration options
if len(expirationTime) > 0 {
t, err := parseRFC3339(expirationTime)
if err != nil {
err = fmt.Errorf("Failed to parse expiration-time: %s", err)
return expiration, err
}

expiration = t
}
if expirationDuration != 0 {
// round up to the nearest second
expiration = time.Now().Add(expirationDuration).Round(time.Second)
}

return
}

// parseRFC3339 parses an RFC3339 date in either RFC3339Nano or RFC3339 format.
func parseRFC3339(s string) (time.Time, error) {
if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil {
return t, nil
}
return time.Parse(time.RFC3339, s)
}

0 comments on commit 8fc6114

Please sign in to comment.