├── LICENSE ├── .build ├── .gitignore ├── cmd ├── utils.go ├── monitor │ ├── Dockerfile │ └── main.go ├── processor │ ├── Dockerfile │ └── main.go ├── files.com-test │ └── main.go └── salesforce-test │ └── main.go ├── athena-monitor.yaml ├── .github ├── workflows │ ├── dependency-check.yaml │ ├── test-and-build.yaml │ ├── ghcr-publish.yaml │ └── docker-publish.yaml └── .jira_sync_config.yaml ├── pkg ├── common │ ├── utils.go │ ├── db │ │ ├── file.go │ │ └── conn.go │ ├── test │ │ ├── config.go │ │ └── client.go │ ├── stringlist.go │ ├── salesforce_test.go │ ├── files-com.go │ └── salesforce.go ├── monitor │ ├── monitor_test.go │ └── monitor.go ├── processor │ ├── processor_test.go │ └── processor.go └── config │ ├── config_test.go │ └── config.go ├── Dockerfile-debug ├── docs ├── source │ ├── index.rst │ └── conf.py ├── Makefile └── make.bat ├── .readthedocs.yaml ├── docker-compose.yml ├── Makefile ├── athena-processor.yaml ├── development-config.yaml ├── go.mod ├── doc └── howto_build.md ├── README.md └── go.sum /LICENSE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.build: -------------------------------------------------------------------------------- 1 | cb358aff-efd8-44c7-b940-5fb9639e5a5e 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea* 2 | *.db 3 | data/* 4 | example-config.yaml 5 | tmp/* 6 | vendor/* 7 | -------------------------------------------------------------------------------- /cmd/utils.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/canonical/athena-core/pkg/config" 5 | ) 6 | 7 | type DaemonParams struct { 8 | Config *config.Config 9 | } 10 | -------------------------------------------------------------------------------- /athena-monitor.yaml: -------------------------------------------------------------------------------- 1 | monitor: 2 | files-delta: 1m 3 | poll-every: 10s 4 | base-tmpdir: "/tmp/athena" 5 | directories: 6 | - "/uploads" 7 | - "/uploads/sosreport" 8 | processor-map: 9 | - type: filename 10 | regex: ".*sosreport.*.tar.[xz|gz]+$" 11 | processor: sosreports 12 | -------------------------------------------------------------------------------- /.github/workflows/dependency-check.yaml: -------------------------------------------------------------------------------- 1 | name: Dependency Review 2 | on: 3 | - pull_request 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | dependency-review: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout Repository 14 | uses: actions/checkout@v4 15 | 16 | - name: Dependency Review 17 | uses: actions/dependency-review-action@v4 18 | -------------------------------------------------------------------------------- /pkg/common/utils.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func RunOnInterval(ctx context.Context, lock *sync.Mutex, d time.Duration, f func(ctx *context.Context, interval time.Duration)) { 10 | ticker := time.Tick(d) 11 | for { 12 | select { 13 | case <-ctx.Done(): 14 | return 15 | case <-ticker: 16 | lock.Lock() 17 | f(&ctx, d) 18 | lock.Unlock() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Dockerfile-debug: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | LABEL maintainer="Canonical Sustaining Engineering " 4 | LABEL org.opencontainers.image.description "Athena Debug Container" 5 | 6 | RUN apt-get update 7 | RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends apt-utils 8 | RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \ 9 | iproute2 \ 10 | iputils-ping 11 | RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \ 12 | default-mysql-client 13 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Athena documentation master file, created by 2 | sphinx-quickstart on Wed Sep 11 20:12:21 2024. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Athena documentation 7 | ==================== 8 | 9 | Add your content using ``reStructuredText`` syntax. See the 10 | `reStructuredText `_ 11 | documentation for details. 12 | 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | :caption: Contents: 17 | 18 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /cmd/monitor/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | LABEL maintainer="Canonical Sustaining Engineering " 3 | LABEL org.opencontainers.image.description "Athena Monitor" 4 | 5 | RUN apt-get update 6 | RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends apt-utils 7 | RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \ 8 | bc \ 9 | bsdmainutils \ 10 | ca-certificates \ 11 | coreutils \ 12 | git \ 13 | jq \ 14 | python3 \ 15 | python3-simplejson \ 16 | python3-yaml \ 17 | xz-utils 18 | RUN update-ca-certificates 19 | RUN mkdir /etc/athena/ 20 | 21 | COPY ./build/athena-monitor /athena-monitor 22 | RUN chmod 755 /athena-monitor 23 | -------------------------------------------------------------------------------- /cmd/processor/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | LABEL maintainer="Canonical Sustaining Engineering " 3 | LABEL org.opencontainers.image.description "Athena Processor" 4 | 5 | RUN apt-get update 6 | RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends apt-utils 7 | RUN DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends \ 8 | bc \ 9 | bsdmainutils \ 10 | ca-certificates \ 11 | coreutils \ 12 | git \ 13 | jq \ 14 | patool \ 15 | pipx \ 16 | python3 \ 17 | python3-simplejson \ 18 | python3-yaml \ 19 | xz-utils 20 | RUN update-ca-certificates 21 | RUN mkdir /etc/athena/ 22 | 23 | COPY ./build/athena-processor /athena-processor 24 | RUN chmod 755 /athena-processor 25 | -------------------------------------------------------------------------------- /pkg/common/db/file.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | type File struct { 10 | gorm.Model 11 | 12 | Created time.Time `gorm:"autoCreateTime"` // Use unix seconds as creating time 13 | Dispatched bool `gorm:"default:false"` 14 | Path string `gorm:"primary_key,size:10240"` 15 | Reports []Report 16 | } 17 | 18 | type Report struct { 19 | gorm.Model 20 | 21 | Created time.Time `gorm:"<-:create"` 22 | Commented bool `gorm:"default:false"` 23 | Subscriber string 24 | Name string 25 | FileName string 26 | FileID uint 27 | FilePath string 28 | CaseID string 29 | Scripts []Script 30 | } 31 | 32 | type Script struct { 33 | gorm.Model 34 | 35 | Output string `gorm:"type:longtext"` 36 | Name string 37 | UploadLocation string 38 | ReportID uint 39 | } 40 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /.github/workflows/test-and-build.yaml: -------------------------------------------------------------------------------- 1 | name: Test and Build 2 | 3 | on: 4 | schedule: 5 | - cron: '38 11 * * *' 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | workflow_dispatch: 13 | # We will include this workflow in the ghcr-build-and-publish.yaml workflow. 14 | # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_call 15 | workflow_call: 16 | 17 | jobs: 18 | test-and-build: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Install Go 23 | uses: actions/setup-go@v5 24 | with: 25 | go-version: "1.23" 26 | 27 | - name: Checkout code 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 0 31 | 32 | - name: Test and Build 33 | run: make all 34 | 35 | - name: Upload build artifacts 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: packages 39 | path: build/* 40 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | project = 'Athena' 10 | copyright = '2024, Sustaining Engineering' 11 | author = 'Sustaining Engineering' 12 | 13 | # -- General configuration --------------------------------------------------- 14 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 15 | 16 | extensions = [] 17 | 18 | templates_path = ['_templates'] 19 | exclude_patterns = [] 20 | 21 | 22 | 23 | # -- Options for HTML output ------------------------------------------------- 24 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 25 | 26 | html_theme = 'alabaster' 27 | html_static_path = ['_static'] 28 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version and other tools you might need 8 | build: 9 | os: ubuntu-22.04 10 | tools: 11 | python: "3.12" 12 | # You can also specify other tool versions: 13 | # nodejs: "20" 14 | # rust: "1.70" 15 | # golang: "1.20" 16 | 17 | # Build documentation in the "docs/" directory with Sphinx 18 | sphinx: 19 | configuration: docs/source/conf.py 20 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 21 | # builder: "dirhtml" 22 | # Fail on all warnings to avoid broken references 23 | # fail_on_warning: true 24 | 25 | # Optionally build your docs in additional formats such as PDF and ePub 26 | # formats: 27 | # - pdf 28 | # - epub 29 | 30 | # Optional but recommended, declare the Python requirements required 31 | # to build your documentation 32 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 33 | # python: 34 | # install: 35 | # - requirements: docs/requirements.txt 36 | -------------------------------------------------------------------------------- /.github/.jira_sync_config.yaml: -------------------------------------------------------------------------------- 1 | settings: 2 | # Jira project key to create the issue in 3 | jira_project_key: "SET" 4 | 5 | # Dictionary mapping GitHub issue status to Jira issue status 6 | status_mapping: 7 | opened: Wishlist 8 | closed: Done 9 | 10 | # (Optional) Jira project components that should be attached to the created 11 | # issue Component names are case-sensitive 12 | components: 13 | - athena 14 | 15 | # (Optional) GitHub labels. Only issues with one of those labels will be 16 | # synchronized. If not specified, all issues will be synchronized 17 | # labels: 18 | # - jira 19 | 20 | # (Optional) (Default: false) Add a new comment in GitHub with a link to Jira 21 | # created issue 22 | add_gh_comment: true 23 | 24 | # (Optional) (Default: true) Synchronize issue description from GitHub to Jira 25 | sync_description: true 26 | 27 | # (Optional) (Default: true) Synchronize comments from GitHub to Jira 28 | sync_comments: true 29 | 30 | # (Optional) (Default: None) Parent Epic key to link the issue to 31 | epic_key: "SET-926" 32 | 33 | # (Optional) Dictionary mapping GitHub issue labels to Jira issue types. If 34 | # label on the issue is not in specified list, this issue will be created as a 35 | # Bug 36 | label_mapping: 37 | enhancement: Story 38 | bug: Story 39 | -------------------------------------------------------------------------------- /pkg/common/test/config.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | var DefaultTestConfig = ` 4 | db: 5 | dialect: sqlite 6 | dsn: "athena:athena@tcp(db:3306)/athena?charset=utf8&parseTime=true" 7 | 8 | monitor: 9 | poll-every: 1s 10 | files-delta: 1s 11 | directories: 12 | - "/uploads" 13 | processor-map: 14 | - type: filename 15 | regex: ".*sosreport.*.tar.xz$" 16 | processor: sosreports 17 | 18 | processor: 19 | batch-comments-every: 1m 20 | base-tmpdir: "/tmp/athena" 21 | subscribers: 22 | sosreports: 23 | sf-comment-enabled: true 24 | sf-comment-batch: 5m 25 | sf-comment-public: false 26 | sf-comment: | 27 | Athena 28 | 29 | Processor: {{ processor }} Subscriber: {{ subscriber }} has run the following reports: 30 | 31 | {% for report in reports %} 32 | * {{ report.Name }}: https://files.support.canonical.com/files/{{report.UploadLocation}} 33 | {% endfor %} 34 | 35 | reports: 36 | hotsos: 37 | exit-codes: 0 2 127 126 38 | script: | 39 | #!/bin/bash 40 | echo ${{filepath}} ${{basedir}} && exit 0 41 | 42 | filescom: 43 | key: "xxx" 44 | endpoint: "https://app.files.com" 45 | 46 | salesforce: 47 | endpoint: "https://xxx--xxxx.my.salesforce.com/" 48 | username: "xxx@xxx.com.xxx" 49 | password: "xxx" 50 | security-token: "xxx"` 51 | -------------------------------------------------------------------------------- /pkg/monitor/monitor_test.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "context" 5 | "github.com/canonical/athena-core/pkg/common/db" 6 | "github.com/canonical/athena-core/pkg/common/test" 7 | "github.com/canonical/athena-core/pkg/config" 8 | "github.com/lileio/pubsub/v2/providers/memory" 9 | "github.com/sirupsen/logrus" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/suite" 12 | "gorm.io/driver/sqlite" 13 | "gorm.io/gorm" 14 | "io" 15 | "testing" 16 | "time" 17 | ) 18 | 19 | func init() { 20 | logrus.SetOutput(io.Discard) 21 | } 22 | 23 | type MonitorTestSuite struct { 24 | suite.Suite 25 | config *config.Config 26 | db *gorm.DB 27 | } 28 | 29 | func (s *MonitorTestSuite) SetupTest() { 30 | s.config, _ = config.NewConfigFromBytes([]byte(test.DefaultTestConfig)) 31 | assert.Equal(s.T(), "sqlite", s.config.Db.Dialect) 32 | s.db, _ = gorm.Open(sqlite.Open("file::memory:?cache=shared")) 33 | s.db.AutoMigrate(db.File{}, db.Report{}) 34 | } 35 | 36 | func (s *MonitorTestSuite) TestRunMonitor() { 37 | provider := &memory.MemoryProvider{} 38 | monitor, err := NewMonitor(provider, s.config, s.db, &test.SalesforceClientFactory{}, &test.FilesComClientFactory{}) 39 | assert.Nil(s.T(), err) 40 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 41 | defer cancel() 42 | _ = monitor.Run(ctx) 43 | assert.NotZero(s.T(), len(provider.Msgs["sosreports"])) 44 | } 45 | 46 | func TestMonitor(t *testing.T) { 47 | suite.Run(t, &MonitorTestSuite{}) 48 | } 49 | -------------------------------------------------------------------------------- /pkg/common/test/client.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "time" 5 | 6 | files_sdk "github.com/Files-com/files-sdk-go" 7 | "github.com/canonical/athena-core/pkg/common" 8 | "github.com/canonical/athena-core/pkg/common/db" 9 | "github.com/canonical/athena-core/pkg/config" 10 | ) 11 | 12 | type SalesforceClient struct { 13 | common.BaseSalesforceClient 14 | } 15 | 16 | func (sf *SalesforceClient) GetCaseByNumber(number string) (*common.Case, error) { 17 | return nil, nil 18 | } 19 | 20 | type SalesforceClientFactory struct{} 21 | 22 | func (sf *SalesforceClientFactory) NewSalesforceClient(config *config.Config) (common.SalesforceClient, error) { 23 | return &SalesforceClient{}, nil 24 | } 25 | 26 | type FilesComClient struct { 27 | common.BaseFilesComClient 28 | } 29 | 30 | type FilesComClientFactory struct{} 31 | 32 | func (fc *FilesComClientFactory) NewFilesComClient(apiKey, endpoint string) (common.FilesComClient, error) { 33 | return &FilesComClient{}, nil 34 | } 35 | 36 | var files = []db.File{ 37 | {Path: "/uploads/sosreport-testing-1.tar.xz"}, 38 | {Path: "/uploads/sosreport-testing-2.tar.xz"}, 39 | {Path: "/uploads/sosreport-testing-3.tar.xz"}, 40 | } 41 | 42 | func (fc *FilesComClient) GetFiles(dirs []string) ([]db.File, error) { 43 | for i := range files { 44 | files[i].Created = time.Now() 45 | } 46 | return files, nil 47 | } 48 | 49 | func (fc *FilesComClient) Download(toDownload *db.File, downloadPath string) (*files_sdk.File, error) { 50 | return &files_sdk.File{}, nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/common/stringlist.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | log "github.com/sirupsen/logrus" 9 | "gopkg.in/alecthomas/kingpin.v2" 10 | ) 11 | 12 | type stringList []string 13 | 14 | func (i *stringList) Set(value string) error { 15 | *i = append(*i, value) 16 | return nil 17 | } 18 | 19 | func (i *stringList) String() string { 20 | return strings.Join(*i, ", ") 21 | } 22 | 23 | func (i *stringList) IsCumulative() bool { 24 | return true 25 | } 26 | 27 | func StringList(s kingpin.Settings) (target *[]string) { 28 | target = new([]string) 29 | s.SetValue((*stringList)(target)) 30 | return 31 | } 32 | 33 | type AthenaFormatter struct{} 34 | 35 | func (f *AthenaFormatter) Format(entry *log.Entry) ([]byte, error) { 36 | return []byte(fmt.Sprintf("%s [%s]: %s\n", entry.Time.Format("2006-01-02 15:04:05"), entry.Level.String(), entry.Message)), nil 37 | } 38 | 39 | func ParseCommandline() { 40 | kingpin.HelpFlag.Short('h') 41 | kingpin.Parse() 42 | } 43 | 44 | func InitLogging(logLevel *string) { 45 | // Log as JSON instead of the default ASCII formatter. 46 | log.SetFormatter(&AthenaFormatter{}) 47 | 48 | // Output to stdout instead of the default stderr 49 | // Can be any io.Writer, see below for File example 50 | log.SetOutput(os.Stdout) 51 | 52 | // Only log the warning severity or above. 53 | level, err := log.ParseLevel(*logLevel) 54 | if err != nil { 55 | log.Errorf("Cannot init set logger level: %s", err) 56 | os.Exit(-1) 57 | } 58 | 59 | log.SetLevel(level) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/common/db/conn.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/canonical/athena-core/pkg/config" 7 | log "github.com/sirupsen/logrus" 8 | "gorm.io/driver/mysql" 9 | "gorm.io/driver/sqlite" 10 | "gorm.io/gorm" 11 | ) 12 | 13 | func GetDBConn(cfg *config.Config) (*gorm.DB, error) { 14 | var dBDriver gorm.Dialector = nil 15 | switch cfg.Db.Dialect { 16 | case "sqlite": 17 | dBDriver = sqlite.Open(cfg.Db.DSN) 18 | case "mysql": 19 | dBDriver = mysql.Open(cfg.Db.DSN) 20 | default: 21 | return nil, fmt.Errorf("unknown database dialect %s", cfg.Db.Dialect) 22 | } 23 | dbInstance, err := gorm.Open(dBDriver) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | switch cfg.Db.Dialect { 29 | case "sqlite": 30 | log.Debugln("Will not change collation") 31 | dbInstance.AutoMigrate(File{}, Report{}, Script{}) 32 | case "mysql": 33 | var lockName = "migrate_lock" 34 | var timeout = 10 // seconds 35 | var lock int 36 | dbInstance.Raw("SELECT GET_LOCK(?, ?)", lockName, timeout).Scan(&lock) 37 | if lock == 1 { 38 | if !dbInstance.Migrator().HasColumn(&File{}, "Path") { 39 | log.Debugln("Changing collation to UTF-8") 40 | dbInstance.AutoMigrate(File{}, Report{}, Script{}) 41 | err = dbInstance.Exec("ALTER TABLE files MODIFY Path VARCHAR(10240) CHARACTER SET utf8 COLLATE utf8_general_ci").Error 42 | if err != nil { 43 | log.Errorln("Could not change collation of files table") 44 | } 45 | } 46 | dbInstance.Exec("DO RELEASE_LOCK(?)", lockName) 47 | } else { 48 | log.Errorln("Could not get lock on database") 49 | } 50 | } 51 | return dbInstance, nil 52 | } 53 | -------------------------------------------------------------------------------- /cmd/files.com-test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | files_sdk "github.com/Files-com/files-sdk-go" 10 | "github.com/Files-com/files-sdk-go/file" 11 | "github.com/Files-com/files-sdk-go/folder" 12 | "github.com/canonical/athena-core/pkg/common" 13 | "github.com/canonical/athena-core/pkg/config" 14 | "gopkg.in/alecthomas/kingpin.v2" 15 | ) 16 | 17 | var configs = common.StringList( 18 | kingpin.Flag("config", "Path to the athena configuration file").Default("/etc/athena/main.yaml").Short('c'), 19 | ) 20 | 21 | var path = kingpin.Flag("path", "Path to check").Default("/").String() 22 | var uploadFile = kingpin.Flag("upload", "File to upload to path").String() 23 | 24 | func main() { 25 | kingpin.HelpFlag.Short('h') 26 | kingpin.Parse() 27 | 28 | cfg, err := config.NewConfigFromFile(*configs) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | filesConfig := files_sdk.Config{ 34 | APIKey: cfg.FilesCom.Key, 35 | Endpoint: cfg.FilesCom.Endpoint, 36 | } 37 | 38 | if *uploadFile != "" { 39 | filename := filepath.Base(*uploadFile) 40 | fileClient := file.Client{Config: filesConfig} 41 | status := fileClient.UploadFile(context.Background(), &file.UploadParams{Source: *uploadFile, Destination: filepath.Join(*path, filename)}) 42 | fmt.Println(status.Files()) 43 | } 44 | 45 | folderClient := folder.Client{Config: filesConfig} 46 | folderIterator, err := folderClient.ListFor(context.Background(), files_sdk.FolderListForParams{Path: *path}) 47 | if err != nil { 48 | fmt.Printf("Error reading folder: %s", err) 49 | os.Exit(1) 50 | } 51 | for folderIterator.Next() { 52 | fmt.Println(folderIterator.Folder().Path) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cmd/monitor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "strings" 8 | "syscall" 9 | 10 | "github.com/canonical/athena-core/pkg/common" 11 | "github.com/canonical/athena-core/pkg/config" 12 | "github.com/canonical/athena-core/pkg/monitor" 13 | "github.com/lileio/pubsub/v2" 14 | "github.com/lileio/pubsub/v2/providers/nats" 15 | "github.com/nats-io/stan.go" 16 | log "github.com/sirupsen/logrus" 17 | "gopkg.in/alecthomas/kingpin.v2" 18 | ) 19 | 20 | var ( 21 | logLevel = kingpin.Flag("log.level", "Log level: [debug, info, warn, error, fatal]").Default("info").String() 22 | configs = common.StringList(kingpin.Flag("config", "Path to the athena configuration file").Default("/etc/athena/main.yaml").Short('c')) 23 | natsUrl = kingpin.Flag("nats-url", "URL of the nats service").Default("nats://nats-streaming:4222").String() 24 | commit string 25 | ) 26 | 27 | func init() { 28 | common.ParseCommandline() 29 | common.InitLogging(logLevel) 30 | } 31 | 32 | func main() { 33 | cfg, err := config.NewConfigFromFile(*configs) 34 | if err != nil { 35 | panic(err) 36 | } 37 | log.Infof("Starting athena-monitor (%s)", commit) 38 | log.Debug("Configuration") 39 | for _, line := range strings.Split(cfg.String(), "\n") { 40 | log.Debug(line) 41 | } 42 | 43 | natsClient, err := nats.NewNats("test-cluster", stan.NatsURL(*natsUrl)) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | salesforceClientFactory := &common.BaseSalesforceClientFactory{} 49 | filesComClientFactory := &common.BaseFilesComClientFactory{} 50 | m, err := monitor.NewMonitor(natsClient, cfg, nil, salesforceClientFactory, filesComClientFactory) 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | ctx, cancel := context.WithCancel(context.Background()) 56 | defer cancel() 57 | if err = m.Run(ctx); err != nil { 58 | panic(err) 59 | } 60 | c := make(chan os.Signal, 1) 61 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 62 | <-c 63 | cancel() 64 | pubsub.Shutdown() 65 | } 66 | -------------------------------------------------------------------------------- /pkg/common/salesforce_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | const validCaseNumber = "123456" 9 | const invalidCaseNumber = "999999" 10 | const authenticationErrorCaseNumber = "111111" 11 | 12 | type MockedSalesforceClient struct { 13 | BaseSalesforceClient 14 | } 15 | 16 | func (sf *MockedSalesforceClient) GetCaseByNumber(number string) (*Case, error) { 17 | if number == validCaseNumber { 18 | return &Case{CaseNumber: number}, nil 19 | } else if number == authenticationErrorCaseNumber { 20 | return nil, ErrAuthentication 21 | } 22 | return nil, ErrNoCaseFound{number} 23 | } 24 | 25 | func TestGetCaseByNumber(t *testing.T) { 26 | client := &MockedSalesforceClient{} 27 | caseNumber := validCaseNumber 28 | 29 | caseDetails, err := client.GetCaseByNumber(caseNumber) 30 | if err != nil { 31 | t.Fatalf("Expected no error, got %v", err) 32 | } 33 | 34 | if caseDetails.CaseNumber != caseNumber { 35 | t.Errorf("Expected case number %s, got %s", caseNumber, caseDetails.CaseNumber) 36 | } 37 | } 38 | 39 | func TestGetCaseByNumberNotFound(t *testing.T) { 40 | client := &MockedSalesforceClient{} 41 | caseNumber := invalidCaseNumber 42 | invalidCaseErr := ErrNoCaseFound{caseNumber} 43 | 44 | caseDetails, err := client.GetCaseByNumber(caseNumber) 45 | if err == nil { 46 | t.Errorf("Expected error, got nil") 47 | } 48 | 49 | if !errors.Is(err, invalidCaseErr) { 50 | t.Errorf("Expected %v, got %v", invalidCaseErr, err) 51 | } 52 | 53 | if caseDetails != nil { 54 | t.Errorf("Expected nil case details, got %+v", caseDetails) 55 | } 56 | } 57 | 58 | func TestGetCaseByNumberAuthentication(t *testing.T) { 59 | client := &MockedSalesforceClient{} 60 | caseNumber := authenticationErrorCaseNumber 61 | 62 | caseDetails, err := client.GetCaseByNumber(caseNumber) 63 | if err == nil { 64 | t.Errorf("Expected error, got nil") 65 | } 66 | 67 | if !errors.Is(err, ErrAuthentication) { 68 | t.Errorf("Expected %v, got %v", ErrAuthentication, err) 69 | } 70 | 71 | if caseDetails != nil { 72 | t.Errorf("Expected nil case details, got %+v", caseDetails) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | services: 3 | 4 | db: 5 | container_name: db 6 | image: mysql:5.7 7 | environment: 8 | MYSQL_ROOT_PASSWORD: root 9 | MYSQL_DATABASE: athena 10 | MYSQL_USER: athena 11 | MYSQL_PASSWORD: athena 12 | networks: 13 | - athena 14 | ports: 15 | - "3306:3306" 16 | restart: always 17 | 18 | athena-monitor: 19 | container_name: athena-monitor 20 | image: athena/athena-monitor:${BRANCH:-main} 21 | volumes: 22 | - ./creds-athena.yaml:/etc/athena/main.yaml 23 | - ./athena-monitor.yaml:/etc/athena/monitor.yaml 24 | - ./athena-monitor-directories.yaml:/etc/athena/monitor-directories.yaml 25 | - ./tmp:/tmp/athena 26 | environment: 27 | http_proxy: ${http_proxy} 28 | https_proxy: ${https_proxy} 29 | no_proxy: ${no_proxy} 30 | command: /athena-monitor --config /etc/athena/main.yaml --config /etc/athena/monitor.yaml --config /etc/athena/monitor-directories.yaml --log.level=debug 31 | depends_on: 32 | - nats-streaming 33 | - db 34 | networks: 35 | - athena 36 | restart: always 37 | 38 | athena-processor: 39 | container_name: athena-processor 40 | image: athena/athena-processor:${BRANCH:-main} 41 | volumes: 42 | - ./creds-athena.yaml:/etc/athena/main.yaml 43 | - ./athena-processor.yaml:/etc/athena/processor.yaml 44 | - ./athena-processor-upload.yaml:/etc/athena/processor-upload.yaml 45 | - ./tmp:/tmp/athena 46 | environment: 47 | http_proxy: ${http_proxy} 48 | https_proxy: ${https_proxy} 49 | no_proxy: ${no_proxy} 50 | command: /athena-processor --config /etc/athena/main.yaml --config /etc/athena/processor.yaml --config /etc/athena/processor-upload.yaml --log.level="debug" 51 | depends_on: 52 | - nats-streaming 53 | - db 54 | networks: 55 | - athena 56 | restart: always 57 | 58 | nats-streaming: 59 | container_name: nats-streaming 60 | image: nats-streaming 61 | ports: 62 | - '4222:4222' 63 | - '8223:8223' 64 | - 4222 65 | environment: 66 | - file 67 | networks: 68 | - athena 69 | restart: always 70 | 71 | debug: 72 | container_name: debug 73 | image: debug-container 74 | command: /bin/bash -c 'sleep 10000' 75 | networks: 76 | - athena 77 | restart: always 78 | 79 | networks: 80 | athena: 81 | driver: bridge 82 | -------------------------------------------------------------------------------- /cmd/processor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "strings" 8 | "syscall" 9 | 10 | "github.com/canonical/athena-core/pkg/common" 11 | "github.com/canonical/athena-core/pkg/config" 12 | "github.com/canonical/athena-core/pkg/processor" 13 | "github.com/lileio/pubsub/v2" 14 | "github.com/lileio/pubsub/v2/providers/nats" 15 | "github.com/nats-io/stan.go" 16 | log "github.com/sirupsen/logrus" 17 | "gopkg.in/alecthomas/kingpin.v2" 18 | "gorm.io/gorm" 19 | ) 20 | 21 | var ( 22 | logLevel = kingpin.Flag("log.level", "Log level: [debug, info, warn, error, fatal]").Default("info").String() 23 | configs = common.StringList(kingpin.Flag("config", "Path to the athena configuration file").Default("/etc/athena/main.yaml").Short('c')) 24 | natsUrl = kingpin.Flag("nats-url", "URL of the nats service").Default("nats://nats-streaming:4222").String() 25 | commit string 26 | ) 27 | 28 | func init() { 29 | common.ParseCommandline() 30 | common.InitLogging(logLevel) 31 | } 32 | 33 | func main() { 34 | cfg, err := config.NewConfigFromFile(*configs) 35 | if err != nil { 36 | panic(err) 37 | } 38 | log.Infof("Starting athena-processor (%s)", commit) 39 | log.Debug("Configuration") 40 | for _, line := range strings.Split(cfg.String(), "\n") { 41 | log.Debug(line) 42 | } 43 | 44 | natsClient, err := nats.NewNats("test-cluster", stan.NatsURL(*natsUrl)) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | salesforceClientFactory := &common.BaseSalesforceClientFactory{} 50 | filesComClientFactory := &common.BaseFilesComClientFactory{} 51 | 52 | p, err := processor.NewProcessor(filesComClientFactory, salesforceClientFactory, natsClient, cfg, nil) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | ctx, cancel := context.WithCancel(context.Background()) 58 | 59 | if err := p.Run(ctx, func( 60 | filesComClientFactory common.FilesComClientFactory, 61 | salesforceClientFactory common.SalesforceClientFactory, 62 | name, topic string, 63 | reports map[string]config.Report, cfg *config.Config, dbConn *gorm.DB) pubsub.Subscriber { 64 | log.Infof("Subscribing: %s - to topic: %s", name, topic) 65 | return processor.NewBaseSubscriber(filesComClientFactory, salesforceClientFactory, name, topic, reports, cfg, dbConn) 66 | }); err != nil { 67 | panic(err) 68 | } 69 | 70 | c := make(chan os.Signal, 1) 71 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 72 | <-c 73 | cancel() 74 | pubsub.Shutdown() 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/ghcr-publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish to ghcr.io 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | IMAGE_NAME: ${{ github.repository }} 11 | 12 | jobs: 13 | test-and-build: 14 | # Include the build workflow. 15 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_iduses 16 | uses: ./.github/workflows/test-and-build.yaml 17 | 18 | ghcr-publish: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: read 22 | packages: write 23 | needs: test-and-build 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v4 28 | 29 | - name: Get packages 30 | uses: actions/download-artifact@v4 31 | with: 32 | name: packages 33 | path: build 34 | 35 | - name: Set up Docker Buildx 36 | uses: docker/setup-buildx-action@v3 37 | 38 | - name: Log into registry ${{ env.REGISTRY }} 39 | if: github.event_name != 'pull_request' 40 | uses: docker/login-action@v3 41 | with: 42 | registry: ${{ env.REGISTRY }} 43 | username: ${{ github.actor }} 44 | password: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | - name: Extract Docker metadata - processor 47 | id: processor_meta 48 | uses: docker/metadata-action@v5 49 | with: 50 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/athena-processor 51 | 52 | - name: Build and publish Docker - processor 53 | id: processor-build-and-push 54 | uses: docker/build-push-action@v6 55 | with: 56 | context: . 57 | file: ./cmd/processor/Dockerfile 58 | platforms: linux/amd64,linux/arm64 59 | push: ${{ github.event_name != 'pull_request' }} 60 | tags: ${{ steps.processor_meta.outputs.tags }} 61 | labels: ${{ steps.processor_meta.outputs.labels }} 62 | 63 | - name: Extract Docker metadata - monitor 64 | id: monitor_meta 65 | uses: docker/metadata-action@v5 66 | with: 67 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/athena-monitor 68 | 69 | - name: Build and publish Docker - monitor 70 | id: monitor_build-and-push 71 | uses: docker/build-push-action@v6 72 | with: 73 | context: . 74 | file: ./cmd/monitor/Dockerfile 75 | platforms: linux/amd64,linux/arm64 76 | push: ${{ github.event_name != 'pull_request' }} 77 | tags: ${{ steps.monitor_meta.outputs.tags }} 78 | labels: ${{ steps.monitor_meta.outputs.labels }} 79 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish to Docker Hub 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | env: 9 | REGISTRY: docker.io 10 | IMAGE_NAME: ${{ github.repository }} 11 | DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME || 'unpublished' }} 12 | 13 | jobs: 14 | test-and-build: 15 | # Include the build workflow. 16 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_iduses 17 | uses: ./.github/workflows/test-and-build.yaml 18 | 19 | docker-publish: 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | packages: write 24 | needs: test-and-build 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | 30 | - name: Get packages 31 | uses: actions/download-artifact@v4 32 | with: 33 | name: packages 34 | path: build 35 | 36 | - name: Set up Docker Buildx 37 | uses: docker/setup-buildx-action@v3 38 | 39 | - name: Log into registry ${{ env.REGISTRY }} 40 | if: github.event_name != 'pull_request' 41 | uses: docker/login-action@v3 42 | with: 43 | registry: ${{ env.REGISTRY }} 44 | username: ${{ secrets.DOCKERHUB_USERNAME }} 45 | password: ${{ secrets.DOCKERHUB_TOKEN }} 46 | 47 | - name: Extract Docker metadata - processor 48 | id: processor_meta 49 | uses: docker/metadata-action@v5 50 | with: 51 | images: ${{ env.DOCKERHUB_USERNAME }}/athena-processor 52 | 53 | - name: Build and publish Docker - processor 54 | id: processor-build-and-push 55 | uses: docker/build-push-action@v6 56 | with: 57 | context: . 58 | file: ./cmd/processor/Dockerfile 59 | platforms: linux/amd64,linux/arm64 60 | push: ${{ github.event_name != 'pull_request' }} 61 | tags: ${{ steps.processor_meta.outputs.tags }} 62 | labels: ${{ steps.processor_meta.outputs.labels }} 63 | 64 | - name: Extract Docker metadata - monitor 65 | id: monitor_meta 66 | uses: docker/metadata-action@v5 67 | with: 68 | images: ${{ env.DOCKERHUB_USERNAME }}/athena-monitor 69 | 70 | - name: Build and publish Docker - monitor 71 | id: monitor_build-and-push 72 | uses: docker/build-push-action@v6 73 | with: 74 | context: . 75 | file: ./cmd/monitor/Dockerfile 76 | platforms: linux/amd64,linux/arm64 77 | push: ${{ github.event_name != 'pull_request' }} 78 | tags: ${{ steps.monitor_meta.outputs.tags }} 79 | labels: ${{ steps.monitor_meta.outputs.labels }} 80 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: lint build test install 3 | 4 | .PHONY: docker-compose 5 | docker-compose: docker-build 6 | if docker-compose version > /dev/null 2>&1; then \ 7 | DOCKER_COMPOSE="docker-compose"; \ 8 | else \ 9 | if docker compose version > /dev/null 2>&1; then \ 10 | DOCKER_COMPOSE="docker compose"; \ 11 | else \ 12 | echo "Please install the docker-compose command"; \ 13 | exit 1; \ 14 | fi; \ 15 | fi; \ 16 | $${DOCKER_COMPOSE} down --remove-orphans; \ 17 | mkdir --parents tmp; \ 18 | BRANCH=$(shell git branch --show-current | sed -e 's:/:-:g') $${DOCKER_COMPOSE} up --force-recreate --build 19 | 20 | .PHONY: devel 21 | devel: install docker-build docker-compose 22 | 23 | .PHONY: docker-build monitor processor 24 | docker-build: install docker-build-monitor docker-build-processor docker-build-debug-container 25 | 26 | .PHONY: docker-build-monitor docker-build-processor 27 | docker-build-monitor docker-build-processor: docker-build-%: 28 | docker build \ 29 | --tag athena/athena-$*:$(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) \ 30 | --file cmd/$*/Dockerfile \ 31 | $(if $(NOCACHE),--no-cache,) \ 32 | --build-arg ARCH=amd64 \ 33 | --build-arg OS=linux \ 34 | . 35 | 36 | .PHONY: docker-build-debug-container 37 | docker-build-debug-container: 38 | docker build \ 39 | --tag debug-container \ 40 | --file Dockerfile-debug \ 41 | . 42 | 43 | .PHONY: build 44 | build: athena-monitor athena-processor salesforce-test 45 | 46 | .PHONY: athena-monitor 47 | athena-monitor: 48 | go build -v -o $@ -ldflags="-X main.commit=$$(git describe --tags)" cmd/monitor/main.go 49 | 50 | .PHONY: athena-processor 51 | athena-processor: 52 | go build -v -o $@ -ldflags="-X main.commit=$$(git describe --tags)" cmd/processor/main.go 53 | 54 | .PHONY: salesforce-test 55 | salesforce-test: 56 | go build -v -o $@ -ldflags="-X main.commit=$$(git describe --tags)" cmd/salesforce-test/main.go 57 | 58 | .PHONY: files.com-test 59 | files.com-test: 60 | go build -v -o $@ -ldflags="-X main.commit=$$(git describe --tags)" cmd/files.com-test/main.go 61 | 62 | .PHONY: lint 63 | lint: check_modules gofmt 64 | 65 | .PHONY: check_modules 66 | check_modules: 67 | go mod tidy 68 | 69 | .PHONY: gofmt 70 | gofmt: check_modules 71 | @fmt_result=$$(gofmt -d $$(find . -name '*.go' -print)); \ 72 | if [ -n "$${fmt_result}" ]; then \ 73 | echo "gofmt checking failed"; \ 74 | echo; \ 75 | echo "$${fmt_result}"; \ 76 | false; \ 77 | fi 78 | 79 | .PHONY: test 80 | test: 81 | go test -v ./... 82 | 83 | .PHONY: install 84 | install: build 85 | rm -rf build 86 | mkdir build 87 | cp --verbose athena-monitor athena-processor build/ 88 | 89 | .PHONY: docs 90 | docs: 91 | python3 -m venv venv 92 | ./venv/bin/pip install --upgrade pip 93 | ./venv/bin/pip install sphinx 94 | . venv/bin/activate && make -C docs html 95 | -------------------------------------------------------------------------------- /athena-processor.yaml: -------------------------------------------------------------------------------- 1 | processor: 2 | batch-comments-every: 1m 3 | base-tmpdir: "/tmp/athena" 4 | keep-processing-output: true 5 | subscribers: 6 | sosreports: 7 | sf-comment-enabled: true 8 | sf-comment-public: false 9 | sf-comment: | 10 | Athena processor: {{ processor }} subscriber: {{ subscriber }} has run the following reports. 11 | {% for report in reports -%} 12 | {% for script in report.Scripts -%} 13 | {% if script.Name == "hotsos-short" %} 14 | {% if script.Output != "" -%} 15 | Summary for report: {{ report.Name }} - filepath: {{ report.FilePath }} 16 | ------------------------------------------------------------------------- 17 | {{ script.Output }} 18 | {% endif %} 19 | {% endif %} 20 | {%- endfor -%} 21 | {%- endfor -%} 22 | {%- for report in reports -%} 23 | {%- for script in report.Scripts -%} 24 | {% if script.Name == "hotsos-full" -%} 25 | Full {{ report.Name }} output can be found at: https://files.support.canonical.com/files/{{ script.UploadLocation }} 26 | {% endif %} 27 | {%- endfor -%} 28 | {%- endfor -%} 29 | 30 | reports: 31 | hotsos: 32 | scripts: 33 | hotsos-full: 34 | exit-codes: 0 2 127 126 35 | run: | 36 | #!/bin/bash 37 | set -e -u 38 | pipx install hotsos &>/dev/null 39 | pipx upgrade hotsos &>/dev/null 40 | tar -xf {{filepath}} -C {{basedir}} &>/dev/null || true 41 | ~/.local/bin/hotsos --save --output-path hotsos-out --all-logs {{basedir}}/$(basename {{filepath}} .tar.xz)/ &>/dev/null || true 42 | if [ -s hotsos-out/*/summary/full/yaml/hotsos-summary.all.yaml ]; then 43 | cat hotsos-out/*/summary/full/yaml/hotsos-summary.all.yaml 44 | else 45 | echo "No full sosreport generated." 46 | fi 47 | exit 0 48 | hotsos-short: 49 | exit-codes: 0 2 127 126 50 | run: | 51 | #!/bin/bash 52 | set -e -u 53 | pipx install hotsos &>/dev/null 54 | pipx upgrade hotsos &>/dev/null 55 | tar -xf {{filepath}} -C {{basedir}} &>/dev/null || true 56 | ~/.local/bin/hotsos --short --save --output-path hotsos-out --all-logs {{basedir}}/$(basename {{filepath}} .tar.xz)/ &>/dev/null || true 57 | if [ -s hotsos-out/*/summary/short/yaml/hotsos-summary.all.yaml ]; then 58 | cat hotsos-out/*/summary/short/yaml/hotsos-summary.all.yaml 59 | else 60 | echo "No known bugs or issues found on sosreport." 61 | fi 62 | exit 0 63 | -------------------------------------------------------------------------------- /development-config.yaml: -------------------------------------------------------------------------------- 1 | db: 2 | dialect: mysql 3 | dsn: "athena:athena@tcp(db:3306)/athena?charset=utf8&parseTime=true" 4 | 5 | monitor: 6 | poll-every: 10s 7 | files-delta: 10m 8 | directories: 9 | - "/uploads" 10 | processor-map: 11 | - type: filename 12 | regex: ".*sosreport.*.txt$" 13 | processor: sosreports 14 | 15 | processor: 16 | batch-comments-every: 10s 17 | reports-upload-dir: "/customers/athena-reports/" 18 | base-tmpdir: "/tmp/athena" 19 | subscribers: 20 | sosreports: 21 | sf-comment-enabled: true 22 | sf-comment-public: false 23 | sf-comment: | 24 | Athena processor: {{ processor }} subscriber: {{ subscriber }} has run the following reports. 25 | {% for report in reports -%} 26 | {% for script in report.Scripts -%} 27 | {% if script.Name == "hotsos-short" %} 28 | {% if script.Output != "" %} 29 | Summary for report: {{ report.Name }} - filepath: {{ report.FilePath }} 30 | ------------------------------------------------------------------------- 31 | {{ script.Output }} 32 | {% endif %} 33 | {% endif %} 34 | {%- endfor -%} 35 | {%- endfor -%} 36 | {%- for report in reports -%} 37 | {%- for script in report.Scripts -%} 38 | {% if script.Name == "hotsos-full" %} 39 | Full output for report: {{ report.Name }} can be found: https://files.support.canonical.com/files/{{ script.UploadLocation }} 40 | {% endif %} 41 | {%- endfor -%} 42 | {%- endfor -%} 43 | 44 | reports: 45 | hotsos: 46 | scripts: 47 | hotsos-full: 48 | exit-codes: 0 2 127 126 49 | run: | 50 | #!/bin/bash 51 | git clone --quiet https://github.com/dosaboy/hotsos.git {{basedir}}/hotsos &>/dev/null 52 | tar -xf {{filepath}} -C {{basedir}} &>/dev/null 53 | {{basedir}}/hotsos/hotsos.sh -s -a {{basedir}}/$(basename {{filepath}} .tar.xz)/ &>/dev/null 54 | cat *.summary 55 | rm -f *.summary 56 | exit 0 57 | hotsos-short: 58 | exit-codes: 0 2 127 126 59 | run: | 60 | #!/bin/bash 61 | git clone --quiet https://github.com/dosaboy/hotsos.git {{basedir}}/hotsos &>/dev/null 62 | tar -xf {{filepath}} -C {{basedir}} &>/dev/null 63 | {{basedir}}/hotsos/hotsos.sh -s --short {{basedir}}/$(basename {{filepath}} .tar.xz)/ &>/dev/null 64 | [ -s *.summary ] || echo "No known bugs or issues found on sosreport" 65 | rm -f *.summary 66 | exit 0 67 | 68 | filescom: 69 | key: "xxx" 70 | endpoint: "https://app.files.com" 71 | 72 | salesforce: 73 | endpoint: "https://xxx--xxx.my.salesforce.com/" 74 | username: "xx.xx@xxx.com.xxx" 75 | password: "xxx" 76 | security-token: "xxx" 77 | -------------------------------------------------------------------------------- /pkg/common/files-com.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "path" 7 | "path/filepath" 8 | "time" 9 | 10 | filessdk "github.com/Files-com/files-sdk-go" 11 | "github.com/Files-com/files-sdk-go/file" 12 | "github.com/Files-com/files-sdk-go/folder" 13 | "github.com/canonical/athena-core/pkg/common/db" 14 | log "github.com/sirupsen/logrus" 15 | ) 16 | 17 | const DefaultFilesAgeDelta = 10 * time.Second 18 | 19 | type FilesComClient interface { 20 | GetFiles(dirs []string) ([]db.File, error) 21 | Download(toDownload *db.File, downloadPath string) (*filessdk.File, error) 22 | Upload(contents, destinationPath string) (*filessdk.File, error) 23 | } 24 | 25 | type FilesComClientFactory interface { 26 | NewFilesComClient(apiKey, endpoint string) (FilesComClient, error) 27 | } 28 | 29 | type BaseFilesComClient struct { 30 | FilesComClient 31 | ApiClient file.Client 32 | } 33 | 34 | type BaseFilesComClientFactory struct{} 35 | 36 | func (client *BaseFilesComClientFactory) NewFilesComClient(apiKey, endpoint string) (FilesComClient, error) { 37 | return NewFilesComClient(apiKey, endpoint) 38 | } 39 | 40 | func (client *BaseFilesComClient) Upload(contents, destinationPath string) (*filessdk.File, error) { 41 | log.Infof("Uploading to '%s'", destinationPath) 42 | tmpfile, err := os.CreateTemp("", "upload") 43 | if err != nil { 44 | return nil, err 45 | } 46 | err = os.WriteFile(tmpfile.Name(), []byte(contents), 0644) 47 | if err != nil { 48 | return nil, err 49 | } 50 | defer os.Remove(tmpfile.Name()) 51 | status := client.ApiClient.UploadFile(context.Background(), &file.UploadParams{Source: tmpfile.Name(), Destination: destinationPath}) 52 | return &status.Files()[0], nil 53 | } 54 | 55 | func (client *BaseFilesComClient) Download(toDownload *db.File, downloadPath string) (*filessdk.File, error) { 56 | log.Infof("Downloading '%s' to '%s'", toDownload.Path, downloadPath) 57 | fileEntry, err := client.ApiClient.DownloadToFile(context.Background(), filessdk.FileDownloadParams{Path: toDownload.Path}, path.Join(downloadPath, filepath.Base(toDownload.Path))) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return &fileEntry, nil 62 | } 63 | 64 | func (client *BaseFilesComClient) GetFiles(dirs []string) ([]db.File, error) { 65 | var files []db.File 66 | newClient := folder.Client{Config: client.ApiClient.Config} 67 | for _, directory := range dirs { 68 | log.Infof("Listing files available on %s", directory) 69 | it, err := newClient.ListFor(context.Background(), filessdk.FolderListForParams{Path: directory}) 70 | if err != nil { 71 | return nil, err 72 | } 73 | for it.Next() { 74 | filePath := it.Folder().Path 75 | if it.Folder().Type == "directory" { 76 | continue 77 | } 78 | log.Debugf("Found file with path: %s", filePath) 79 | files = append(files, db.File{Created: time.Now(), Path: filePath}) 80 | } 81 | } 82 | log.Infof("Found %d files on the target directories", len(files)) 83 | return files, nil 84 | } 85 | 86 | func NewFilesComClient(apiKey, endpoint string) (FilesComClient, error) { 87 | log.Infof("Creating new files.com client") 88 | return &BaseFilesComClient{ApiClient: file.Client{Config: filessdk.Config{APIKey: apiKey, Endpoint: endpoint}}}, nil 89 | } 90 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/canonical/athena-core 2 | 3 | go 1.23 4 | 5 | toolchain go1.23.1 6 | 7 | require ( 8 | github.com/Files-com/files-sdk-go v1.2.1218 9 | github.com/flosch/pongo2/v4 v4.0.2 10 | github.com/lileio/pubsub/v2 v2.6.1 11 | github.com/makyo/snuffler v0.0.0-20190210075944-33446730a4fe 12 | github.com/nats-io/stan.go v0.10.4 13 | github.com/simpleforce/simpleforce v0.0.0-20220429021116-acf4ac67ef68 14 | github.com/sirupsen/logrus v1.9.3 15 | github.com/stretchr/testify v1.8.3 16 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 17 | gopkg.in/yaml.v3 v3.0.1 18 | gorm.io/driver/mysql v1.5.2 19 | gorm.io/driver/sqlite v1.5.4 20 | gorm.io/gorm v1.25.5 21 | ) 22 | 23 | require ( 24 | github.com/BurntSushi/toml v0.3.1 // indirect 25 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 26 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect 27 | github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc // indirect 28 | github.com/beorn7/perks v1.0.1 // indirect 29 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 30 | github.com/chilts/sid v0.0.0-20190607042430-660e94789ec9 // indirect 31 | github.com/davecgh/go-spew v1.1.1 // indirect 32 | github.com/dropbox/godropbox v0.0.0-20200228041828-52ad444d3502 // indirect 33 | github.com/go-sql-driver/mysql v1.7.0 // indirect 34 | github.com/gogo/protobuf v1.3.2 // indirect 35 | github.com/golang/protobuf v1.5.3 // indirect 36 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 37 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 38 | github.com/jinzhu/inflection v1.0.0 // indirect 39 | github.com/jinzhu/now v1.1.5 // indirect 40 | github.com/klauspost/compress v1.16.7 // indirect 41 | github.com/kr/pretty v0.3.0 // indirect 42 | github.com/lileio/logr v1.1.0 // indirect 43 | github.com/lpar/date v1.0.0 // indirect 44 | github.com/mattn/go-sqlite3 v1.14.17 // indirect 45 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 46 | github.com/nats-io/nats-server/v2 v2.9.23 // indirect 47 | github.com/nats-io/nats.go v1.28.0 // indirect 48 | github.com/nats-io/nkeys v0.4.6 // indirect 49 | github.com/nats-io/nuid v1.0.1 // indirect 50 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect 51 | github.com/opentracing/opentracing-go v1.1.0 // indirect 52 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3 // indirect 53 | github.com/openzipkin/zipkin-go v0.2.2 // indirect 54 | github.com/pkg/errors v0.9.1 // indirect 55 | github.com/pmezard/go-difflib v1.0.0 // indirect 56 | github.com/prometheus/client_golang v1.11.1 // indirect 57 | github.com/prometheus/client_model v0.4.0 // indirect 58 | github.com/prometheus/common v0.26.0 // indirect 59 | github.com/prometheus/procfs v0.7.1 // indirect 60 | github.com/rogpeppe/go-internal v1.9.0 // indirect 61 | github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f // indirect 62 | github.com/sanity-io/litter v1.2.0 // indirect 63 | github.com/segmentio/ksuid v1.0.3 // indirect 64 | github.com/stretchr/objx v0.5.0 // indirect 65 | github.com/zenthangplus/goccm v1.1.3 // indirect 66 | golang.org/x/crypto v0.25.0 // indirect 67 | golang.org/x/sync v0.3.0 // indirect 68 | golang.org/x/sys v0.22.0 // indirect 69 | google.golang.org/grpc v1.58.3 // indirect 70 | google.golang.org/protobuf v1.34.2 // indirect 71 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 72 | gopkg.in/yaml.v2 v2.3.0 // indirect 73 | moul.io/http2curl v1.0.0 // indirect 74 | ) 75 | -------------------------------------------------------------------------------- /doc/howto_build.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | ``` 3 | sudo apt install golang make 4 | sudo snap install docker 5 | ``` 6 | 7 | ## Build Athena 8 | 9 | ``` 10 | $ make common-build 11 | >> building binaries 12 | GO111MODULE=on /home/ubuntu/go/bin/promu build --prefix /home/ubuntu/git/canonical/athena-core 13 | > athena-processor 14 | > athena-monitor 15 | $ mv athena-monitor cmd/monitor/ 16 | $ mv athena-processor cmd/processor/ 17 | ``` 18 | 19 | ## Build Images 20 | 21 | Get docker network info and ensure that your firewall allows it: 22 | 23 | ``` 24 | # docker network ls 25 | NETWORK ID NAME DRIVER SCOPE 26 | d257092905a7 bridge bridge local 27 | a1e33a499c70 host host local 28 | 2c959b426f97 none null local 29 | # docker network inspect d257092905a7 30 | ... 31 | ``` 32 | 33 | If a build fails, look in dmesg to see if anything got blocked and update firewall if needed. 34 | 35 | See cmd/monitor/Dockerfile and cmd/processor/Dockerfile for image config. 36 | 37 | Build monitor: 38 | 39 | ``` 40 | # docker build --file cmd/monitor/Dockerfile . 41 | Sending build context to Docker daemon 17.3MB 42 | Step 1/6 : FROM ubuntu:20.04 43 | 20.04: Pulling from library/ubuntu 44 | 846c0b181fff: Downloading [=======================> ] 13.25MB/28.58MB846c0b181fff: Pull complete 45 | Digest: sha256:0e0402cd13f68137edb0266e1d2c682f217814420f2d43d300ed8f65479b14fb 46 | Status: Downloaded newer image for ubuntu:20.04 47 | ---> d5447fc01ae6 48 | Step 2/6 : LABEL maintainer="Canonical Sustaining Engineering " 49 | ---> Running in fd6940b8b5d0 50 | Removing intermediate container fd6940b8b5d0 51 | ---> 55d857651b9d 52 | Step 3/6 : RUN apt update -yyq && apt -yyq install ca-certificates git xz-utils python3 python3-yaml coreutils bsdmainutils jq bc python3-simplejson 53 | ---> Running in e02cbee5e4f6 54 | ... 55 | Successfully built 32ca0a3b8176 56 | ``` 57 | 58 | Build processor: 59 | 60 | ``` 61 | # docker build --file cmd/processor/Dockerfile . 62 | Sending build context to Docker daemon 17.9MB 63 | Step 1/6 : FROM ubuntu:20.04 64 | ---> d5447fc01ae6 65 | Step 2/6 : LABEL maintainer="Canonical Sustaining Engineering " 66 | ---> Using cache 67 | ---> 55d857651b9d 68 | Step 3/6 : RUN apt update -yyq && apt -yyq install ca-certificates git xz-utils python3 python3-yaml coreutils bsdmainutils jq bc python3-simplejson 69 | ---> Using cache 70 | ---> 8014d38bde4e 71 | Step 4/6 : RUN update-ca-certificates 72 | ---> Using cache 73 | ---> 2718ebbbbb87 74 | Step 5/6 : RUN mkdir /etc/athena/ 75 | ---> Using cache 76 | ---> 135bf991e1b1 77 | Step 6/6 : COPY ./athena-processor /athena-processor 78 | ---> d44deeb0cfb9 79 | Successfully built d44deeb0cfb9 80 | ``` 81 | 82 | Tag/name images: 83 | 84 | ``` 85 | # docker image ls 86 | REPOSITORY TAG IMAGE ID CREATED SIZE 87 | 96d6e2650a4e 30 seconds ago 268MB 88 | 7508809b8547 About a minute ago 267MB 89 | ubuntu 20.04 d5447fc01ae6 4 weeks ago 72.8MB 90 | # docker image tag 32ca0a3b8176 athena-monitor:latest 91 | # docker image tag d44deeb0cfb9 athena-processor:latest 92 | ``` 93 | 94 | The following should return nothing if all build successfully: 95 | 96 | ``` 97 | # docker ps -a 98 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 99 | ``` 100 | 101 | # Test Build 102 | 103 | ``` 104 | make devel 105 | ``` 106 | -------------------------------------------------------------------------------- /pkg/processor/processor_test.go: -------------------------------------------------------------------------------- 1 | package processor 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io/ioutil" 7 | "testing" 8 | "time" 9 | 10 | "github.com/canonical/athena-core/pkg/common" 11 | "github.com/canonical/athena-core/pkg/common/db" 12 | "github.com/canonical/athena-core/pkg/common/test" 13 | "github.com/canonical/athena-core/pkg/config" 14 | "github.com/lileio/pubsub/v2" 15 | "github.com/lileio/pubsub/v2/providers/memory" 16 | "github.com/sirupsen/logrus" 17 | "github.com/stretchr/testify/assert" 18 | "github.com/stretchr/testify/mock" 19 | "github.com/stretchr/testify/suite" 20 | "gorm.io/driver/sqlite" 21 | "gorm.io/gorm" 22 | ) 23 | 24 | type ProcessorTestSuite struct { 25 | suite.Suite 26 | config *config.Config 27 | db *gorm.DB 28 | } 29 | 30 | func init() { 31 | logrus.SetOutput(ioutil.Discard) 32 | } 33 | 34 | func (s *ProcessorTestSuite) SetupTest() { 35 | s.config, _ = config.NewConfigFromBytes([]byte(test.DefaultTestConfig)) 36 | assert.Equal(s.T(), "sqlite", s.config.Db.Dialect) 37 | s.db, _ = gorm.Open(sqlite.Open("file::memory:?cache=shared")) 38 | s.db.AutoMigrate(db.File{}, db.Report{}) 39 | } 40 | 41 | type MockSubscriber struct { 42 | mock.Mock 43 | Options pubsub.HandlerOptions 44 | } 45 | 46 | func (s *MockSubscriber) Setup(c *pubsub.Client) { 47 | c.On(s.Options) 48 | } 49 | 50 | func (s *ProcessorTestSuite) TestRunProcessor() { 51 | provider := &memory.MemoryProvider{} 52 | processor, _ := NewProcessor(&test.FilesComClientFactory{}, &test.SalesforceClientFactory{}, provider, s.config, s.db) 53 | 54 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 55 | defer cancel() 56 | 57 | b, _ := json.Marshal(db.File{Path: "/uploads/sosreport-123.tar.xz"}) 58 | b1, _ := json.Marshal(db.File{Path: "/uploads/sosreport-321.tar.xz"}) 59 | b2, _ := json.Marshal(db.File{Path: "/uploads/sosreport-abc.tar.xz"}) 60 | 61 | _ = provider.Publish(context.Background(), "sosreports", &pubsub.Msg{Data: b}) 62 | _ = provider.Publish(context.Background(), "sosreports", &pubsub.Msg{Data: b1}) 63 | _ = provider.Publish(context.Background(), "sosreports", &pubsub.Msg{Data: b2}) 64 | 65 | var called = 0 66 | 67 | _ = processor.Run(ctx, func( 68 | filesComClientFactory common.FilesComClientFactory, 69 | salesforceClientFactory common.SalesforceClientFactory, 70 | name string, topic string, reports map[string]config.Report, cfg *config.Config, dbConn *gorm.DB) pubsub.Subscriber { 71 | var subscriber = MockSubscriber{Options: pubsub.HandlerOptions{ 72 | Topic: topic, 73 | Name: "athena-processor-" + name, 74 | AutoAck: false, 75 | JSON: true, 76 | }} 77 | 78 | subscriber.Options.Handler = func(ctx context.Context, msg *db.File, m *pubsub.Msg) error { 79 | called++ 80 | return nil 81 | } 82 | return &subscriber 83 | }) 84 | 85 | assert.Equal(s.T(), called, 3) 86 | assert.Equal(s.T(), len(provider.Msgs["sosreports"]), 3) 87 | } 88 | 89 | func TestNewProcessor(t *testing.T) { 90 | suite.Run(t, &ProcessorTestSuite{}) 91 | } 92 | 93 | func TestSplitComment(t *testing.T) { 94 | var inputs []string = []string{ 95 | "123456789\n", 96 | "1234\n567890\n123\n", 97 | "1234567890123456789", 98 | } 99 | var expected [][]string = [][]string{ 100 | { 101 | "123456789\n", 102 | }, 103 | { 104 | "1234\n567890", 105 | "123", 106 | }, 107 | { 108 | "1234567890123456789", 109 | }, 110 | } 111 | var got []string = []string{} 112 | for i, input := range inputs { 113 | got = splitComment(input, 12) 114 | assert.Equal(t, expected[i], got) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /pkg/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestNewDb(t *testing.T) { 10 | db := NewDb() 11 | 12 | if db.Dialect != "sqlite" { 13 | t.Errorf("Expected Dialect to be 'sqlite', got '%s'", db.Dialect) 14 | } 15 | 16 | if db.DSN != "" { 17 | t.Errorf("Expected DSN to be '', got '%s'", db.DSN) 18 | } 19 | } 20 | 21 | func TestNewMonitor(t *testing.T) { 22 | monitor := NewMonitor() 23 | 24 | if monitor.PollEvery != "5" { 25 | t.Errorf("Expected PollEvery to be '5', got '%s'", monitor.PollEvery) 26 | } 27 | 28 | if monitor.FilesDelta != "10m" { 29 | t.Errorf("Expected FilesDelta to be '10m', got '%s'", monitor.FilesDelta) 30 | } 31 | 32 | if len(monitor.Filetypes) != 0 { 33 | t.Errorf("Expected Filetypes to be empty, got '%v'", monitor.Filetypes) 34 | } 35 | 36 | if monitor.BaseTmpDir != "" { 37 | t.Errorf("Expected BaseTmpDir to be '', got '%s'", monitor.BaseTmpDir) 38 | } 39 | 40 | if len(monitor.Directories) != 0 { 41 | t.Errorf("Expected Directories to be empty, got '%v'", monitor.Directories) 42 | } 43 | 44 | if len(monitor.ProcessorMap) != 0 { 45 | t.Errorf("Expected ProcessorMap to be empty, got '%v'", monitor.ProcessorMap) 46 | } 47 | } 48 | 49 | func TestNewProcessor(t *testing.T) { 50 | processor := NewProcessor() 51 | 52 | if processor.ReportsUploadPath != "/customers/athena-reports/" { 53 | t.Errorf("Expected ReportsUploadPath to be '/customers/athena-reports/', got '%s'", processor.ReportsUploadPath) 54 | } 55 | 56 | if processor.BatchCommentsEvery != "10m" { 57 | t.Errorf("Expected BatchCommentsEvery to be '10m', got '%s'", processor.BatchCommentsEvery) 58 | } 59 | 60 | if processor.BaseTmpDir != "" { 61 | t.Errorf("Expected BaseTmpDir to be '', got '%s'", processor.BaseTmpDir) 62 | } 63 | 64 | if len(processor.SubscribeTo) != 0 { 65 | t.Errorf("Expected SubscribeTo to be empty, got '%v'", processor.SubscribeTo) 66 | } 67 | } 68 | 69 | func TestNewSalesforce(t *testing.T) { 70 | salesforce := NewSalesForce() 71 | 72 | if salesforce.Endpoint != "" { 73 | t.Errorf("Expected Endpoint to be '', got '%s'", salesforce.Endpoint) 74 | } 75 | 76 | if salesforce.Username != "" { 77 | t.Errorf("Expected Username to be '', got '%s'", salesforce.Username) 78 | } 79 | 80 | if salesforce.Password != "" { 81 | t.Errorf("Expected Password to be '', got '%s'", salesforce.Password) 82 | } 83 | 84 | if salesforce.SecurityToken != "" { 85 | t.Errorf("Expected SecurityToken to be '', got '%s'", salesforce.SecurityToken) 86 | } 87 | 88 | if salesforce.MaxCommentLength != 3000 { 89 | t.Errorf("Expected MaxCommentLength to be 3000, got '%d'", salesforce.MaxCommentLength) 90 | } 91 | 92 | if salesforce.EnableChatter { 93 | t.Errorf("Expected EnableChatter to be false, got true") 94 | } 95 | } 96 | 97 | func TestNewConfigFromFile(t *testing.T) { 98 | // Create a temporary file 99 | tempFile, err := ioutil.TempFile("", "config.yaml") 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | defer os.Remove(tempFile.Name()) // Clean up 104 | 105 | // Write some YAML content to the file 106 | yamlContent := ` 107 | db: 108 | dialect: postgres 109 | monitor: 110 | poll-every: 10 111 | files-delta: 20m 112 | ` 113 | if _, err := tempFile.Write([]byte(yamlContent)); err != nil { 114 | t.Fatal(err) 115 | } 116 | if err := tempFile.Close(); err != nil { 117 | t.Fatal(err) 118 | } 119 | 120 | // Call NewConfigFromFile with the path of the temporary file 121 | config, err := NewConfigFromFile([]string{tempFile.Name()}) 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | 126 | // Check if the config has the expected values 127 | if config.Db.Dialect != "postgres" { 128 | t.Errorf("Expected Db.Dialect to be 'postgres', got '%s'", config.Db.Dialect) 129 | } 130 | 131 | if config.Monitor.PollEvery != "10" { 132 | t.Errorf("Expected Monitor.PollEvery to be '10', got '%s'", config.Monitor.PollEvery) 133 | } 134 | 135 | if config.Monitor.FilesDelta != "20m" { 136 | t.Errorf("Expected Monitor.FilesDelta to be '20m', got '%s'", config.Monitor.FilesDelta) 137 | } 138 | 139 | if config.Salesforce.MaxCommentLength != 3000 { 140 | t.Errorf("Expected MaxCommentLength to be 3000, got '%d'", config.Salesforce.MaxCommentLength) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/makyo/snuffler" 5 | "gopkg.in/yaml.v3" 6 | ) 7 | 8 | type Script struct { 9 | Timeout string `yaml:"timeout" default:"0s"` 10 | ExitCodes string `yaml:"exit-codes" default:"any"` 11 | Run string `yaml:"run"` 12 | RunScript string 13 | } 14 | 15 | type Report struct { 16 | Timeout string `yaml:"timeout" default:"0s"` 17 | Scripts map[string]Script `yaml:"scripts"` 18 | } 19 | 20 | type Subscriber struct { 21 | Topic string `yaml:"topic"` 22 | SFCommentEnabled bool `yaml:"sf-comment-enabled"` 23 | SFCommentIsPublic bool `yaml:"sf-comment-public" default:"false"` 24 | SFComment string `yaml:"sf-comment"` 25 | Reports map[string]Report `yaml:"reports"` 26 | } 27 | 28 | type Db struct { 29 | Dialect string `yaml:"dialect" default:"sqlite"` 30 | DSN string `yaml:"dsn"` 31 | } 32 | 33 | func NewDb() Db { 34 | return Db{ 35 | Dialect: "sqlite", 36 | } 37 | } 38 | 39 | type Monitor struct { 40 | PollEvery string `yaml:"poll-every"` 41 | FilesDelta string `yaml:"files-delta"` 42 | Filetypes []string `yaml:"filetypes"` 43 | BaseTmpDir string `yaml:"base-tmpdir"` 44 | Directories []string `yaml:"directories"` 45 | ProcessorMap []struct { 46 | Type string `yaml:"type"` 47 | Regex string `yaml:"regex"` 48 | Processor string `yaml:"processor"` 49 | } `yaml:"processor-map"` 50 | } 51 | 52 | func NewMonitor() Monitor { 53 | return Monitor{ 54 | PollEvery: "5", 55 | FilesDelta: "10m", 56 | } 57 | } 58 | 59 | type Processor struct { 60 | ReportsUploadPath string `yaml:"reports-upload-dir"` 61 | BatchCommentsEvery string `yaml:"batch-comments-every"` 62 | BaseTmpDir string `yaml:"base-tmpdir"` 63 | KeepProcessingOutput bool `yaml:"keep-processing-output"` 64 | SubscribeTo map[string]Subscriber `yaml:"subscribers,omitempty"` 65 | } 66 | 67 | func NewProcessor() Processor { 68 | return Processor{ 69 | ReportsUploadPath: "/customers/athena-reports/", 70 | BatchCommentsEvery: "10m", 71 | } 72 | } 73 | 74 | type SalesForce struct { 75 | EnableChatter bool `yaml:"enable-chatter"` 76 | Endpoint string `yaml:"endpoint"` 77 | MaxCommentLength int `yaml:"max-comment-length"` 78 | Password string `yaml:"password"` 79 | SecurityToken string `yaml:"security-token"` 80 | Username string `yaml:"username"` 81 | } 82 | 83 | func NewSalesForce() SalesForce { 84 | return SalesForce{ 85 | MaxCommentLength: 4000 - 1000, // A very conservative buffer of max length per Salesforce comment (4000) without header text for comments 86 | EnableChatter: false, 87 | } 88 | } 89 | 90 | type Config struct { 91 | Db Db `yaml:"db,omitempty"` 92 | Monitor Monitor `yaml:"monitor,omitempty"` 93 | Processor Processor `yaml:"processor,omitempty"` 94 | Salesforce SalesForce `yaml:"salesforce,omitempty"` 95 | FilesCom struct { 96 | Key string `yaml:"key"` 97 | Endpoint string `yaml:"endpoint"` 98 | } `yaml:"filescom,omitempty"` 99 | } 100 | 101 | func NewConfig() Config { 102 | return Config{ 103 | Db: NewDb(), 104 | Monitor: NewMonitor(), 105 | Processor: NewProcessor(), 106 | Salesforce: NewSalesForce(), 107 | } 108 | } 109 | 110 | func (cfg *Config) String() string { 111 | tempCfg := *cfg 112 | // Sanitize output, i.e. remove sensitive information. 113 | tempCfg.Salesforce.Password = "**********" 114 | tempCfg.Salesforce.SecurityToken = "**********" 115 | tempCfg.FilesCom.Key = "**********" 116 | result, err := yaml.Marshal(tempCfg) 117 | if err != nil { 118 | return "could not marshal config" 119 | } 120 | return string(result) 121 | } 122 | 123 | func NewConfigFromFile(filePaths []string) (*Config, error) { 124 | var config Config = NewConfig() 125 | 126 | s := snuffler.New(&config) 127 | for _, filepath := range filePaths { 128 | if err := s.AddFile(filepath); err != nil { 129 | return nil, err 130 | } 131 | } 132 | 133 | if err := s.Snuffle(); err != nil { 134 | return nil, err 135 | } 136 | 137 | return &config, nil 138 | } 139 | 140 | func NewConfigFromBytes(data []byte) (*Config, error) { 141 | var config Config 142 | if err := yaml.Unmarshal(data, &config); err != nil { 143 | return nil, err 144 | } 145 | return &config, nil 146 | } 147 | -------------------------------------------------------------------------------- /pkg/common/salesforce.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "html" 6 | "regexp" 7 | 8 | log "github.com/sirupsen/logrus" 9 | 10 | "github.com/canonical/athena-core/pkg/config" 11 | "github.com/simpleforce/simpleforce" 12 | ) 13 | 14 | type ErrNoCaseFound struct { 15 | number string 16 | } 17 | 18 | func (e ErrNoCaseFound) Error() string { 19 | return fmt.Sprintf("no case found in Salesforce with number '%s'", e.number) 20 | } 21 | 22 | var ErrAuthentication = simpleforce.ErrAuthentication 23 | 24 | type SalesforceClient interface { 25 | DescribeGlobal() (*simpleforce.SObjectMeta, error) 26 | GetCaseByNumber(number string) (*Case, error) 27 | PostChatter(caseId, body string, isPublic bool) *simpleforce.SObject 28 | PostComment(caseId, body string, isPublic bool) *simpleforce.SObject 29 | Query(query string) (*simpleforce.QueryResult, error) 30 | SObject(objectName ...string) *simpleforce.SObject 31 | } 32 | 33 | type SalesforceClientFactory interface { 34 | NewSalesforceClient(config *config.Config) (SalesforceClient, error) 35 | } 36 | 37 | type BaseSalesforceClient struct { 38 | *simpleforce.Client 39 | } 40 | 41 | type BaseSalesforceClientFactory struct{} 42 | 43 | func NewSalesforceClient(config *config.Config) (SalesforceClient, error) { 44 | log.Infof("Creating new Salesforce client") 45 | client := simpleforce.NewClient(config.Salesforce.Endpoint, simpleforce.DefaultClientID, simpleforce.DefaultAPIVersion) 46 | if err := client.LoginPassword(config.Salesforce.Username, config.Salesforce.Password, config.Salesforce.SecurityToken); err != nil { 47 | return nil, err 48 | } 49 | return &BaseSalesforceClient{client}, nil 50 | } 51 | 52 | func (sf *BaseSalesforceClientFactory) NewSalesforceClient(config *config.Config) (SalesforceClient, error) { 53 | return NewSalesforceClient(config) 54 | } 55 | 56 | type Case struct { 57 | Id, CaseNumber, AccountId, Customer string 58 | } 59 | 60 | func (sf *BaseSalesforceClient) GetCaseByNumber(number string) (*Case, error) { 61 | q := "SELECT Id,CaseNumber,AccountId FROM Case WHERE CaseNumber LIKE '%" + number + "%'" 62 | result, err := sf.Query(q) 63 | if err != nil { 64 | if err == simpleforce.ErrAuthentication { 65 | return nil, ErrAuthentication 66 | } 67 | return nil, err 68 | } 69 | 70 | for _, record := range result.Records { 71 | account := sf.SObject("Account").Get(record.StringField("AccountId")) 72 | if account != nil { 73 | return &Case{ 74 | Id: record.StringField("Id"), 75 | CaseNumber: record.StringField("CaseNumber"), 76 | AccountId: record.StringField("AccountId"), 77 | Customer: account.StringField("Name"), 78 | }, nil 79 | } 80 | } 81 | return nil, ErrNoCaseFound{number} 82 | } 83 | 84 | func (sf *BaseSalesforceClient) PostComment(caseId, body string, isPublic bool) *simpleforce.SObject { 85 | log.Debugf("Posting comment for case %s", caseId) 86 | return sf.SObject("CaseComment"). 87 | Set("ParentId", caseId). 88 | Set("CommentBody", html.UnescapeString(body)). 89 | Set("IsPublished", isPublic). 90 | Create() 91 | } 92 | 93 | func (sf *BaseSalesforceClient) PostChatter(caseId, body string, isPublic bool) *simpleforce.SObject { 94 | log.Debugf("Posting comment to chatter for case %s", caseId) 95 | visibility := "InternalUsers" 96 | if isPublic { 97 | visibility = "AllUsers" 98 | } 99 | newComment := sf.SObject("FeedItem"). 100 | Set("ParentId", caseId). 101 | Set("Body", body). 102 | Set("Visibility", visibility). 103 | Create() 104 | if newComment != nil { 105 | log.Debugf("Successfully posted comment as FeedItem to case %s", caseId) 106 | return newComment 107 | } 108 | log.Warnf("Unable to post comment as FeedItem object to case %s", caseId) 109 | newComment = sf.SObject("CaseFeed"). 110 | Set("ParentId", caseId). 111 | Set("Body", body). 112 | Set("Visibility", visibility). 113 | Create() 114 | if newComment != nil { 115 | log.Debugf("Successfully posted comment as CaseFeed object to case %s", caseId) 116 | return newComment 117 | } 118 | log.Errorf("Unable to post comment as either FeedItem or CaseFeed object for %s", caseId) 119 | return newComment 120 | } 121 | 122 | func GetCaseNumberFromFilename(filename string) (string, error) { 123 | regex, err := regexp.Compile(`(\d{6,})`) 124 | if err != nil { 125 | return "", err 126 | } 127 | 128 | for _, candidate := range regex.FindAll([]byte(filename), 1) { 129 | if len(candidate) <= 8 && len(candidate) > 0 { 130 | return string(candidate), nil 131 | } 132 | } 133 | 134 | return "", fmt.Errorf("failed to identify case number from filename '%s'", filename) 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Athena 2 | 3 | [![Athena Processor](https://img.shields.io/badge/Container_Image-Athena_Processor-blue)](https://github.com/canonical/athena-core/pkgs/container/athena-core%2Fathena-processor) 4 | [![Athena Monitor](https://img.shields.io/badge/Container_Image-Athena_Monitor-blue)](https://github.com/canonical/athena-core/pkgs/container/athena-core%2Fathena-monitor) 5 | [![Docker publish (ghcr.io)](https://github.com/canonical/athena-core/actions/workflows/ghcr-publish.yaml/badge.svg)](https://github.com/canonical/athena-core/actions/workflows/ghcr-publish.yaml) 6 | 7 | Athena is a file processor service, that consumes files stored in the files.com 8 | API and runs a series of reports over the downloaded artifacts and subsequently 9 | it talks with the Salesforce API for performing actions (currently, only 10 | comments are supported) 11 | 12 | ## Basics 13 | 14 | There are 3 software components in athena: 15 | 16 | 1. *Athena-monitor*: Monitor changes in several directories across a file.com 17 | account and if new files are found those are sent to the processor for 18 | background processing. 19 | 20 | 2. *Nats*: Nats is a light messaging daemon that allows a pubsub system to be 21 | implemented on top, its used to dispatch messages from *athena-monitor* to a 22 | *athena-processor* 23 | 24 | 3. *Athena-processor*: Subscribes to messages from monitor and routes the 25 | reports that have to be run over a given detected file, subsequently it will 26 | perform an action on salesforce (such as posting a comment, etc) 27 | 28 | The basic flowchart of interaction is as follows 29 | 30 | [![](https://mermaid.ink/img/eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW0F0aGVuYSBNb25pdG9yXSAtLT58RmV0Y2ggRmlsZXN8IEIoRmlsZXMuY29tIEFQSSlcbiAgICBCIC0tPiBDe05ldyBmaWxlcyB0byBwcm9jZXNzP31cbiAgICBDIC0tPnxZZXN8IEQoTmF0cyBNZXNzYWdlKVxuICAgIEMgLS0-fE5vfCBBXG4gICAgRCAtLT58RmlsZXBhdGh8RVtBdGhlbmEgUHJvY2Vzc29yXVxuICAgIEVbQXRoZW5hIFByb2Nlc3Nvcl0tLT4gRntQb3N0IGNvbW1lbnQgb24gY2FzZT99XG4gICAgRiAtLT58WWVzfCBHKFNhbGVzZm9yY2UgQVBJKVxuICAgIEYgLS0-fE5vfEFcbiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW0F0aGVuYSBNb25pdG9yXSAtLT58RmV0Y2ggRmlsZXN8IEIoRmlsZXMuY29tIEFQSSlcbiAgICBCIC0tPiBDe05ldyBmaWxlcyB0byBwcm9jZXNzP31cbiAgICBDIC0tPnxZZXN8IEQoTmF0cyBNZXNzYWdlKVxuICAgIEMgLS0-fE5vfCBBXG4gICAgRCAtLT58RmlsZXBhdGh8RVtBdGhlbmEgUHJvY2Vzc29yXVxuICAgIEVbQXRoZW5hIFByb2Nlc3Nvcl0tLT4gRntQb3N0IGNvbW1lbnQgb24gY2FzZT99XG4gICAgRiAtLT58WWVzfCBHKFNhbGVzZm9yY2UgQVBJKVxuICAgIEYgLS0-fE5vfEFcbiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9) 31 | 32 | ## Configuration 33 | 34 | The monitor and the processor can be configured via `yaml` configuration files 35 | by calling 36 | 37 | ```console 38 | athena-monitor --config config.yaml [--config config2.yaml [...]] 39 | athena-processor --config config.yaml [--config config2.yaml [...]] 40 | ``` 41 | 42 | where configuration files are loaded in the order they are given on the command 43 | line and values in later configuration files superseed values read earlier. 44 | 45 | The basic structure looks like 46 | 47 | ```yaml 48 | monitor: 49 | files-delta: 1m 50 | poll-every: 10s 51 | base-tmpdir: "/tmp/athena" 52 | directories: 53 | - "/uploads" 54 | processor-map: 55 | - type: filename 56 | regex: ".*sosreport.*.tar.[xz|gz]+$" 57 | processor: sosreports 58 | ``` 59 | 60 | ### Monitor Configuration 61 | 62 | ### Processor Configuration 63 | 64 | ## Hacking 65 | 66 | In order to stand up a development environment, you will need 67 | 68 | - `make` 69 | - `docker` 70 | - `docker-compose` 71 | - `golang >= 1.19` 72 | 73 | For running a docker based installation locally you will need a sandbox account 74 | on Salesforce and a sandbox directory on files.com. Supply 75 | 76 | 1. A list of the corresponding credentials in `creds.yaml`, 77 | 78 | ```yaml 79 | db: 80 | dialect: mysql 81 | dsn: "athena:athena@tcp(db:3306)/athena?charset=utf8&parseTime=true" 82 | 83 | filescom: 84 | key : "***" 85 | endpoint: "https://..." 86 | 87 | salesforce: 88 | endpoint: "https://..." 89 | username: "***" 90 | password: "***" 91 | security-token: "***" 92 | ``` 93 | 94 | 1. A list of directories to monitor in `athena-monitor-directories.yaml`, 95 | 96 | ```yaml 97 | monitor: 98 | directories: 99 | - "/sandbox/..." 100 | - "/sandbox/..." 101 | ``` 102 | 103 | 1. A path for where the report uploads will go in 104 | `athena-processor-upload.yaml`, 105 | 106 | ```yaml 107 | processor: 108 | reports-upload-dir: "/sandbox/..." 109 | ``` 110 | 111 | And finally run 112 | 113 | ```shell 114 | make devel 115 | ``` 116 | 117 | In case the `docker-build` step fails you can try to re-run the `make` command 118 | without using the cache, 119 | 120 | ```shell 121 | NOCACHE=1 make devel 122 | ``` 123 | 124 | The `devel` deployment includes a `debug` container which can be used to inspect 125 | the database. 126 | 127 | ```shell 128 | $ docker exec --interactive --tty debug bash 129 | # mysql -h db -u athena -pathena athena 130 | mysql> describe files; 131 | +------------+---------------------+------+-----+---------+----------------+ 132 | | Field | Type | Null | Key | Default | Extra | 133 | +------------+---------------------+------+-----+---------+----------------+ 134 | | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | 135 | | created_at | datetime(3) | YES | | NULL | | 136 | | updated_at | datetime(3) | YES | | NULL | | 137 | | deleted_at | datetime(3) | YES | MUL | NULL | | 138 | | created | datetime(3) | YES | | NULL | | 139 | | dispatched | tinyint(1) | YES | | 0 | | 140 | | path | longtext | YES | | NULL | | 141 | +------------+---------------------+------+-----+---------+----------------+ 142 | 7 rows in set (0.01 sec) 143 | ``` 144 | -------------------------------------------------------------------------------- /pkg/monitor/monitor.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "regexp" 8 | "sync" 9 | "time" 10 | 11 | "github.com/canonical/athena-core/pkg/common" 12 | "github.com/canonical/athena-core/pkg/common/db" 13 | "github.com/canonical/athena-core/pkg/config" 14 | "github.com/lileio/pubsub/v2" 15 | "github.com/lileio/pubsub/v2/middleware/defaults" 16 | log "github.com/sirupsen/logrus" 17 | "gorm.io/gorm" 18 | ) 19 | 20 | type Monitor struct { 21 | Config *config.Config // Configuration instance 22 | Db *gorm.DB // Database connection 23 | FilesComClientFactory common.FilesComClientFactory // How to create a new Files.com client 24 | mu *sync.Mutex // A mutex 25 | Provider pubsub.Provider // Messaging provider 26 | SalesforceClientFactory common.SalesforceClientFactory // How to create a new Salesforce client 27 | } 28 | 29 | func (m *Monitor) GetMatchingProcessors(filename string, c *common.Case) ([]string, error) { 30 | var processors []string 31 | for _, processor := range m.Config.Monitor.ProcessorMap { 32 | switch processor.Type { 33 | case "filename": 34 | { 35 | if ok, _ := regexp.Match(processor.Regex, []byte(filename)); ok { 36 | processors = append(processors, processor.Processor) 37 | } 38 | } 39 | case "case": 40 | { 41 | if c == nil { 42 | continue 43 | } 44 | if ok, _ := regexp.Match(processor.Regex, []byte(c.CaseNumber)); ok { 45 | processors = append(processors, processor.Processor) 46 | } 47 | } 48 | default: 49 | fmt.Printf("No handler found for type=%s", processor.Type) 50 | } 51 | } 52 | if len(processors) <= 0 { 53 | return nil, fmt.Errorf("no processor found for file=%s", filename) 54 | } 55 | return processors, nil 56 | } 57 | 58 | func (m *Monitor) GetLatestFiles(dirs []string, duration time.Duration) ([]db.File, error) { 59 | log.Debugf("Getting files in %v", dirs) 60 | filesClient, err := m.FilesComClientFactory.NewFilesComClient(m.Config.FilesCom.Key, m.Config.FilesCom.Endpoint) 61 | if err != nil { 62 | panic(err) 63 | } 64 | files, err := filesClient.GetFiles(dirs) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | for _, file := range files { 70 | m.Db.Where(db.File{Path: file.Path}).FirstOrCreate(&file) 71 | } 72 | 73 | m.Db.Where("created > ?", time.Now().Add(-duration)).Find(&files) 74 | return files, nil 75 | } 76 | 77 | func (m *Monitor) GetMatchingProcessorByFile(files []db.File) (map[string][]db.File, error) { 78 | var sfCase = &common.Case{} 79 | var results = make(map[string][]db.File) 80 | 81 | salesforceClient, err := m.SalesforceClientFactory.NewSalesforceClient(m.Config) 82 | if err != nil { 83 | panic(err) 84 | } 85 | 86 | for _, file := range files { 87 | var processors []string 88 | 89 | log.Debugf("Analyzing file %s", file.Path) 90 | caseNumber, err := common.GetCaseNumberFromFilename(file.Path) 91 | if err == nil { 92 | sfCase, err = salesforceClient.GetCaseByNumber(caseNumber) 93 | if err != nil { 94 | log.Warningf("Failed to get a case from number: '%s'", caseNumber) 95 | } else { 96 | log.Debugf("Found customer '%s' for case number %s", sfCase.Customer, caseNumber) 97 | } 98 | } else { 99 | log.Warningf("Failed to identify case from filename '%s': %s", file.Path, err) 100 | } 101 | 102 | if sfCase != nil { 103 | processors, err = m.GetMatchingProcessors(file.Path, sfCase) 104 | } else { 105 | processors, err = m.GetMatchingProcessors(file.Path, nil) 106 | } 107 | 108 | for _, processor := range processors { 109 | results[processor] = append(results[processor], file) 110 | } 111 | 112 | if err != nil { 113 | log.Errorf("Failed to identify processor(s) for '%s' (case=%s): %s", file.Path, caseNumber, err) 114 | continue 115 | } 116 | } 117 | 118 | return results, nil 119 | } 120 | 121 | func NewMonitor(provider pubsub.Provider, cfg *config.Config, dbConn *gorm.DB, 122 | salesforceClientFactory common.SalesforceClientFactory, 123 | filesComClientFactory common.FilesComClientFactory) (*Monitor, error) { 124 | var err error 125 | if dbConn == nil { 126 | dbConn, err = db.GetDBConn(cfg) 127 | if err != nil { 128 | return nil, err 129 | } 130 | } 131 | 132 | return &Monitor{ 133 | Config: cfg, 134 | Db: dbConn, 135 | FilesComClientFactory: filesComClientFactory, 136 | mu: new(sync.Mutex), 137 | Provider: provider, 138 | SalesforceClientFactory: salesforceClientFactory, 139 | }, nil 140 | } 141 | 142 | func (m *Monitor) PollNewFiles(ctx *context.Context, duration time.Duration) { 143 | filesDelta, err := time.ParseDuration(m.Config.Monitor.FilesDelta) 144 | if err != nil { 145 | log.Error(err) 146 | return 147 | } 148 | 149 | latestFiles, err := m.GetLatestFiles(m.Config.Monitor.Directories, filesDelta) 150 | if err != nil { 151 | log.Error(err) 152 | return 153 | } 154 | 155 | processors, err := m.GetMatchingProcessorByFile(latestFiles) 156 | if err != nil { 157 | log.Error(err) 158 | return 159 | } 160 | 161 | filesClient, err := m.FilesComClientFactory.NewFilesComClient(m.Config.FilesCom.Key, m.Config.FilesCom.Endpoint) 162 | if err != nil { 163 | panic(err) 164 | } 165 | 166 | log.Infof("Found %d new files, %d to be processed", len(latestFiles), len(processors)) 167 | for processor, files := range processors { 168 | for _, file := range files { 169 | if file.Dispatched { 170 | log.Infof("File %s already dispatched, skipping", file.Path) 171 | continue 172 | } 173 | log.Infof("Downloading file %s to shared folder", file.Path) 174 | basePath := m.Config.Monitor.BaseTmpDir 175 | if basePath == "" { 176 | basePath = "/tmp" 177 | } 178 | if _, err := os.Stat(basePath); os.IsNotExist(err) { 179 | log.Debugf("Temporary base path '%s' doesn't exist - creating", basePath) 180 | if err = os.MkdirAll(basePath, 0755); err != nil { 181 | log.Errorf("Failed to create temporary base path: %s - skipping", err.Error()) 182 | continue 183 | } 184 | } 185 | log.Debugf("Using temporary base path: %s", basePath) 186 | fileEntry, err := filesClient.Download(&file, basePath) 187 | if err != nil { 188 | log.Errorf("Failed to download %s: %s - skipping", file.Path, err) 189 | continue 190 | } 191 | log.Infof("Downloaded %s", fileEntry.Path) 192 | 193 | log.Infof("Sending file: %s to processor: %s", file.Path, processor) 194 | publishResults := pubsub.PublishJSON(*ctx, processor, file) 195 | if publishResults.Err != nil { 196 | file.Dispatched = false 197 | log.Errorf("Cannot dispatch file: %s to processor, error: %s", file.Path, err) 198 | } else { 199 | file.Dispatched = true 200 | log.Debugf("File: %s -- flagged as dispatched", file.Path) 201 | } 202 | m.Db.Save(file) 203 | } 204 | } 205 | } 206 | func (m *Monitor) Run(ctx context.Context) error { 207 | pubsub.SetClient(&pubsub.Client{ 208 | ServiceName: "athena-processor", 209 | Provider: m.Provider, 210 | Middleware: defaults.Middleware, 211 | }) 212 | 213 | if ctx == nil { 214 | var cancel context.CancelFunc 215 | ctx, cancel = context.WithCancel(context.Background()) 216 | defer cancel() 217 | } 218 | 219 | pollEvery, err := time.ParseDuration(m.Config.Monitor.PollEvery) 220 | if err != nil { 221 | return err 222 | } 223 | 224 | go common.RunOnInterval(ctx, m.mu, pollEvery, m.PollNewFiles) 225 | <-ctx.Done() 226 | return nil 227 | } 228 | -------------------------------------------------------------------------------- /cmd/salesforce-test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sort" 7 | "strconv" 8 | 9 | "github.com/canonical/athena-core/pkg/common" 10 | "github.com/canonical/athena-core/pkg/config" 11 | "gopkg.in/alecthomas/kingpin.v2" 12 | ) 13 | 14 | var configs = common.StringList( 15 | kingpin.Flag("config", "Path to the athena configuration file").Default("/etc/athena/main.yaml").Short('c'), 16 | ) 17 | 18 | var commit string 19 | 20 | var ( 21 | addCaseFeed = kingpin.Flag("add-case-feed", "Add new CaseFeed object to case").Default("").String() 22 | addFeedItem = kingpin.Flag("add-feed-item", "Add a new FeedItem object to the case").Default("").String() 23 | allCase = kingpin.Flag("all-cases", "Get all Case objects").Default("false").Bool() 24 | allCaseFeed = kingpin.Flag("all-case-feed", "Get all CaseFeed objects").Default("false").Bool() 25 | allFeedComment = kingpin.Flag("all-feed-comments", "Get all FeedComment objects").Default("false").Bool() 26 | allFeedItem = kingpin.Flag("all-feed-items", "Get all FeedItem objects").Default("false").Bool() 27 | caseNumber = kingpin.Flag("case-id", "The case ID to query").Default("").String() 28 | commentVisibility = kingpin.Flag("visibility", "Set the comment visibility {public, private)").Default("private").String() 29 | describe = kingpin.Flag("describe", "Describe object").Default("").String() 30 | describeGlobal = kingpin.Flag("describe-global", "Get the List of all available objects and their metadata for your organization's data").Default("false").Bool() 31 | getCaseComment = kingpin.Flag("case-comment", "Get all CaseComment objects of case").Default("false").Bool() 32 | getCaseFeed = kingpin.Flag("case-feed", "Get all CaseFeed objects from a case").Default("false").Bool() 33 | getFeedItem = kingpin.Flag("feed-item", "Get all FeedItem objects of case").Default("false").Bool() 34 | runQuery = kingpin.Flag("query", "Run query").Default("").String() 35 | ) 36 | 37 | func main() { 38 | log.Printf("Starting version %s", commit) 39 | 40 | kingpin.HelpFlag.Short('h') 41 | kingpin.Parse() 42 | 43 | switch *commentVisibility { 44 | case "public", "private": 45 | // All good, do nothing. 46 | default: 47 | log.Fatal("Invalid visibility value. Allowed values are 'public' and 'private'.") 48 | } 49 | 50 | cfg, err := config.NewConfigFromFile(*configs) 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | sfClient, err := common.NewSalesforceClient(cfg) 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | if *allCase { 61 | getAllCases(sfClient) 62 | } 63 | 64 | if *allFeedComment { 65 | getAllFeedComments(sfClient) 66 | } 67 | 68 | if *allFeedItem { 69 | getAllFeedItems(sfClient) 70 | } 71 | 72 | if *allCaseFeed { 73 | getAllCaseFeed(sfClient) 74 | } 75 | 76 | if *describeGlobal { 77 | getDescribeGlobal(sfClient) 78 | } 79 | 80 | if len(*describe) > 0 { 81 | getDescribe(sfClient, *describe) 82 | } 83 | 84 | if len(*caseNumber) > 0 { 85 | caseId := getCase(sfClient) 86 | if *getCaseComment { 87 | getCaseComments(caseId, sfClient) 88 | } 89 | 90 | if *getFeedItem { 91 | getFeedItems(caseId, sfClient) 92 | } 93 | 94 | if *getCaseFeed { 95 | getCaseFeeds(caseId, sfClient) 96 | } 97 | 98 | if len(*addFeedItem) > 0 { 99 | newFeedItem(sfClient, caseId, *addFeedItem) 100 | } 101 | 102 | if len(*addCaseFeed) > 0 { 103 | newCaseFeed(sfClient, caseId, *addCaseFeed) 104 | } 105 | } 106 | 107 | if len(*runQuery) > 0 { 108 | getQueryResult(sfClient, *runQuery) 109 | } 110 | } 111 | 112 | func getQueryResult(sfClient common.SalesforceClient, queryString string) { 113 | log.Printf("Running query: '%s'", queryString) 114 | records, err := sfClient.Query(queryString) 115 | if err != nil { 116 | log.Fatalf("Failed to run query: %v", err) 117 | } 118 | if len(records.Records) == 0 { 119 | log.Fatal("Could not find any records") 120 | } 121 | for _, record := range records.Records { 122 | log.Printf("%v", record) 123 | } 124 | } 125 | 126 | func newFeedItem(sfClient common.SalesforceClient, caseId string, comment string) { 127 | log.Print("Added new FeedItem object to case") 128 | visibility := "" 129 | switch *commentVisibility { 130 | case "public": 131 | visibility = "AllUsers" 132 | case "private": 133 | visibility = "InternalUsers" 134 | default: 135 | log.Fatal("Unknown visibility") 136 | } 137 | sfClient.SObject("FeedItem"). 138 | Set("ParentId", caseId). 139 | Set("Body", comment). 140 | Set("Visibility", visibility). 141 | Create() 142 | } 143 | 144 | func newCaseFeed(sfClient common.SalesforceClient, caseId string, comment string) { 145 | log.Printf("Added new CaseFeed object to case %s", caseId) 146 | visibility := "" 147 | switch *commentVisibility { 148 | case "public": 149 | visibility = "AllUsers" 150 | case "private": 151 | visibility = "InternalUsers" 152 | default: 153 | log.Fatal("Unknown visibility") 154 | } 155 | sfClient.SObject("CaseFeed"). 156 | Set("ParentId", caseId). 157 | Set("Body", comment). 158 | Set("Visibility", visibility). 159 | Create() 160 | } 161 | 162 | func getFeedItems(caseId string, sfClient common.SalesforceClient) { 163 | log.Print("Getting all FeedItem objects for case") 164 | query := fmt.Sprintf("SELECT Id, Body FROM FeedItem WHERE ParentID = '%s'", caseId) 165 | log.Printf("Running query: '%s'", query) 166 | records, err := sfClient.Query(query) 167 | if err != nil { 168 | log.Fatalf("Failed to get chatter comments: %v", err) 169 | } 170 | if len(records.Records) == 0 { 171 | log.Fatal("Could not find any chatter comments") 172 | } 173 | for _, comment := range records.Records { 174 | log.Printf("%s: %s", comment["Id"], comment["Body"]) 175 | } 176 | } 177 | 178 | func getCaseFeeds(caseId string, sfClient common.SalesforceClient) { 179 | log.Print("Getting all CaseFeed objects for case") 180 | query := fmt.Sprintf("SELECT Id, Body FROM CaseFeed WHERE ParentID = '%s'", caseId) 181 | log.Printf("Running query: '%s'", query) 182 | records, err := sfClient.Query(query) 183 | if err != nil { 184 | log.Fatalf("Failed to get chatter comments: %v", err) 185 | } 186 | if len(records.Records) == 0 { 187 | log.Fatal("Could not find any chatter comments") 188 | } 189 | for _, comment := range records.Records { 190 | if comment["Body"] != nil { 191 | log.Printf("%s: %s", comment["Id"], comment["Body"]) 192 | } 193 | } 194 | } 195 | 196 | func getCaseComments(caseId string, sfClient common.SalesforceClient) { 197 | log.Print("Getting case comments") 198 | query := fmt.Sprintf("SELECT Id, CommentBody FROM CaseComment WHERE ParentId = '%s'", caseId) 199 | records, err := sfClient.Query(query) 200 | if err != nil { 201 | log.Fatalf("Failed to get case comments: %v", err) 202 | } 203 | if len(records.Records) == 0 { 204 | log.Fatal("Could not find any case comments") 205 | } 206 | for _, comment := range records.Records { 207 | log.Printf("%s", comment["CommentBody"]) 208 | } 209 | } 210 | 211 | func getCase(sfClient common.SalesforceClient) string { 212 | caseNumberAsInt, err := strconv.ParseInt(*caseNumber, 10, 64) 213 | if err != nil { 214 | log.Fatalf("Failed to parse the case number %s", *caseNumber) 215 | } 216 | caseNumberFormatted := fmt.Sprintf("%08d", caseNumberAsInt) 217 | 218 | log.Printf("Searching for case %s", caseNumberFormatted) 219 | query := fmt.Sprintf("SELECT Id, CaseNumber FROM Case WHERE CaseNumber = '%s'", caseNumberFormatted) 220 | records, err := sfClient.Query(query) 221 | if err != nil { 222 | log.Fatalf("Failed to query Salesforce: %v", err) 223 | } 224 | if len(records.Records) > 0 { 225 | log.Printf("%s: %s", records.Records[0]["Id"], records.Records[0]["CaseNumber"]) 226 | } else { 227 | log.Fatalf("Case with ID %s does not exist.\n", *caseNumber) 228 | } 229 | return fmt.Sprintf("%s", records.Records[0]["Id"]) 230 | } 231 | 232 | func getAllFeedItems(sfClient common.SalesforceClient) { 233 | log.Println("All FeedItems:") 234 | records, err := sfClient.Query("SELECT Id from FeedItem") 235 | if err != nil { 236 | log.Fatalln("Failed to query for all FeedItems") 237 | } 238 | for _, result := range records.Records { 239 | log.Printf("%s", result["Id"]) 240 | } 241 | } 242 | 243 | func getAllCaseFeed(sfClient common.SalesforceClient) { 244 | log.Println("All CaseFeed:") 245 | records, err := sfClient.Query("SELECT Id, Body, ParentId from CaseFeed") 246 | if err != nil { 247 | log.Fatalln("Failed to query for all CaseFeed") 248 | } 249 | for _, result := range records.Records { 250 | if result["Body"] != nil { 251 | log.Printf("%s (%s): %s", result["Id"], result["ParentId"], result["Body"]) 252 | } else { 253 | log.Printf("%s (%s): empty body", result["Id"], result["ParentId"]) 254 | } 255 | } 256 | } 257 | 258 | func getDescribeGlobal(sfClient common.SalesforceClient) { 259 | log.Println("Getting all available global objects") 260 | describeResult, err := sfClient.DescribeGlobal() 261 | if err != nil { 262 | log.Fatalf("Failed to get all global objects: %s", err) 263 | } 264 | for key, record := range *describeResult { 265 | if key == "sobjects" { 266 | log.Printf("Type of record is %T", record) 267 | if recordList, ok := record.([]interface{}); ok { 268 | log.Printf("Type of records[0] is %T", recordList[0]) 269 | if record, ok := recordList[0].(map[string]interface{}); ok { 270 | log.Printf("Object '%s'", record["name"]) 271 | for key, object := range record { 272 | log.Printf(" Field %s: %+v", key, object) 273 | } 274 | } 275 | } else { 276 | log.Printf("Object %s is not to type []interface{}", recordList) 277 | } 278 | } else { 279 | log.Printf("Object %s: %+v", key, record) 280 | } 281 | } 282 | } 283 | 284 | func getDescribe(sfClient common.SalesforceClient, objectName string) { 285 | log.Printf("Getting description of '%s'", objectName) 286 | meta := sfClient.SObject(objectName).Describe() 287 | fieldNames := []string{} 288 | log.Println("Fields") 289 | for metaKey, metaValue := range *meta { 290 | if metaKey == "fields" { 291 | if fields, ok := metaValue.([]interface{}); ok { 292 | for _, field := range fields { 293 | if fieldMap, ok := field.(map[string]interface{}); ok { 294 | for fieldName, fieldValue := range fieldMap { 295 | if fieldName == "name" { 296 | fieldNames = append(fieldNames, fmt.Sprintf("%s", fieldValue)) 297 | } 298 | } 299 | } 300 | } 301 | } 302 | } 303 | } 304 | sort.Strings(fieldNames) 305 | for _, field := range fieldNames { 306 | log.Printf(" %s", field) 307 | } 308 | } 309 | 310 | func getAllFeedComments(sfClient common.SalesforceClient) { 311 | log.Println("All FeedComments:") 312 | records, err := sfClient.Query("SELECT Id from FeedComment") 313 | if err != nil { 314 | log.Fatalln("Failed to query for all FeedComments") 315 | } 316 | for _, result := range records.Records { 317 | log.Printf("%s", result["Id"]) 318 | } 319 | } 320 | 321 | func getAllCases(sfClient common.SalesforceClient) { 322 | log.Println("All cases:") 323 | records, err := sfClient.Query("SELECT Id, CaseNumber from Case") 324 | if err != nil { 325 | log.Fatalln("Failed to query for all cases") 326 | } 327 | for _, result := range records.Records { 328 | log.Printf("%s: %s", result["Id"], result["CaseNumber"]) 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /pkg/processor/processor.go: -------------------------------------------------------------------------------- 1 | package processor 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "path" 9 | "path/filepath" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/canonical/athena-core/pkg/common" 15 | "github.com/canonical/athena-core/pkg/common/db" 16 | "github.com/canonical/athena-core/pkg/config" 17 | "github.com/flosch/pongo2/v4" 18 | "github.com/lileio/pubsub/v2" 19 | "github.com/lileio/pubsub/v2/middleware/defaults" 20 | "github.com/simpleforce/simpleforce" 21 | log "github.com/sirupsen/logrus" 22 | "gorm.io/gorm" 23 | ) 24 | 25 | type Processor struct { 26 | Config *config.Config 27 | Db *gorm.DB 28 | FilesComClientFactory common.FilesComClientFactory 29 | Hostname string 30 | Provider pubsub.Provider 31 | SalesforceClientFactory common.SalesforceClientFactory 32 | } 33 | 34 | type BaseSubscriber struct { 35 | Config *config.Config 36 | Db *gorm.DB 37 | FilesComClientFactory common.FilesComClientFactory 38 | Name string 39 | Options pubsub.HandlerOptions 40 | Reports map[string]config.Report 41 | SalesforceClientFactory common.SalesforceClientFactory 42 | } 43 | 44 | func (s *BaseSubscriber) Setup(c *pubsub.Client) { 45 | c.On(s.Options) 46 | } 47 | 48 | type ReportToExecute struct { 49 | File *db.File 50 | Name, BaseDir, Subscriber, FileName string 51 | Output []byte 52 | Scripts map[string]string 53 | Timeout time.Duration 54 | } 55 | 56 | type ReportRunner struct { 57 | Config *config.Config 58 | Db *gorm.DB 59 | FilesComClientFactory common.FilesComClientFactory 60 | Name, Subscriber, Basedir string 61 | Reports []ReportToExecute 62 | SalesforceClientFactory common.SalesforceClientFactory 63 | } 64 | 65 | func RunWithTimeout(baseDir string, timeout time.Duration, command string) ([]byte, error) { 66 | log.Debugf("Running script with %s timeout in %s", timeout, baseDir) 67 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 68 | defer cancel() 69 | cmd := exec.CommandContext(ctx, "bash", "-c", command) 70 | cmd.Dir = baseDir 71 | output, err := cmd.CombinedOutput() 72 | if ctx.Err() == context.DeadlineExceeded { 73 | return nil, nil 74 | } 75 | return output, err 76 | } 77 | 78 | func RunWithoutTimeout(baseDir string, command string) ([]byte, error) { 79 | log.Debugf("Running script without timeout in %s", baseDir) 80 | cmd := exec.Command("bash", "-c", command) 81 | cmd.Dir = baseDir 82 | return cmd.CombinedOutput() 83 | } 84 | 85 | func RunReport(report *ReportToExecute) (map[string][]byte, error) { 86 | var output = make(map[string][]byte) 87 | 88 | for scriptName, script := range report.Scripts { 89 | log.Debugf("Running script '%s' on sosreport '%s'", scriptName, filepath.Base(report.FileName)) 90 | var ret []byte 91 | var err error 92 | if report.Timeout > 0 { 93 | ret, err = RunWithTimeout(report.BaseDir, report.Timeout, script) 94 | } else { 95 | ret, err = RunWithoutTimeout(report.BaseDir, script) 96 | } 97 | log.Debugf("Script '%s' on '%s' completed", scriptName, filepath.Base(report.FileName)) 98 | if err != nil { 99 | log.Errorf("Error occurred (test) while running script: %s", err) 100 | for _, line := range strings.Split(string(ret), "\n") { 101 | log.Error(line) 102 | } 103 | return nil, err 104 | } 105 | output[scriptName] = ret 106 | } 107 | 108 | return output, nil 109 | } 110 | 111 | const DefaultReportOutputFormat = "%s.athena-%s.%s" 112 | 113 | func (runner *ReportRunner) UploadAndSaveReport(report *ReportToExecute, caseNumber string, scriptOutputs map[string][]byte) error { 114 | var file db.File 115 | var uploadPath string 116 | filePath := report.File.Path 117 | 118 | log.Debugf("Fetching files for path '%s' from db", filePath) 119 | result := runner.Db.Where("path = ?", filePath).First(&file) 120 | if result.Error != nil { 121 | return fmt.Errorf("file not found with path '%s' in database", filePath) 122 | } 123 | 124 | log.Infof("Fetching case with number '%s' from Salesforce", caseNumber) 125 | salesforceClient, err := runner.SalesforceClientFactory.NewSalesforceClient(runner.Config) 126 | if err != nil { 127 | log.Errorf("failed to get Salesforce connection: %s", err) 128 | return err 129 | } 130 | sfCase, err := salesforceClient.GetCaseByNumber(caseNumber) 131 | if err != nil { 132 | log.Error(err) 133 | return err 134 | } 135 | 136 | log.Debugf("Case %s successfully fetched from Salesforce", sfCase) 137 | var newReport = new(db.Report) 138 | 139 | newReport.CaseID = sfCase.Id 140 | newReport.Created = time.Now() 141 | newReport.FileID = file.ID 142 | newReport.FileName = filepath.Base(file.Path) 143 | newReport.FilePath = file.Path 144 | newReport.Name = report.Name 145 | newReport.Subscriber = report.Subscriber 146 | 147 | if runner.Config.Processor.ReportsUploadPath == "" { 148 | uploadPath = filePath 149 | } else { 150 | uploadPath = path.Join(runner.Config.Processor.ReportsUploadPath, newReport.FileName) 151 | } 152 | 153 | filesComClient, err := runner.FilesComClientFactory.NewFilesComClient(runner.Config.FilesCom.Key, runner.Config.FilesCom.Endpoint) 154 | if err != nil { 155 | log.Errorf("failed to get new file.com client: %s", err) 156 | return err 157 | } 158 | log.Debugf("Uploading script output(s) to files.com") 159 | for scriptName, output := range scriptOutputs { 160 | dst_fname := fmt.Sprintf(DefaultReportOutputFormat, uploadPath, report.Name, scriptName) 161 | log.Debugf("Uploading script output %s", dst_fname) 162 | uploadedFilePath, err := filesComClient.Upload(string(output), dst_fname) 163 | if err != nil { 164 | return fmt.Errorf("failed to upload file '%s': %s", dst_fname, err.Error()) 165 | } 166 | 167 | log.Debugf("Successfully uploaded file '%s'", uploadedFilePath.Path) 168 | script_result := db.Script{ 169 | Output: string(output), 170 | Name: scriptName, 171 | UploadLocation: uploadedFilePath.Path, 172 | } 173 | newReport.Scripts = append(newReport.Scripts, script_result) 174 | } 175 | 176 | if r := runner.Db.Create(newReport); r.Error != nil { 177 | log.Errorf("Failed to create new report for '%s' in db", newReport.FilePath) 178 | return err 179 | } 180 | 181 | if r := runner.Db.Save(newReport); r.Error != nil { 182 | log.Errorf("Failed to save new report for '%s' in db", newReport.FilePath) 183 | return err 184 | } 185 | 186 | log.Infof("Saved report '%s' in db for case id '%s'", report.Name, sfCase.CaseNumber) 187 | return nil 188 | } 189 | 190 | func (runner *ReportRunner) Run(reportFn func(report *ReportToExecute) (map[string][]byte, error)) error { 191 | for _, report := range runner.Reports { 192 | var err error 193 | 194 | caseNumber, err := common.GetCaseNumberFromFilename(report.File.Path) 195 | if err != nil { 196 | log.Info(err) 197 | continue 198 | } 199 | 200 | log.Debugf("Running '%s' on '%s'", report.Name, report.File.Path) 201 | scriptOutputs, err := reportFn(&report) 202 | if err != nil { 203 | log.Error(err) 204 | continue 205 | } 206 | 207 | log.Debugf("Uploading and saving results of running '%s' on '%s' (count=%d)", report.Name, report.FileName, len(scriptOutputs)) 208 | if err := runner.UploadAndSaveReport(&report, caseNumber, scriptOutputs); err != nil { 209 | log.Errorf("Failed to upload and save output of '%s': %s", report.Name, err) 210 | continue 211 | } 212 | } 213 | 214 | return nil 215 | } 216 | 217 | const DefaultExecutionTimeout = "0s" 218 | 219 | func renderTemplate(ctx *pongo2.Context, data string) (string, error) { 220 | tpl, err := pongo2.FromString(data) 221 | if err != nil { 222 | return "", err 223 | } 224 | out, err := tpl.Execute(*ctx) 225 | if err != nil { 226 | return "", err 227 | } 228 | return out, nil 229 | } 230 | 231 | func NewReportRunner(cfg *config.Config, dbConn *gorm.DB, 232 | salesforceClientFactory common.SalesforceClientFactory, 233 | filesComClientFactory common.FilesComClientFactory, 234 | subscriber, name string, 235 | file *db.File, reports map[string]config.Report) (*ReportRunner, error) { 236 | 237 | var reportRunner ReportRunner 238 | 239 | basePath := cfg.Processor.BaseTmpDir 240 | if basePath == "" { 241 | basePath = "/tmp" 242 | } 243 | 244 | log.Debugf("Using temporary base path: %s", basePath) 245 | if _, err := os.Stat(basePath); os.IsNotExist(err) { 246 | log.Debugf("Temporary base path '%s' doesn't exist - creating", basePath) 247 | if err = os.MkdirAll(basePath, 0755); err != nil { 248 | return nil, err 249 | } 250 | } 251 | 252 | dir, err := os.MkdirTemp(basePath, "athena-report-"+name) 253 | if err != nil { 254 | return nil, err 255 | } 256 | log.Debugf("Created basedir %s", dir) 257 | 258 | err = os.Rename(filepath.Join(basePath, filepath.Base(file.Path)), filepath.Join(dir, filepath.Base(file.Path))) 259 | if err != nil { 260 | return nil, err 261 | } 262 | log.Debugf("Moved file to %s", dir) 263 | 264 | reportRunner.Basedir = dir 265 | reportRunner.Config = cfg 266 | reportRunner.Db = dbConn 267 | reportRunner.FilesComClientFactory = filesComClientFactory 268 | reportRunner.Name = name 269 | reportRunner.SalesforceClientFactory = salesforceClientFactory 270 | reportRunner.Subscriber = subscriber 271 | 272 | //TODO: document the template variables 273 | tplContext := pongo2.Context{ 274 | "basedir": reportRunner.Basedir, // base dir used to generate reports 275 | "file": filepath.Base(file.Path), // file entry as returned by the files.com api client 276 | "filepath": path.Join(reportRunner.Basedir, filepath.Base(file.Path)), // directory where the file lives on 277 | } 278 | 279 | var scripts = make(map[string]string) 280 | 281 | for reportName, report := range reports { 282 | log.Debugf("running %d '%s' script(s)", len(report.Scripts), reportName) 283 | for scriptName, script := range report.Scripts { 284 | if script.Run == "" { 285 | log.Errorf("No script provided to run on '%s'", scriptName) 286 | continue 287 | } 288 | fd, err := os.CreateTemp(reportRunner.Basedir, "run-script-") 289 | if err != nil { 290 | return nil, err 291 | } 292 | if err = fd.Chmod(0700); err != nil { 293 | return nil, err 294 | } 295 | 296 | out, err := renderTemplate(&tplContext, script.Run) 297 | if err != nil { 298 | return nil, err 299 | } 300 | 301 | if _, err = fd.WriteString(out); err != nil { 302 | return nil, err 303 | } 304 | 305 | if err = fd.Close(); err != nil { 306 | return nil, err 307 | } 308 | 309 | scripts[scriptName] = fd.Name() 310 | } 311 | 312 | timeout, err := time.ParseDuration(report.Timeout) 313 | if err != nil { 314 | timeout, _ = time.ParseDuration(DefaultExecutionTimeout) 315 | } 316 | 317 | reportToExecute := ReportToExecute{} 318 | reportToExecute.BaseDir = reportRunner.Basedir 319 | reportToExecute.File = file 320 | reportToExecute.FileName = file.Path 321 | reportToExecute.Name = reportName 322 | reportToExecute.Scripts = scripts 323 | reportToExecute.Subscriber = reportRunner.Subscriber 324 | reportToExecute.Timeout = timeout 325 | reportRunner.Reports = append(reportRunner.Reports, reportToExecute) 326 | } 327 | 328 | return &reportRunner, nil 329 | } 330 | 331 | func (runner *ReportRunner) Clean() error { 332 | if runner.Config.Processor.KeepProcessingOutput { 333 | log.Infof("Keeping base direcotry %s for report %s", runner.Basedir, runner.Name) 334 | return nil 335 | } 336 | log.Infof("Removing base directory: %s for report: %s", runner.Basedir, runner.Name) 337 | return os.RemoveAll(runner.Basedir) 338 | } 339 | 340 | func (s *BaseSubscriber) Handler(_ context.Context, file *db.File, msg *pubsub.Msg) error { 341 | runner, err := NewReportRunner(s.Config, s.Db, s.SalesforceClientFactory, s.FilesComClientFactory, s.Name, s.Options.Topic, file, s.Reports) 342 | if err != nil { 343 | log.Errorf("Failed to get new runner: %s", err) 344 | msg.Ack() 345 | return err 346 | } 347 | if err := runner.Run(RunReport); err != nil { 348 | log.Errorf("Runner failed: %s", err) 349 | msg.Ack() 350 | _ = runner.Clean() 351 | return err 352 | } 353 | msg.Ack() 354 | return runner.Clean() 355 | } 356 | 357 | const defaultHandlerDeadline = 10 * time.Minute 358 | 359 | func NewBaseSubscriber( 360 | filesComClientFactory common.FilesComClientFactory, salesforceClientFactory common.SalesforceClientFactory, 361 | name, topic string, reports map[string]config.Report, cfg *config.Config, dbConn *gorm.DB) *BaseSubscriber { 362 | var subscriber = BaseSubscriber{ 363 | Options: pubsub.HandlerOptions{ 364 | Topic: topic, 365 | Name: "athena-processor-" + name, 366 | AutoAck: false, 367 | JSON: true, 368 | Deadline: defaultHandlerDeadline, 369 | }, 370 | Reports: reports, 371 | } 372 | 373 | subscriber.Config = cfg 374 | subscriber.Db = dbConn 375 | subscriber.FilesComClientFactory = filesComClientFactory 376 | subscriber.Name = topic 377 | subscriber.Options.Handler = subscriber.Handler 378 | subscriber.SalesforceClientFactory = salesforceClientFactory 379 | return &subscriber 380 | } 381 | 382 | func NewProcessor( 383 | filesComClientFactory common.FilesComClientFactory, salesforceClientFactory common.SalesforceClientFactory, 384 | provider pubsub.Provider, cfg *config.Config, dbConn *gorm.DB) (*Processor, error) { 385 | var err error 386 | if dbConn == nil { 387 | dbConn, err = db.GetDBConn(cfg) 388 | if err != nil { 389 | return nil, err 390 | } 391 | } 392 | 393 | hostname, err := os.Hostname() 394 | if err != nil { 395 | return nil, err 396 | } 397 | 398 | return &Processor{ 399 | Config: cfg, 400 | Db: dbConn, 401 | FilesComClientFactory: filesComClientFactory, 402 | Hostname: hostname, 403 | Provider: provider, 404 | SalesforceClientFactory: salesforceClientFactory, 405 | }, nil 406 | } 407 | 408 | func (p *Processor) getReportsByTopic(topic string) map[string]config.Report { 409 | results := make(map[string]config.Report) 410 | for event, subscriber := range p.Config.Processor.SubscribeTo { 411 | if event == topic { 412 | for name, report := range subscriber.Reports { 413 | results[name] = report 414 | } 415 | } 416 | } 417 | return results 418 | } 419 | 420 | var reportMap map[string]map[string]map[string][]db.Report 421 | 422 | // splitComment splits the given comment into several pieces at most 423 | // maxLength characters long. 424 | // The function returns the resulting slice. 425 | func splitComment(comment string, maxLength int) []string { 426 | // Check length and split across MaxCommentLength character 427 | // boundaries. 428 | if len(comment) > maxLength { 429 | log.Infof("Comment exceeds %d characters; splitting", maxLength) 430 | var commentChunks []string = []string{} 431 | commentLines := strings.Split(strings.TrimRight(comment, "\n "), "\n") 432 | chunk := []string{} 433 | chunkLength := 0 434 | for _, line := range commentLines { 435 | if chunkLength+len(line) < maxLength || len(chunk) == 0 { 436 | chunkLength += len(line) + 1 // Account for newline 437 | chunk = append(chunk, line) 438 | } else { 439 | commentChunks = append(commentChunks, strings.Join(chunk, "\n")) 440 | chunkLength = len(line) + 1 // Account for newline 441 | chunk = []string{line} 442 | } 443 | } 444 | commentChunks = append(commentChunks, strings.Join(chunk, "\n")) 445 | log.Infof("Comment was split into %d chunks", len(commentChunks)) 446 | return commentChunks 447 | } else { 448 | return []string{comment} 449 | } 450 | } 451 | 452 | func (p *Processor) BatchSalesforceComments(ctx *context.Context, interval time.Duration) { 453 | var reports []db.Report 454 | if reportMap == nil { 455 | reportMap = make(map[string]map[string]map[string][]db.Report) 456 | } 457 | 458 | log.Infof("Running process to send batched comments to salesforce every %s", interval) 459 | if results := p.Db.Preload("Scripts").Where("created <= ? and commented = ?", time.Now().Add(-interval), false).Find(&reports); results.Error != nil { 460 | log.Errorf("Error getting batched comments: %s", results.Error) 461 | return 462 | } 463 | 464 | if len(reports) <= 0 { 465 | log.Info("No reports found to be processed - skipping") 466 | return 467 | } 468 | 469 | log.Infof("Found %d reports to be sent to Salesforce", len(reports)) 470 | for _, report := range reports { 471 | if reportMap[report.Subscriber] == nil { 472 | reportMap[report.Subscriber] = make(map[string]map[string][]db.Report) 473 | } 474 | if reportMap[report.Subscriber][report.CaseID] == nil { 475 | reportMap[report.Subscriber][report.CaseID] = make(map[string][]db.Report) 476 | } 477 | 478 | if reportMap[report.Subscriber][report.CaseID][report.Name] == nil { 479 | reportMap[report.Subscriber][report.CaseID][report.Name] = make([]db.Report, 0) 480 | } 481 | 482 | reportMap[report.Subscriber][report.CaseID][report.Name] = append(reportMap[report.Subscriber][report.CaseID][report.Name], report) 483 | } 484 | 485 | salesforceClient, err := p.SalesforceClientFactory.NewSalesforceClient(p.Config) 486 | if err != nil { 487 | log.Errorf("failed to get Salesforce client: %s", err) 488 | return 489 | } 490 | for subscriberName, caseMap := range reportMap { 491 | for caseId, reportsByType := range caseMap { 492 | for _, reports := range reportsByType { 493 | var tplContext pongo2.Context 494 | subscriber, ok := p.Config.Processor.SubscribeTo[subscriberName] 495 | if !ok { 496 | log.Errorf("No subscription found for subscriber '%s'", subscriberName) 497 | continue 498 | } 499 | 500 | if !subscriber.SFCommentEnabled { 501 | log.Warnf("Salesforce comments have been disabled, skipping comments") 502 | continue 503 | } 504 | 505 | //TODO: document variables 506 | tplContext = pongo2.Context{ 507 | "processor": p.Hostname, 508 | "subscriber": subscriberName, 509 | "reports": reports, 510 | } 511 | 512 | renderedComment, err := renderTemplate(&tplContext, subscriber.SFComment) 513 | if err != nil { 514 | log.Error(err) 515 | continue 516 | } 517 | 518 | log.Infof("Processing comment for case %s", caseId) 519 | commentChunks := splitComment(renderedComment, p.Config.Salesforce.MaxCommentLength) 520 | 521 | wasPosted := true 522 | for i, chunk := range commentChunks { 523 | var chunkHeader string 524 | if len(commentChunks) > 1 { 525 | chunkHeader = fmt.Sprintf("Split comment %d of %d\n\n", i+1, len(commentChunks)) 526 | } 527 | var comment *simpleforce.SObject 528 | if p.Config.Salesforce.EnableChatter { 529 | comment = salesforceClient.PostChatter(caseId, 530 | chunkHeader+chunk, subscriber.SFCommentIsPublic) 531 | } else { 532 | comment = salesforceClient.PostComment(caseId, 533 | chunkHeader+chunk, subscriber.SFCommentIsPublic) 534 | } 535 | if comment == nil { 536 | log.Errorf("Failed to post comment to case id: %s", caseId) 537 | wasPosted = false 538 | continue 539 | } 540 | } 541 | 542 | if wasPosted { 543 | log.Infof("Successfully posted comment on case %s for %d reports", caseId, len(reports)) 544 | for _, report := range reports { 545 | report.Commented = true 546 | p.Db.Save(report) 547 | } 548 | reportMap = nil 549 | } else { 550 | log.Errorf("Could not post comment to case id: %s", caseId) 551 | } 552 | } 553 | } 554 | } 555 | } 556 | 557 | func (p *Processor) Run(ctx context.Context, newSubscriberFn func( 558 | filesComClientFactory common.FilesComClientFactory, 559 | salesforceClientFactory common.SalesforceClientFactory, 560 | name, topic string, reports map[string]config.Report, 561 | cfg *config.Config, dbConn *gorm.DB) pubsub.Subscriber) error { 562 | 563 | if ctx == nil { 564 | var cancel context.CancelFunc 565 | ctx, cancel = context.WithCancel(context.Background()) 566 | defer cancel() 567 | } 568 | 569 | pubsub.SetClient(&pubsub.Client{ 570 | ServiceName: "athena-processor", 571 | Provider: p.Provider, 572 | Middleware: defaults.Middleware, 573 | }) 574 | 575 | for event := range p.Config.Processor.SubscribeTo { 576 | go pubsub.Subscribe(newSubscriberFn(p.FilesComClientFactory, p.SalesforceClientFactory, 577 | p.Hostname, event, p.getReportsByTopic(event), p.Config, p.Db)) 578 | } 579 | 580 | interval, err := time.ParseDuration(p.Config.Processor.BatchCommentsEvery) 581 | if err != nil { 582 | return err 583 | } 584 | 585 | go common.RunOnInterval(ctx, &sync.Mutex{}, interval, p.BatchSalesforceComments) 586 | 587 | <-ctx.Done() 588 | return nil 589 | } 590 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 17 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 18 | cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= 19 | cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= 20 | cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 21 | cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= 22 | cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= 23 | cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= 24 | cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= 25 | cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= 26 | cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= 27 | cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= 28 | cloud.google.com/go v0.95.0/go.mod h1:MzZUAH870Y7E+c14j23Ir66FC1+PK8WLG7OG4SjP+0k= 29 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 30 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 31 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 32 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 33 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 34 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 35 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 36 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 37 | cloud.google.com/go/kms v0.1.0/go.mod h1:8Qp8PCAypHg4FdmlyW1QRAv09BGQ9Uzh7JnmIZxPk+c= 38 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 39 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 40 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 41 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 42 | cloud.google.com/go/pubsub v1.17.0/go.mod h1:bBIeYx9ftf/hr7eoSUim6cRaOYZE/hHuigwdwLLByi8= 43 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 44 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 45 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 46 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 47 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 48 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 49 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 50 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 51 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 52 | github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= 53 | github.com/Files-com/files-sdk-go v1.2.1218 h1:cPkbrxfplVT5letzggHMCoChNORhG1zCdDLD8euMwrE= 54 | github.com/Files-com/files-sdk-go v1.2.1218/go.mod h1:Fo/MOSJTSB52Bx9GlxeIJCquS4J6WqvTurcLVM8fNWU= 55 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 56 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 57 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 58 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 59 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 60 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 61 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 62 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 63 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= 64 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 65 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 66 | github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc h1:LoL75er+LKDHDUfU5tRvFwxH0LjPpZN8OoG8Ll+liGU= 67 | github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc/go.mod h1:w648aMHEgFYS6xb0KVMMtZ2uMeemhiKCuD2vj6gY52A= 68 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= 69 | github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= 70 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 71 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 72 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 73 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 74 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 75 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 76 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 77 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 78 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 79 | github.com/chilts/sid v0.0.0-20190607042430-660e94789ec9 h1:z0uK8UQqjMVYzvk4tiiu3obv2B44+XBsvgEJREQfnO8= 80 | github.com/chilts/sid v0.0.0-20190607042430-660e94789ec9/go.mod h1:Jl2neWsQaDanWORdqZ4emBl50J4/aRBBS4FyyG9/PFo= 81 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 82 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 83 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 84 | github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= 85 | github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= 86 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 87 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 88 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 89 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 90 | github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 91 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 92 | github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 93 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 94 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 95 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 96 | github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= 97 | github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= 98 | github.com/dropbox/godropbox v0.0.0-20180512210157-31879d3884b9/go.mod h1:glr97hP/JuXb+WMYCizc4PIFuzw1lCR97mwbe1VVXhQ= 99 | github.com/dropbox/godropbox v0.0.0-20200228041828-52ad444d3502 h1:tEkxjWg9OqJbkpgLaYbBjFO45+XygMJAEhOS62s+jLY= 100 | github.com/dropbox/godropbox v0.0.0-20200228041828-52ad444d3502/go.mod h1:Bv2UWEUnUi8YN4834GVjZlRcJbeOAUPp7QjRU2LhBqI= 101 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 102 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 103 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 104 | github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 105 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 106 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 107 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 108 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 109 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 110 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 111 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= 112 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 113 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 114 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 115 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 116 | github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= 117 | github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= 118 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 119 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 120 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 121 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 122 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 123 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 124 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 125 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 126 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 127 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 128 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 129 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 130 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 131 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 132 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 133 | github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 134 | github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= 135 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 136 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 137 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 138 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 139 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 140 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 141 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 142 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 143 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 144 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 145 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 146 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 147 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 148 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 149 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 150 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 151 | github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 152 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 153 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 154 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 155 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 156 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 157 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 158 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 159 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 160 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 161 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 162 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 163 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 164 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 165 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 166 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 167 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 168 | github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 169 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 170 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 171 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 172 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 173 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 174 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 175 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 176 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 177 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 178 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 179 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 180 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 181 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 182 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 183 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 184 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 185 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 186 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 187 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 188 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 189 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 190 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 191 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 192 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 193 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 194 | github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= 195 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 196 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 197 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 198 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 199 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 200 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 201 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 202 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 203 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 204 | github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 205 | github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 206 | github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 207 | github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 208 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 209 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 210 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 211 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 212 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 213 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 214 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 215 | github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= 216 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 217 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 218 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 219 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 220 | github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= 221 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 222 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 223 | github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 224 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 225 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 226 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 227 | github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 228 | github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 229 | github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= 230 | github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= 231 | github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= 232 | github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= 233 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 234 | github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 235 | github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= 236 | github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= 237 | github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= 238 | github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= 239 | github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= 240 | github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= 241 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 242 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 243 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 244 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 245 | github.com/hashicorp/raft v1.3.1 h1:zDT8ke8y2aP4wf9zPTB2uSIeavJ3Hx/ceY4jxI2JxuY= 246 | github.com/hashicorp/raft v1.3.1/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= 247 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 248 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 249 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 250 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 251 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 252 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 253 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 254 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= 255 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 256 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 257 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 258 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 259 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 260 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 261 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 262 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 263 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 264 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 265 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 266 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 267 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 268 | github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 269 | github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 270 | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= 271 | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 272 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 273 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 274 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 275 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 276 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 277 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 278 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 279 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 280 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 281 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 282 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 283 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 284 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 285 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 286 | github.com/lileio/logr v1.1.0 h1:LXFoQueH0I683/P/k1vmOHXnr0OWTuj3FwMEuRNxgs0= 287 | github.com/lileio/logr v1.1.0/go.mod h1:tuh1La46EvEN9PYHlCNvZsk473ULi0GDsEvcEe6nQr0= 288 | github.com/lileio/pubsub/v2 v2.6.1 h1:NIVUw1eudlPIbhJEx2ScdE62Mk0yLLjTtxZobBLB0Lc= 289 | github.com/lileio/pubsub/v2 v2.6.1/go.mod h1:I7nZCz676zC8hH0ThjORHusO/pqTB3wPNWKuLiz2zJc= 290 | github.com/lpar/date v1.0.0 h1:bq/zVqFTUmsxvd/CylidY4Udqpr9BOFrParoP6p0x/I= 291 | github.com/lpar/date v1.0.0/go.mod h1:KjYe0dDyMQTgpqcUz4LEIeM5VZwhggjVx/V2dtc8NSo= 292 | github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= 293 | github.com/makyo/snuffler v0.0.0-20190210075944-33446730a4fe h1:JUEY4H83AMPje1KhtA3Zm4aji+E3VYAqc53ABuHqb/I= 294 | github.com/makyo/snuffler v0.0.0-20190210075944-33446730a4fe/go.mod h1:61N/3245XjXhBYl4VnmukHYCy34uodZX9vOtdckn3DQ= 295 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 296 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 297 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 298 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 299 | github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 300 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 301 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 302 | github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= 303 | github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 304 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 305 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 306 | github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= 307 | github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= 308 | github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= 309 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 310 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 311 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 312 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 313 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= 314 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 315 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 316 | github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= 317 | github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= 318 | github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= 319 | github.com/nats-io/jwt/v2 v2.5.0 h1:WQQ40AAlqqfx+f6ku+i0pOVm+ASirD4fUh+oQsiE9Ak= 320 | github.com/nats-io/jwt/v2 v2.5.0/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI= 321 | github.com/nats-io/nats-server/v2 v2.3.3/go.mod h1:3mtbaN5GkCo/Z5T3nNj0I0/W1fPkKzLiDC6jjWJKp98= 322 | github.com/nats-io/nats-server/v2 v2.6.1/go.mod h1:Az91TbZiV7K4a6k/4v6YYdOKEoxCXj+iqhHVf/MlrKo= 323 | github.com/nats-io/nats-server/v2 v2.9.23 h1:6Wj6H6QpP9FMlpCyWUaNu2yeZ/qGj+mdRkZ1wbikExU= 324 | github.com/nats-io/nats-server/v2 v2.9.23/go.mod h1:wEjrEy9vnqIGE4Pqz4/c75v9Pmaq7My2IgFmnykc4C0= 325 | github.com/nats-io/nats-streaming-server v0.22.1 h1:YKDdLAWZud3UnEBvUPaYppMxSDuh+9czTCDriq19tJY= 326 | github.com/nats-io/nats-streaming-server v0.22.1/go.mod h1:1WpVkVV5NyZbHuGGxkaPWopLFnxNthO/TK/BkzFdnPE= 327 | github.com/nats-io/nats.go v1.11.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= 328 | github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= 329 | github.com/nats-io/nats.go v1.12.3/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= 330 | github.com/nats-io/nats.go v1.22.1/go.mod h1:tLqubohF7t4z3du1QDPYJIQQyhb4wl6DhjxEajSI7UA= 331 | github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c= 332 | github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc= 333 | github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= 334 | github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= 335 | github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY= 336 | github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= 337 | github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= 338 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 339 | github.com/nats-io/stan.go v0.10.0/go.mod h1:0jEuBXKauB1HHJswHM/lx05K48TJ1Yxj6VIfM4k+aB4= 340 | github.com/nats-io/stan.go v0.10.4 h1:19GS/eD1SeQJaVkeM9EkvEYattnvnWrZ3wkSWSw4uXw= 341 | github.com/nats-io/stan.go v0.10.4/go.mod h1:3XJXH8GagrGqajoO/9+HgPyKV5MWsv7S5ccdda+pc6k= 342 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 343 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 344 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 345 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 346 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= 347 | github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= 348 | github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= 349 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 350 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3 h1:XudIMByQMXJ6oDHy4SipNyo35LxjA69Z7v1nL0aAZvA= 351 | github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.3/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= 352 | github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= 353 | github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI= 354 | github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= 355 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 356 | github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= 357 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 358 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 359 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 360 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 361 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 362 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 363 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 364 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 365 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 366 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 367 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 368 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 369 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 370 | github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= 371 | github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 372 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 373 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 374 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 375 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 376 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 377 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 378 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 379 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 380 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 381 | github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= 382 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 383 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 384 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 385 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 386 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 387 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 388 | github.com/prometheus/procfs v0.7.1 h1:TlEtJq5GvGqMykEwWzbZWjjztF86swFhsPix1i0bkgA= 389 | github.com/prometheus/procfs v0.7.1/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 390 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 391 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 392 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 393 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 394 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 395 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 396 | github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f h1:8P2MkG70G76gnZBOPGwmMIgwBb/rESQuwsJ7K8ds4NE= 397 | github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= 398 | github.com/sanity-io/litter v1.2.0 h1:DGJO0bxH/+C2EukzOSBmAlxmkhVMGqzvcx/rvySYw9M= 399 | github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= 400 | github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= 401 | github.com/segmentio/ksuid v1.0.2/go.mod h1:BXuJDr2byAiHuQaQtSKoXh1J0YmUDurywOXgB2w+OSU= 402 | github.com/segmentio/ksuid v1.0.3 h1:FoResxvleQwYiPAVKe1tMUlEirodZqlqglIuFsdDntY= 403 | github.com/segmentio/ksuid v1.0.3/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= 404 | github.com/simpleforce/simpleforce v0.0.0-20220429021116-acf4ac67ef68 h1:EW/NT+Lr1n7bASyO4QF9oOM5TvK3Bd/+nHd1O1qbCFc= 405 | github.com/simpleforce/simpleforce v0.0.0-20220429021116-acf4ac67ef68/go.mod h1:/trShGwjho17PsOcwG8PT6QoQ2HnZUooZX625+7qZ20= 406 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 407 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 408 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 409 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 410 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 411 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 412 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 413 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 414 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 415 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 416 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= 417 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 418 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 419 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 420 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 421 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 422 | github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 423 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 424 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 425 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 426 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 427 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 428 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 429 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 430 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 431 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 432 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 433 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 434 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 435 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 436 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 437 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 438 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 439 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 440 | github.com/zenthangplus/goccm v0.0.0-20200608171100-39e9e08b694a/go.mod h1:PPYr3s9FhH/9fs7kfozlHKs2VXzk4Foyzb3Mke/Bg0U= 441 | github.com/zenthangplus/goccm v1.1.3 h1:66XVj24yexO2fCkBum8b+y6tOJ7Giq05LIn2vn3whGE= 442 | github.com/zenthangplus/goccm v1.1.3/go.mod h1:DUzu/BC4TkgUfXP8J1P6Md73Djt+0l0CHq001Pt4weA= 443 | go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= 444 | go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= 445 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 446 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 447 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 448 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 449 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 450 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 451 | go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 452 | go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU= 453 | go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= 454 | go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= 455 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 456 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 457 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 458 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 459 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 460 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 461 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 462 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 463 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 464 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 465 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 466 | golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 467 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 468 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 469 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 470 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= 471 | golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= 472 | golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= 473 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 474 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 475 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 476 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 477 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 478 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 479 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 480 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 481 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 482 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 483 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 484 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 485 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 486 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 487 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 488 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 489 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 490 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 491 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 492 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 493 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 494 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 495 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 496 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 497 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 498 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 499 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 500 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 501 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 502 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 503 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 504 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 505 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 506 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 507 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 508 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 509 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 510 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 511 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 512 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 513 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 514 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 515 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 516 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 517 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 518 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 519 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 520 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 521 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 522 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 523 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 524 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 525 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 526 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 527 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 528 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 529 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 530 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 531 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 532 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 533 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 534 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 535 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 536 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 537 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 538 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 539 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 540 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 541 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 542 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 543 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 544 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 545 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 546 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 547 | golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 548 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 549 | golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= 550 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 551 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 552 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 553 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 554 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 555 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 556 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 557 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 558 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 559 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 560 | golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 561 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 562 | golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 563 | golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 564 | golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 565 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 566 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 567 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 568 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 569 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 570 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 571 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 572 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 573 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 574 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 575 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 576 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 577 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 578 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 579 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 580 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 581 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 582 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 583 | golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 584 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 585 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 586 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 587 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 588 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 589 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 590 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 591 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 592 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 593 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 594 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 595 | golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 596 | golang.org/x/sys v0.0.0-20191018095205-727590c5006e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 597 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 598 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 599 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 600 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 601 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 602 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 603 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 604 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 605 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 606 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 607 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 608 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 609 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 610 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 611 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 612 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 613 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 614 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 615 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 616 | golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 617 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 618 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 619 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 620 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 621 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 622 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 623 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 624 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 625 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 626 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 627 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 628 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 629 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 630 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 631 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 632 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 633 | golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 634 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 635 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 636 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 637 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 638 | golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 639 | golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 640 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 641 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 642 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 643 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 644 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 645 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 646 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 647 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 648 | golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= 649 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 650 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 651 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 652 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 653 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 654 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 655 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 656 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 657 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 658 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 659 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 660 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 661 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 662 | golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 663 | golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 664 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 665 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 666 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 667 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 668 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 669 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 670 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 671 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 672 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 673 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 674 | golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 675 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 676 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 677 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 678 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 679 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 680 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 681 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 682 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 683 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 684 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 685 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 686 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 687 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 688 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 689 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 690 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 691 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 692 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 693 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 694 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 695 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 696 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 697 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 698 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 699 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 700 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 701 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 702 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 703 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 704 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 705 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 706 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 707 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 708 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 709 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 710 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 711 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 712 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 713 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 714 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 715 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 716 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 717 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 718 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 719 | golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 720 | golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 721 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 722 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 723 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 724 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 725 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 726 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 727 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 728 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 729 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 730 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 731 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 732 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 733 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 734 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 735 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 736 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 737 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 738 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 739 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 740 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 741 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 742 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 743 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 744 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 745 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 746 | google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= 747 | google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 748 | google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= 749 | google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= 750 | google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= 751 | google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= 752 | google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= 753 | google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= 754 | google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= 755 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 756 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 757 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 758 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 759 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 760 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 761 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 762 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 763 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 764 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 765 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 766 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 767 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 768 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 769 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 770 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 771 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 772 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 773 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 774 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 775 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 776 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 777 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 778 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 779 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 780 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 781 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 782 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 783 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 784 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 785 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 786 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 787 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 788 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 789 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 790 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 791 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 792 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 793 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 794 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 795 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 796 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 797 | google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 798 | google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 799 | google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 800 | google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 801 | google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= 802 | google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= 803 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 804 | google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 805 | google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 806 | google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= 807 | google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= 808 | google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= 809 | google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= 810 | google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= 811 | google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= 812 | google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 813 | google.golang.org/genproto v0.0.0-20210824181836-a4879c3d0e89/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 814 | google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 815 | google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 816 | google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= 817 | google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= 818 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 819 | google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= 820 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 821 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 822 | google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 823 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 824 | google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 825 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 826 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 827 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 828 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 829 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 830 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 831 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 832 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 833 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 834 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 835 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 836 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 837 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 838 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 839 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 840 | google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 841 | google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 842 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 843 | google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= 844 | google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= 845 | google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= 846 | google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= 847 | google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= 848 | google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= 849 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 850 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 851 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 852 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 853 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 854 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 855 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 856 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 857 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 858 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 859 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 860 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 861 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 862 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 863 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 864 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 865 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 866 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 867 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 868 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 869 | gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 870 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 871 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 872 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 873 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 874 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 875 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 876 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 877 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 878 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 879 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 880 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 881 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 882 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 883 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 884 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 885 | gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= 886 | gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= 887 | gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= 888 | gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= 889 | gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 890 | gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= 891 | gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= 892 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 893 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 894 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 895 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 896 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 897 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 898 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 899 | moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= 900 | moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= 901 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 902 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 903 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 904 | --------------------------------------------------------------------------------