Skip to content

Commit

Permalink
Add flag to delete all limited support reasons for a cluster at once
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexVulaj committed Apr 17, 2023
1 parent 393495d commit 79d0675
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 87 deletions.
6 changes: 3 additions & 3 deletions cmd/cluster/support/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ func NewCmdSupport(streams genericclioptions.IOStreams, flags *genericclioptions
Run: help,
}

supportCmd.AddCommand(newCmdstatus(streams, flags, globalOpts))
supportCmd.AddCommand(newCmdpost(streams, flags, globalOpts))
supportCmd.AddCommand(newCmddelete(streams, flags, globalOpts))
supportCmd.AddCommand(newCmdstatus(streams, globalOpts))
supportCmd.AddCommand(newCmdpost(streams, globalOpts))
supportCmd.AddCommand(newCmddelete(streams, globalOpts))

return supportCmd
}
Expand Down
35 changes: 35 additions & 0 deletions cmd/cluster/support/common.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package support

import (
"errors"
"fmt"
ctlutil "github.com/openshift/osdctl/pkg/utils"
"os"

sdk "github.com/openshift-online/ocm-sdk-go"
)
Expand All @@ -14,3 +17,35 @@ func sendRequest(request *sdk.Request) (*sdk.Response, error) {
}
return response, nil
}

func getLimitedSupportReasons(clusterId string) ([]*ctlutil.LimitedSupportReasonItem, 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
err := ctlutil.IsValidClusterKey(clusterId)
if err != nil {
return nil, err
}

//create connection to sdk
connection := ctlutil.CreateConnection()
defer func() {
if err := connection.Close(); err != nil {
fmt.Printf("Cannot close the connection: %q\n", err)
os.Exit(1)
}
}()

//getting the cluster
cluster, err := ctlutil.GetCluster(connection, clusterId)
if err != nil {
return nil, errors.New(fmt.Sprintf("Can't retrieve cluster: %v\n", err))
}

//getting the limited support reasons for the cluster
clusterLimitedSupportReasons, err := ctlutil.GetClusterLimitedSupportReasons(connection, cluster.ID())
if err != nil {
return nil, errors.New(fmt.Sprintf("Can't retrieve cluster limited support reasons: %v\n", err))
}

return clusterLimitedSupportReasons, nil
}
63 changes: 42 additions & 21 deletions cmd/cluster/support/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package support
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"

Expand All @@ -24,14 +23,16 @@ type deleteOptions struct {
verbose bool
clusterID string
limitedSupportReasonID string
removeAll bool
isDryRun bool

genericclioptions.IOStreams
GlobalOptions *globalflags.GlobalOptions
}

func newCmddelete(streams genericclioptions.IOStreams, flags *genericclioptions.ConfigFlags, globalOpts *globalflags.GlobalOptions) *cobra.Command {
func newCmddelete(streams genericclioptions.IOStreams, globalOpts *globalflags.GlobalOptions) *cobra.Command {

ops := newDeleteOptions(streams, flags, globalOpts)
ops := newDeleteOptions(streams, globalOpts)
deleteCmd := &cobra.Command{
Use: "delete CLUSTER_ID",
Short: "Delete specified limited support reason for a given cluster",
Expand All @@ -44,19 +45,15 @@ func newCmddelete(streams genericclioptions.IOStreams, flags *genericclioptions.
}

// Defined required flags
deleteCmd.Flags().BoolVar(&ops.removeAll, "all", false, "Remove all limited support reasons")
deleteCmd.Flags().StringVarP(&ops.limitedSupportReasonID, "limited-support-reason-id", "i", "", "Limited support reason ID")
deleteCmd.Flags().BoolVarP(&isDryRun, "dry-run", "d", false, "Dry-run - print the limited support reason about to be sent but don't send it.")
deleteCmd.Flags().BoolVarP(&ops.isDryRun, "dry-run", "d", false, "Dry-run - print the limited support reason about to be sent but don't send it.")
deleteCmd.Flags().BoolVarP(&ops.verbose, "verbose", "", false, "Verbose output")

// Mark limited-support-reason-id (-i) flag required
if err := deleteCmd.MarkFlagRequired("limited-support-reason-id"); err != nil {
log.Fatalln("limited-support-reason-id", err)
}

return deleteCmd
}

func newDeleteOptions(streams genericclioptions.IOStreams, flags *genericclioptions.ConfigFlags, globalOpts *globalflags.GlobalOptions) *deleteOptions {
func newDeleteOptions(streams genericclioptions.IOStreams, globalOpts *globalflags.GlobalOptions) *deleteOptions {

return &deleteOptions{
IOStreams: streams,
Expand All @@ -70,6 +67,14 @@ func (o *deleteOptions) complete(cmd *cobra.Command, args []string) error {
return cmdutil.UsageErrorf(cmd, "Provide exactly one internal cluster ID")
}

if o.limitedSupportReasonID == "" && !o.removeAll {
return cmdutil.UsageErrorf(cmd, "Must provide a reason ID or the `all` flag")
}

if o.limitedSupportReasonID != "" && o.removeAll {
return cmdutil.UsageErrorf(cmd, "Cannot provide a reason ID with the `all` flag. Please provide one or the other.")
}

o.clusterID = args[0]
o.output = o.GlobalOptions.Output

Expand All @@ -95,7 +100,7 @@ func (o *deleteOptions) run() error {
}()

// Stop here if dry-run
if isDryRun {
if o.isDryRun {
return nil
}

Expand All @@ -112,18 +117,34 @@ func (o *deleteOptions) run() error {
os.Exit(1)
}

deleteRequest, err := createDeleteRequest(connection, cluster, o.limitedSupportReasonID)
if err != nil {
fmt.Printf("failed post call %q\n", err)
}
deleteResponse, err := sendRequest(deleteRequest)
if err != nil {
fmt.Printf("Failed to get delete call response: %q\n", err)
var limitedSupportReasonIds []string
if o.removeAll {
limitedSupportReasons, err := getLimitedSupportReasons(o.clusterID)
for _, limitedSupportReason := range limitedSupportReasons {
limitedSupportReasonIds = append(limitedSupportReasonIds, limitedSupportReason.ID)
}
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get limited support reasons: %v\n", err)
return err
}
} else {
limitedSupportReasonIds = append(limitedSupportReasonIds, o.limitedSupportReasonID)
}

err = checkDelete(deleteResponse)
if err != nil {
fmt.Printf("check for delete call failed: %q", err)
for _, limitedSupportReasonId := range limitedSupportReasonIds {
deleteRequest, err := createDeleteRequest(connection, cluster, limitedSupportReasonId)
if err != nil {
fmt.Printf("failed post call %q\n", err)
}
deleteResponse, err := sendRequest(deleteRequest)
if err != nil {
fmt.Printf("Failed to get delete call response: %q\n", err)
}

err = checkDelete(deleteResponse)
if err != nil {
fmt.Printf("check for delete call failed: %q", err)
}
}

return nil
Expand Down
64 changes: 32 additions & 32 deletions cmd/cluster/support/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ import (
)

var (
LimitedSupport support.LimitedSupport
template string
isDryRun bool
templateParams, userParameterNames, userParameterValues []string
)

Expand All @@ -34,17 +32,19 @@ const (
)

type postOptions struct {
output string
verbose bool
clusterID string
output string
verbose bool
clusterID string
isDryRun bool
limitedSupport support.LimitedSupport

genericclioptions.IOStreams
GlobalOptions *globalflags.GlobalOptions
}

func newCmdpost(streams genericclioptions.IOStreams, flags *genericclioptions.ConfigFlags, globalOpts *globalflags.GlobalOptions) *cobra.Command {
func newCmdpost(streams genericclioptions.IOStreams, globalOpts *globalflags.GlobalOptions) *cobra.Command {

ops := newPostOptions(streams, flags, globalOpts)
ops := newPostOptions(streams, globalOpts)
postCmd := &cobra.Command{
Use: "post CLUSTER_ID",
Short: "Send limited support reason to a given cluster",
Expand All @@ -58,14 +58,14 @@ func newCmdpost(streams genericclioptions.IOStreams, flags *genericclioptions.Co

// Define required flags
postCmd.Flags().StringVarP(&template, "template", "t", defaultTemplate, "Message template file or URL")
postCmd.Flags().BoolVarP(&isDryRun, "dry-run", "d", false, "Dry-run - print the limited support reason about to be sent but don't send it.")
postCmd.Flags().BoolVarP(&ops.isDryRun, "dry-run", "d", false, "Dry-run - print the limited support reason about to be sent but don't send it.")
postCmd.Flags().StringArrayVarP(&templateParams, "param", "p", templateParams, "Specify a key-value pair (eg. -p FOO=BAR) to set/override a parameter value in the template.")
postCmd.Flags().BoolVarP(&ops.verbose, "verbose", "", false, "Verbose output")

return postCmd
}

func newPostOptions(streams genericclioptions.IOStreams, flags *genericclioptions.ConfigFlags, globalOpts *globalflags.GlobalOptions) *postOptions {
func newPostOptions(streams genericclioptions.IOStreams, globalOpts *globalflags.GlobalOptions) *postOptions {

return &postOptions{
IOStreams: streams,
Expand All @@ -88,8 +88,8 @@ func (o *postOptions) complete(cmd *cobra.Command, args []string) error {
func (o *postOptions) run() error {

// Parse the given JSON template provided via '-t' flag
// and load it into the LimitedSupport variable
readTemplate()
// and load it into the limitedSupport variable
o.readTemplate()

// Parse all the '-p' user flags
parseUserParameters()
Expand All @@ -103,7 +103,7 @@ func (o *postOptions) run() error {

// For every '-p' flag, replace it's related placeholder in the template
for k := range userParameterNames {
replaceWithFlags(userParameterNames[k], userParameterValues[k])
o.replaceWithFlags(userParameterNames[k], userParameterValues[k])
}

//if the cluster key is on the right format
Expand All @@ -118,13 +118,13 @@ func (o *postOptions) run() error {

// Print limited support template to be sent
fmt.Printf("The following limited support reason will be sent to %s:\n", o.clusterID)
if err := printTemplate(); err != nil {
if err := o.printTemplate(); err != nil {
fmt.Printf("Cannot read generated template: %q\n", err)
os.Exit(1)
}

// Stop here if dry-run
if isDryRun {
if o.isDryRun {
return nil
}

Expand All @@ -142,7 +142,7 @@ func (o *postOptions) run() error {
}

// postRequest calls createPostRequest and take in client and clustersmgmt/v1.cluster object
postRequest, err := createPostRequest(connection, cluster)
postRequest, err := o.createPostRequest(connection, cluster)
if err != nil {
fmt.Printf("failed to create post request %q\n", err)
}
Expand All @@ -151,8 +151,8 @@ func (o *postOptions) run() error {
fmt.Printf("Failed to get post call response: %q\n", err)
}

// check if response matches LimitedSupport
err = check(postResponse, LimitedSupport)
// check if response matches limitedSupport
err = check(postResponse)
if err != nil {
fmt.Printf("Failed to check postResponse %q\n", err)
}
Expand All @@ -163,7 +163,7 @@ func (o *postOptions) run() error {
// swagger code gen: https://api.openshift.com/?urls.primaryName=Clusters%20management%20service#/default/post_api_clusters_mgmt_v1_clusters__cluster_id__limited_support_reasons
// SDKConnection is an interface that is satisfied by the sdk.Connection and by our mock connection
// this facilitates unit test and allow us to mock Post() and Delete() api calls
func createPostRequest(ocmClient SDKConnection, cluster *v1.Cluster) (request *sdk.Request, err error) {
func (o *postOptions) createPostRequest(ocmClient SDKConnection, cluster *v1.Cluster) (request *sdk.Request, err error) {

targetAPIPath := "/api/clusters_mgmt/v1/clusters/" + cluster.ID() + "/limited_support_reasons"

Expand All @@ -173,7 +173,7 @@ func createPostRequest(ocmClient SDKConnection, cluster *v1.Cluster) (request *s
return nil, fmt.Errorf("cannot parse API path '%s': %v", targetAPIPath, err)
}

messageBytes, err := json.Marshal(LimitedSupport)
messageBytes, err := json.Marshal(o.limitedSupport)
if err != nil {
return nil, fmt.Errorf("cannot marshal template to json: %v", err)
}
Expand All @@ -182,8 +182,8 @@ func createPostRequest(ocmClient SDKConnection, cluster *v1.Cluster) (request *s
return request, nil
}

// readTemplate loads the template into the LimitedSupport variable
func readTemplate() {
// readTemplate loads the template into the limitedSupport variable
func (o *postOptions) readTemplate() {

if template == defaultTemplate {
log.Fatalf("Template file is not provided. Use '-t' to fix this.")
Expand All @@ -195,7 +195,7 @@ func readTemplate() {
log.Fatal(err)
}

if err = parseTemplate(file); err != nil {
if err = o.parseTemplate(file); err != nil {
log.Fatalf("Cannot not parse the JSON template.\nError: %q\n", err)
}
}
Expand Down Expand Up @@ -227,20 +227,20 @@ func accessFile(filePath string) ([]byte, error) {
}

// parseTemplate reads the template file into a JSON struct
func parseTemplate(jsonFile []byte) error {
return json.Unmarshal(jsonFile, &LimitedSupport)
func (o *postOptions) parseTemplate(jsonFile []byte) error {
return json.Unmarshal(jsonFile, &o.limitedSupport)
}

func printTemplate() error {
func (o *postOptions) printTemplate() error {

limitedSupportMessage, err := json.Marshal(LimitedSupport)
limitedSupportMessage, err := json.Marshal(o.limitedSupport)
if err != nil {
return err
}
return dump.Pretty(os.Stdout, limitedSupportMessage)
}

func validateGoodResponse(body []byte, limitedSupport support.LimitedSupport) (goodReply *support.GoodReply, err error) {
func validateGoodResponse(body []byte) (goodReply *support.GoodReply, err error) {

if !json.Valid(body) {
return nil, fmt.Errorf("Server returned invalid JSON")
Expand All @@ -263,11 +263,11 @@ func validateBadResponse(body []byte) (badReply *support.BadReply, err error) {
return badReply, nil
}

func check(response *sdk.Response, limitedSupport support.LimitedSupport) error {
func check(response *sdk.Response) error {

body := response.Bytes()
if response.Status() == http.StatusCreated {
_, err := validateGoodResponse(body, limitedSupport)
_, err := validateGoodResponse(body)
if err != nil {
return fmt.Errorf("failed to validate good response: %q", err)
}
Expand Down Expand Up @@ -299,16 +299,16 @@ func parseUserParameters() {
}
}

func replaceWithFlags(flagName string, flagValue string) {
func (o *postOptions) replaceWithFlags(flagName string, flagValue string) {
if flagValue == "" {
log.Fatalf("The selected template is using '%[1]s' parameter, but '%[1]s' flag was not set. Use '-p %[1]s=\"FOOBAR\"' to fix this.", flagName)
}

found := false

if LimitedSupport.SearchFlag(flagName) {
if o.limitedSupport.SearchFlag(flagName) {
found = true
LimitedSupport.ReplaceWithFlag(flagName, flagValue)
o.limitedSupport.ReplaceWithFlag(flagName, flagValue)
}

if !found {
Expand Down
2 changes: 1 addition & 1 deletion cmd/cluster/support/post_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestValidateGoodResponse(t *testing.T) {
},
}
for _, tc := range testCases {
_, result := validateGoodResponse(tc.body, tc.lmtSprReason)
_, result := validateGoodResponse(tc.body)
if tc.errExpected {
if result == nil {
t.Fatalf("Test %s failed. Expected error %s, but got none", tc.title, tc.errReason)
Expand Down
Loading

0 comments on commit 79d0675

Please sign in to comment.