TP-0000 | Initialize houston repo (#1)

* TP-0000 | intialize houston repo

* TP-0000 | intialize houston repo
This commit is contained in:
Shubham Kirve
2023-03-29 00:01:17 +05:30
committed by GitHub Enterprise
parent db0f4f09f8
commit b974cb6bf3
89 changed files with 5974 additions and 1 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

0
Makefile Normal file
View File

View File

@@ -1 +1 @@
# houston
# blazeless

View File

@@ -0,0 +1,44 @@
package handler
import (
"houston/model/request"
"houston/pkg/postgres/query"
"net/http"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
)
type severityHandler struct {
gin *gin.Engine
logger *zap.Logger
db *gorm.DB
}
func NewSeverityHandler(gin *gin.Engine, logger *zap.Logger, db *gorm.DB) *severityHandler {
return &severityHandler{
gin: gin,
logger: logger,
db: db,
}
}
func (sh *severityHandler) AddSeverity(c *gin.Context) {
var addSeverityRequest request.AddSeverityRequest
if err := c.ShouldBindJSON(&addSeverityRequest); err != nil {
c.JSON(http.StatusInternalServerError, err)
return
}
sh.logger.Info("add severity request received", zap.String("severity_name", addSeverityRequest.Name))
err := query.AddSeverity(sh.db, sh.logger, addSeverityRequest)
if err != nil {
c.JSON(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, nil)
}

View File

@@ -0,0 +1,95 @@
package handler
import (
"houston/pkg/slack/houston"
"github.com/slack-go/slack"
"github.com/slack-go/slack/slackevents"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type slackHandler struct {
logger *zap.Logger
socketModeClient *socketmode.Client
slackClient *slack.Client
db *gorm.DB
houstonCommandHandler *houston.HoustonCommandHandler
}
func NewSlackHandler(logger *zap.Logger, socketModeClient *socketmode.Client,
db *gorm.DB,
slackClient *slack.Client) *slackHandler {
return &slackHandler{
logger: logger,
socketModeClient: socketModeClient,
db: db,
slackClient: slackClient,
houstonCommandHandler: houston.NewHoustonCommandHandler(socketModeClient, logger, db),
}
}
func (sh *slackHandler) HoustonConnect() {
go func() {
for evt := range sh.socketModeClient.Events {
switch evt.Type {
case socketmode.EventTypeConnecting:
sh.logger.Info("houston connecting to slack with socket mode ...")
case socketmode.EventTypeConnectionError:
sh.logger.Error("Blazelss connection failed. retrying later ...")
case socketmode.EventTypeConnected:
sh.logger.Info("houston connected to slack with socket mode.")
case socketmode.EventTypeEventsAPI:
ev, _ := evt.Data.(slackevents.EventsAPIEvent)
sh.logger.Info("event api", zap.Any("ev", ev))
switch ev.Type {
case slackevents.CallbackEvent:
iev := ev.InnerEvent
switch ev := iev.Data.(type) {
case *slackevents.AppMentionEvent:
case *slackevents.MemberJoinedChannelEvent:
sh.houstonCommandHandler.ProcessMemberJoinEvent(ev, evt.Request)
}
case slackevents.URLVerification:
case string(slackevents.MemberJoinedChannel):
}
case socketmode.EventTypeInteractive:
callback, _ := evt.Data.(slack.InteractionCallback)
switch callback.Type {
case slack.InteractionTypeBlockActions:
sh.logger.Info("received interaction type block action",
zap.String("action_id", callback.ActionID), zap.String("block_id", callback.BlockID))
sh.houstonCommandHandler.ProcessButtonHandler(callback, evt.Request)
case slack.InteractionTypeShortcut:
case slack.InteractionTypeViewSubmission:
sh.logger.Info("received interaction type view submission",
zap.String("action_id", callback.ActionID), zap.String("block_id", callback.BlockID))
sh.logger.Info("payload data", zap.Any("callback", callback), zap.Any("request", evt.Request))
sh.houstonCommandHandler.ProcessModalCallbackEvent(callback, evt.Request)
case slack.InteractionTypeDialogSubmission:
default:
}
// command.ProcessStartIncidentCommand(client, evt.Request, callback.TriggerID)
case socketmode.EventTypeSlashCommand:
cmd, _ := evt.Data.(slack.SlashCommand)
sh.logger.Info("houston processing slash command",
zap.String("command", cmd.Text), zap.String("channel_name", cmd.ChannelName), zap.String("user_name", cmd.UserName))
sh.houstonCommandHandler.ProcessSlashCommand(evt)
// command.ProcessMainCommand(client, evt.Request)
// client.Ack(*evt.Request, payload)
default:
sh.logger.Error("houston unexpected event type received", zap.Any("event_type", evt.Type))
}
}
}()
go sh.socketModeClient.Run()
}

View File

@@ -0,0 +1,42 @@
package handler
import (
"houston/model/request"
"houston/pkg/postgres/query"
"net/http"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
)
type teamHandler struct {
gin *gin.Engine
logger *zap.Logger
db *gorm.DB
}
func NewTeamHandler(gin *gin.Engine, logger *zap.Logger, db *gorm.DB) *teamHandler {
return &teamHandler{
gin: gin,
logger: logger,
db: db,
}
}
func (th *teamHandler) AddTeam(c *gin.Context) {
var addTeamRequest request.AddTeamRequest
if err := c.ShouldBindJSON(&addTeamRequest); err != nil {
c.JSON(http.StatusInternalServerError, err)
return
}
th.logger.Info("add team request received", zap.String("team_name", addTeamRequest.Name))
err := query.AddTeam(th.db, addTeamRequest)
if err != nil {
c.JSON(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, nil)
}

54
cmd/app/server.go Normal file
View File

@@ -0,0 +1,54 @@
package app
import (
"fmt"
"houston/cmd/app/handler"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"go.uber.org/zap"
"gorm.io/gorm"
)
type Server struct {
gin *gin.Engine
logger *zap.Logger
db *gorm.DB
}
func NewServer(gin *gin.Engine, logger *zap.Logger, db *gorm.DB) *Server {
return &Server{
gin: gin,
logger: logger,
db: db,
}
}
func (s *Server) Handler() {
s.teamHandler()
s.severityHandler()
//this should always be at the end since it opens websocket to connect to slack
s.houstonHandler()
}
func (s *Server) houstonHandler() {
houstonClient := NewHoustonClient(s.logger)
houstonHandler := handler.NewSlackHandler(s.logger, houstonClient.socketmodeClient, s.db, houstonClient.slackClient)
houstonHandler.HoustonConnect()
}
func (s *Server) teamHandler() {
teamHandler := handler.NewTeamHandler(s.gin, s.logger, s.db)
s.gin.POST("/team/add", teamHandler.AddTeam)
}
func (s *Server) severityHandler() {
severityHandler := handler.NewSeverityHandler(s.gin, s.logger, s.db)
s.gin.POST("/severity/add", severityHandler.AddSeverity)
}
func (s *Server) Start() {
s.logger.Info("starting houston server", zap.String("port", viper.GetString("port")))
s.gin.Run(fmt.Sprintf(":%v", "8080"))
}

63
cmd/app/slack.go Normal file
View File

@@ -0,0 +1,63 @@
package app
import (
"os"
"strings"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
)
type HoustonSlack struct {
socketmodeClient *socketmode.Client
slackClient *slack.Client
logger *zap.Logger
}
func NewHoustonClient(logger *zap.Logger) *HoustonSlack {
socketmodeClient, slackClient := slackConnect(logger)
return &HoustonSlack{
socketmodeClient: socketmodeClient,
slackClient: slackClient,
logger: logger,
}
}
func slackConnect(logger *zap.Logger) (*socketmode.Client, *slack.Client) {
appToken := os.Getenv("HOUSTON_SLACK_APP_TOKEN")
if appToken == "" {
logger.Error("HOUSTON_SLACK_APP_TOKEN must be set.")
os.Exit(1)
}
if !strings.HasPrefix(appToken, "xapp-") {
logger.Error("HOUSTON_SLACK_APP_TOKEN must have the prefix \"xapp-\".")
os.Exit(1)
}
botToken := os.Getenv("HOUSTON_SLACK_BOT_TOKEN")
if botToken == "" {
logger.Error("HOUSTON_SLACK_BOT_TOKEN must be set.")
os.Exit(1)
}
if !strings.HasPrefix(botToken, "xoxb-") {
logger.Error("HOUSTON_SLACK_BOT_TOKEN must have the prefix \"xoxb-\".")
os.Exit(1)
}
api := slack.New(
botToken,
slack.OptionDebug(false),
slack.OptionAppLevelToken(appToken),
)
client := socketmode.New(
api,
socketmode.OptionDebug(false),
)
return client, api
}

BIN
cmd/cmd Executable file

Binary file not shown.

46
cmd/main.go Normal file
View File

@@ -0,0 +1,46 @@
package main
import (
"houston/cmd/app"
"houston/config"
"houston/pkg/postgres"
"os"
"time"
ginzap "github.com/gin-contrib/zap"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"github.com/spf13/cobra"
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
config.LoadHoustonConfig(logger)
godotenv.Load()
command := &cobra.Command{
Use: "houston",
Short: "houston is replacement for blameless and incident management slack bot at Navi",
Long: "houston is replacement for blameless and incident management slack bot at Navi",
RunE: func(cmd *cobra.Command, args []string) error {
r := gin.New()
r.Use(ginzap.Ginzap(logger, time.RFC3339, true))
r.Use(ginzap.RecoveryWithZap(logger, true))
db := postgres.PQConnection(logger)
sv := app.NewServer(r, logger, db)
sv.Handler()
sv.Start()
return nil
},
}
if err := command.Execute(); err != nil {
logger.Error("alfred core command execution failed", zap.Error(err))
os.Exit(1)
}
}

View File

@@ -0,0 +1,13 @@
BLAZELESS_SLACK_APP_TOKEN=xapp-1-A0444DP8XU5-4136817231606-8e441610b1696fd4f5fa81e9507a3d6f625504ac07588c63543972ff3b147321
BLAZELESS_SLACK_BOT_TOKEN=
BOOTSTRAP_SERVERS=localhost:9092
GROUP_ID=blaze-group
AUTO_OFFSET_RESET=newest
ENABLE=true
ACKS=0
SECURITY_PROTOCOL=
SASL_MECHANISMS=
SASL_USERNAME=
SASL_PASSWORD=
ENVIRONMENT=local
SHOW_INCIDENTS_LIMIT=10

View File

@@ -0,0 +1,12 @@
HOUSTON_SLACK_APP_TOKEN=xapp-1-A0444DP8XU5-4136817231606-8e441610b1696fd4f5fa81e9507a3d6f625504ac07588c63543972ff3b147321
HOUSTON_SLACK_BOT_TOKEN=j22
BOOTSTRAP_SERVERS=localhost:9092
GROUP_ID=blaze-group
AUTO_OFFSET_RESET=newest
ENABLE=true
ACKS=0
SECURITY_PROTOCOL=
SASL_MECHANISMS=
SASL_USERNAME=
SASL_PASSWORD=
ENVIRONMENT=local

24
config/config.go Normal file
View File

@@ -0,0 +1,24 @@
package config
import (
"os"
"strings"
"github.com/spf13/viper"
"go.uber.org/zap"
)
func LoadHoustonConfig(logger *zap.Logger) {
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetConfigName("blazeless-config")
viper.SetConfigType("properties")
viper.SetConfigFile("./config/application-blazeless.properties")
err := viper.ReadInConfig()
if err != nil {
logger.Error("Error while loading blazeless configuration", zap.Error(err))
os.Exit(1)
}
}

16
entity/alerts.go Normal file
View File

@@ -0,0 +1,16 @@
package entity
import "gorm.io/gorm"
type AlertsEntity struct {
gorm.Model
Name string
Team string
Service string
Status bool
Version int
}
func (AlertsEntity) TableName() string {
return "alerts"
}

16
entity/audit.go Normal file
View File

@@ -0,0 +1,16 @@
package entity
import "gorm.io/gorm"
type IncidentAuditEntity struct {
gorm.Model
IncidentId uint
Event string
UserName string
UserId string
Version int
}
func (IncidentAuditEntity) TableName() string {
return "incident_audit"
}

View File

@@ -0,0 +1,13 @@
package entity
import "gorm.io/gorm"
type ContributingFactorEntity struct {
gorm.Model
Label string
Version int
}
func (ContributingFactorEntity) TableName() string {
return "contributing_factor"
}

13
entity/customer_tags.go Normal file
View File

@@ -0,0 +1,13 @@
package entity
import "gorm.io/gorm"
type CustomerTagsEntity struct {
gorm.Model
Label string
Version int
}
func (CustomerTagsEntity) TableName() string {
return "customer_tags"
}

54
entity/incident.go Normal file
View File

@@ -0,0 +1,54 @@
package entity
import (
"time"
"gorm.io/gorm"
)
type IncidentStatus string
const (
Investigating IncidentStatus = "INVESTIGATING"
Identified = "IDENTIFIED"
Monitoring = "MONITORING"
Resolved = "RESOLVED"
Duplicated = "DUPLICATED"
)
type IncidentEntity struct {
gorm.Model
Title string
Description string
Status IncidentStatus
SeverityId int
IncidentName string
SlackChannel string
DetectionTime time.Time
CustomerImpactStartTime time.Time
CustomerImpactEndTime time.Time
TeamsId int
JiraId string
ConfluenceId string
SeverityTat time.Time
RemindMeAt time.Time
EnableReminder bool
CreatedBy string
UpdatedBy string
Version int
}
func (IncidentEntity) TableName() string {
return "incidents"
}
type IncidentStatusEntity struct {
gorm.Model
Name string
Description string
Version int
}
func (IncidentStatusEntity) TableName() string {
return "incident_status"
}

24
entity/incident_roles.go Normal file
View File

@@ -0,0 +1,24 @@
package entity
import "gorm.io/gorm"
type IncidentRole string
const (
RETROSPECTIVE IncidentRole = "RETROSPECTIVE"
RESPONDER IncidentRole = "RESPONDER"
SERVICE_OWNER IncidentRole = "SERVICE_OWNER"
)
type IncidentRoles struct {
gorm.Model
IncidentId int
Role IncidentRole
AssignedToUserSlackId string
AssignedByUserSlackId string
Version int
}
func (IncidentRoles) TableName() string {
return "incident_roles"
}

View File

@@ -0,0 +1,12 @@
package entity
type IncidentSeverityTeamJoinEntity struct {
IncidentId int
Title string
Status IncidentStatus
SeverityId int
SeverityName string
SlackChannel string
TeamsId int
TeamsName string
}

View File

@@ -0,0 +1,14 @@
package entity
import "gorm.io/gorm"
type IncidentTagsContributingFactorMapping struct {
gorm.Model
IncidentId int
ContributingFactorId int
Version int
}
func (IncidentTagsContributingFactorMapping) TableName() string {
return "incidents_tags_contributing_factor_mapping"
}

View File

@@ -0,0 +1,14 @@
package entity
import "gorm.io/gorm"
type IncidentTagsCustomerMapping struct {
gorm.Model
IncidentId int
CustomerTagsId int
Version int
}
func (IncidentTagsCustomerMapping) TableName() string {
return "incidents_tags_customer_mapping"
}

View File

@@ -0,0 +1,14 @@
package entity
import "gorm.io/gorm"
type IncidentsTagsDataPlatformMapping struct {
gorm.Model
IncidentId int
DataPlatformTag string
Version int
}
func (IncidentsTagsDataPlatformMapping) TableName() string {
return "incidents_tags_data_platform_mapping"
}

View File

@@ -0,0 +1,14 @@
package entity
import "gorm.io/gorm"
type IncidentsTagsMapping struct {
gorm.Model
IncidentId int
TagId int
Version int
}
func (IncidentsTagsMapping) TableName() string {
return "incidents_tags_mapping"
}

21
entity/messages.go Normal file
View File

@@ -0,0 +1,21 @@
package entity
import (
"time"
"gorm.io/gorm"
)
type MessageEntity struct {
gorm.Model
SlackChannel string `gorm:"column:slack_channel"`
IncidentName string `gorm:"column:incident_name"`
MessageTimeStamp string `gorm:"column:message_timestamp"`
CreatedAt time.Time
UpdatedAt time.Time
Version int
}
func (MessageEntity) TableName() string {
return "message"
}

15
entity/severity.go Normal file
View File

@@ -0,0 +1,15 @@
package entity
import "gorm.io/gorm"
type SeverityEntity struct {
gorm.Model
Name string `gorm:"column:name"`
Description string `gorm:"column:description"`
Version int
Sla int
}
func (SeverityEntity) TableName() string {
return "severity"
}

13
entity/tags.go Normal file
View File

@@ -0,0 +1,13 @@
package entity
import "gorm.io/gorm"
type TagsEntity struct {
gorm.Model
Label string `gorm:"column:label"`
Version int
}
func (TagsEntity) TableName() string {
return "tags"
}

View File

@@ -0,0 +1,14 @@
package entity
import "gorm.io/gorm"
type TeamTagsMapping struct {
gorm.Model
TeamsId int
TagId int
Version int
}
func (TeamTagsMapping) TableName() string {
return "teams_tags_mapping"
}

20
entity/teams.go Normal file
View File

@@ -0,0 +1,20 @@
package entity
import (
"gorm.io/gorm"
)
type TeamEntity struct {
gorm.Model
Name string `gorm:"column:name"`
OncallHandle string `gorm:"column:oncall_handle"`
SecondaryOncallHandle string `gorm:"column:secondary_oncall_handle"`
ManagerHandle string `gorm:"column:manager_handle"`
SecondaryManagerHandle string `gorm:"column:secondary_manager_handle"`
Active bool `gorm:"column:active"`
Version int `gorm:"column:version"`
}
func (TeamEntity) TableName() string {
return "teams"
}

View File

@@ -0,0 +1,26 @@
package entity
import (
"gorm.io/gorm"
)
type EntityType string
const (
TEAM EntityType = "TEAM"
SEVERITY EntityType = "SEVERITY"
)
type TeamsSeverityUsersMapping struct {
gorm.Model
EntityType EntityType
EntityId int
UsersId int
DefaultAddInIncidents bool
teamRole string
Version int
}
func (TeamsSeverityUsersMapping) TableName() string {
return "teams_severity_user_mapping"
}

11
entity/users.go Normal file
View File

@@ -0,0 +1,11 @@
package entity
type UsersEntity struct {
Name string
SlackUserId string
Active bool
}
func (UsersEntity) TableName() string {
return "users"
}

92
go.mod Normal file
View File

@@ -0,0 +1,92 @@
module houston
go 1.20
require (
github.com/Shopify/sarama v1.38.1
github.com/gin-contrib/zap v0.1.0
github.com/gin-gonic/gin v1.9.0
github.com/google/uuid v1.3.0
github.com/joho/godotenv v1.5.1
github.com/slack-go/slack v0.12.1
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0
go.uber.org/zap v1.24.0
google.golang.org/api v0.107.0
gorm.io/driver/postgres v1.4.8
gorm.io/gorm v1.24.5
)
require (
cloud.google.com/go/compute v1.14.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/bytedance/sonic v1.8.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/eapache/go-resiliency v1.3.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.11.2 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.3.0 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/spf13/afero v1.9.4 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.10 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.13.0 // indirect
go.opentelemetry.io/otel/trace v1.13.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.2.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
google.golang.org/grpc v1.52.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

709
go.sum Normal file
View File

@@ -0,0 +1,709 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0=
cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Shopify/sarama v1.38.1 h1:lqqPUPQZ7zPqYlWpTh+LQ9bhYNu2xJL6k1SJN4WVe2A=
github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSdZ4X2o5g=
github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.8.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY=
github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eapache/go-resiliency v1.3.0 h1:RRL0nge+cWGlxXbUzJ7yMcq6w2XBEr19dCN6HECGaT0=
github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 h1:8yY/I9ndfrgrXUbOGObLHKBR4Fl3nZXwM2c7OYTT8hM=
github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/zap v0.1.0 h1:RMSFFJo34XZogV62OgOzvrlaMNmXrNxmJ3bFmMwl6Cc=
github.com/gin-contrib/zap v0.1.0/go.mod h1:hvnZaPs478H1PGvRP8w89ZZbyJUiyip4ddiI/53WG3o=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
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/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg=
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA=
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/slack-go/slack v0.12.1 h1:X97b9g2hnITDtNsNe5GkGx6O2/Sz/uC20ejRZN6QxOw=
github.com/slack-go/slack v0.12.1/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/spf13/afero v1.9.4 h1:Sd43wM1IWz/s1aVXdOBkjJvuP8UdyqioeE4AmM0QsBs=
github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ=
github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ=
go.opentelemetry.io/otel v1.13.0 h1:1ZAKnNQKwBBxFtww/GwxNUyTf0AxkZzrukO8MeXqe4Y=
go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg=
go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM=
go.opentelemetry.io/otel/trace v1.13.0 h1:CBgRZ6ntv+Amuj1jDsMhZtlAPT6gbyIRdaIzFhfBSdY=
go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY=
golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
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=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
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=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU=
google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY=
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk=
google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.4.8 h1:NDWizaclb7Q2aupT0jkwK8jx1HVCNzt+PQ8v/VnxviA=
gorm.io/driver/postgres v1.4.8/go.mod h1:O9MruWGNLUBUWVYfWuBClpf3HeGjOoybY0SNmCs3wsw=
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

BIN
golang-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
model/.DS_Store vendored Normal file

Binary file not shown.

29
model/create_incident.go Normal file
View File

@@ -0,0 +1,29 @@
package model
import (
"houston/entity"
"time"
)
type CreateIncident struct {
IncidentType string `json:"incident_type,omitempty"`
Pagerduty string `json:"pagerduty,omitempty"`
IncidentTitle string `json:"incident_title,omitempty"`
IncidentDescription string `json:"incident_description,omitempty"`
RequestOriginatedSlackChannel string
IncidentSeverity string `json:"incident_severity,omitempty"`
Status entity.IncidentStatus
IncidentName string
SlackChannel string
DetectionTime time.Time
CustomerImpactStartTime time.Time
CustomerImpactEndTime time.Time
TeamsId int
JiraId string
ConfluenceId string
RemindMeAt time.Time
EnableReminder bool
CreatedBy string
UpdatedBy string
Version int
}

View File

@@ -0,0 +1,7 @@
package request
type CreateMessage struct {
SlackChannel string `gorm:"column:slack_channel"`
IncidentName string `gorm:"column:incident_name"`
MessageTimeStamp string `gorm:"column:message_timestamp"`
}

View File

@@ -0,0 +1,10 @@
package request
import "houston/entity"
type AddIncidentRoleRequest struct {
UserId string `json:"users_select,omitempty"`
Role entity.IncidentRole `json:"role_type,omitempty"`
IncidentId int
CreatedById string
}

View File

@@ -0,0 +1,6 @@
package request
type AddIncidentStatusRequest struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
}

17
model/request/severity.go Normal file
View File

@@ -0,0 +1,17 @@
package request
type AddSeverityRequest struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
}
type AddSeverityUserMappingRequest struct {
SeverityId uint16 `json:"severity_id,omitempty"`
Users []AddSeverityUserData `json:"users,omitempty"`
}
type AddSeverityUserData struct {
SlackUserId string `json:"slack_user_id,omitempty"`
Primary bool `json:"primary,omitempty"`
Secondary bool `json:"secondary,omitempty"`
}

6
model/request/team.go Normal file
View File

@@ -0,0 +1,6 @@
package request
type AddTeamRequest struct {
Name string `json:"name,omitempty"`
OncallHandle string `json:"oncall_handle,omitempty"`
}

BIN
pkg/.DS_Store vendored Normal file

Binary file not shown.

BIN
pkg/kafka/.DS_Store vendored Normal file

Binary file not shown.

80
pkg/kafka/config.go Normal file
View File

@@ -0,0 +1,80 @@
package kafka
import (
"crypto/tls"
"strings"
"time"
"github.com/Shopify/sarama"
"github.com/spf13/viper"
)
func SaramaSyncProducer() (sarama.AsyncProducer, error) {
return sarama.NewAsyncProducer(strings.Split(viper.GetString("kafka.brokers"), ","), kafkaProducerConfig())
}
func SaramaKafkaConsumer(groupID string) (sarama.ConsumerGroup, error) {
consumerGroup, err := sarama.NewConsumerGroup(strings.Split(viper.GetString("kafka.brokers"), ","), groupID, kafkaConsumerConfig(groupID))
if err != nil {
return nil, err
}
return consumerGroup, nil
}
func kafkaProducerConfig() *sarama.Config {
config := kafkaConfig()
config.Producer.Retry.Max = 3
config.Producer.RequiredAcks = sarama.WaitForLocal
config.Producer.Compression = sarama.CompressionSnappy
config.Producer.Return.Successes = true
config.Producer.Flush.Bytes = 100
config.Producer.Flush.Frequency = 100
config.Producer.Flush.Messages = 100
config.Producer.Flush.MaxMessages = 100
config.Metadata.RefreshFrequency = 1 * time.Minute
return config
}
func kafkaConsumerConfig(groupId string) *sarama.Config {
config := kafkaConfig()
config.Version = sarama.V3_3_1_0
config.Consumer.Offsets.Initial = sarama.OffsetNewest
config.Consumer.Return.Errors = true
config.ClientID = groupId
config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRoundRobin
return config
}
func kafkaConfig() *sarama.Config {
config := sarama.NewConfig()
env := viper.GetString("env")
if env == "local" {
return config
}
if env == "prod" {
config.Net.SASL.Mechanism = sarama.SASLTypePlaintext
} else {
config.Net.SASL.Mechanism = sarama.SASLTypeSCRAMSHA512
config.Net.SASL.SCRAMClientGeneratorFunc = func() sarama.SCRAMClient {
return &XDGSCRAMClient{HashGeneratorFcn: SHA512}
}
}
config.Net.SASL.User = viper.GetString("kafka.username")
config.Net.SASL.Password = viper.GetString("kafka.password")
config.Net.SASL.Enable = viper.GetBool("kafka.sasl.enabled")
config.Net.TLS.Enable = viper.GetBool("kafka.tls.enabled")
config.Net.TLS.Config = &tls.Config{
InsecureSkipVerify: viper.GetBool("kafka.tls.insecureSkipVerify"),
}
return config
}

View File

@@ -0,0 +1,63 @@
package kafka
import (
"blaze/pkg/kafka"
"os"
"github.com/Shopify/sarama"
"go.uber.org/zap"
)
type KProducer struct {
sarama.AsyncProducer
logger *zap.Logger
}
func NewKProducer(logger *zap.Logger) *KProducer {
producer, err := kafka.SaramaSyncProducer()
if err != nil {
logger.Error("sarama kafka producer failed", zap.Error(err))
os.Exit(1)
}
kProducer := &KProducer{
AsyncProducer: producer,
logger: logger,
}
kProducer.Errors()
kProducer.Successes()
return kProducer
}
// Errors keep the track of failed messages.
func (kp *KProducer) Errors() {
go func() {
for err := range kp.AsyncProducer.Errors() {
keyBytes, errEncode := err.Msg.Key.Encode()
if errEncode != nil {
kp.logger.Error("key encoding failed for failed message", zap.String("topic", err.Msg.Topic))
}
// metrics.KafkaEventIngestionEventFailureCounter.WithLabelValues(err.Msg.Topic).Inc()
kp.logger.Error("failed to emit event to kafka", zap.String("topic", err.Msg.Topic),
zap.String("key", string(keyBytes)), zap.Any("value", string(keyBytes)), zap.String("error", err.Error()))
}
}()
}
// Successes is to check if message successfully delivered to kafka
func (kp *KProducer) Successes() {
go func() {
for msg := range kp.AsyncProducer.Successes() {
keyBytes, errEncode := msg.Key.Encode()
if errEncode != nil {
kp.logger.Error("key encoding failed for failed message", zap.String("topic", msg.Topic))
}
// metrics.KafkaEventIngestionEventSuccessCounter.WithLabelValues(msg.Topic).Inc()
kp.logger.Info("successfully ingested event to kafka", zap.String("topic", msg.Topic), zap.String("key", string(keyBytes)))
}
}()
}

21
pkg/postgres/config.go Normal file
View File

@@ -0,0 +1,21 @@
package postgres
import (
"os"
"go.uber.org/zap"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func PQConnection(logger *zap.Logger) *gorm.DB {
dsn := ""
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
logger.Error("database connection failed", zap.Error(err))
os.Exit(1)
}
logger.Info("database connection successful")
return db
}

View File

@@ -0,0 +1,71 @@
package query
import (
"houston/entity"
"time"
"gorm.io/gorm"
)
func FindContributingFactorsByIncidentId(db *gorm.DB, incidentId int) (*entity.ContributingFactorEntity, error) {
var cf entity.ContributingFactorEntity
result := db.Where("incidents_tags_contributing_factor_mapping.incident_id = ? AND contributing_factor.deleted_at is NULL AND incidents_tags_contributing_factor_mapping.deleted_at is NULL", incidentId).Joins("JOIN incidents_tags_contributing_factor_mapping on incidents_tags_contributing_factor_mapping.contributing_factor_id = contributing_factor.id").Find(&cf)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
return nil, nil
}
return &cf, nil
}
func FetchAllContributingFactors(db *gorm.DB) ([]entity.ContributingFactorEntity, error) {
var cf []entity.ContributingFactorEntity
result := db.Find(&cf).Where("deleted_at is NULL")
if result.Error != nil {
return nil, result.Error
}
return cf, nil
}
func UpsertContributingFactorIdToIncidentId(db *gorm.DB, cfId int, incidentId int) error {
incidentTagsContributingFactorMapping := &entity.IncidentTagsContributingFactorMapping{
ContributingFactorId: cfId,
IncidentId: incidentId,
}
var cf entity.IncidentTagsContributingFactorMapping
result := db.Find(&cf).Where("incident_id = ? and contributing_factor_id = ? and deleted_at is NULL", incidentId, cfId)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 1 {
cf.ContributingFactorId = cfId
cf.UpdatedAt = time.Now()
result := db.Save(cf)
if result != nil {
return result.Error
}
return nil
} else if result.RowsAffected == 0 {
addResult := db.Create(incidentTagsContributingFactorMapping)
if addResult != nil {
return addResult.Error
}
return nil
}
return gorm.ErrInvalidData
}
func SetContributingFactorDeletedAt(db *gorm.DB, incidentId int) error {
var cf entity.IncidentTagsContributingFactorMapping
result := db.Model(&cf).Where(" incident_id = ?", incidentId).Update("deleted_at", time.Now())
if result.Error != nil {
return result.Error
}
return nil
}

View File

@@ -0,0 +1,63 @@
package query
import (
"houston/entity"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
)
func FindCustomerTagsByIncidentId(logger *zap.Logger, db *gorm.DB, incidentId int) ([]entity.CustomerTagsEntity, error) {
var customerTags []entity.CustomerTagsEntity
result := db.Where("incidents_tags_customer_mapping.incident_id = ? AND customer_tags.deleted_at is NULL AND incidents_tags_customer_mapping.deleted_at is NULL", incidentId).Joins("JOIN incidents_tags_customer_mapping on incidents_tags_customer_mapping.customer_tags_id = customer_tags.id").Find(&customerTags)
if result.Error != nil {
return nil, result.Error
}
return customerTags, nil
}
func FetchAllCustomerTags(db *gorm.DB) ([]entity.CustomerTagsEntity, error) {
var customerTags []entity.CustomerTagsEntity
result := db.Find(&customerTags).Where("deleted_at is NULL")
if result.Error != nil {
return nil, result.Error
}
return customerTags, nil
}
func FindCustomerTagIdMappingWithIncidentByIncidentId(db *gorm.DB, incidentId int) ([]entity.IncidentTagsCustomerMapping, error) {
var incidentTagsCustomerMapping []entity.IncidentTagsCustomerMapping
result := db.Find(&incidentTagsCustomerMapping).Where("incident_id = ? AND deleted_at is NULL")
if result.Error != nil {
return nil, result.Error
}
return incidentTagsCustomerMapping, nil
}
func SetIncidentCustomerTagDeletedAt(db *gorm.DB, customerTagId int, incidentId int) error {
var incidentTagsCustomerMapping entity.IncidentTagsCustomerMapping
result := db.Model(&incidentTagsCustomerMapping).Where("customer_tags_id = ? AND incident_id = ?", customerTagId, incidentId).Update("deleted_at", time.Now())
if result.Error != nil {
return result.Error
}
return nil
}
func AddCustomerIdMappingToIncidentId(db *gorm.DB, customerTagId int, incidentId int) error {
var incidentTagsCustomerMapping = &entity.IncidentTagsCustomerMapping{
IncidentId: incidentId,
CustomerTagsId: customerTagId,
}
result := db.Create(incidentTagsCustomerMapping)
if result.Error != nil {
return result.Error
}
return nil
}

View File

@@ -0,0 +1,104 @@
package query
import (
"fmt"
"houston/entity"
"houston/model"
"strconv"
"gorm.io/gorm"
)
func CreateIncident(db *gorm.DB, request *model.CreateIncident) (*entity.IncidentEntity, error) {
severityId, err := strconv.Atoi(request.IncidentSeverity)
if err != nil {
return nil, fmt.Errorf("fetch channel conversationInfo failed. err: %v", err)
}
incidentEntity := &entity.IncidentEntity{
Title: request.IncidentTitle,
Description: request.IncidentDescription,
Status: request.Status,
SeverityId: severityId,
IncidentName: request.IncidentName,
SlackChannel: request.SlackChannel,
DetectionTime: request.DetectionTime,
CustomerImpactStartTime: request.CustomerImpactStartTime,
CustomerImpactEndTime: request.CustomerImpactEndTime,
TeamsId: request.TeamsId,
JiraId: request.JiraId,
ConfluenceId: request.ConfluenceId,
RemindMeAt: request.RemindMeAt,
EnableReminder: request.EnableReminder,
CreatedBy: request.CreatedBy,
UpdatedBy: request.UpdatedBy,
Version: request.Version,
}
result := db.Create(incidentEntity)
if result.Error != nil {
return nil, result.Error
}
return incidentEntity, nil
}
func UpdateIncident(db *gorm.DB, incidentEntity *entity.IncidentEntity) error {
result := db.Updates(incidentEntity)
if result.Error != nil {
return result.Error
}
return nil
}
func FindIncidentById(db *gorm.DB, incidentId string) (*entity.IncidentEntity, error) {
var incidentEntity entity.IncidentEntity
result := db.Find(&incidentEntity, "id = ?", incidentId)
if result.Error != nil {
return nil, result.Error
}
return &incidentEntity, nil
}
func FindNotResolvedLatestIncidents(db *gorm.DB, limit int) ([]entity.IncidentSeverityTeamJoinEntity, error) {
var incidentSeverityTeamJoinEntity []entity.IncidentSeverityTeamJoinEntity
result := db.Limit(limit).Where("status <> ? AND incidents.deleted_at IS NULL", entity.Resolved).Order("incidents.created_at desc").Joins("JOIN severity ON severity.id = incidents.severity_id").Joins("JOIN teams ON teams.id = incidents.teams_id").Select("incidents.title,incidents.status,incidents.slack_channel,severity.id as severity_id,severity.name as severity_name,teams.id as teams_id,teams.name as teams_name").Find(&entity.IncidentEntity{}).Scan(&incidentSeverityTeamJoinEntity)
if result.Error != nil {
return nil, result.Error
}
return incidentSeverityTeamJoinEntity, nil
}
func FindIncidentByChannelId(db *gorm.DB, channelId string) (*entity.IncidentEntity, error) {
var incidentEntity entity.IncidentEntity
result := db.Find(&incidentEntity, "slack_channel = ?", channelId)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
return nil, nil
}
return &incidentEntity, nil
}
func FindIncidentSeverityTeamJoin(db *gorm.DB, slackChannelId string) (*entity.IncidentSeverityTeamJoinEntity, error) {
var incidentSeverityTeamJoinEntity entity.IncidentSeverityTeamJoinEntity
result := db.Where("incidents.slack_channel = ? and incidents.deleted_at IS NULL", slackChannelId).Joins("JOIN severity ON severity.id = incidents.severity_id").Joins("JOIN teams ON teams.id = incidents.teams_id").Select("incidents.id as incident_id,incidents.title,incidents.status,incidents.slack_channel,severity.id as severity_id,severity.name as severity_name,teams.id as teams_id,teams.name as teams_name").Find(&entity.IncidentEntity{}).Scan(&incidentSeverityTeamJoinEntity)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
return nil, nil
}
return &incidentSeverityTeamJoinEntity, nil
}

View File

@@ -0,0 +1,42 @@
package query
import (
"houston/entity"
"houston/model/request"
"time"
"gorm.io/gorm"
)
func UpsertIncidentRole(db *gorm.DB, addIncidnentRoleRequest *request.AddIncidentRoleRequest) error {
incidentRolesEntity := &entity.IncidentRoles{
IncidentId: addIncidnentRoleRequest.IncidentId,
Role: addIncidnentRoleRequest.Role,
AssignedToUserSlackId: addIncidnentRoleRequest.UserId,
AssignedByUserSlackId: addIncidnentRoleRequest.CreatedById,
}
var incidentRoles entity.IncidentRoles
result := db.Find(&incidentRoles, "incident_id = ? AND role = ?", addIncidnentRoleRequest.IncidentId, addIncidnentRoleRequest.Role)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 1 {
incidentRolesEntity.ID = incidentRoles.ID
incidentRolesEntity.CreatedAt = incidentRoles.CreatedAt
incidentRolesEntity.UpdatedAt = time.Now()
addResult := db.Save(incidentRolesEntity)
if addResult != nil {
return addResult.Error
}
return nil
} else if result.RowsAffected == 0 {
addResult := db.Create(incidentRolesEntity)
if addResult != nil {
return addResult.Error
}
return nil
}
return gorm.ErrInvalidData
}

View File

@@ -0,0 +1,45 @@
package query
import (
"houston/entity"
"houston/model/request"
"gorm.io/gorm"
)
func CreateIncidentStatus(db *gorm.DB, request *request.AddIncidentStatusRequest) error {
incidentStatusEntity := &entity.IncidentStatusEntity{
Name: request.Name,
Description: request.Description,
}
result := db.Create(incidentStatusEntity)
if result.Error != nil {
return result.Error
}
return nil
}
func FindIncidentStatusById(db *gorm.DB, incidentStatusId int) (*entity.IncidentStatusEntity, error) {
var incidentStatusEntity entity.IncidentStatusEntity
result := db.Find(&incidentStatusEntity, "id = ?", incidentStatusId)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
return nil, nil
}
return &incidentStatusEntity, nil
}
func FetchAllIncidentStatus(db *gorm.DB) ([]entity.IncidentStatusEntity, error) {
var incidentStatusEntity []entity.IncidentStatusEntity
result := db.Find(&incidentStatusEntity)
if result.Error != nil {
return nil, result.Error
}
return incidentStatusEntity, nil
}

View File

@@ -0,0 +1,52 @@
package query
import (
"houston/entity"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
)
func FindDataPlataformTagsByIncidentId(incidenttId int, db *gorm.DB, logger *zap.Logger) (*entity.IncidentsTagsDataPlatformMapping, error) {
var cf entity.IncidentsTagsDataPlatformMapping
result := db.Where("incidents_tags_data_platform_mapping.incident_id = ? AND incidents_tags_data_platform_mapping.deleted_at is NULL", incidenttId).Find(&cf)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
return nil, nil
}
return &cf, nil
}
func UpsertDataPlatformTagToIncidentId(db *gorm.DB, dataPlatform string, incidentId int) error {
dpEntity := &entity.IncidentsTagsDataPlatformMapping{
DataPlatformTag: dataPlatform,
IncidentId: incidentId,
}
var dp entity.IncidentsTagsDataPlatformMapping
result := db.Where("incident_id = ? and deleted_at is NULL", incidentId).Find(&dp)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 1 {
dp.DataPlatformTag = dataPlatform
dp.UpdatedAt = time.Now()
addResult := db.Save(dp)
if addResult != nil {
return addResult.Error
}
return nil
} else if result.RowsAffected == 0 {
addResult := db.Create(dpEntity)
if addResult != nil {
return addResult.Error
}
return nil
}
return gorm.ErrInvalidData
}

View File

@@ -0,0 +1,38 @@
package query
import (
"houston/entity"
"houston/model/request"
"time"
"gorm.io/gorm"
)
func CreateMessage(db *gorm.DB, request *request.CreateMessage) (error) {
messageEntity := &entity.MessageEntity {
SlackChannel: request.SlackChannel,
MessageTimeStamp: request.MessageTimeStamp,
IncidentName: request.IncidentName,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Version: 0,
}
result := db.Create(&messageEntity)
if result.Error != nil {
return result.Error
}
return nil
}
func FindMessageByIncidentName(db *gorm.DB, incidentName string) ([]entity.MessageEntity, error) {
var messages []entity.MessageEntity
result := db.Find(&messages, "incident_name = ?", incidentName)
if result.Error != nil {
return nil, result.Error
}
return messages, nil
}

View File

@@ -0,0 +1,72 @@
package query
import (
"houston/entity"
"houston/model/request"
"go.uber.org/zap"
"gorm.io/gorm"
)
func FindSeverity(db *gorm.DB, logger *zap.Logger) ([]entity.SeverityEntity, error) {
var severityEntity []entity.SeverityEntity
result := db.Find(&severityEntity)
if result.Error != nil {
logger.Error("fetching severity query failed", zap.Error(result.Error))
return nil, result.Error
}
return severityEntity, nil
}
func FindIncidentSeverityEntity(db *gorm.DB, logger *zap.Logger) ([]entity.SeverityEntity, error) {
var severityEntity []entity.SeverityEntity
result := db.Find(&severityEntity).Where("deleted_at is NULL")
if result.Error != nil {
logger.Error("fetching severity query failed", zap.Error(result.Error))
return nil, result.Error
}
return severityEntity, nil
}
func FindIncidentSeverityEntityById(db *gorm.DB, logger *zap.Logger, id int) (*entity.SeverityEntity, error) {
var severityEntity entity.SeverityEntity
result := db.Find(&severityEntity, "id = ?", id)
if result.Error != nil {
logger.Error("fetching severity query failed", zap.Error(result.Error))
return nil, result.Error
} else if result.RowsAffected == 0 {
logger.Error("SeverityEntity not found", zap.Error(result.Error))
return nil, nil
}
return &severityEntity, nil
}
func AddSeverity(db *gorm.DB, logger *zap.Logger, addSeverityRequest request.AddSeverityRequest) error {
severityEntity := &entity.SeverityEntity{
Name: addSeverityRequest.Name,
Description: addSeverityRequest.Description,
}
result := db.Create(severityEntity)
if result.Error != nil {
logger.Error("failed to add severity in the database", zap.Error(result.Error))
return result.Error
}
return nil
}
func FindSeverityById(db *gorm.DB, severityId int) (*entity.SeverityEntity, error) {
var severityEntity entity.SeverityEntity
result := db.Find(&severityEntity, "id = ?", severityId)
if result.Error != nil {
return nil, result.Error
}
return &severityEntity, nil
}

133
pkg/postgres/query/tags.go Normal file
View File

@@ -0,0 +1,133 @@
package query
import (
"houston/entity"
"time"
"gorm.io/gorm"
)
func FindTagsByTeamsId(db *gorm.DB, teamsId int) ([]entity.TagsEntity, error) {
var tags []entity.TagsEntity
result := db.Where("teams_tags_mapping.teams_id = ? AND teams_tags_mapping.deleted_at IS NULL", teamsId).Joins("JOIN teams_tags_mapping ON tags.id = teams_tags_mapping.tag_id").Find(&tags)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
return nil, nil
}
return tags, nil
}
func FindTagsByIncidentId(db *gorm.DB, incidentId int) ([]entity.TagsEntity, error) {
var tags []entity.TagsEntity
result := db.Where("incidents_tags_mapping.incident_id = ? AND incidents_tags_mapping.deleted_at IS NULL", incidentId).Joins("JOIN incidents_tags_mapping ON incidents_tags_mapping.tag_id = tags.id").Find(&tags)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
return nil, nil
}
return tags, nil
}
func AddTagsIdMappingToTeamsId(db *gorm.DB, tagsId int, teamsId int) error {
teamsTagsMapping := &entity.TeamTagsMapping{
TeamsId: teamsId,
TagId: tagsId,
}
result := db.Create(teamsTagsMapping)
if result.Error != nil {
return result.Error
}
return nil
}
func FindTagRecordByTeamsIdAndTagsId(db *gorm.DB, tagsId int, teamsId int) (*entity.TagsEntity, error) {
var tagsMapping entity.TagsEntity
result := db.Find(tagsMapping).Where("tagsId = ? AND teamsId = ? AND deleted_at IS NULL", tagsId, teamsId).Joins("JOIN teams_tags_mapping ON tags.id = teams_tags_mapping.tags_id")
if result.Error != nil {
return nil, result.Error
}
return &tagsMapping, nil
}
func FindTagRecordByIncidentSlackChannelIddAndTagsId(db *gorm.DB, tagsId int, incidentSlackChannelId string) (*entity.IncidentsTagsMapping, error) {
var incidentsTagsMapping entity.IncidentsTagsMapping
result := db.Find(incidentsTagsMapping).Where("incident_id = ? AND tags_id = ? AND deleted_at IS NULL", incidentSlackChannelId, tagsId)
if result.Error != nil {
return nil, result.Error
}
return &incidentsTagsMapping, nil
}
func FindTagRecordListByIncidentId(db *gorm.DB, incidenId int) ([]entity.IncidentsTagsMapping, error) {
var incidentsTagsMapping []entity.IncidentsTagsMapping
result := db.Find(&incidentsTagsMapping).Where("incident_id = ? AND deleted_at IS NULL", incidenId)
if result.Error != nil {
return nil, result.Error
}
return incidentsTagsMapping, nil
}
func AddTagsIdMappingToIncidentId(db *gorm.DB, tagId, incidenId int) error {
var incidentsTagsMapping = &entity.IncidentsTagsMapping{
IncidentId: incidenId,
TagId: tagId,
}
result := db.Create(incidentsTagsMapping)
if result.Error != nil {
return result.Error
}
return nil
}
func SetTagsDeletedAt(db *gorm.DB, tagId int, resultId int) error {
var incidentTags entity.IncidentsTagsMapping
result := db.Model(&incidentTags).Where("tag_id = ? AND incident_id = ?", tagId, resultId).Update("deleted_at", time.Now())
if result.Error != nil {
return result.Error
}
return nil
}
func FindTagsById(db *gorm.DB, tagId int) (*entity.TagsEntity, error) {
var tagsEntity entity.TagsEntity
result := db.Find(tagsEntity).Where("id = ? AND deleted_at IS NULL", tagId)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
return nil, nil
}
return &tagsEntity, nil
}

View File

@@ -0,0 +1,70 @@
package query
import (
"houston/entity"
"houston/model/request"
"gorm.io/gorm"
)
func FindTeam(db *gorm.DB) ([]string, error) {
teams := make([]string, 0)
var teamEntity []entity.TeamEntity
result := db.Find(&teamEntity, "active = ?", true)
if result.Error != nil {
return nil, result.Error
}
for _, team := range teamEntity {
teams = append(teams, team.Name)
}
return teams, nil
}
func FindTeamList(db *gorm.DB) ([]entity.TeamEntity, error) {
var teamEntity []entity.TeamEntity
result := db.Find(&teamEntity, "active = ?", true)
if result.Error != nil {
return nil, result.Error
}
return teamEntity, nil
}
func FindTeamByName(db *gorm.DB, name string) (*entity.TeamEntity, error) {
var teamEntity entity.TeamEntity
result := db.Find(&teamEntity, "name = ?", name)
if result.Error != nil {
return nil, result.Error
}
return &teamEntity, nil
}
func AddTeam(db *gorm.DB, addTeamRequest request.AddTeamRequest) error {
teamEntity := &entity.TeamEntity{
Name: addTeamRequest.Name,
OncallHandle: addTeamRequest.OncallHandle,
}
result := db.Create(teamEntity)
if result.Error != nil {
return result.Error
}
return nil
}
func FindTeamById(db *gorm.DB, teamId int) (*entity.TeamEntity, error) {
var teamEntity entity.TeamEntity
result := db.Find(&teamEntity, "id = ?", teamId)
if result.Error != nil {
return nil, result.Error
} else if result.RowsAffected == 0 {
return nil, nil
}
return &teamEntity, nil
}

View File

@@ -0,0 +1,35 @@
package query
import (
"houston/entity"
"gorm.io/gorm"
)
func FindDefaultUserIdToBeAddedBySeverity(db *gorm.DB, severityId int) ([]string, error) {
userIds := make([]string, 0)
var user []entity.UsersEntity
result := db.Where("users.active = true And teams_severity_user_mapping.deleted_at is NULL AND teams_severity_user_mapping.default_add_in_incidents = ? AND teams_severity_user_mapping.entity_type = ? AND teams_severity_user_mapping.entity_id = ?", true, entity.SEVERITY, severityId).Joins("JOIN teams_severity_user_mapping on teams_severity_user_mapping.users_id = users.id").Find(&user)
if result.Error != nil {
return nil, result.Error
}
for _, users := range user {
userIds = append(userIds, users.SlackUserId)
}
return userIds, nil
}
func FindDefaultUserIdToBeAddedByTeam(db *gorm.DB, teamId int) ([]string, error) {
userIds := make([]string, 0)
var user []entity.UsersEntity
result := db.Where("users.active = true And teams_severity_user_mapping.deleted_at is NULL AND teams_severity_user_mapping.default_add_in_incidents = ? AND teams_severity_user_mapping.entity_type = ? AND teams_severity_user_mapping.entity_id = ?", true, entity.TEAM, teamId).Joins("JOIN teams_severity_user_mapping on teams_severity_user_mapping.users_id = users.id").Find(&user)
if result.Error != nil {
return nil, result.Error
}
for _, users := range user {
userIds = append(userIds, users.SlackUserId)
}
return userIds, nil
}

BIN
pkg/slack/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,49 @@
package common
import (
"fmt"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
)
func FindParticipants(client *socketmode.Client, logger *zap.Logger, channelId string) ([]string, error) {
request := &slack.GetUsersInConversationParameters{
ChannelID: channelId,
Limit: 1000,
}
channelInfo, _, err := client.GetUsersInConversation(request)
if err != nil {
return nil, fmt.Errorf("fetch channel conversationInfo failed. err: %v", err)
}
return channelInfo, nil
}
func CreateChannel(client *socketmode.Client, logger *zap.Logger, channelName string) (string, error) {
request := slack.CreateConversationParams{
ChannelName: channelName,
IsPrivate: false,
}
channel, err := client.CreateConversation(request)
if err != nil {
logger.Error("create slack channel failed", zap.String("channel_name", channelName), zap.Error(err))
return "", err
}
logger.Info("created slack channel successfully", zap.String("channel_name", channelName), zap.String("channel_id", channel.ID))
return channel.ID, nil
}
func InviteUsersToConversation(client *socketmode.Client, logger *zap.Logger, channelId string, userId ...string) {
_, err := client.InviteUsersToConversation(channelId, userId...)
if err != nil {
logger.Error("invite users to conversation failed",
zap.String("channel_id", channelId), zap.Any("user_ids", userId), zap.Error(err))
return
}
logger.Info("successfully invite users to conversation", zap.String("channel_id", channelId), zap.Any("user_ids", userId))
}

1
pkg/slack/config.go Normal file
View File

@@ -0,0 +1 @@
package slack

View File

@@ -0,0 +1,38 @@
package houston
import (
"houston/pkg/postgres/query"
houston "houston/pkg/slack/houston/design"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
func HoustonMainCommand(db *gorm.DB, client *socketmode.Client, logger *zap.Logger, evt *socketmode.Event) {
cmd, ok := evt.Data.(slack.SlashCommand)
logger.Info("processing houston command", zap.Any("payload", cmd))
if !ok {
logger.Error("event data to slash command conversion failed", zap.Any("data", evt))
return
}
//TODO - DOES NOT THROW ERROR IF SAME SLACK ID IS PRESENT MULTIPLE TIMES
result, err := query.FindIncidentByChannelId(db, cmd.ChannelID)
if err != nil {
logger.Error("FindIncidentBySlackChannelId errors",
zap.String("channel_id", cmd.ChannelID), zap.String("channel", cmd.ChannelName),
zap.String("user_id", cmd.UserID), zap.Error(err))
return
}
if result != nil {
logger.Info("Result", zap.String("result", result.IncidentName))
payload := houston.OptionsBlock()
client.Ack(*evt.Request, payload)
}
payload := houston.ButtonDesign()
client.Ack(*evt.Request, payload)
}

View File

@@ -0,0 +1,599 @@
020003558014
020003900893
020003333489
027620996
020003445139
021702862
027409590
020004267232
025883146
020000040077
027259677
020003732743
020003341765
020004269102
020003447657
020003300976
020003524585
020003454356
020004506061
021583078
020003576779
022465360
020003341738
026592869
020004408853
020003680059
020003521018
020004464121
020004295982
020004699471
020003492076
020004421882
020003620905
021844073
020003905104
020004835161
021830531
020003530514
020003331307
020003424364
020004395272
025911471
020003830373
020003509994
020004066362
020004205112
020162216
020004010831
026504309
020003765224
020003565794
024415636
024086936
020003423080
020003841894
021628510
020003422987
026117018
020004575492
020004259951
020003718823
027599428
020003181757
028267509
029988750
020003502316
020004649161
022524458
022408293
020004653651
020003318189
020004017401
020003446105
020003734153
020003404739
022535780
020003020055
020003305629
020003641184
020003309586
021866406
025109686
020663707
028204765
020003519985
020003719493
020003509334
020003984191
020004263641
020003654445
020006116642
020007731381
020005767123
020004848105
020005929313
020006498112
020004477507
020005393927
020004781476
020006272862
020005322935
020005236375
020005303595
020005488425
020004654559
020006051913
020005488175
020004608600
020005492575
020005312368
020005304248
020005315735
020007508881
020004823974
020006194523
020005246918
020006404662
020007650991
020005356386
020005786724
020005968223
020005821414
020006612462
020007973331
020008056531
020007582001
020004823859
020006077182
020008088441
020007926721
020003841686
020005397835
020006364113
020004593200
020005882723
020005439094
020008022001
020006649852
020007854771
020005859074
020005151627
020005354737
020007799021
020004753940
020005431224
020005414436
020005428978
020006698082
020005613886
020005283297
020007780281
020007091041
020006582162
020008101671
020006309823
020008344001
020008364981
020005674135
020005019339
020006693612
020005633437
020004730030
020005927584
020005447816
020005660997
020006171863
020005667075
020005314367
020006163912
020004791270
020008274491
020007904631
020004441634
020004442564
020003977800
020005083822
020003883850
020003901247
020003800026
020005794061
020005262551
020004367433
020004474784
020005925561
020006074041
020004749644
020004862063
020004767843
020004060670
020004066814
025221920
020003707965
020003605755
020005994721
020005172043
020004949973
020004225623
020004185030
020003851584
020004266845
020004048067
020004523853
020004529807
020003750367
020004016159
020004291054
020004687113
020004518973
020006395481
020004622625
020004051316
020003562855
020004031785
020004391227
020005664332
020004105055
020004280939
020004105319
020004351819
020004845424
020004359819
020006265381
020005322412
020004719983
020005631321
020003895569
020003912236
020006657611
020004048521
020003843318
020004308010
020003956458
020004516044
020004028898
020006388711
020005244533
020004855953
020004317519
020005589062
020004403309
020005639722
020006468191
020004475026
020006503751
020004094780
020005344703
020004962484
020004085627
020003857609
020006681381
020004603504
020006700661
020003756527
020004029457
020004579728
020005137302
020004580898
020004640226
020005324463
020004611985
020004589018
020004701175
020005067154
020006844231
020004378755
020004705107
020004728556
020005225482
020004324990
020005703772
020003958907
020005829962
020004812545
020005599603
020004553915
020005909552
020004592108
020004373517
020004236989
020004740387
020004953424
020005419151
020006204921
020004432370
020005246083
020004809305
020004921568
020006414051
020004699517
020004686966
020004507368
020004845566
020006843091
020005607562
020005928102
020005490352
020004696605
020004865257
020005575043
020004987926
020003414947
028026488
020003597024
020004738471
020003564164
020004553411
020003461578
022691120
020003182638
020004777461
020004064332
020744216
027498632
020003506595
023032271
020005744212
020004885505
020005900452
020004693747
020005074815
020005468072
020003764580
020004965517
020007176641
020005283484
020005713413
020005236293
020005720442
020004330539
020005531753
020007034831
020007305511
020005861982
020005775983
020003788957
020005975172
020004663899
020005386144
020006609581
020004133039
020005035375
020004884588
020005099556
020007390911
020005760873
020005021048
020006199092
020005190004
020004722715
020005527164
020007537971
020004980066
020004704249
020005892283
020004732207
020006232942
020004918868
020005192896
020007667311
020004989398
020007634441
020005155257
020006957991
020007106211
020005268715
020007660811
020004970205
020004627675
020004703536
020007729571
020007634671
020005573314
020004676109
020004638768
020005226084
020005117416
020005688292
020005659304
020004749349
020006235552
020004527110
020007661711
020005982203
020005295306
020007188841
020007780811
020004480188
020007569521
020006321722
020004855608
020005397565
020005542684
020007799701
020005576724
020005320687
020006260652
020005651084
021699853
020003211467
020004212672
020003311478
023115669
022053955
020004028623
020003854803
022269713
020004367491
020003489234
020003968141
020004088703
020004781631
020003590698
020003608224
021764301
024569436
028897136
020003347254
027004176
020003804756
020003269885
020004230903
020909972
020003690530
020003226696
020004083264
020003742206
025347015
020004291153
020004498423
020003794844
020003834190
020003756936
020003511559
020003877095
020003487918
020004685822
020003885889
020003859649
020003564235
020004316152
020003844290
020003810679
020004611121
020004261744
020003281276
020003797567
020004515253
020004924352
020003741998
020004576931
020003953776
020004879391
020003606930
020003836000
020003820747
020004514243
020004065301
020004002255
020003701809
020003967615
020004673341
023079283
020004371223
020003834269
020003998142
020005491341
020004762351
020005058981
020004007182
020004365432
020004243373
020004081526
020004109482
020003898056
020005321451
020004066579
020004152485
020004386274
020003626450
020004306653
020003746067
020004007525
020004174104
020004034759
020004118316
020005108101
020003575255
020005378251
020004803142
020004605093
020003578367
020004499083
020003684524
020003956310
020004024798
020004911582
020004676942
020005315441
020005450321
020004520313
020005014731
020003667279
020005111272
020005045472
020003872436
020004030080
020005636231
020003800375
020004406494
020004175355
020004066941
020005318352
020005022482
020003932950
020003842090
020003950158
020004532024
023848787
020004698222
020003320654
020004430581
020003317020
020003697824
020003997801
025731975
020003413630
020003374006
020003980663
027051673
027208549
029903465
027792784
020003311545
020003356864
022120906
023757053
020004074581
020003527228
020003297107
020002220051
020003526434
020003740864
020003438775
020003958341
025944636
027254319
028068077
026641741
020004125482
020003629339
020004398392
022283492
020003444186
022260920
020003506690
021615995
020003505914
023691708
020004385372
020004292781
020003413417
026580479
020004034222
028432029
020003384586
020003438709
020004034062
020003506854
020003373047
022850287
020003844314
020004087263
020004657911
020003376667
020004576882
020003421694
020003333266
024739738
020003609754
022630719
020003403645
020003483055
020004141901
023640399
020004177831
020003979512
024127386
024093227
027507965
020004251691
020003269657

View File

@@ -0,0 +1 @@
["020007091041","020006582162","020008101671","020006309823","020008344001","020008364981","020005674135","020005019339","020006693612","020005633437","020004730030","020005927584","020005447816","020005660997","020006171863","020005667075","020005314367","020006163912","020004791270","020008274491","020007904631","020004441634","020004442564","020003977800","020005083822","020003883850","020003901247","020003800026","020005794061","020005262551","020004367433","020004474784","020005925561","020006074041","020004749644","020004862063","020004767843","020004060670","020004066814","025221920","020003707965","020003605755","020005994721","020005172043","020004949973","020004225623","020004185030","020003851584","020004266845","020004048067"]

View File

@@ -0,0 +1,94 @@
package command
import (
"encoding/json"
"fmt"
"houston/model/request"
"houston/pkg/postgres/query"
houston "houston/pkg/slack/houston/design"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type incidentAssignProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewIncidentAssignProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentAssignProcessor {
return &incidentAssignProcessor{
client: client,
db: db,
logger: logger,
}
}
func (iap *incidentAssignProcessor) IncidentAssignProcess(callback slack.InteractionCallback, request *socketmode.Request) {
modalRequest := houston.GenerateModalForIncidentAssign(callback.Channel)
_, err := iap.client.OpenView(callback.TriggerID, modalRequest)
if err != nil {
iap.logger.Error("houston slack openview command failed.",
zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err))
return
}
var payload interface{}
iap.client.Ack(*request, payload)
}
func (iap *incidentAssignProcessor) IncidentAssignModalCommandProcessing(callback slack.InteractionCallback, request *socketmode.Request) {
incidentEntity, err := query.FindIncidentByChannelId(iap.db, callback.View.PrivateMetadata)
if err != nil {
iap.logger.Error("FindIncidentByChannelId error",
zap.String("incident_slack_channel_id", callback.View.PrivateMetadata),
zap.String("user_id", callback.User.ID), zap.Error(err))
} else if incidentEntity == nil {
iap.logger.Error("IncidentEntity not found ",
zap.String("incident_slack_channel_id", callback.View.PrivateMetadata), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
assignIncidentRoleRequest := buildAssignIncidentRoleRequest(callback.View.State.Values)
assignIncidentRoleRequest.CreatedById = callback.User.ID
assignIncidentRoleRequest.IncidentId = int(incidentEntity.ID)
iap.logger.Info("request", zap.Any("request", assignIncidentRoleRequest))
err = query.UpsertIncidentRole(iap.db, assignIncidentRoleRequest)
if err != nil {
iap.logger.Error("UpsertIncidentRole failed", zap.Error(err))
return
}
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> is assigned to %s by <@%s>", assignIncidentRoleRequest.UserId, assignIncidentRoleRequest.Role, assignIncidentRoleRequest.CreatedById), false)
_, _, errMessage := iap.client.PostMessage(callback.View.PrivateMetadata, msgOption)
if errMessage != nil {
iap.logger.Error("post response failed for IncidentAssignModalCommandProcessing", zap.Error(errMessage))
return
}
var payload interface{}
iap.client.Ack(*request, payload)
}
func buildAssignIncidentRoleRequest(blockActions map[string]map[string]slack.BlockAction) *request.AddIncidentRoleRequest {
var addIncidentRoleRequest request.AddIncidentRoleRequest
var requestMap = make(map[string]string, 0)
for _, actions := range blockActions {
for actionID, action := range actions {
if action.Type == "users_select" {
requestMap[actionID] = action.SelectedUser
}
if action.Type == "static_select" {
requestMap[actionID] = action.SelectedOption.Value
}
}
}
desRequestMap, _ := json.Marshal(requestMap)
json.Unmarshal(desRequestMap, &addIncidentRoleRequest)
return &addIncidentRoleRequest
}

View File

@@ -0,0 +1,62 @@
package command
import (
"fmt"
"houston/entity"
"houston/pkg/postgres/query"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type incidentResolveProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewIncidentResolveProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentResolveProcessor {
return &incidentResolveProcessor{
client: client,
db: db,
logger: logger,
}
}
func (irp *incidentResolveProcessor) IncidentResolveProcess(callback slack.InteractionCallback, request *socketmode.Request) {
channelId := callback.Channel.ID
incidentEntity, err := query.FindIncidentByChannelId(irp.db, channelId)
if err != nil {
irp.logger.Error("incident not found",
zap.String("channel", channelId),
zap.String("user_id", callback.User.ID), zap.Error(err))
}
incidentEntity.Status = entity.Resolved
err = query.UpdateIncident(irp.db, incidentEntity)
if err != nil {
irp.logger.Error("failed to update incident to resolve state",
zap.String("channel", channelId),
zap.String("user_id", callback.User.ID), zap.Error(err))
}
irp.logger.Info("successfully resolved the incident",
zap.String("channel", channelId),
zap.String("user_id", callback.User.ID))
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set status to %s", callback.User.ID, incidentEntity.Status), false)
_, _, errMessage := irp.client.PostMessage(callback.Channel.ID, msgOption)
if errMessage != nil {
irp.logger.Error("post response failed for ResolveIncident", zap.Error(errMessage))
return
}
irp.client.ArchiveConversation(channelId)
var payload interface{}
irp.client.Ack(*request, payload)
}

View File

@@ -0,0 +1,108 @@
package command
import (
"fmt"
"houston/pkg/postgres/query"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type incidenShowTagsProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewIncidentShowTagsProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidenShowTagsProcessor {
return &incidenShowTagsProcessor{
client: client,
db: db,
logger: logger,
}
}
func (isp *incidenShowTagsProcessor) IncidentShowTagsRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
result, err := query.FindIncidentSeverityTeamJoin(isp.db, callback.Channel.ID)
if err != nil {
isp.logger.Error("IncidentShowTagsRequestProcess error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
} else if result == nil {
isp.logger.Error("IncidentSeverityTeamJoin Not found",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
savedContributingFactor, err := query.FindContributingFactorsByIncidentId(isp.db, result.IncidentId)
if err != nil {
isp.logger.Error("FindContributingFactorsByIncidentId error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
cfString := "No tags"
if savedContributingFactor != nil {
cfString = savedContributingFactor.Label
}
savedCustomerTags, err := query.FindCustomerTagsByIncidentId(isp.logger, isp.db, result.IncidentId)
if err != nil {
isp.logger.Error("FindCustomerTagsByIncidentId error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
var customerTagString string
if len(savedCustomerTags) == 0 {
customerTagString = "No tags"
} else {
for _, o := range savedCustomerTags {
customerTagString = customerTagString + " " + o.Label
}
}
savedTags, err := query.FindTagsByIncidentId(isp.db, result.IncidentId)
if err != nil {
isp.logger.Error("FindTagsByIncidentId error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
var tagString string
if len(savedTags) == 0 {
tagString = "No tags"
} else {
for _, o := range savedTags {
tagString = tagString + " " + o.Label
}
}
savedDp, err := query.FindDataPlataformTagsByIncidentId(result.IncidentId, isp.db, isp.logger)
if err != nil {
isp.logger.Error("FindDataPlataformTagsByIncidentId error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
var dpString string = "No tags"
if savedDp != nil && (len(savedDp.DataPlatformTag)) != 0 {
dpString = savedDp.DataPlatformTag
}
msgOption := slack.MsgOptionText(fmt.Sprintf("\n\nCONTRIBUTING-FACTORS: \n %s \n\n CUSTOMER: \n %s \n\n CUSTOMER:%s \n %s \n\n DATA-PLATFORM: \n %s", cfString, customerTagString, result.TeamsName, tagString, dpString), true)
_, errMessage := isp.client.PostEphemeral(callback.Channel.ID, callback.User.ID, msgOption)
if errMessage != nil {
isp.logger.Error("PostEphemeralmresponse failed for IncidentShowTagsRequestProcess", zap.Error(errMessage))
return
}
var payload interface{}
isp.client.Ack(*request, payload)
}

View File

@@ -0,0 +1,100 @@
package command
import (
"fmt"
"houston/pkg/postgres/query"
houston "houston/pkg/slack/houston/design"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type incidentUpdateDescriptionProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewIncidentUpdateDescriptionProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateDescriptionProcessor {
return &incidentUpdateDescriptionProcessor{
client: client,
db: db,
logger: logger,
}
}
func (idp *incidentUpdateDescriptionProcessor) IncidentUpdateDescriptionRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
result, err := query.FindIncidentByChannelId(idp.db, callback.Channel.ID)
if err != nil {
idp.logger.Error("FindIncidentByChannelId error ",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
} else if result == nil {
idp.logger.Error("IncidentEntity not found ",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
modalRequest := houston.BuildIncidentUpdateDescriptionModal(idp.db, callback.Channel, result.Description)
_, err = idp.client.OpenView(callback.TriggerID, modalRequest)
if err != nil {
idp.logger.Error("houston slack openview command for IncidentUpdateDescriptionRequestProcess failed.",
zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err))
return
}
var payload interface{}
idp.client.Ack(*request, payload)
}
func (itp *incidentUpdateDescriptionProcessor) IncidentUpdateDescription(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) {
incidentEntity, err := query.FindIncidentByChannelId(itp.db, callback.View.PrivateMetadata)
if err != nil {
itp.logger.Error("FindIncidentByChannelId error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
} else if incidentEntity == nil {
itp.logger.Error("IncidentEntity not found ",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
incidentDescription := buildUpdateIncidentDescriptionRequest(callback.View.State.Values)
incidentEntity.Description = incidentDescription
incidentEntity.UpdatedBy = user.ID
err = query.UpdateIncident(itp.db, incidentEntity)
if err != nil {
itp.logger.Error("IncidentUpdateDescription error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
}
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set description to %s", user.ID, incidentEntity.Description), false)
_, _, errMessage := itp.client.PostMessage(callback.View.PrivateMetadata, msgOption)
if errMessage != nil {
itp.logger.Error("post response failed for IncidentUpdateDescription", zap.Error(errMessage))
return
}
var payload interface{}
itp.client.Ack(*request, payload)
}
func buildUpdateIncidentDescriptionRequest(blockActions map[string]map[string]slack.BlockAction) string {
var requestMap = make(map[string]string, 0)
for _, actions := range blockActions {
for actionID, action := range actions {
if action.Type == "plain_text_input" {
requestMap[actionID] = action.Value
}
}
}
return requestMap["incident_description"]
}

View File

@@ -0,0 +1,125 @@
package command
import (
"fmt"
"houston/pkg/postgres/query"
"houston/pkg/slack/common"
houston "houston/pkg/slack/houston/design"
"strconv"
"time"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type incidentUpdateSevertityProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewIncidentUpdateSeverityProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateSevertityProcessor {
return &incidentUpdateSevertityProcessor{
client: client,
db: db,
logger: logger,
}
}
func (isp *incidentUpdateSevertityProcessor) IncidentUpdateSeverityRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
incidentSeverity, err := query.FindIncidentSeverityEntity(isp.db, isp.logger)
if err != nil {
isp.logger.Error("FindSeverityEntity error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
modalRequest := houston.BuildIncidentUpdateSeverityModal(callback.Channel, incidentSeverity)
_, err = isp.client.OpenView(callback.TriggerID, modalRequest)
if err != nil {
isp.logger.Error("houston slack openview command failed.",
zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err))
return
}
var payload interface{}
isp.client.Ack(*request, payload)
}
func (isp *incidentUpdateSevertityProcessor) IncidentUpdateSeverity(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) {
incidentEntity, err := query.FindIncidentByChannelId(isp.db, callback.View.PrivateMetadata)
if err != nil {
isp.logger.Error("FindIncidentByChannelId error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
} else if incidentEntity == nil {
isp.logger.Error("IncidentEntity not found ",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
incidentSeverityId := buildUpdateIncidentSeverityRequest(isp.logger, callback.View.State.Values)
result, err := query.FindIncidentSeverityEntityById(isp.db, isp.logger, incidentSeverityId)
if err != nil {
isp.logger.Error("FindIncidentSeverityEntityById error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
} else if result == nil {
isp.logger.Error("SeverityEntity not found",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
}
incidentEntity.SeverityId = int(result.ID)
incidentEntity.UpdatedBy = user.ID
incidentEntity.SeverityTat = time.Now().AddDate(0, 0, result.Sla)
err = query.UpdateIncident(isp.db, incidentEntity)
if err != nil {
isp.logger.Error("UpdateIncident error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
}
userIdList, err := query.FindDefaultUserIdToBeAddedBySeverity(isp.db, int(result.ID))
if err != nil {
isp.logger.Error("FindDefaultUserIdToBeAddedBySeverity error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
}
for _, o := range userIdList {
common.InviteUsersToConversation(isp.client, isp.logger, callback.View.PrivateMetadata, o)
}
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set severity to %s", user.ID, result.Name), false)
_, _, errMessage := isp.client.PostMessage(callback.View.PrivateMetadata, msgOption)
if errMessage != nil {
isp.logger.Error("post response failed for IncidentUpdateSeverity", zap.Error(errMessage))
return
}
var payload interface{}
isp.client.Ack(*request, payload)
}
//TODO - ADD USER ACCORDING TO SEVERITY
func buildUpdateIncidentSeverityRequest(logger *zap.Logger, blockActions map[string]map[string]slack.BlockAction) int {
var requestMap = make(map[string]string, 0)
for _, actions := range blockActions {
for actionID, action := range actions {
if action.Type == "static_select" {
requestMap[actionID] = action.SelectedOption.Value
}
}
}
selectedValue := requestMap["incident_severity_modal_request"]
selectedValueInInt, err := strconv.Atoi(selectedValue)
if err != nil {
logger.Error("String conversion to int faileed in buildUpdateIncidentTypeRequest for "+selectedValue, zap.Error(err))
}
return selectedValueInInt
}

View File

@@ -0,0 +1,111 @@
package command
import (
"fmt"
"houston/entity"
"houston/pkg/postgres/query"
houston "houston/pkg/slack/houston/design"
"strconv"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type incidentUpdateStatusProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewIncidentUpdateStatusProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateStatusProcessor {
return &incidentUpdateStatusProcessor{
client: client,
db: db,
logger: logger,
}
}
func (isp *incidentUpdateStatusProcessor) IncidentUpdateStatusRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
modalRequest := houston.BuildIncidentUpdateStatusModal(isp.db, callback.Channel)
_, err := isp.client.OpenView(callback.TriggerID, modalRequest)
if err != nil {
isp.logger.Error("houston slack openview command failed.",
zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err))
return
}
var payload interface{}
isp.client.Ack(*request, payload)
}
func (isp *incidentUpdateStatusProcessor) IncidentUpdateStatus(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) {
incidentEntity, err := query.FindIncidentByChannelId(isp.db, callback.View.PrivateMetadata)
if err != nil {
isp.logger.Error("FindIncidentBySlackChannelId error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
} else if incidentEntity == nil {
isp.logger.Error("IncidentEntity Object Not Found",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
}
incidentStatusId := buildUpdateIncidentStatusRequest(isp.logger, callback.View.State.Values)
result, err := query.FindIncidentStatusById(isp.db, incidentStatusId)
if err != nil {
isp.logger.Error("FindIncidentStatusById error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
} else if result == nil {
isp.logger.Error("IncidentStatusEntity Object not found",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
}
incidentEntity.Status = entity.IncidentStatus(result.Name)
incidentEntity.UpdatedBy = user.ID
err = query.UpdateIncident(isp.db, incidentEntity)
if err != nil {
isp.logger.Error("UpdateIncident error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
}
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set status to %s", user.ID, incidentEntity.Status), false)
_, _, errMessage := isp.client.PostMessage(callback.View.PrivateMetadata, msgOption)
if errMessage != nil {
isp.logger.Error("post response failed for IncidentUpdateStatus", zap.Error(errMessage))
return
}
if incidentEntity.Status == "RESOLVED" {
isp.client.ArchiveConversation(callback.View.PrivateMetadata)
}
var payload interface{}
isp.client.Ack(*request, payload)
}
//TODO - FOR RESOLVED SCENARIO
func buildUpdateIncidentStatusRequest(logger *zap.Logger, blockActions map[string]map[string]slack.BlockAction) int {
var requestMap = make(map[string]string, 0)
for _, actions := range blockActions {
for actionID, action := range actions {
if action.Type == "static_select" {
requestMap[actionID] = action.SelectedOption.Value
}
}
}
selectedValue := requestMap["incident_status_modal_request"]
selectedValueInInt, err := strconv.Atoi(selectedValue)
if err != nil {
logger.Error("String conversion to int faileed in buildUpdateIncidentTypeRequest for "+selectedValue, zap.Error(err))
}
return selectedValueInInt
}

View File

@@ -0,0 +1,315 @@
package command
import (
"houston/entity"
"houston/pkg/postgres/query"
houston "houston/pkg/slack/houston/design"
"strconv"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type incidentUpdateTagsProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewIncidentUpdateTagsProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateTagsProcessor {
return &incidentUpdateTagsProcessor{
client: client,
db: db,
logger: logger,
}
}
func (idp *incidentUpdateTagsProcessor) IncidentUpdateTagsRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
result, err := query.FindIncidentSeverityTeamJoin(idp.db, callback.Channel.ID)
if err != nil {
idp.logger.Error("FindIncidentSeverityTeamJoin error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
} else if result == nil {
idp.logger.Error("IncidentSeverityTeamJoin Not found",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
tags, err := query.FindTagsByTeamsId(idp.db, result.TeamsId)
if err != nil {
idp.logger.Error("FindTagsByTeamsId error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
incidentTags, err := query.FindTagsByIncidentId(idp.db, result.IncidentId)
if err != nil {
idp.logger.Error("FindTagsByIncidentId error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
cf, savedCf, err := findContributingFactors(result.IncidentId, idp.db, idp.logger)
if err != nil {
idp.logger.Error("findContrxibutingFactors error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
customerTags, savedCustomrTags, err := findCustomerTags(result.IncidentId, idp.db, idp.logger)
if err != nil {
idp.logger.Error("findCustomerTags error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
savedDataPlatformTags, err := query.FindDataPlataformTagsByIncidentId(result.IncidentId, idp.db, idp.logger)
if err != nil {
idp.logger.Error("findDataPlataformTagsByIncidentInt error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
modalRequest := houston.BuildIncidentUpdateTagModal(callback.Channel, tags, result.TeamsName, incidentTags, savedCf, cf, savedCustomrTags, customerTags, savedDataPlatformTags)
_, err = idp.client.OpenView(callback.TriggerID, modalRequest)
if err != nil {
idp.logger.Error("houston slack openview command for IncidentUpdateTagsRequestProcess failed.",
zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err))
return
}
var payload interface{}
idp.client.Ack(*request, payload)
}
func (itp *incidentUpdateTagsProcessor) IncidentUpdateTags(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) {
result, err := query.FindIncidentSeverityTeamJoin(itp.db, callback.View.PrivateMetadata)
if err != nil {
itp.logger.Error("FindIncidentSeverityTeamJoin error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
} else if result == nil {
itp.logger.Error("IncidentSeverityTeamJoin Not found",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
cfId, customerTagIdRequests, incidentTagIdRequests, dataPlatformString := buildUpdateIncidentTagsRequest(itp.logger, callback.View.State.Values)
//Update Contributing factors
if cfId == 0 {
err = query.SetContributingFactorDeletedAt(itp.db, result.IncidentId)
if err != nil {
itp.logger.Error("SetContributingFactorDeletedAt err",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
} else {
err = query.UpsertContributingFactorIdToIncidentId(itp.db, cfId, result.IncidentId)
if err != nil {
itp.logger.Error("UpsertContributingFactorIdToIncidentId err",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
}
// Update Customer Tag Id mapping
customerTagIdExist, err := query.FindCustomerTagIdMappingWithIncidentByIncidentId(itp.db, result.IncidentId)
var customerTagIdList []int
for _, o := range customerTagIdExist {
customerTagIdList = append(customerTagIdList, o.CustomerTagsId)
}
UpdateCustomerTagId(result, callback, itp.db, itp.logger, customerTagIdList, customerTagIdRequests)
// Team tag Id Mapping
tagsExist, err := query.FindTagRecordListByIncidentId(itp.db, result.IncidentId)
var tagIdList []int
for _, o := range tagsExist {
tagIdList = append(tagIdList, o.TagId)
}
UpdateTeamTagId(result, callback, itp.db, itp.logger, tagIdList, incidentTagIdRequests)
// Update Data Platform tags
err = query.UpsertDataPlatformTagToIncidentId(itp.db, dataPlatformString, result.IncidentId)
if err != nil {
itp.logger.Error("UpsertDataPlatformTagToIncidentId err",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
var payload interface{}
itp.client.Ack(*request, payload)
}
func buildUpdateIncidentTagsRequest(logger *zap.Logger, blockActions map[string]map[string]slack.BlockAction) (int, []int, []int, string) {
var requestMap = make(map[string][]slack.OptionBlockObject)
var requestMapString = make(map[string]string, 0)
for _, actions := range blockActions {
for actionID, action := range actions {
if action.Type == "multi_static_select" {
requestMap[actionID] = action.SelectedOptions
} else if action.Type == "static_select" {
requestMapString[actionID] = action.SelectedOption.Value
} else if action.Type == "plain_text_input" {
requestMapString[actionID] = action.Value
}
}
}
var customerTags []int
var tagsList []int
for _, o := range requestMap["incident_customer_tags_modal_request"] {
selectedValueInInt, err := strconv.Atoi(o.Value)
if err != nil {
logger.Error("String to int conversion failed in buildUpdateIncidentTagseRequest for " + o.Value)
} else {
customerTags = append(customerTags, selectedValueInInt)
}
}
for _, o := range requestMap["incident_tags_modal_request"] {
selectedValueInInt, err := strconv.Atoi(o.Value)
if err != nil {
logger.Error("String to int conversion failed in buildUpdateIncidentTagseRequest for " + o.Value)
} else {
tagsList = append(tagsList, selectedValueInInt)
}
}
var cfId int
var err1 error
if len(requestMapString["incident_cf_modal_request"]) != 0 {
cfId, err1 = strconv.Atoi(requestMapString["incident_cf_modal_request"])
if err1 != nil {
logger.Error("String to int conversion in CF failed in buildUpdateIncidentTagseRequest for " + requestMapString["incident_cf_modal_request"])
}
}
return cfId, customerTags, tagsList, requestMapString["incident_data_platform_tag_modal_request"]
}
func diffLists(list1 []int, list2 []int) ([]int, []int) {
set1 := make(map[int]bool)
set2 := make(map[int]bool)
result1 := []int{}
result2 := []int{}
for _, num := range list1 {
set1[num] = true
}
for _, num := range list2 {
set2[num] = true
}
for num, _ := range set1 {
if _, ok := set2[num]; !ok {
result1 = append(result1, num)
}
}
for num, _ := range set2 {
if _, ok := set1[num]; !ok {
result2 = append(result2, num)
}
}
return result1, result2
}
func findContributingFactors(incidentId int, db *gorm.DB, logger *zap.Logger) ([]entity.ContributingFactorEntity, *entity.ContributingFactorEntity, error) {
cf, err := query.FetchAllContributingFactors(db)
if err != nil {
logger.Error("FetchAllContributingFactors error")
return nil, nil, err
}
savedCf, err := query.FindContributingFactorsByIncidentId(db, incidentId)
if err != nil {
logger.Error("FindContributingFactorsByIncidentId error")
return nil, nil, err
}
return cf, savedCf, nil
}
func findCustomerTags(incidentId int, db *gorm.DB, logger *zap.Logger) ([]entity.CustomerTagsEntity, []entity.CustomerTagsEntity, error) {
customerTags, err := query.FetchAllCustomerTags(db)
if err != nil {
logger.Error("FetchAllCustomerTags error")
return nil, nil, err
}
savedCustomerTags, err := query.FindCustomerTagsByIncidentId(logger, db, incidentId)
if err != nil {
logger.Error("FindCustomerTagsByIncidentId error")
return nil, nil, err
}
return customerTags, savedCustomerTags, nil
}
func UpdateCustomerTagId(result *entity.IncidentSeverityTeamJoinEntity, callback slack.InteractionCallback, db *gorm.DB, logger *zap.Logger, customerTagIdList []int, customerTagIdRequests []int) {
listCustomerTag1, listCustomerTag2 := diffLists(customerTagIdList, customerTagIdRequests)
for _, o := range listCustomerTag1 {
err := query.SetIncidentCustomerTagDeletedAt(db, o, result.IncidentId)
if err != nil {
logger.Error("SetIncidentCustomerTagDeletedAt error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
}
for _, index := range listCustomerTag2 {
err := query.AddCustomerIdMappingToIncidentId(db, index, result.IncidentId)
if err != nil {
logger.Error("AddCustomerIMappingToIncidentId error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
}
}
func UpdateTeamTagId(result *entity.IncidentSeverityTeamJoinEntity, callback slack.InteractionCallback, db *gorm.DB, logger *zap.Logger, tagIdList []int, tagIdRequests []int) {
list1, list2 := diffLists(tagIdList, tagIdRequests)
for _, o := range list1 {
err := query.SetTagsDeletedAt(db, o, result.IncidentId)
if err != nil {
logger.Error("SetDeletedAt error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
}
for _, index := range list2 {
err := query.AddTagsIdMappingToIncidentId(db, index, result.IncidentId)
if err != nil {
logger.Error("AddTagsIdMappingToIncidentId error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
}
}

View File

@@ -0,0 +1,111 @@
package command
import (
"fmt"
"houston/pkg/postgres/query"
houston "houston/pkg/slack/houston/design"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type incidentUpdateTitleProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewIncidentUpdateTitleProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateTitleProcessor {
return &incidentUpdateTitleProcessor{
client: client,
db: db,
logger: logger,
}
}
func (itp *incidentUpdateTitleProcessor) IncidentUpdateTitleRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
result, err := query.FindIncidentByChannelId(itp.db, callback.Channel.ID)
if err != nil {
itp.logger.Error("FindIncidentByChannelId error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
} else if result == nil {
itp.logger.Error("IncidentEntity not found ",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
modalRequest := houston.BuildIncidentUpdateTitleModal(itp.db, callback.Channel, result.Title)
_, err = itp.client.OpenView(callback.TriggerID, modalRequest)
if err != nil {
itp.logger.Error("houston slack openview command for IncidentUpdateTitleRequestProcess failed.",
zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err))
return
}
var payload interface{}
itp.client.Ack(*request, payload)
}
func (itp *incidentUpdateTitleProcessor) IncidentUpdateTitle(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) {
incidentEntity, err := query.FindIncidentByChannelId(itp.db, callback.View.PrivateMetadata)
if err != nil {
itp.logger.Error("FindIncidentByChannelId error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
} else if incidentEntity == nil {
itp.logger.Error("IncidentEntity not found ",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
incidentTitle := buildUpdateIncidentTitleRequest(callback.View.State.Values)
incidentEntity.Title = incidentTitle
incidentEntity.UpdatedBy = user.ID
err = query.UpdateIncident(itp.db, incidentEntity)
if err != nil {
itp.logger.Error("IncidentUpdateTitle error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
}
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set title to %s", user.ID, incidentEntity.Title), false)
_, _, errMessage := itp.client.PostMessage(callback.View.PrivateMetadata, msgOption)
if errMessage != nil {
itp.logger.Error("post response failed for IncidentUpdateTitle", zap.Error(errMessage))
return
}
result, err := query.FindIncidentSeverityTeamJoin(itp.db, incidentEntity.SlackChannel)
if err != nil {
itp.logger.Error("query failed for FindIncidentSeverityTeamJoin", zap.Error(errMessage))
return
}
msgOption = slack.MsgOptionText(fmt.Sprintf("set the channel topic: %s : %s %s | %s", result.TeamsName, result.SeverityName, incidentEntity.IncidentName, incidentEntity.Title), false)
_, _, errMessage = itp.client.PostMessage(callback.View.PrivateMetadata, msgOption)
if errMessage != nil {
itp.logger.Error("post response failed for IncidentUpdateTitle", zap.Error(errMessage))
return
}
var payload interface{}
itp.client.Ack(*request, payload)
}
func buildUpdateIncidentTitleRequest(blockActions map[string]map[string]slack.BlockAction) string {
var requestMap = make(map[string]string, 0)
for _, actions := range blockActions {
for actionID, action := range actions {
if action.Type == "plain_text_input" {
requestMap[actionID] = action.Value
}
}
}
return requestMap["incident_title"]
}

View File

@@ -0,0 +1,123 @@
package command
import (
"fmt"
"houston/pkg/postgres/query"
"houston/pkg/slack/common"
houston "houston/pkg/slack/houston/design"
"strconv"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type incidentUpdateTypeProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewIncidentUpdateTypeProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateTypeProcessor {
return &incidentUpdateTypeProcessor{
client: client,
db: db,
logger: logger,
}
}
func (itp *incidentUpdateTypeProcessor) IncidentUpdateTypeRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
teams, err := query.FindTeamList(itp.db)
if err != nil {
itp.logger.Error("FindTeamList error",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
modalRequest := houston.BuildIncidentUpdateTypeModal(callback.Channel, teams)
_, err = itp.client.OpenView(callback.TriggerID, modalRequest)
if err != nil {
itp.logger.Error("houston slack openview command failed.",
zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err))
return
}
var payload interface{}
itp.client.Ack(*request, payload)
}
func (isp *incidentUpdateTypeProcessor) IncidentUpdateType(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) {
incidentEntity, err := query.FindIncidentByChannelId(isp.db, callback.View.PrivateMetadata)
if err != nil {
isp.logger.Error("FindIncidentByChannelId error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
} else if incidentEntity == nil {
isp.logger.Error("IncidentEntity not found ",
zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name),
zap.String("user_id", callback.User.ID), zap.Error(err))
return
}
incidentTypeId := buildUpdateIncidentTypeRequest(isp.logger, callback.View.State.Values)
result, err := query.FindTeamById(isp.db, incidentTypeId)
if err != nil {
isp.logger.Error("FindTeamEntityById error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
} else if result == nil {
isp.logger.Error("Team Not Found",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
}
incidentEntity.TeamsId = int(result.ID)
incidentEntity.UpdatedBy = user.ID
err = query.UpdateIncident(isp.db, incidentEntity)
if err != nil {
isp.logger.Error("UpdateIncident error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
}
userIdList, err := query.FindDefaultUserIdToBeAddedByTeam(isp.db, int(result.ID))
if err != nil {
isp.logger.Error("FindDefaultUserIdToBeAddedByTeam error",
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
zap.String("user_id", user.ID), zap.Error(err))
return
}
for _, o := range userIdList {
common.InviteUsersToConversation(isp.client, isp.logger, callback.View.PrivateMetadata, o)
}
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set Team to %s", user.ID, result.Name), false)
_, _, errMessage := isp.client.PostMessage(callback.View.PrivateMetadata, msgOption)
if errMessage != nil {
isp.logger.Error("post response failed for IncidentUpdateType", zap.Error(errMessage))
return
}
var payload interface{}
isp.client.Ack(*request, payload)
}
func buildUpdateIncidentTypeRequest(logger *zap.Logger, blockActions map[string]map[string]slack.BlockAction) int {
var requestMap = make(map[string]string, 0)
for _, actions := range blockActions {
for actionID, action := range actions {
if action.Type == "static_select" {
requestMap[actionID] = action.SelectedOption.Value
}
}
}
selectedValue := requestMap["incident_type_modal_request"]
selectedValueInInt, err := strconv.Atoi(selectedValue)
if err != nil {
logger.Error("String conversion to int faileed in buildUpdateIncidentTypeRequest for "+selectedValue, zap.Error(err))
}
return selectedValueInInt
}

View File

@@ -0,0 +1,55 @@
package command
import (
"houston/pkg/postgres/query"
houston "houston/pkg/slack/houston/design"
"github.com/slack-go/slack"
"github.com/slack-go/slack/slackevents"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type memberJoinProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewMemberJoinProcessor(socketmodeClient *socketmode.Client, db *gorm.DB, logger *zap.Logger) *memberJoinProcessor {
return &memberJoinProcessor{
client: socketmodeClient,
db: db,
logger: logger,
}
}
func (mp *memberJoinProcessor) MemberJoinProcessCommand(memberJoinedChannelEvent *slackevents.MemberJoinedChannelEvent) {
mp.logger.Info("processing member join event", zap.String("channel", memberJoinedChannelEvent.Channel))
incidentEntity, err := query.FindIncidentByChannelId(mp.db, memberJoinedChannelEvent.Channel)
if err != nil {
mp.logger.Error("error in searching incident", zap.String("channel", memberJoinedChannelEvent.Channel),
zap.String("user_id", memberJoinedChannelEvent.User), zap.Error(err))
return
} else if err == nil && incidentEntity == nil {
mp.logger.Info("incident not found", zap.String("channel", memberJoinedChannelEvent.Channel),
zap.String("user_id", memberJoinedChannelEvent.User), zap.Error(err))
return
}
blocks, err := houston.IncidentSummarySection(incidentEntity, mp.db)
if err != nil {
mp.logger.Error("error in creating incident summary section inside incident",
zap.String("channel", memberJoinedChannelEvent.Channel),
zap.String("user_id", memberJoinedChannelEvent.User), zap.Error(err))
return
}
mp.logger.Info("member join block", zap.Any("blocks", blocks))
msgOption := slack.MsgOptionBlocks(blocks...)
_, err = mp.client.PostEphemeral(memberJoinedChannelEvent.Channel, memberJoinedChannelEvent.User, msgOption)
if err != nil {
mp.logger.Error("post response failed", zap.Error(err))
}
}

View File

@@ -0,0 +1,49 @@
package command
import (
"houston/pkg/postgres/query"
houston "houston/pkg/slack/houston/design"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"github.com/spf13/viper"
"go.uber.org/zap"
"gorm.io/gorm"
)
type ShowIncidentsButtonProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func ShowIncidentsProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *ShowIncidentsButtonProcessor {
return &ShowIncidentsButtonProcessor{
client: client,
db: db,
logger: logger,
}
}
func (sip *ShowIncidentsButtonProcessor) ProcessShowIncidentsButtonCommand(channel slack.Channel, user slack.User, triggerId string, request *socketmode.Request) {
limit := viper.GetInt("SHOW_INCIDENTS_LIMT")
s, err := query.FindNotResolvedLatestIncidents(sip.db, limit)
if err != nil {
sip.logger.Error("FindNotResolvedLatestIncidents query failed.",
zap.String("trigger_id", triggerId), zap.String("channel_id", channel.ID), zap.Error(err))
return
}
blocks := houston.GenerateModalForShowIncidentsButtonSection(s)
msgOption := slack.MsgOptionBlocks(blocks...)
_, err = sip.client.Client.PostEphemeral(channel.ID, user.ID, msgOption)
if err != nil {
sip.logger.Error("houston slack PostEphemeral command failed for ProcessShowIncidentsButtonCommand.",
zap.String("trigger_id", triggerId), zap.String("channel_id", channel.ID), zap.String("user_id", user.ID), zap.Error(err))
return
}
var payload interface{}
sip.client.Ack(*request, payload)
}

View File

@@ -0,0 +1,36 @@
package command
import (
houston "houston/pkg/slack/houston/design"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type startIncidentButtonProcessor struct {
client *socketmode.Client
db *gorm.DB
logger *zap.Logger
}
func NewStartIncidentProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *startIncidentButtonProcessor {
return &startIncidentButtonProcessor{
client: client,
db: db,
logger: logger,
}
}
func (sip *startIncidentButtonProcessor) ProcessStartIncidentButtonCommand(channelId, triggerId string) {
modal := houston.GenerateModalRequest(sip.db, sip.logger, channelId)
_, err := sip.client.OpenView(triggerId, modal)
if err != nil {
sip.logger.Error("houston slack openview command failed.",
zap.String("trigger_id", triggerId), zap.String("channel_id", channelId), zap.Error(err))
return
}
sip.logger.Info("houston successfully send modal to slack", zap.String("trigger_id", triggerId))
// client.Ack(*request, payload)
}

View File

@@ -0,0 +1,203 @@
package command
import (
"context"
"encoding/json"
"fmt"
"houston/entity"
"houston/model"
"houston/model/request"
"houston/pkg/postgres/query"
"houston/pkg/slack/common"
houston "houston/pkg/slack/houston/design"
"time"
"github.com/google/uuid"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"google.golang.org/api/calendar/v3"
"google.golang.org/api/option"
"gorm.io/gorm"
)
type createIncidentProcessor struct {
client *socketmode.Client
logger *zap.Logger
db *gorm.DB
}
func NewCreateIncidentProcessor(client *socketmode.Client, logger *zap.Logger, db *gorm.DB) *createIncidentProcessor {
return &createIncidentProcessor{
client: client,
logger: logger,
db: db,
}
}
func (cip *createIncidentProcessor) CreateIncidentModalCommandProcessing(callback slack.InteractionCallback, request *socketmode.Request) {
// Build create incident request
createIncidentRequest := buildCreateIncidentRequest(callback.View.State.Values)
createIncidentRequest.RequestOriginatedSlackChannel = callback.View.PrivateMetadata
cip.logger.Info("incident request created", zap.Any("request", createIncidentRequest))
// Create a new Slack channel for the incident
channelName := fmt.Sprintf("incident-%s", time.Now().Format("150405"))
channelID, err := common.CreateChannel(cip.client, cip.logger, channelName)
if err != nil {
cip.logger.Error("Unable to create channel", zap.Error(err))
}
createIncidentRequest.Status = entity.Investigating
createIncidentRequest.IncidentName = channelName
createIncidentRequest.SlackChannel = channelID
// Set default values for some fields
createIncidentRequest.DetectionTime = time.Now()
createIncidentRequest.CustomerImpactStartTime = time.Now()
createIncidentRequest.CustomerImpactEndTime = time.Now()
createIncidentRequest.TeamsId = 1
createIncidentRequest.JiraId = ""
createIncidentRequest.ConfluenceId = ""
createIncidentRequest.RemindMeAt = time.Now()
createIncidentRequest.EnableReminder = false
createIncidentRequest.CreatedBy = callback.User.ID
createIncidentRequest.UpdatedBy = callback.User.ID
createIncidentRequest.Version = 0
// Save the incident to the database
incidentEntity, err := query.CreateIncident(cip.db, createIncidentRequest)
if err != nil {
cip.logger.Error("Error while creating incident", zap.Error(err))
}
// Post incident summary to Blaze Group channel and incident channel
err = postIncidentSummary(cip.client, callback.View.PrivateMetadata, channelID, incidentEntity, cip.db)
if err != nil {
cip.logger.Error("error while posting incident summary", zap.Error(err))
}
err = addDefaultUsersToIncident(cip, channelID, incidentEntity.SeverityId, incidentEntity.TeamsId)
if err != nil {
cip.logger.Error("error while adding default users to incident", zap.Error(err))
}
//gmeet := createGmeetLink(cip.logger, channelName)
//if len(gmeet) != 0 {
// msgOption := slack.MsgOptionText(fmt.Sprintf("gmeet Link :", gmeet), false)
// cip.client.PostMessage(channelID, msgOption)
//}
// Acknowledge the interaction callback
var payload interface{}
cip.client.Ack(*request, payload)
}
func addDefaultUsersToIncident(cip *createIncidentProcessor, channelId string, severityId, teamId int) (error) {
userIdList, err := query.FindDefaultUserIdToBeAddedBySeverity(cip.db, severityId)
if err != nil {
return err
}
for _, o := range userIdList {
common.InviteUsersToConversation(cip.client, cip.logger, channelId, o)
}
userIdList, err = query.FindDefaultUserIdToBeAddedByTeam(cip.db, teamId)
if err != nil {
return err
}
for _, o := range userIdList {
common.InviteUsersToConversation(cip.client, cip.logger, channelId, o)
}
return nil
}
func postIncidentSummary(
client *socketmode.Client,
blazeGroupChannelID, incidentChannelID string,
incidentEntity *entity.IncidentEntity,
db *gorm.DB) error {
// Post incident summary to Blaze Group channel and incident channel
blocks, err := houston.IncidentSummarySection(incidentEntity, db)
if err != nil {
return fmt.Errorf("error in creating incident summary err: %v", err)
}
msgOptions := []slack.MsgOption{slack.MsgOptionBlocks(blocks...)}
_, timestamp, err := client.PostMessage(blazeGroupChannelID, msgOptions...)
if (err == nil) {
query.CreateMessage(db, &request.CreateMessage{
SlackChannel: blazeGroupChannelID,
MessageTimeStamp: timestamp,
IncidentName: incidentEntity.IncidentName,
})
} else {
return fmt.Errorf("error in saving message %v", err)
}
_, timestamp, err = client.PostMessage(incidentChannelID, msgOptions...)
if (err == nil) {
query.CreateMessage(db, &request.CreateMessage{
SlackChannel: incidentChannelID,
MessageTimeStamp: timestamp,
IncidentName: incidentEntity.IncidentName,
})
} else {
return fmt.Errorf("error in saving message %v", err)
}
return nil
}
func buildCreateIncidentRequest(blockActions map[string]map[string]slack.BlockAction) *model.CreateIncident {
var createIncidentRequest model.CreateIncident
var requestMap = make(map[string]string, 0)
for _, actions := range blockActions {
for actionID, action := range actions {
if action.Type == "plain_text_input" {
requestMap[actionID] = action.Value
}
if action.Type == "static_select" {
requestMap[actionID] = action.SelectedOption.Value
}
}
}
desRequestMap, _ := json.Marshal(requestMap)
json.Unmarshal(desRequestMap, &createIncidentRequest)
return &createIncidentRequest
}
func createGmeetLink(logger *zap.Logger, channelName string) string {
calclient, err := calendar.NewService(context.Background(), option.WithCredentialsFile(""))
if err != nil {
logger.Error("Unable to read client secret file: ", zap.Error(err))
return ""
}
t0 := time.Now().Format(time.RFC3339)
t1 := time.Now().Add(1 * time.Hour).Format(time.RFC3339)
event := &calendar.Event{
Summary: channelName,
Description: "Incident",
Start: &calendar.EventDateTime{
DateTime: t0,
},
End: &calendar.EventDateTime{
DateTime: t1,
},
ConferenceData: &calendar.ConferenceData{
CreateRequest: &calendar.CreateConferenceRequest{
RequestId: uuid.NewString(),
ConferenceSolutionKey: &calendar.ConferenceSolutionKey{
Type: "hangoutsMeet",
},
Status: &calendar.ConferenceRequestStatus{
StatusCode: "success",
},
},
},
}
calendarID := "primary" //use "primary"
event, err = calclient.Events.Insert(calendarID, event).ConferenceDataVersion(1).Do()
if err != nil {
logger.Error("Unable to create event. %v\n", zap.Error(err))
return ""
}
calclient.Events.Delete(calendarID, event.Id).Do()
return event.HangoutLink
}

View File

@@ -0,0 +1,56 @@
package houston
import (
"fmt"
"houston/entity"
"houston/pkg/postgres/query"
"strconv"
"github.com/slack-go/slack"
"gorm.io/gorm"
)
func BuildIncidentUpdateStatusModal(db *gorm.DB, channel slack.Channel) slack.ModalViewRequest {
titleText := slack.NewTextBlockObject("plain_text", "Set Status of Incident", false, false)
closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false)
headerText := slack.NewTextBlockObject("mrkdwn", "Status", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
incidentStatus, _ := query.FetchAllIncidentStatus(db)
incidentStatusBlockOption := createIncidentStatusBlock(incidentStatus)
incidentStatusText := slack.NewTextBlockObject(slack.PlainTextType, "Incident Status", false, false)
incidentStatusOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, nil, "incident_status_modal_request", incidentStatusBlockOption...)
incidentStatusBlock := slack.NewInputBlock("incident_status_modal_request_input", incidentStatusText, nil, incidentStatusOption)
blocks := slack.Blocks{
BlockSet: []slack.Block{
headerSection,
incidentStatusBlock,
},
}
return slack.ModalViewRequest{
Type: slack.ViewType("modal"),
Title: titleText,
Close: closeText,
Submit: submitText,
Blocks: blocks,
PrivateMetadata: channel.ID,
CallbackID: "setIncidentStatus",
}
}
func createIncidentStatusBlock(options []entity.IncidentStatusEntity) []*slack.OptionBlockObject {
optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options))
for _, o := range options {
txt := fmt.Sprintf("%s - %s", o.Name, o.Description)
optionText := slack.NewTextBlockObject(slack.PlainTextType, txt, false, false)
optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.FormatUint(uint64(o.ID), 10), optionText, nil))
}
return optionBlockObjects
}

View File

@@ -0,0 +1,92 @@
package houston
import (
"houston/entity"
"houston/pkg/postgres/query"
"strconv"
"github.com/slack-go/slack"
"go.uber.org/zap"
"gorm.io/gorm"
)
func GenerateModalRequest(db *gorm.DB, logger *zap.Logger, channel string) slack.ModalViewRequest {
// Create a ModalViewRequest with a header and two inputs
titleText := slack.NewTextBlockObject("plain_text", "Houston", false, false)
closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
submitText := slack.NewTextBlockObject("plain_text", "Send", false, false)
headerText := slack.NewTextBlockObject("mrkdwn", "Start Incident", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
teams, _ := query.FindTeam(db)
incidentTypeOptions := createOptionBlockObjectsForTeams(teams)
incidentTypeText := slack.NewTextBlockObject(slack.PlainTextType, "Incident Type", false, false)
incidentTypePlaceholder := slack.NewTextBlockObject("plain_text", "The incident type for the incident to create", false, false)
incidentTypeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, incidentTypePlaceholder, "incident_type", incidentTypeOptions...)
incidentTypeBlock := slack.NewInputBlock("incident_type", incidentTypeText, nil, incidentTypeOption)
severities, _ := query.FindSeverity(db, logger)
severityOptions := createOptionBlockObjectsForSeverity(severities)
severityText := slack.NewTextBlockObject(slack.PlainTextType, "Severity", false, false)
severityTextPlaceholder := slack.NewTextBlockObject("plain_text", "The severity for the incident to create", false, false)
severityOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, severityTextPlaceholder, "incident_severity", severityOptions...)
severityBlock := slack.NewInputBlock("incident_severity", severityText, nil, severityOption)
pagerDutyImpactedServiceText := slack.NewTextBlockObject("plain_text", "Pagerduty", false, false)
pagerDutyImpactedServicePlaceholder := slack.NewTextBlockObject("plain_text", "Select pagerduty impacted services", false, false)
pagerDutyImpactedServiceElement := slack.NewPlainTextInputBlockElement(pagerDutyImpactedServicePlaceholder, "pagerduty_impacted")
pagerDutyImpactedService := slack.NewInputBlock("Pagerduty impacted service", pagerDutyImpactedServiceText, nil, pagerDutyImpactedServiceElement)
pagerDutyImpactedService.Optional = true
incidentTitleText := slack.NewTextBlockObject("plain_text", "Incident Title", false, false)
incidentTitlePlaceholder := slack.NewTextBlockObject("plain_text", "Write something", false, false)
incidentTitleElement := slack.NewPlainTextInputBlockElement(incidentTitlePlaceholder, "incident_title")
incidentTitle := slack.NewInputBlock("Incident title", incidentTitleText, nil, incidentTitleElement)
incidentDescriptionText := slack.NewTextBlockObject("plain_text", "Incident description", false, false)
incidentDescriptionPlaceholder := slack.NewTextBlockObject("plain_text", "Write something", false, false)
incidentDescriptionElement := slack.NewPlainTextInputBlockElement(incidentDescriptionPlaceholder, "incident_description")
incidentDescriptionElement.Multiline = true
incidentDescription := slack.NewInputBlock("Incident description", incidentDescriptionText, nil, incidentDescriptionElement)
incidentDescription.Optional = true
blocks := slack.Blocks{
BlockSet: []slack.Block{
headerSection,
incidentTypeBlock,
severityBlock,
pagerDutyImpactedService,
incidentTitle,
incidentDescription,
},
}
return slack.ModalViewRequest{
Type: slack.ViewType("modal"),
Title: titleText,
Close: closeText,
Submit: submitText,
Blocks: blocks,
PrivateMetadata: channel,
CallbackID: "start_incident_button",
}
}
func createOptionBlockObjectsForSeverity(options []entity.SeverityEntity) []*slack.OptionBlockObject {
optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options))
for _, o := range options {
optionText := slack.NewTextBlockObject(slack.PlainTextType, o.Name, false, false)
optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.Itoa(int(o.ID)), optionText, nil))
}
return optionBlockObjects
}
func createOptionBlockObjectsForTeams(options []string) []*slack.OptionBlockObject {
optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options))
for _, o := range options {
optionText := slack.NewTextBlockObject(slack.PlainTextType, o, false, false)
optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(o, optionText, nil))
}
return optionBlockObjects
}

View File

@@ -0,0 +1,285 @@
package houston
import "github.com/slack-go/slack"
func ButtonDesign() map[string]interface{} {
payload := map[string]interface{}{
"blocks": []slack.Block{
slack.NewActionBlock("start_incident_button",
slack.NewButtonBlockElement(
"start_incident_button",
"start_incident_button_value",
&slack.TextBlockObject{
Type: slack.PlainTextType,
Text: "Start Incident",
},
),
slack.NewButtonBlockElement(
"show_incidents_button",
"show_incidents_button_value",
&slack.TextBlockObject{
Type: slack.PlainTextType,
Text: "Show Incidents",
},
),
slack.NewButtonBlockElement(
"show_on_call_button",
"show_on_call_button_value",
&slack.TextBlockObject{
Type: slack.PlainTextType,
Text: "Show On-Call",
},
),
slack.NewButtonBlockElement(
"help_commands_button",
"help_commands_button_value",
&slack.TextBlockObject{
Type: slack.PlainTextType,
Text: "Help-Commands",
},
)),
},
}
return payload
// client.Ack(*request, payload)
}
func OptionsBlock() map[string]interface{} {
return map[string]interface{}{
"blocks": []slack.Block{
incidentSectionBlock(),
taskSectionBlock(),
tagsSectionBlock(),
onCallSectionBlock(),
botHelpSectionBlock(),
},
}
}
func incidentSectionBlock() *slack.SectionBlock {
textBlock := &slack.TextBlockObject{
Type: "mrkdwn",
Text: "Incident",
}
optionBlockObjects := []*slack.OptionBlockObject{
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Assign Incident Role",
},
Value: "assignIncidentRole",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Resolve Incident",
},
Value: "resolveIncident",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Set Incident Status",
},
Value: "setIncidentStatus",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Set Incident Type",
},
Value: "setIncidentType",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Set Incident Severity",
},
Value: "setIncidentSeverity",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Set Incident Title",
},
Value: "setIncidentTitle",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Set Incident Description",
},
Value: "setIncidentDescription",
},
}
accessoryOption := &slack.Accessory{
SelectElement: &slack.SelectBlockElement{
Type: "static_select",
ActionID: "incident",
Options: optionBlockObjects,
Placeholder: slack.NewTextBlockObject("plain_text", "Select Command", false, false),
},
}
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("incident"))
}
func taskSectionBlock() *slack.SectionBlock {
textBlock := &slack.TextBlockObject{
Type: "mrkdwn",
Text: "Task",
}
optionBlockObjects := []*slack.OptionBlockObject{
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Add Task",
},
Value: "addTask",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Assign Task",
},
Value: "assignTask",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "View Your Tasks",
},
Value: "viewYourTasks",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "View All Tasks",
},
Value: "viewAllTasks",
},
}
accessoryOption := &slack.Accessory{
SelectElement: &slack.SelectBlockElement{
Type: "static_select",
ActionID: "task",
Options: optionBlockObjects,
Placeholder: slack.NewTextBlockObject("plain_text", "Select Command", false, false),
},
}
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("task"))
}
func tagsSectionBlock() *slack.SectionBlock {
textBlock := &slack.TextBlockObject{
Type: "mrkdwn",
Text: "Tags",
}
optionBlockObjects := []*slack.OptionBlockObject{
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Add Tag(s)",
},
Value: "addTags",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Show Tags",
},
Value: "showTags",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Remove Tag",
},
Value: "removeTag",
},
}
accessoryOption := &slack.Accessory{
SelectElement: &slack.SelectBlockElement{
Type: "static_select",
ActionID: "tags",
Options: optionBlockObjects,
Placeholder: slack.NewTextBlockObject("plain_text", "Select Command", false, false),
},
}
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("tags"))
}
func onCallSectionBlock() *slack.SectionBlock {
textBlock := &slack.TextBlockObject{
Type: "mrkdwn",
Text: "On-Call",
}
optionBlockObjects := []*slack.OptionBlockObject{
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Show On-Call",
},
Value: "showOncall",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Show Escalation Policy",
},
Value: "showEscalationPolicy",
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Trigger Alert",
},
Value: "triggerAlert",
},
}
accessoryOption := &slack.Accessory{
SelectElement: &slack.SelectBlockElement{
Type: "static_select",
ActionID: "on-call",
Options: optionBlockObjects,
Placeholder: slack.NewTextBlockObject("plain_text", "Select Command", false, false),
},
}
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("on-call"))
}
func botHelpSectionBlock() *slack.SectionBlock {
textBlock := &slack.TextBlockObject{
Type: "mrkdwn",
Text: "Bot Help",
}
optionBlockObjects := []*slack.OptionBlockObject{
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: "Help - Commands",
},
Value: "helpCommands",
},
}
accessoryOption := &slack.Accessory{
SelectElement: &slack.SelectBlockElement{
Type: "static_select",
ActionID: "bot-helps",
Options: optionBlockObjects,
Placeholder: slack.NewTextBlockObject("plain_text", "Select Command", false, false),
},
}
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("bot-helps"))
}

View File

@@ -0,0 +1,29 @@
package houston
import (
"houston/entity"
"github.com/slack-go/slack"
)
func GenerateModalForShowIncidentsButtonSection(incident []entity.IncidentSeverityTeamJoinEntity) []slack.Block {
contextBlock := slack.NewContextBlock("", slack.NewTextBlockObject("mrkdwn", ":eye: Only visible to you", false, false))
sectionBlocks := []*slack.SectionBlock{}
for i := 0; i < len(incident); i++ {
fields := []*slack.TextBlockObject{
slack.NewTextBlockObject("mrkdwn", "\n`"+incident[i].SeverityName+"` `"+incident[i].TeamsName+" Incident` \n "+incident[i].Title+"\n <#"+incident[i].SlackChannel+">", false, false),
}
sectionBlocks = append(sectionBlocks, slack.NewSectionBlock(nil, fields, nil))
}
blocks := []slack.Block{
contextBlock,
}
for i := 0; i < len(sectionBlocks); i++ {
blocks = append(blocks, sectionBlocks[i])
}
return blocks
}

View File

@@ -0,0 +1,79 @@
package houston
import (
"fmt"
"houston/entity"
"houston/pkg/postgres/query"
"github.com/slack-go/slack"
"gorm.io/gorm"
)
func SummarySection(incidentEntity *entity.IncidentEntity) []slack.Block {
return []slack.Block{
buildSummaryHeader(incidentEntity.Title),
buildTypeAndChannelSectionBlock(incidentEntity, "", ""),
buildSeverityAndTicketSectionBlock(incidentEntity),
buildStatusAndMeetingSectionBlock(incidentEntity),
}
}
func IncidentSummarySection(incidentEntity *entity.IncidentEntity, db *gorm.DB) ([]slack.Block, error) {
teamEntity, err := query.FindTeamById(db, incidentEntity.TeamsId)
if err != nil {
return nil, fmt.Errorf("error in searching team with id: %d, err: %v", incidentEntity.TeamsId, err)
}
severityEntity, err := query.FindSeverityById(db, incidentEntity.SeverityId)
if err != nil {
return nil, fmt.Errorf("error in searching severity with id: %d, err: %v", incidentEntity.SeverityId, err)
}
return []slack.Block{
buildTypeAndChannelSectionBlock(incidentEntity, teamEntity.Name, severityEntity.Name),
}, nil
}
func buildSummaryHeader(incidentTitle string) *slack.SectionBlock {
headerText := slack.NewTextBlockObject("plain_text", incidentTitle, true, true)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
return headerSection
}
func buildTypeAndChannelSectionBlock(incidentEntity *entity.IncidentEntity, teamName, severityName string) *slack.SectionBlock {
fields := []*slack.TextBlockObject{
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*<@%s>* \n*%s* - *%s*\n", incidentEntity.CreatedBy, incidentEntity.IncidentName, incidentEntity.Title), false, false),
slack.NewTextBlockObject("mrkdwn", "\n", false, false),
slack.NewTextBlockObject("mrkdwn", incidentEntity.Description, false, false),
slack.NewTextBlockObject("mrkdwn", "\n", false, false),
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Type*\n%s", teamName), false, false),
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Channel*\n<#%s>", incidentEntity.SlackChannel), false, false),
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Severity*\n%s", severityName), false, false),
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Ticket*\n%s", "Integration Disabled"), false, false),
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Status*\n%s", incidentEntity.Status), false, false),
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Meeting*\n%s", "Integration Disabled"), false, false),
}
block := slack.NewSectionBlock(nil, fields, nil)
return block
}
func buildSeverityAndTicketSectionBlock(incidentEntity *entity.IncidentEntity) *slack.SectionBlock {
fields := []*slack.TextBlockObject{
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Severity*\n%d", incidentEntity.SeverityId), false, false),
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Ticket*\n%s", "integration disabled"), false, false),
}
block := slack.NewSectionBlock(nil, fields, nil)
return block
}
func buildStatusAndMeetingSectionBlock(incidentEntity *entity.IncidentEntity) *slack.SectionBlock {
fields := []*slack.TextBlockObject{
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Status*\n%s", incidentEntity.Status), false, false),
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Meeting*\n%s", "integration disabled"), false, false),
}
block := slack.NewSectionBlock(nil, fields, nil)
return block
}

View File

@@ -0,0 +1,67 @@
package houston
import (
"houston/entity"
"github.com/slack-go/slack"
)
func GenerateModalForIncidentAssign(channel slack.Channel) slack.ModalViewRequest {
titleText := slack.NewTextBlockObject("plain_text", "Houston", false, false)
closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false)
headerText := slack.NewTextBlockObject("mrkdwn", ":information_source: Incident roles are customizable by your organization. While a user can be assigned multiple roles, a role cannot be assigned to multiple users.", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
optionBlockObjects := []*slack.OptionBlockObject{
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: string(entity.RESPONDER),
},
Value: string(entity.RESPONDER),
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: string(entity.SERVICE_OWNER),
},
Value: string(entity.SERVICE_OWNER),
},
{
Text: &slack.TextBlockObject{
Type: "plain_text",
Text: string(entity.RETROSPECTIVE),
},
Value: string(entity.RETROSPECTIVE),
},
}
rolePlaceholder := slack.NewTextBlockObject("plain_text", "Select a role", false, false)
roleTypeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, rolePlaceholder, "role_type", optionBlockObjects...)
roleTypeText := slack.NewTextBlockObject(slack.PlainTextType, "Roles", false, false)
roleBlock := slack.NewInputBlock("role_type", roleTypeText, nil, roleTypeOption)
userPlaceholder := slack.NewTextBlockObject("plain_text", "Select people", false, false)
userTypeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeUser, userPlaceholder, "users_select")
userTypeText := slack.NewTextBlockObject(slack.PlainTextType, "Users", false, false)
userBlock := slack.NewInputBlock("user_type", userTypeText, nil, userTypeOption)
blocks := slack.Blocks{
BlockSet: []slack.Block{
headerSection,
roleBlock,
userBlock,
},
}
return slack.ModalViewRequest{
Type: slack.ViewType("modal"),
Title: titleText,
Close: closeText,
Submit: submitText,
Blocks: blocks,
PrivateMetadata: channel.ID,
CallbackID: "assignIncidentRole",
}
}

View File

@@ -0,0 +1,42 @@
package houston
import (
"github.com/slack-go/slack"
"gorm.io/gorm"
)
func BuildIncidentUpdateDescriptionModal(db *gorm.DB, channel slack.Channel, description string) slack.ModalViewRequest {
titleText := slack.NewTextBlockObject("plain_text", "Set Description", false, false)
closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false)
headerText := slack.NewTextBlockObject("mrkdwn", "Description", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
incidentDescriptionText := slack.NewTextBlockObject("plain_text", "Incident description", false, false)
incidentDescriptionPlaceholder := slack.NewTextBlockObject("plain_text", "Write something", false, false)
incidentDescriptionElement := slack.NewPlainTextInputBlockElement(incidentDescriptionPlaceholder, "incident_description")
incidentDescriptionElement.Multiline = true
incidentDescriptionElement.InitialValue = description
incidentDescriptionElement.MaxLength = 3000
incidentDescription := slack.NewInputBlock("Incident description", incidentDescriptionText, nil, incidentDescriptionElement)
incidentDescription.Optional = true
blocks := slack.Blocks{
BlockSet: []slack.Block{
headerSection,
incidentDescription,
},
}
return slack.ModalViewRequest{
Type: slack.ViewType("modal"),
Title: titleText,
Close: closeText,
Submit: submitText,
Blocks: blocks,
PrivateMetadata: channel.ID,
CallbackID: "setIncidentDescription",
}
}

View File

@@ -0,0 +1,52 @@
package houston
import (
"fmt"
"houston/entity"
"strconv"
"github.com/slack-go/slack"
)
func BuildIncidentUpdateSeverityModal(channel slack.Channel, incidentSeverity []entity.SeverityEntity) slack.ModalViewRequest {
titleText := slack.NewTextBlockObject("plain_text", "Set Severity of Incident", false, false)
closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false)
headerText := slack.NewTextBlockObject("mrkdwn", "Severity", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
incidentStatusBlockOption := createIncidentSeverityBlock(incidentSeverity)
incidentSeverityText := slack.NewTextBlockObject(slack.PlainTextType, "Incident Severity", false, false)
incidentSeverityOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, nil, "incident_severity_modal_request", incidentStatusBlockOption...)
incidentSeverityBlock := slack.NewInputBlock("incident_severity_modal_request_input", incidentSeverityText, nil, incidentSeverityOption)
blocks := slack.Blocks{
BlockSet: []slack.Block{
headerSection,
incidentSeverityBlock,
},
}
return slack.ModalViewRequest{
Type: slack.ViewType("modal"),
Title: titleText,
Close: closeText,
Submit: submitText,
Blocks: blocks,
PrivateMetadata: channel.ID,
CallbackID: "setIncidentSeverity",
}
}
func createIncidentSeverityBlock(options []entity.SeverityEntity) []*slack.OptionBlockObject {
optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options))
for _, o := range options {
txt := fmt.Sprintf("%s (%s)", o.Name, o.Description)
optionText := slack.NewTextBlockObject(slack.PlainTextType, txt, false, false)
optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.FormatUint(uint64(o.ID), 10), optionText, nil))
}
return optionBlockObjects
}

View File

@@ -0,0 +1,40 @@
package houston
import (
"github.com/slack-go/slack"
"gorm.io/gorm"
)
func BuildIncidentUpdateTitleModal(db *gorm.DB, channel slack.Channel, title string) slack.ModalViewRequest {
titleText := slack.NewTextBlockObject("plain_text", "Set Title of Incident", false, false)
closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false)
headerText := slack.NewTextBlockObject("mrkdwn", "Title", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
incidentTitleText := slack.NewTextBlockObject("plain_text", "Incident Title", false, false)
incidentTitlePlaceholder := slack.NewTextBlockObject("plain_text", "Write something", false, false)
incidentTitleElement := slack.NewPlainTextInputBlockElement(incidentTitlePlaceholder, "incident_title")
incidentTitleElement.InitialValue = title
incidentBelowText := slack.NewTextBlockObject("plain_text", "The title to set for the incident.", false, false)
incidentTitle := slack.NewInputBlock("Incident title", incidentTitleText, incidentBelowText, incidentTitleElement)
blocks := slack.Blocks{
BlockSet: []slack.Block{
headerSection,
incidentTitle,
},
}
return slack.ModalViewRequest{
Type: slack.ViewType("modal"),
Title: titleText,
Close: closeText,
Submit: submitText,
Blocks: blocks,
PrivateMetadata: channel.ID,
CallbackID: "setIncidentTitle",
}
}

View File

@@ -0,0 +1,105 @@
package houston
import (
"fmt"
"houston/entity"
"strconv"
"strings"
"github.com/slack-go/slack"
)
func BuildIncidentUpdateTagModal(channel slack.Channel, tags []entity.TagsEntity, teamName string, savedTags []entity.TagsEntity,
savedContributingFactor *entity.ContributingFactorEntity, contributingFactors []entity.ContributingFactorEntity,
savedCustomerTags []entity.CustomerTagsEntity, customerTags []entity.CustomerTagsEntity, savedDataPlatformTags *entity.IncidentsTagsDataPlatformMapping) slack.ModalViewRequest {
titleText := slack.NewTextBlockObject("plain_text", "Edit Tags", false, false)
closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false)
contributingFactorBlockOption := createContributingFactorBlocks(contributingFactors)
contributingFactorText := slack.NewTextBlockObject(slack.PlainTextType, "CONTRIBUTING-FACTORS", false, false)
contributingFactorPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, fmt.Sprint("SELECT CONTRIBUTING-FACTORS tags"), false, false)
contributingFactorOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, contributingFactorPlaceholder, "incident_cf_modal_request", contributingFactorBlockOption...)
if savedContributingFactor != nil {
contributingFactorOption.InitialOption = slack.NewOptionBlockObject(strconv.FormatUint(uint64(savedContributingFactor.ID), 10), slack.NewTextBlockObject(slack.PlainTextType, savedContributingFactor.Label, false, false), nil)
}
contributingFactorHint := slack.NewTextBlockObject(slack.PlainTextType, "CONTRIBUTING-FACTORS is configured to have one single tag, please select appropriate single tag from the dropdown", false, false)
contributingFactorBlock := slack.NewInputBlock("incident_cf_modal_request_input", contributingFactorText, contributingFactorHint, contributingFactorOption)
contributingFactorBlock.Optional = true
customerBlockOption := createCustomerBlocks(customerTags)
customerText := slack.NewTextBlockObject(slack.PlainTextType, "CUSTOMER", false, false)
customerPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, fmt.Sprint("SELECT CUSTOMER tags"), false, false)
customerOption := slack.NewOptionsMultiSelectBlockElement(slack.MultiOptTypeStatic, customerPlaceholder, "incident_customer_tags_modal_request", customerBlockOption...)
customerOption.InitialOptions = createCustomerBlocks(savedCustomerTags)
customerBlock := slack.NewInputBlock("incident_customer_tags_modal_request_input", customerText, nil, customerOption)
customerBlock.Optional = true
incidentStatusBlockOption := createIncidentTagsBlock(tags)
incidentTagText := slack.NewTextBlockObject(slack.PlainTextType, fmt.Sprint("CUSTOMER: "+strings.ToUpper(teamName)), false, false)
incidentTagPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, fmt.Sprint("SELECT CUSTOMER: "+strings.ToUpper(teamName)+" tags"), false, false)
incidentTypeOption := slack.NewOptionsMultiSelectBlockElement(slack.MultiOptTypeStatic, incidentTagPlaceholder, "incident_tags_modal_request", incidentStatusBlockOption...)
incidentTypeOption.InitialOptions = createIncidentTagsBlock(savedTags)
incidentTypeBlock := slack.NewInputBlock("incident_tags_modal_request_input", incidentTagText, nil, incidentTypeOption)
incidentTypeBlock.Optional = true
dataPlatformText := slack.NewTextBlockObject("plain_text", "DATA PLATFORM", false, false)
dataPlatformPlaceholder := slack.NewTextBlockObject("plain_text", "Comma seperated list of tags", false, false)
dataPlatformElement := slack.NewPlainTextInputBlockElement(dataPlatformPlaceholder, "incident_data_platform_tag_modal_request")
if savedDataPlatformTags != nil {
dataPlatformElement.InitialValue = savedDataPlatformTags.DataPlatformTag
}
dataPlatformTitle := slack.NewInputBlock("DATA_PLATFORM", dataPlatformText, nil, dataPlatformElement)
dataPlatformTitle.Optional = true
blocks := slack.Blocks{
BlockSet: []slack.Block{
contributingFactorBlock,
customerBlock,
incidentTypeBlock,
dataPlatformTitle,
},
}
return slack.ModalViewRequest{
Type: slack.ViewType("modal"),
Title: titleText,
Close: closeText,
Submit: submitText,
Blocks: blocks,
PrivateMetadata: channel.ID,
CallbackID: "updateTag",
}
}
func createIncidentTagsBlock(options []entity.TagsEntity) []*slack.OptionBlockObject {
optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options))
for _, o := range options {
txt := fmt.Sprintf(o.Label)
optionText := slack.NewTextBlockObject(slack.PlainTextType, txt, false, false)
optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.FormatUint(uint64(o.ID), 10), optionText, nil))
}
return optionBlockObjects
}
func createContributingFactorBlocks(options []entity.ContributingFactorEntity) []*slack.OptionBlockObject {
optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options))
for _, o := range options {
txt := fmt.Sprintf(o.Label)
optionText := slack.NewTextBlockObject(slack.PlainTextType, txt, false, false)
optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.FormatUint(uint64(o.ID), 10), optionText, nil))
}
return optionBlockObjects
}
func createCustomerBlocks(options []entity.CustomerTagsEntity) []*slack.OptionBlockObject {
optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options))
for _, o := range options {
txt := fmt.Sprintf("%s", o.Label)
optionText := slack.NewTextBlockObject(slack.PlainTextType, txt, false, false)
optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.FormatUint(uint64(o.ID), 10), optionText, nil))
}
return optionBlockObjects
}

View File

@@ -0,0 +1,52 @@
package houston
import (
"fmt"
"houston/entity"
"strconv"
"github.com/slack-go/slack"
)
func BuildIncidentUpdateTypeModal(channel slack.Channel, teams []entity.TeamEntity) slack.ModalViewRequest {
titleText := slack.NewTextBlockObject("plain_text", "Set Type of Incident", false, false)
closeText := slack.NewTextBlockObject("plain_text", "Close", false, false)
submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false)
headerText := slack.NewTextBlockObject("mrkdwn", "Type", false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
incidentStatusBlockOption := createIncidentTypeBlock(teams)
incidentTypeText := slack.NewTextBlockObject(slack.PlainTextType, "Incident Type", false, false)
incidentTypeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, nil, "incident_type_modal_request", incidentStatusBlockOption...)
incidentTypeBlock := slack.NewInputBlock("incident_type_modal_request_input", incidentTypeText, nil, incidentTypeOption)
blocks := slack.Blocks{
BlockSet: []slack.Block{
headerSection,
incidentTypeBlock,
},
}
return slack.ModalViewRequest{
Type: slack.ViewType("modal"),
Title: titleText,
Close: closeText,
Submit: submitText,
Blocks: blocks,
PrivateMetadata: channel.ID,
CallbackID: "setIncidentType",
}
}
func createIncidentTypeBlock(options []entity.TeamEntity) []*slack.OptionBlockObject {
optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options))
for _, o := range options {
txt := fmt.Sprintf("%s", o.Name)
optionText := slack.NewTextBlockObject(slack.PlainTextType, txt, false, false)
optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.FormatUint(uint64(o.ID), 10), optionText, nil))
}
return optionBlockObjects
}

View File

@@ -0,0 +1,181 @@
package houston
import (
"houston/pkg/postgres/query"
"houston/pkg/slack/houston/command"
houston "houston/pkg/slack/houston/design"
"github.com/slack-go/slack"
"github.com/slack-go/slack/slackevents"
"github.com/slack-go/slack/socketmode"
"go.uber.org/zap"
"gorm.io/gorm"
)
type HoustonCommandHandler struct {
logger *zap.Logger
socketmodeClient *socketmode.Client
db *gorm.DB
}
func NewHoustonCommandHandler(socketmodeClient *socketmode.Client, logger *zap.Logger, db *gorm.DB) *HoustonCommandHandler {
return &HoustonCommandHandler{
socketmodeClient: socketmodeClient,
logger: logger,
db: db,
}
}
func (bch *HoustonCommandHandler) ProcessSlashCommand(evt socketmode.Event) {
HoustonMainCommand(bch.db, bch.socketmodeClient, bch.logger, &evt)
}
func (bch *HoustonCommandHandler) ProcessCallbackEvent(callback slack.InteractionCallback) {
bch.logger.Info("process callback event", zap.Any("callback", callback))
}
func (bch *HoustonCommandHandler) ProcessModalCallbackEvent(callback slack.InteractionCallback, request *socketmode.Request) {
var callbackId = callback.View.CallbackID
switch callbackId {
case "start_incident_button":
cip := command.NewCreateIncidentProcessor(bch.socketmodeClient, bch.logger, bch.db)
cip.CreateIncidentModalCommandProcessing(callback, request)
case "assignIncidentRole":
iap := command.NewIncidentAssignProcessor(bch.socketmodeClient, bch.db, bch.logger)
iap.IncidentAssignModalCommandProcessing(callback, request)
case "setIncidentStatus":
isp := command.NewIncidentUpdateStatusProcessor(bch.socketmodeClient, bch.db, bch.logger)
isp.IncidentUpdateStatus(callback, request, callback.Channel, callback.User)
case "setIncidentTitle":
itp := command.NewIncidentUpdateTitleProcessor(bch.socketmodeClient, bch.db, bch.logger)
itp.IncidentUpdateTitle(callback, request, callback.Channel, callback.User)
case "setIncidentDescription":
idp := command.NewIncidentUpdateDescriptionProcessor(bch.socketmodeClient, bch.db, bch.logger)
idp.IncidentUpdateDescription(callback, request, callback.Channel, callback.User)
case "setIncidentSeverity":
isp := command.NewIncidentUpdateSeverityProcessor(bch.socketmodeClient, bch.db, bch.logger)
isp.IncidentUpdateSeverity(callback, request, callback.Channel, callback.User)
case "setIncidentType":
itp := command.NewIncidentUpdateTypeProcessor(bch.socketmodeClient, bch.db, bch.logger)
itp.IncidentUpdateType(callback, request, callback.Channel, callback.User)
case "updateTag":
itp := command.NewIncidentUpdateTagsProcessor(bch.socketmodeClient, bch.db, bch.logger)
itp.IncidentUpdateTags(callback, request, callback.Channel, callback.User)
}
incidentEntity, _ := query.FindIncidentByChannelId(bch.db, callback.View.PrivateMetadata)
if incidentEntity != nil {
messages, _ := query.FindMessageByIncidentName(bch.db, incidentEntity.IncidentName)
blocks, _ := houston.IncidentSummarySection(incidentEntity, bch.db)
msgOptions := []slack.MsgOption{slack.MsgOptionBlocks(blocks...)}
for _, message := range messages {
bch.socketmodeClient.UpdateMessage(message.SlackChannel, message.MessageTimeStamp, msgOptions...)
}
}
}
func (bch *HoustonCommandHandler) ProcessButtonHandler(callback slack.InteractionCallback, request *socketmode.Request) {
actionId := callback.ActionCallback.BlockActions[0].ActionID
bch.logger.Info("process button callback event", zap.Any("action_id", actionId),
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
switch actionId {
case "start_incident_button":
bch.logger.Info("start incident button command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
sip := command.NewStartIncidentProcessor(bch.socketmodeClient, bch.db, bch.logger)
sip.ProcessStartIncidentButtonCommand(callback.Channel.ID, callback.TriggerID)
case "show_incidents_button":
bch.logger.Info("show incidents button command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
sip := command.ShowIncidentsProcessor(bch.socketmodeClient, bch.db, bch.logger)
sip.ProcessShowIncidentsButtonCommand(callback.Channel, callback.User, callback.TriggerID, request)
case "incident":
bch.processIncidentCommands(callback, request)
case "tags":
bch.processTagsCommands(callback, request)
}
}
func (bch *HoustonCommandHandler) ProcessMemberJoinEvent(memberJoinedChannelEvent *slackevents.MemberJoinedChannelEvent, request *socketmode.Request) {
memberJoinProcessor := command.NewMemberJoinProcessor(bch.socketmodeClient, bch.db, bch.logger)
memberJoinProcessor.MemberJoinProcessCommand(memberJoinedChannelEvent)
var payload interface{}
bch.socketmodeClient.Ack(*request, payload)
}
func (bch *HoustonCommandHandler) processIncidentCommands(callback slack.InteractionCallback, request *socketmode.Request) {
action := callback.ActionCallback.BlockActions[0].SelectedOption.Value
switch action {
case "assignIncidentRole":
bch.logger.Info("incident assign button command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
iap := command.NewIncidentAssignProcessor(bch.socketmodeClient, bch.db, bch.logger)
iap.IncidentAssignProcess(callback, request)
case "resolveIncident":
bch.logger.Info("incident update button command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
irp := command.NewIncidentResolveProcessor(bch.socketmodeClient, bch.db, bch.logger)
irp.IncidentResolveProcess(callback, request)
case "setIncidentStatus":
bch.logger.Info("incident update status command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
isp := command.NewIncidentUpdateStatusProcessor(bch.socketmodeClient, bch.db, bch.logger)
isp.IncidentUpdateStatusRequestProcess(callback, request)
case "setIncidentType":
bch.logger.Info("incident update type command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
itp := command.NewIncidentUpdateTypeProcessor(bch.socketmodeClient, bch.db, bch.logger)
itp.IncidentUpdateTypeRequestProcess(callback, request)
case "setIncidentSeverity":
bch.logger.Info("incident update severity command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
itp := command.NewIncidentUpdateSeverityProcessor(bch.socketmodeClient, bch.db, bch.logger)
itp.IncidentUpdateSeverityRequestProcess(callback, request)
case "setIncidentTitle":
bch.logger.Info("incident update title command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
itp := command.NewIncidentUpdateTitleProcessor(bch.socketmodeClient, bch.db, bch.logger)
itp.IncidentUpdateTitleRequestProcess(callback, request)
case "setIncidentDescription":
bch.logger.Info("incident update description command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
idp := command.NewIncidentUpdateDescriptionProcessor(bch.socketmodeClient, bch.db, bch.logger)
idp.IncidentUpdateDescriptionRequestProcess(callback, request)
}
}
func (bch *HoustonCommandHandler) processTagsCommands(callback slack.InteractionCallback, request *socketmode.Request) {
action := callback.ActionCallback.BlockActions[0].SelectedOption.Value
switch action {
case "addTags":
bch.logger.Info("Add Tags command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
itp := command.NewIncidentUpdateTagsProcessor(bch.socketmodeClient, bch.db, bch.logger)
itp.IncidentUpdateTagsRequestProcess(callback, request)
case "showTags":
bch.logger.Info("Show Tags command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
itp := command.NewIncidentShowTagsProcessor(bch.socketmodeClient, bch.db, bch.logger)
itp.IncidentShowTagsRequestProcess(callback, request)
case "removeTag":
bch.logger.Info("Remove Tags command received",
zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID),
zap.String("user_name", callback.User.Name))
itp := command.NewIncidentUpdateTagsProcessor(bch.socketmodeClient, bch.db, bch.logger)
itp.IncidentUpdateTagsRequestProcess(callback, request)
}
}

231
schema.sql Normal file
View File

@@ -0,0 +1,231 @@
CREATE TABLE tenant (
id SERIAL PRIMARY KEY,
name text,
key text,
url text,
vertical text,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE grafana (
id SERIAL PRIMARY KEY,
name text,
url text,
tenant_id bigint,
status boolean DEFAULT false,
alert_id bigint,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE alerts (
id SERIAL PRIMARY KEY,
name text,
team_name text,
service_name text,
status boolean DEFAULT false,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE incidents (
id SERIAL PRIMARY KEY,
title text,
description text,
status varchar(50),
severity_id bigint,
incident_name text,
slack_channel varchar(100),
detection_time timestamp without time zone,
customer_impact_start_time timestamp without time zone,
customer_impact_end_time timestamp without time zone,
teams_id bigint,
jira_id varchar(100),
confluence_id varchar(100),
created_by varchar(100),
updated_by varchar(100),
severity_tat timestamp without time zone,
remind_me_at timestamp without time zone,
enable_reminder boolean DEFAULT false,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone,
version bigint default 0
);
CREATE TABLE teams (
id SERIAL PRIMARY KEY,
name varchar(50),
oncall_handle varchar(100),
secondary_oncall_handle varchar(100),
manager_handle varchar(100),
secondary_manager_handle varchar(100),
active boolean DEFAULT false,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE teams_severity_user_mapping (
id SERIAL PRIMARY KEY,
entity_type varchar(100),
entity_id bigint,
users_id bigint,
version bigint default 0,
default_add_in_incidents boolean DEFAULT false,
team_role varchar(100),
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name varchar(50),
slack_user_id varchar(100),
active boolean DEFAULT true
);
CREATE TABLE severity (
id SERIAL PRIMARY KEY,
name varchar(50),
description text,
version bigint default 0,
sla int,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE teams_tags_mapping (
id SERIAL PRIMARY KEY,
teams_id bigint,
tag_id bigint,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE incidents_tags_mapping (
id SERIAL PRIMARY KEY,
incident_id bigint,
tag_id bigint,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE incidents_tags_contributing_factor_mapping (
id SERIAL PRIMARY KEY,
incident_id bigint,
contributing_factor_id bigint,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE contributing_factor (
id SERIAL PRIMARY KEY,
label varchar(100),
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE incidents_tags_customer_mapping (
id SERIAL PRIMARY KEY,
incident_id bigint,
customer_tags_id bigint,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE customer_tags (
id SERIAL PRIMARY KEY,
label varchar(100),
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE incidents_tags_data_platform_mapping (
id SERIAL PRIMARY KEY,
incident_id bigint,
data_platform_tag text,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE tags (
id SERIAL PRIMARY KEY,
label text,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE incident_status (
id SERIAL PRIMARY KEY,
name varchar(50),
description text,
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE incident_roles (
id SERIAL PRIMARY KEY,
incident_id bigint,
role varchar(100),
assigned_to_user_slack_id varchar(100),
assigned_by_user_slack_id varchar(100),
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE messages (
id SERIAL PRIMARY KEY,
slack_channel varchar(100),
incident_name text,
message_timestamp varchar(100),
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);
CREATE TABLE audit (
id SERIAL PRIMARY KEY,
incident_id bigint,
event text,
user_name varchar(100),
user_id varchar(100),
version bigint default 0,
created_at timestamp without time zone,
updated_at timestamp without time zone,
deleted_at timestamp without time zone
);