Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

CLI: Download Detector Configuration as File #229

Merged
merged 6 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 48 additions & 28 deletions cli/cmd/cat.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
entity "esad/internal/entity/ad"
"esad/internal/handler/ad"
"fmt"
"io"
"os"

"github.com/spf13/cobra"
)
Expand All @@ -33,30 +35,35 @@ var catCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
//If no args, display usage
if len(args) < 1 {
if err := cmd.Usage(); err != nil {
fmt.Println(err)
}
displayError(cmd.Usage(), commandCat)
return
}
idStatus, _ := cmd.Flags().GetBool("id")
commandHandler, err := getCommandHandler()
if err != nil {
fmt.Println(err)
}
// default is name
action := ad.GetAnomalyDetectorsByNamePattern
if idStatus {
action = getDetectorsByID
}
results, err := getDetectors(commandHandler, args, action)
if err != nil {
fmt.Println(err)
return
}
printDetectors(results)
err := printDetectors(Println, cmd, args)
displayError(err, commandCat)
},
}

type Display func(*cobra.Command, *entity.DetectorOutput) error

//printDetectors print detectors
func printDetectors(display Display, cmd *cobra.Command, detectors []string) error {
idStatus, _ := cmd.Flags().GetBool("id")
commandHandler, err := getCommandHandler()
if err != nil {
return err
}
// default is name
action := ad.GetAnomalyDetectorsByNamePattern
if idStatus {
action = getDetectorsByID
}
results, err := getDetectors(commandHandler, detectors, action)
if err != nil {
return err
}
return print(cmd, display, results)
}

func getDetectors(
commandHandler *ad.Handler, args []string, get func(*ad.Handler, string) (
[]*entity.DetectorOutput, error)) ([]*entity.DetectorOutput, error) {
Expand All @@ -81,20 +88,33 @@ func getDetectorsByID(commandHandler *ad.Handler, ID string) ([]*entity.Detector
return []*entity.DetectorOutput{output}, nil
}

//printDetectors displays the list of output. Since this is json format, use indent function to
// pretty print before printing on console
func printDetectors(results []*entity.DetectorOutput) {
//print displays the list of output.
func print(cmd *cobra.Command, display Display, results []*entity.DetectorOutput) error {
if results == nil {
return
return nil
}
for _, d := range results {
formattedOutput, err := json.MarshalIndent(d, "", " ")
if err != nil {
fmt.Println(err)
return
if err := display(cmd, d); err != nil {
return err
}
fmt.Println(string(formattedOutput))
}
return nil
}

//FPrint prints detector configuration on writer
//Since this is json format, use indent function to pretty print before printing on writer
func FPrint(writer io.Writer, d *entity.DetectorOutput) error {
formattedOutput, err := json.MarshalIndent(d, "", " ")
if err != nil {
return err
}
_, err = fmt.Fprintln(writer, string(formattedOutput))
return err
}

//Println prints detector configuration on stdout
func Println(cmd *cobra.Command, d *entity.DetectorOutput) error {
return FPrint(os.Stdout, d)
}

func init() {
Expand Down
9 changes: 2 additions & 7 deletions cli/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,12 @@ var createCmd = &cobra.Command{
}
//If no args, display usage
if len(args) < 1 {
if err := cmd.Usage(); err != nil {
fmt.Println(err)
}
displayError(cmd.Usage(), commandCreate)
return
}
status, _ := cmd.Flags().GetBool(interactive)
err := createDetectors(args, status)
if err != nil {
fmt.Println(commandCreate, "command failed")
fmt.Println("Reason:", err)
}
displayError(err, commandCreate)
},
}

Expand Down
10 changes: 2 additions & 8 deletions cli/cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ package cmd

import (
handler "esad/internal/handler/ad"
"fmt"

"github.com/spf13/cobra"
)
Expand All @@ -30,9 +29,7 @@ var deleteCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
//If no args, display usage
if len(args) < 1 {
if err := cmd.Usage(); err != nil {
fmt.Println(err)
}
displayError(cmd.Usage(), commandDelete)
return
}
force, _ := cmd.Flags().GetBool("force")
Expand All @@ -42,10 +39,7 @@ var deleteCmd = &cobra.Command{
action = handler.DeleteAnomalyDetectorByID
}
err := deleteDetectors(args, force, action)
if err != nil {
fmt.Println(commandDelete, "command failed")
fmt.Println("Reason:", err)
}
displayError(err, commandDelete)
},
}

Expand Down
107 changes: 107 additions & 0 deletions cli/cmd/download.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
* http://www.apache.org/licenses/LICENSE-2.0
* or in the "license" file accompanying this file. This file 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 cmd

import (
entity "esad/internal/entity/ad"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
)

const (
commandDownload = "download"
flagOutput = "output"
fileExtensionJSON = "json"
)

//downloadCmd downloads detectors configuration on current working directory
//based on detector id or name patter
var downloadCmd = &cobra.Command{
Use: commandDownload + " [flags] [list of detectors]",
Short: "Downloads detectors configurations based on id or name pattern",
Long: fmt.Sprintf("Description:\n " +
`Downloads detectors configurations based on id or name pattern, use "" to make sure the name is not matched with pwd lists'`),
Run: func(cmd *cobra.Command, args []string) {
//If no args, display usage
if len(args) < 1 {
displayError(cmd.Usage(), commandDownload)
return
}
err := printDetectors(WriteInFile, cmd, args)
displayError(err, commandDownload)
},
}

//WriteInFile writes detector's configuration on file
//file will be created inside current working directory,
//with detector name as file name
func WriteInFile(cmd *cobra.Command, d *entity.DetectorOutput) error {
output, _ := cmd.Flags().GetString(flagOutput)
if _, err := os.Stat(output); os.IsNotExist(err) {
return fmt.Errorf("output directory [%s] does not exists", output)
}
filePath := filepath.Join(output, fmt.Sprintf("%s.%s", d.Name, fileExtensionJSON))
if ok := isCreateFileAllowed(filePath); !ok {
return nil
}
f, err := os.Create(filePath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the filePath exists, will os.Create override the existing file?

Copy link
Contributor

@ylwu-amzn ylwu-amzn Sep 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From this doc, https://golang.org/pkg/os/#Create

If the file already exists, it is truncated

How about check file exists or not before create ? If file exists, we should not truncate it directly in case user execute command by mistake.

Copy link
Member Author

@VijayanB VijayanB Sep 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I introduced a flag -i similar to cp in linux commands, which prompt to stdout whether to overwrite the file or not.
Screen Shot 2020-09-16 at 3 40 49 PM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flag -i is optional? What if user don't add -i?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is interactive. By default download will overwrite but if user passes -i, it will prompt for user confirmation. Do you think prompt should be default?

Copy link
Contributor

@ylwu-amzn ylwu-amzn Sep 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think prompt should be default for this case, not good user experience/safe to override user's existing file without any prompt.

defer func() {
f.Close()
}()
if err != nil {
return err
}
return FPrint(f, d)
}

func isCreateFileAllowed(path string) bool {
if _, err := os.Stat(path); os.IsNotExist(err) {
return true
}
return askForConfirmation(path)
}

func askForConfirmation(path string) bool {

fmt.Printf("overwrite %s? (y/n [n])", filepath.Base(path))
var response string
_, err := fmt.Fscanln(os.Stdin, &response)
if err != nil {
//Exit if for some reason, we are not able to accept user input
fmt.Printf("failed to accept value from user due to %s", err)
return false
}
switch strings.ToLower(response) {
case "y", "yes":
return true
case "n", "no":
return false
default:
return false
}
}

func init() {
esadCmd.AddCommand(downloadCmd)
downloadCmd.Flags().BoolP("name", "", true, "input is name or pattern")
downloadCmd.Flags().BoolP("id", "", false, "input is id")
cwd, err := os.Getwd()
if err != nil {
fmt.Println("failed to find current working directory due to ", err)
}
downloadCmd.Flags().StringP(flagOutput, "o", cwd, "downloads detectors inside this folder path")
}
7 changes: 7 additions & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,10 @@ func GetHandler(c *client.Client, u *client.UserConfig) *handler.Handler {
ctr := controller.New(os.Stdin, esc, g)
return handler.New(ctr)
}

func displayError(err error, cmdName string) {
if err != nil {
fmt.Println(cmdName, "command failed")
fmt.Println("Reason: ", err)
}
}
19 changes: 7 additions & 12 deletions cli/cmd/start_stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ package cmd
import (
"esad/internal/client"
"esad/internal/handler/ad"
"fmt"

"github.com/spf13/cobra"
)
Expand All @@ -32,16 +31,17 @@ var startCmd = &cobra.Command{
Short: "Start detectors based on an ID or name pattern",
Long: `Start detectors based on a pattern. Use "" to make sure the name does not match with pwd lists'`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
displayError(cmd.Usage(), commandStop)
return
}
idStatus, _ := cmd.Flags().GetBool("id")
action := ad.StartAnomalyDetectorByNamePattern
if idStatus {
action = ad.StartAnomalyDetectorByID
}
err := execute(action, args)
if err != nil {
fmt.Println(commandStart, "command failed")
fmt.Println("Reason:", err)
}
displayError(err, commandStart)
},
}

Expand All @@ -54,9 +54,7 @@ var stopCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
//If no args, display usage
if len(args) < 1 {
if err := cmd.Usage(); err != nil {
fmt.Println(err)
}
displayError(cmd.Usage(), commandStop)
return
}
idStatus, _ := cmd.Flags().GetBool("id")
Expand All @@ -65,10 +63,7 @@ var stopCmd = &cobra.Command{
action = ad.StopAnomalyDetectorByID
}
err := execute(action, args)
if err != nil {
fmt.Println(commandStop, "command failed")
fmt.Println("Reason:", err)
}
displayError(err, commandStop)
},
}

Expand Down