Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] feat(cli): add Markdown support for CLI #1356

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
46 changes: 46 additions & 0 deletions api/alerts_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,37 @@ import (
"github.com/pkg/errors"
)

type alertCommentFormat int

const (
AlertCommentFormatPlaintext alertCommentFormat = iota
AlertCommentFormatMarkdown
)

// String returns the string representation of an Alert comment format
func (i alertCommentFormat) String() string {
return AlertCommentFormats[i]
}

type alertCommentFormats map[alertCommentFormat]string

// AlertCommentFormats is the list of available Alert comment formats
var AlertCommentFormats = alertCommentFormats{
AlertCommentFormatPlaintext: "Plaintext",
AlertCommentFormatMarkdown: "Markdown",
}

func (acf alertCommentFormats) GetOrderedFormatStrings() []string {
formats := []string{}
for i := 0; i < len(acf); i++ {
formats = append(formats, acf[alertCommentFormat(i)])
}
return formats
}

type AlertsCommentRequest struct {
Comment string `json:"comment"`
Format string `json:"format"`
}

type AlertsCommentResponse struct {
Expand All @@ -48,3 +77,20 @@ func (svc *AlertsService) Comment(id int, comment string) (
)
return
}

func (svc *AlertsService) CommentFromRequest(id int, request AlertsCommentRequest) (
response AlertsCommentResponse,
err error,
) {
if request.Comment == "" {
err = errors.New("alert comment must be provided")
return
}
err = svc.client.RequestEncoderDecoder(
"POST",
fmt.Sprintf(apiV2AlertsComment, id),
request,
&response,
)
return
}
1 change: 1 addition & 0 deletions cli/cmd/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type alertCmdStateType struct {
Status string
Start string
Type string
Format string
}

// hasFilters returns true if certain filters are present
Expand Down
38 changes: 37 additions & 1 deletion cli/cmd/alert_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"fmt"
"strconv"

"github.com/AlecAivazis/survey/v2"
"github.com/lacework/go-sdk/api"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
Expand All @@ -34,6 +36,12 @@ var (
Long: `Post a user comment on an alert's timeline .

Comments may be provided inline or via editor.

The following comment formats are allowed:
* Plaintext
* Markdown

Note: only markdown comments will be rendered using markdown.
`,
Args: cobra.ExactArgs(1),
RunE: commentAlert,
Expand All @@ -49,6 +57,28 @@ func init() {
"comment", "c", "",
"a comment to add to the alert",
)

// format flag
alertCommentCmd.Flags().StringVarP(
&alertCmdState.Format,
"format", "f", "",
"the format of the comment (default Plaintext)",
)
}

func inputFormat() (format string, err error) {
if alertCmdState.Format != "" {
format = alertCmdState.Format
return
}

prompt := &survey.Select{
Message: "Format:",
Options: api.AlertCommentFormats.GetOrderedFormatStrings(),
}
err = survey.AskOne(prompt, &format)

return
}

func commentAlert(_ *cobra.Command, args []string) error {
Expand All @@ -74,8 +104,14 @@ func commentAlert(_ *cobra.Command, args []string) error {
return errors.Wrap(err, "unable to process alert comment")
}

format, err := inputFormat()
if err != nil {
return errors.Wrap(err, "unable to process alert comment format")
}

cli.StartProgress(" Adding alert comment...")
commentResponse, err := cli.LwApi.V2.Alerts.Comment(id, comment)
commentResponse, err := cli.LwApi.V2.Alerts.CommentFromRequest(id,
api.AlertsCommentRequest{Comment: comment, Format: format})
cli.StopProgress()
if err != nil {
return errors.Wrap(err, "unable to add alert comment")
Expand Down
59 changes: 59 additions & 0 deletions cli/cmd/alert_comment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package cmd

import (
"fmt"
"net/http"
"strconv"
"testing"

"github.com/lacework/go-sdk/api"
"github.com/lacework/go-sdk/internal/lacework"
"github.com/stretchr/testify/assert"
)

func TestAlertComment(t *testing.T) {
tests := []struct {
name string
args []string
commentFlag string
formatFlag string
}{
{
name: "Valid Comment with Markdown Format",
args: []string{"12345"},
commentFlag: "This is a **markdown** comment.",
formatFlag: "Markdown",
},
{
name: "Valid Comment with Plain Text Format",
args: []string{"12344"},
commentFlag: "This is a plain text comment.",
formatFlag: "Plaintext",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
id, _ := strconv.Atoi(test.args[0])
fakeServer := lacework.MockServer()
fakeServer.MockAPI(
fmt.Sprintf("Alerts/%d/comment", id),
func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "POST", r.Method, "should be a POST method")
fmt.Fprint(w, "{}")
},
)
defer fakeServer.Close()

c, err := api.NewClient("test",
api.WithToken("TOKEN"),
api.WithURL(fakeServer.URL()),
)
assert.Nil(t, err)

_, err = c.V2.Alerts.CommentFromRequest(id,
api.AlertsCommentRequest{Comment: test.commentFlag, Format: test.formatFlag})
assert.Nil(t, err)
})
}
}
52 changes: 50 additions & 2 deletions cli/cmd/alert_show_timeline.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package cmd

import (
"bytes"
"runtime"
"strconv"
"strings"

"github.com/lacework/go-sdk/api"
"github.com/olekukonko/tablewriter"

"github.com/lacework/go-sdk/api"
"github.com/pkg/errors"

"github.com/Delta456/box-cli-maker/v2"
markdown "github.com/MichaelMure/go-term-markdown"
)

func alertTimelineTable(timelines []api.AlertTimeline) (out [][]string) {
Expand Down Expand Up @@ -35,6 +42,42 @@ func renderAlertTimelineTable(timelines []api.AlertTimeline) {
)
}

func renderAlertTimelineBox(b box.Box, timeline api.AlertTimeline) {
if timeline.Message.Value == "" {
return
}
value := []byte(timeline.Message.Value)
if strings.HasPrefix(timeline.Message.Format, api.AlertCommentFormatMarkdown.String()) {
// Replace CR LF \r\n (Windows) with LF \n (Unix)
value = bytes.Replace(value, []byte{13, 10}, []byte{10}, -1)
// Replace CF \r (Mac) with LF \n (Unix)
value = bytes.Replace(value, []byte{13}, []byte{10}, -1)
renderedValue := markdown.Render(string(value), 80, 0)
value = []byte(renderedValue)
}
title := timeline.User.Name
// we need to avoid a box panic when the comment is shorter than the title; specifically
// panic: Title must be shorter than the Top & Bottom Bars
// to do this, we will set horizontal padding to 1 instead of 2...and
// add our own space-based padding as a prefix
customPadding := []byte(strings.Repeat(" ", len(title)) + "\n")
value = append(customPadding, value...)
// we will also make sure that the value trails with a newline...
// such that we have consistent horizontal bottom padding
if !strings.HasSuffix(string(value), "\n") {
value = append(value, []byte("\n")...)
}
b.Println(title, string(value))
}

func renderAlertTimelineBoxes(timelines []api.AlertTimeline) {
timelineBox := box.New(box.Config{Px: 2, Py: 2, Type: "Single", Color: "Cyan", TitlePos: "Top"})

for _, t := range timelines {
renderAlertTimelineBox(timelineBox, t)
}
}

func showAlertTimeline(id int) error {
cli.StartProgress(" Fetching alert timeline...")
timelineResponse, err := cli.LwApi.V2.Alerts.GetTimeline(id)
Expand All @@ -52,6 +95,11 @@ func showAlertTimeline(id int) error {
return nil
}

renderAlertTimelineTable(timelineResponse.Data)
if runtime.GOOS == "windows" {
renderAlertTimelineTable(timelineResponse.Data)
} else {
renderAlertTimelineBoxes(timelineResponse.Data)
}

return nil
}
6 changes: 6 additions & 0 deletions cli/docs/lacework_alert_comment.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ Post a user comment on an alert's timeline .

Comments may be provided inline or via editor.

The following comment formats are allowed:
* Plaintext
* Markdown

Note: only markdown comments will be rendered using markdown.

```
lacework alert comment <alert_id> [flags]
Expand All @@ -23,6 +28,7 @@ lacework alert comment <alert_id> [flags]

```
-c, --comment string a comment to add to the alert
-f, --format string the format of the comment (default Plaintext)
-h, --help help for comment
```

Expand Down
18 changes: 17 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ require (
)

require (
github.com/Delta456/box-cli-maker/v2 v2.3.0
Copy link
Contributor

Choose a reason for hiding this comment

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

Are there packages cross platform compatible? Especially for windows?

Copy link
Collaborator

Choose a reason for hiding this comment

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

So box-cli-maker seems to render fine in windows command line. go-term-markdown is another story...

Copy link
Collaborator

Choose a reason for hiding this comment

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

For windows me might have to revert back to just showing a table (not sure how tables even look with really long comments today). I'm not aware of any go+markdown+windows-cli packages.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can maybe look into this...https://github.com/charmbracelet/glow

github.com/MichaelMure/go-term-markdown v0.1.4
github.com/aws/aws-sdk-go-v2/service/iam v1.18.23
github.com/aws/aws-sdk-go-v2/service/ssm v1.33.1
github.com/aws/smithy-go v1.13.5
Expand All @@ -71,10 +73,12 @@ require (
require (
cloud.google.com/go v0.105.0 // indirect
cloud.google.com/go/longrunning v0.3.0 // indirect
github.com/MichaelMure/go-term-text v0.3.1 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
github.com/alecthomas/chroma v0.7.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.8 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect
Expand All @@ -87,7 +91,11 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.18.0 // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/dlclark/regexp2 v1.1.6 // indirect
github.com/eliukblau/pixterm/pkg/ansimage v0.0.0-20191210081756-9fb6cf8c2f75 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01 // indirect
Expand All @@ -98,27 +106,33 @@ require (
github.com/go-git/go-billy/v5 v5.4.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gomarkdown/markdown v0.0.0-20191123064959-2c17d62f5098 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/gookit/color v1.5.2 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.15.9 // indirect
github.com/kr/pty v1.1.8 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pjbgf/sha1cd v0.2.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect
Expand All @@ -128,9 +142,11 @@ require (
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/image v0.0.0-20191206065243-da761ea9ff43 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sys v0.6.0 // indirect
Expand Down
Loading