Skip to content
Closed
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
51 changes: 39 additions & 12 deletions admin/server/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,16 @@ func (s *Server) UnsubscribeAlert(ctx context.Context, req *adminv1.UnsubscribeA
opts := recreateAlertOptionsFromSpec(spec)

found := false
for idx, email := range opts.Recipients {
for idx, email := range opts.EmailRecipients {
if strings.EqualFold(user.Email, email) {
opts.Recipients = slices.Delete(opts.Recipients, idx, idx+1)
opts.EmailRecipients = slices.Delete(opts.EmailRecipients, idx, idx+1)
found = true
break
}
}
for idx, email := range opts.SlackEmails {
if strings.EqualFold(user.Email, email) {
opts.SlackEmails = slices.Delete(opts.SlackEmails, idx, idx+1)
found = true
break
}
Expand All @@ -289,7 +296,7 @@ func (s *Server) UnsubscribeAlert(ctx context.Context, req *adminv1.UnsubscribeA
return nil, status.Error(codes.InvalidArgument, "user is not subscribed to alert")
}

if len(opts.Recipients) == 0 {
if len(opts.EmailRecipients) == 0 && len(opts.SlackEmails) == 0 && len(opts.SlackChannels) == 0 && len(opts.SlackWebhooks) == 0 {
err = s.admin.DB.UpdateVirtualFileDeleted(ctx, proj.ID, proj.ProdBranch, virtualFilePathForManagedAlert(req.Name))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update virtual file: %s", err.Error())
Expand Down Expand Up @@ -449,9 +456,13 @@ func (s *Server) yamlForManagedAlert(opts *adminv1.AlertOptions, ownerUserID str
res.Query.ArgsJSON = opts.QueryArgsJson
// Hard code the user id to run for (to avoid exposing data through alert creation)
res.Query.For.UserID = ownerUserID
res.Email.Recipients = opts.Recipients
res.Email.Renotify = opts.EmailRenotify
res.Email.RenotifyAfter = opts.EmailRenotifyAfterSeconds
// Notification options
res.Notify.Renotify = opts.Renotify
res.Notify.RenotifyAfter = opts.RenotifyAfterSeconds
res.Notify.Email.Emails = opts.EmailRecipients
res.Notify.Slack.Channels = opts.SlackChannels
res.Notify.Slack.Emails = opts.SlackEmails
res.Notify.Slack.Webhooks = opts.SlackWebhooks
res.Annotations.AdminOwnerUserID = ownerUserID
res.Annotations.AdminManaged = true
res.Annotations.AdminNonce = time.Now().Format(time.RFC3339Nano)
Expand All @@ -477,9 +488,13 @@ func (s *Server) yamlForCommittedAlert(opts *adminv1.AlertOptions) ([]byte, erro
res.Intervals.Duration = opts.IntervalDuration
res.Query.Name = opts.QueryName
res.Query.Args = args
res.Email.Recipients = opts.Recipients
res.Email.Renotify = opts.EmailRenotify
res.Email.RenotifyAfter = opts.EmailRenotifyAfterSeconds
// Notification options
res.Notify.Renotify = opts.Renotify
res.Notify.RenotifyAfter = opts.RenotifyAfterSeconds
res.Notify.Email.Emails = opts.EmailRecipients
res.Notify.Slack.Channels = opts.SlackChannels
res.Notify.Slack.Emails = opts.SlackEmails
res.Notify.Slack.Webhooks = opts.SlackWebhooks
return yaml.Marshal(res)
}

Expand Down Expand Up @@ -527,9 +542,9 @@ func recreateAlertOptionsFromSpec(spec *runtimev1.AlertSpec) *adminv1.AlertOptio
opts.IntervalDuration = spec.IntervalsIsoDuration
opts.QueryName = spec.QueryName
opts.QueryArgsJson = spec.QueryArgsJson
opts.Recipients = spec.EmailRecipients
opts.EmailRenotify = spec.EmailRenotify
opts.EmailRenotifyAfterSeconds = spec.EmailRenotifyAfterSeconds
opts.EmailRecipients = spec.EmailRecipients
opts.Renotify = spec.Renotify
opts.RenotifyAfterSeconds = spec.RenotifyAfterSeconds
return opts
}

Expand All @@ -555,6 +570,18 @@ type alertYAML struct {
Renotify bool `yaml:"renotify"`
RenotifyAfter uint32 `yaml:"renotify_after"`
} `yaml:"email"`
Notify struct {
Renotify bool `yaml:"renotify"`
RenotifyAfter uint32 `yaml:"renotify_after"`
Slack struct {
Channels []string `yaml:"channels"`
Emails []string `yaml:"emails"`
Webhooks []string `yaml:"webhooks"`
}
Email struct {
Emails []string `yaml:"emails"`
}
}
Annotations alertAnnotations `yaml:"annotations,omitempty"`
}

Expand Down
41 changes: 32 additions & 9 deletions admin/server/reports.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,16 @@ func (s *Server) UnsubscribeReport(ctx context.Context, req *adminv1.Unsubscribe
opts := recreateReportOptionsFromSpec(spec)

found := false
for idx, email := range opts.Recipients {
for idx, email := range opts.EmailRecipients {
if strings.EqualFold(user.Email, email) {
opts.Recipients = slices.Delete(opts.Recipients, idx, idx+1)
opts.EmailRecipients = slices.Delete(opts.EmailRecipients, idx, idx+1)
found = true
break
}
}
for idx, email := range opts.SlackEmails {
if strings.EqualFold(user.Email, email) {
opts.SlackEmails = slices.Delete(opts.SlackEmails, idx, idx+1)
found = true
break
}
Expand All @@ -261,7 +268,7 @@ func (s *Server) UnsubscribeReport(ctx context.Context, req *adminv1.Unsubscribe
return nil, status.Error(codes.InvalidArgument, "user is not subscribed to report")
}

if len(opts.Recipients) == 0 {
if len(opts.EmailRecipients) == 0 && len(opts.SlackEmails) == 0 && len(opts.SlackChannels) == 0 && len(opts.SlackWebhooks) == 0 {
err = s.admin.DB.UpdateVirtualFileDeleted(ctx, proj.ID, proj.ProdBranch, virtualFilePathForManagedReport(req.Name))
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update virtual file: %s", err.Error())
Expand Down Expand Up @@ -430,7 +437,10 @@ func (s *Server) yamlForManagedReport(opts *adminv1.ReportOptions, ownerUserID s
res.Query.ArgsJSON = opts.QueryArgsJson
res.Export.Format = opts.ExportFormat.String()
res.Export.Limit = uint(opts.ExportLimit)
res.Email.Recipients = opts.Recipients
res.Notify.Email.Recipients = opts.EmailRecipients
res.Notify.Slack.Channels = opts.SlackChannels
res.Notify.Slack.Emails = opts.SlackEmails
res.Notify.Slack.Webhooks = opts.SlackWebhooks
res.Annotations.AdminOwnerUserID = ownerUserID
res.Annotations.AdminManaged = true
res.Annotations.AdminNonce = time.Now().Format(time.RFC3339Nano)
Expand Down Expand Up @@ -470,7 +480,10 @@ func (s *Server) yamlForCommittedReport(opts *adminv1.ReportOptions) ([]byte, er
res.Query.Args = args
res.Export.Format = exportFormat
res.Export.Limit = uint(opts.ExportLimit)
res.Email.Recipients = opts.Recipients
res.Notify.Email.Recipients = opts.EmailRecipients
res.Notify.Slack.Channels = opts.SlackChannels
res.Notify.Slack.Emails = opts.SlackEmails
res.Notify.Slack.Webhooks = opts.SlackWebhooks
res.Annotations.WebOpenProjectSubpath = opts.OpenProjectSubpath
return yaml.Marshal(res)
}
Expand Down Expand Up @@ -527,7 +540,10 @@ func recreateReportOptionsFromSpec(spec *runtimev1.ReportSpec) *adminv1.ReportOp
opts.ExportLimit = spec.ExportLimit
opts.ExportFormat = spec.ExportFormat
opts.OpenProjectSubpath = annotations.WebOpenProjectSubpath
opts.Recipients = spec.EmailRecipients
opts.EmailRecipients = spec.EmailRecipients
opts.SlackChannels = spec.SlackChannels
opts.SlackEmails = spec.SlackEmails
opts.SlackWebhooks = spec.SlackWebhooks
return opts
}

Expand All @@ -548,9 +564,16 @@ type reportYAML struct {
Format string `yaml:"format"`
Limit uint `yaml:"limit"`
} `yaml:"export"`
Email struct {
Recipients []string `yaml:"recipients"`
} `yaml:"email"`
Notify struct {
Email struct {
Recipients []string `yaml:"recipients"`
} `yaml:"email"`
Slack struct {
Channels []string `yaml:"channels"`
Emails []string `yaml:"emails"`
Webhooks []string `yaml:"webhooks"`
} `yaml:"slack"`
} `yaml:"notify"`
Annotations reportAnnotations `yaml:"annotations,omitempty"`
}

Expand Down
1 change: 1 addition & 0 deletions cli/cmd/runtime/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
_ "github.com/rilldata/rill/runtime/drivers/redshift"
_ "github.com/rilldata/rill/runtime/drivers/s3"
_ "github.com/rilldata/rill/runtime/drivers/salesforce"
_ "github.com/rilldata/rill/runtime/drivers/slack"
_ "github.com/rilldata/rill/runtime/drivers/snowflake"
_ "github.com/rilldata/rill/runtime/drivers/sqlite"
_ "github.com/rilldata/rill/runtime/reconcilers"
Expand Down
90 changes: 90 additions & 0 deletions docs/docs/reference/connectors/slack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
title: Slack
description: Deliver notifications to Slack
sidebar_label: Slack
sidebar_position: 999
---


## Overview

[Slack](https://slack.com/) is a popular messaging platform that allows teams to communicate and collaborate in real-time.
Rill supports sending notifications to Slack channels using the [Slack API](https://api.slack.com/).
This can be useful for sending alerts and reports to your team members.

Although Rill does not support reading data from Slack, Slack support is implemented as a connector
with a corresponding bonus like passing a token as an environment variable.

Slack connector can send notifications to channels, direct messages, and via webhooks.
All of these methods require a Slack application to be added to your workspace.
Rill Data has a Slack application named "Rill Data" (__coming soon__),
but one can also [create](https://api.slack.com/start/quickstart) their own Slack application and use it to send notifications.

### Slack channels

Sending notifications to a channel requires [`chat:write`](https://api.slack.com/scopes/chat:write) scope. The application needs to be added to the channel.

### Direct messages

Sending notifications to a direct message requires [`chat:write`](https://api.slack.com/scopes/chat:write), [`users:read`](https://api.slack.com/scopes/users:read), and [`users:read.email`](https://api.slack.com/scopes/users:read.email) scopes.
The last two scopes are required to find the user's ID by email.

### Webhooks

Sending notifications via webhooks requires [`incoming-webhook`](https://api.slack.com/scopes/incoming-webhook) scope only and no bot token is required.
The application needs to be added to the channel.

## Local credentials

When using Rill Developer on your local machine (i.e. `rill start`), Rill expects a [bot token](https://api.slack.com/authentication/token-types#bot) to be passed as follows:

```bash
start devproject --env connector.slack.bot_token=xoxb-...
```

## Cloud deployment

Once a project that requires Slack notifications has been deployed using `rill deploy`, Rill Cloud will need to be able to have access to the [bot token](https://api.slack.com/authentication/token-types#bot). This can be done as follows:

```bash
rill env configure
```

## Example usage

### Reports
```yaml
...
notify:
email:
recipients:
- recipient@example.com
...
slack:
emails:
- recipient@example.com
...
channels:
- alerts
...
```
### Alerts
```yaml
...
notify:
on_recover: true
renotify: true
renotify_after: 24h
email:
recipients:
- recipient@example.com
...
slack:
emails:
- recipient@example.com
...
channels:
- alerts
...
```

10 changes: 6 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ require (
github.com/rs/cors v1.9.0
github.com/santhosh-tekuri/jsonschema/v5 v5.2.0
github.com/sashabaranov/go-openai v1.19.3
github.com/slack-go/slack v0.12.5
github.com/snowflakedb/gosnowflake v1.7.2
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
Expand All @@ -96,7 +97,7 @@ require (
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/oauth2 v0.14.0
golang.org/x/sync v0.5.0
golang.org/x/sys v0.16.0
golang.org/x/sys v0.18.0
golang.org/x/text v0.14.0
google.golang.org/api v0.151.0
google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0
Expand Down Expand Up @@ -221,6 +222,7 @@ require (
github.com/google/wire v0.5.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
Expand Down Expand Up @@ -303,10 +305,10 @@ require (
github.com/zeebo/xxh3 v1.0.2 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/term v0.16.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/time v0.4.0 // indirect
golang.org/x/tools v0.14.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
Expand Down
22 changes: 14 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,8 @@ github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
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/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA=
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
Expand Down Expand Up @@ -1432,6 +1434,8 @@ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
Expand Down Expand Up @@ -2019,6 +2023,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE=
github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
github.com/slack-go/slack v0.12.5 h1:ddZ6uz6XVaB+3MTDhoW04gG+Vc/M/X1ctC+wssy2cqs=
github.com/slack-go/slack v0.12.5/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
Expand Down Expand Up @@ -2280,8 +2286,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down Expand Up @@ -2423,8 +2429,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down Expand Up @@ -2619,8 +2625,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand All @@ -2633,8 +2639,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
Loading