From e57f78d0dcde84c5ea4ccb3b364cd5cfaccaa842 Mon Sep 17 00:00:00 2001 From: Blake Barnett Date: Tue, 27 Jun 2023 16:15:42 -0700 Subject: [PATCH] Update to slack-go/slack v0.12.2 - Uses events rather than a select loop - Removes the HTTP API usage - Makes approvals work - Still requires a legacy bot token --- bot/slack/approvals.go | 2 +- bot/slack/slack.go | 89 ++++++++++++--------------- bot/slack/slack_test.go | 13 ++-- extension/notification/slack/slack.go | 2 +- go.mod | 11 ++-- go.sum | 21 ++++--- 6 files changed, 67 insertions(+), 71 deletions(-) diff --git a/bot/slack/approvals.go b/bot/slack/approvals.go index 523215f2..f64e2e94 100644 --- a/bot/slack/approvals.go +++ b/bot/slack/approvals.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/keel-hq/keel/types" - "github.com/nlopes/slack" + "github.com/slack-go/slack" ) // Request - request approval diff --git a/bot/slack/slack.go b/bot/slack/slack.go index a1c83be1..08bbec75 100644 --- a/bot/slack/slack.go +++ b/bot/slack/slack.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/nlopes/slack" + "github.com/slack-go/slack" "github.com/keel-hq/keel/bot" "github.com/keel-hq/keel/constants" @@ -19,12 +19,6 @@ import ( log "github.com/sirupsen/logrus" ) -// SlackImplementer - implementes slack HTTP functionality, used to -// send messages with attachments -type SlackImplementer interface { - PostMessage(channelID string, options ...slack.MsgOption) (string, string, error) -} - // Bot - main slack bot container type Bot struct { id string // bot id @@ -37,8 +31,6 @@ type Bot struct { slackClient *slack.Client slackRTM *slack.RTM - slackHTTPClient SlackImplementer - approvalsChannel string // slack approvals channel name ctx context.Context @@ -54,12 +46,14 @@ func (b *Bot) Configure(approvalsRespCh chan *bot.ApprovalResponse, botMessagesC if os.Getenv(constants.EnvSlackToken) != "" { b.name = "keel" - if bootName := os.Getenv(constants.EnvSlackBotName); bootName != "" { - b.name = bootName + if botName := os.Getenv(constants.EnvSlackBotName); botName != "" { + b.name = botName } token := os.Getenv(constants.EnvSlackToken) - client := slack.New(token) + + debug, _ := strconv.ParseBool(os.Getenv("DEBUG")) + client := slack.New(token, slack.OptionDebug(debug)) b.approvalsChannel = "general" if channel := os.Getenv(constants.EnvSlackApprovalsChannel); channel != "" { @@ -67,7 +61,6 @@ func (b *Bot) Configure(approvalsRespCh chan *bot.ApprovalResponse, botMessagesC } b.slackClient = client - b.slackHTTPClient = client b.approvalsRespCh = approvalsRespCh b.botMessagesChannel = botMessagesChannel @@ -114,34 +107,30 @@ func (b *Bot) startInternal() error { b.slackRTM = b.slackClient.NewRTM() go b.slackRTM.ManageConnection() - for { - select { - case <-b.ctx.Done(): - return nil - case msg := <-b.slackRTM.IncomingEvents: - switch ev := msg.Data.(type) { - case *slack.HelloEvent: - // Ignore hello - case *slack.ConnectedEvent: - // nothing to do - case *slack.MessageEvent: - b.handleMessage(ev) - case *slack.PresenceChangeEvent: - // nothing to do - case *slack.RTMError: - log.Errorf("Error: %s", ev.Error()) - case *slack.InvalidAuthEvent: - log.Error("Invalid credentials") - return fmt.Errorf("invalid credentials") + for msg := range b.slackRTM.IncomingEvents { + switch ev := msg.Data.(type) { + case *slack.HelloEvent: + // Ignore hello + case *slack.ConnectedEvent: + // nothing to do + case *slack.MessageEvent: + b.handleMessage(ev) + case *slack.PresenceChangeEvent: + // nothing to do + case *slack.RTMError: + log.Errorf("Error: %s", ev.Error()) + case *slack.InvalidAuthEvent: + log.Error("Invalid credentials") + return fmt.Errorf("invalid credentials") - default: + default: - // Ignore other events.. - // fmt.Printf("Unexpected: %v\n", msg.Data) - } + // Ignore other events.. + // fmt.Printf("Unexpected: %v\n", msg.Data) } } + return fmt.Errorf("No more events?") } func (b *Bot) postMessage(title, message, color string, fields []slack.AttachmentField) error { @@ -149,7 +138,7 @@ func (b *Bot) postMessage(title, message, color string, fields []slack.Attachmen params.Username = b.name params.IconURL = b.getBotUserIconURL() - attachements := []slack.Attachment{ + attachments := []slack.Attachment{ { Fallback: message, Color: color, @@ -162,9 +151,9 @@ func (b *Bot) postMessage(title, message, color string, fields []slack.Attachmen var mgsOpts []slack.MsgOption mgsOpts = append(mgsOpts, slack.MsgOptionPostMessageParameters(params)) - mgsOpts = append(mgsOpts, slack.MsgOptionAttachments(attachements...)) + mgsOpts = append(mgsOpts, slack.MsgOptionAttachments(attachments...)) - _, _, err := b.slackHTTPClient.PostMessage(b.approvalsChannel, mgsOpts...) + _, _, err := b.slackClient.PostMessage(b.approvalsChannel, mgsOpts...) if err != nil { log.WithFields(log.Fields{ "error": err, @@ -177,26 +166,26 @@ func (b *Bot) postMessage(title, message, color string, fields []slack.Attachmen // checking if message was received in approvals channel func (b *Bot) isApprovalsChannel(event *slack.MessageEvent) bool { - channel, err := b.slackClient.GetChannelInfo(event.Channel) + channel, err := b.slackClient.GetConversationInfo(&slack.GetConversationInfoInput{ChannelID: event.Channel}) if err != nil { - // looking for private channel - conv, err := b.slackRTM.GetConversationInfo(event.Channel, true) - if err != nil { - log.Errorf("couldn't find amongst private conversations: %s", err) - } else if conv.Name == b.approvalsChannel { - return true + if channel != (&slack.Channel{}) && channel != nil { + if channel.GroupConversation.Name == b.approvalsChannel && + channel.GroupConversation.Conversation.IsPrivate { + return true + } else { + log.Errorf("couldn't find amongst private conversations: %s", err) + } } - log.WithError(err).Errorf("channel with ID %s could not be retrieved", event.Channel) return false } - log.Debugf("checking if approvals channel: %s==%s", channel.Name, b.approvalsChannel) - if channel.Name == b.approvalsChannel { + log.Debugf("checking if approvals channel: %s==%s", channel.GroupConversation.Name, b.approvalsChannel) + if channel.GroupConversation.Name == b.approvalsChannel { return true } - log.Debugf("message was received not on approvals channel (%s)", channel.Name) + log.Debugf("message was received not on approvals channel (%s)", channel.GroupConversation.Name) return false } diff --git a/bot/slack/slack_test.go b/bot/slack/slack_test.go index cd54ca82..4406c1d1 100644 --- a/bot/slack/slack_test.go +++ b/bot/slack/slack_test.go @@ -8,7 +8,7 @@ import ( "path/filepath" "time" - "github.com/nlopes/slack" + "github.com/slack-go/slack" "github.com/keel-hq/keel/extension/approval" "github.com/keel-hq/keel/pkg/store/sql" @@ -31,7 +31,7 @@ var approvalsRespCh chan *b.ApprovalResponse func New(name, token, channel string, k8sImplementer kubernetes.Implementer, - approvalsManager approvals.Manager, fi SlackImplementer) *Bot { + approvalsManager approvals.Manager) *Bot { approvalsRespCh = make(chan *b.ApprovalResponse) botMessagesChannel = make(chan *b.BotMessage) @@ -39,7 +39,6 @@ func New(name, token, channel string, slack := &Bot{} b.RegisterBot(name, slack) b.Run(k8sImplementer, approvalsManager) - slack.slackHTTPClient = fi return slack } @@ -125,7 +124,7 @@ func TestBotRequest(t *testing.T) { Store: store, }) - New("keel", token, "approvals", f8s, am, fi) + New("keel", token, "approvals", f8s, am) defer b.Stop() time.Sleep(1 * time.Second) @@ -172,7 +171,7 @@ func TestProcessApprovedResponse(t *testing.T) { Store: store, }) - New("keel", token, "approvals", f8s, am, fi) + New("keel", token, "approvals", f8s, am) defer b.Stop() time.Sleep(1 * time.Second) @@ -239,7 +238,7 @@ func TestProcessApprovalReply(t *testing.T) { t.Fatalf("unexpected error while creating : %s", err) } - bot := New("keel", token, "approvals", f8s, am, fi) + bot := New("keel", token, "approvals", f8s, am) defer b.Stop() time.Sleep(1 * time.Second) @@ -309,7 +308,7 @@ func TestProcessRejectedReply(t *testing.T) { t.Fatalf("unexpected error while creating : %s", err) } - bot := New("keel", "random", "approvals", f8s, am, fi) + bot := New("keel", "random", "approvals", f8s, am) defer b.Stop() collector := approval.New() diff --git a/extension/notification/slack/slack.go b/extension/notification/slack/slack.go index e51e9a80..21970e39 100644 --- a/extension/notification/slack/slack.go +++ b/extension/notification/slack/slack.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/nlopes/slack" + "github.com/slack-go/slack" "github.com/keel-hq/keel/constants" "github.com/keel-hq/keel/extension/notification" diff --git a/go.mod b/go.mod index dbf6a0b1..acc6d3ea 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/jinzhu/gorm v1.9.16 - github.com/nlopes/slack v0.6.0 + github.com/jmoiron/sqlx v1.3.5 // indirect github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0-rc2 github.com/prometheus/client_golang v1.14.0 @@ -48,6 +48,7 @@ require ( github.com/rusenask/docker-registry-client v0.0.0-20200210164146-049272422097 github.com/ryanuber/go-glob v1.0.0 github.com/sirupsen/logrus v1.9.0 + github.com/slack-go/slack v0.12.2 github.com/stretchr/testify v1.8.2 github.com/tbruyelle/hipchat-go v0.0.0-20170717082847-35aebc99209a github.com/urfave/negroni v1.0.0 @@ -122,7 +123,6 @@ require ( github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.11.13 // indirect @@ -132,8 +132,8 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-sqlite3 v1.14.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -153,6 +153,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/rubenv/sql-migrate v1.3.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect @@ -168,7 +169,7 @@ require ( golang.org/x/crypto v0.5.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.7.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/go.sum b/go.sum index 01852dd0..65a41ed9 100644 --- a/go.sum +++ b/go.sum @@ -227,6 +227,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= @@ -336,7 +338,6 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -466,11 +467,13 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= @@ -522,8 +525,6 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/nelsam/hel/v2 v2.3.3/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA= -github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= @@ -585,6 +586,9 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -616,6 +620,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ= +github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -908,8 +914,9 @@ golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=