diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 404f9595..915629dc 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -2,7 +2,8 @@ name: CI on: pull_request_target: - branches: [master] + branches: + - '**' push: branches: - master @@ -12,17 +13,18 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up Go 1.x - uses: actions/setup-go@v2 - with: - go-version: ^1.18 - - name: Check out code into the Go module directory - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 1 ref: ${{ github.event.pull_request.head.sha }} + - name: Set up Go 1.x + uses: actions/setup-go@v3 + with: + go-version: ^1.18 + + - name: Build the binary run: make build @@ -30,11 +32,14 @@ jobs: run: make test_setup env: CODE_PATH: /home/runner/code + APP_PATH: /home/runner/app - name: Run tests run: make test env: CODE_PATH: /home/runner/code + APP_PATH: /home/runner/app + TOOLBOX_PATH: /home/runner/toolbox - name: Report test coverage to DeepSource uses: deepsourcelabs/test-coverage-action@master diff --git a/.gitignore b/.gitignore index d9e0a2ed..eecd835d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,9 @@ dist coverage.out cover.out +# node +node_modules + +# Editor workspaces +.idea/ +.vscode/ diff --git a/Makefile b/Makefile index 35929b8d..263c3bcc 100644 --- a/Makefile +++ b/Makefile @@ -5,18 +5,16 @@ build_local: cd cmd/deepsource && go build -tags static_all -o /tmp/deepsource . test: - CGO_ENABLED=0 go test -v ./command/report/tests/... -run TestReportKeyValueWorkflow -count=1 - CGO_ENABLED=0 go test -v ./command/report/tests/... -run TestReportKeyValueFileWorkflow -count=1 - echo "\n====TESTING DEEPSOURCE PACKAGE====\n" - CGO_ENABLED=0 go test -v ./deepsource/tests/... - echo "\n====TESTING CONFIG VALIDATOR PACKAGE====\n" - go test -v ./configvalidator/... -count=1 - echo "\n====CALCULATING TEST COVERAGE FOR ENTIRE PACKAGE====\n" go test -v -coverprofile=coverage.out -count=1 ./... test_setup: - mkdir -p ${CODE_PATH} + mkdir -p ${CODE_PATH} ${APP_PATH} cd ${CODE_PATH} && ls -A1 | xargs rm -rf git clone https://github.com/deepsourcelabs/cli ${CODE_PATH} chmod +x /tmp/deepsource cp ./command/report/tests/dummy/python_coverage.xml /tmp + # Setup git user and email on CI. + ifeq ($(CI),true) + git config --global user.name github-actions + git config --global user.email github-actions@github.com + endif diff --git a/analysis/config/config.go b/analysis/config/config.go new file mode 100644 index 00000000..5a2f3dc2 --- /dev/null +++ b/analysis/config/config.go @@ -0,0 +1,98 @@ +package config + +import ( + "errors" + "fmt" + "os" + "path" + + "github.com/pelletier/go-toml/v2" +) + +type AnalysisRun struct { + AnalyzerName string + LocalCodePath string + ContainerCodePath string + DSConfig DSConfig + AnalysisFiles []string + ExcludedFiles []string + TestFiles []string +} + +/* Checks for excluded and test files based on the `exclude_patterns` and `test_patterns` + * configured by the user */ +func (r *AnalysisRun) checkExcludedAndTestFiles() (err error) { + // Get a list of the files that match the `exclude_patterns` configured in `.deepsource.toml` + r.ExcludedFiles, err = r.getMatchingFiles(r.DSConfig.ExcludePatterns) + if err != nil { + return err + } + + // Get a list of the files that match the `test_patterns` configured in `.deepsource.toml` + r.TestFiles, err = r.getMatchingFiles(r.DSConfig.TestPatterns) + if err != nil { + return err + } + return nil +} + +/* Parses the DeepSource config (.deepsource.toml) located at the root of the project at CODE_PATH as does + * .git directory and resolves the config data into AnalysisRun */ +func (r *AnalysisRun) parseDeepSourceConfig() (err error) { + r.DSConfig = DSConfig{} + + // Having resolved the project root path, check for the presence of .deepsource.toml in that path + // Read it if it exists and throw error if it doesn't + if _, err := os.Stat(path.Join(r.LocalCodePath, ".deepsource.toml")); err != nil { + if errors.Is(err, os.ErrNotExist) { + fmt.Println("Could not find .deepsource.toml config at project root path:", r.LocalCodePath) + // If DeepSource config is not present, keep empty r.DSConfig + return nil + } + } + + // If present, read the DeepSource config and decode it into r.DSConfig + config, err := os.ReadFile(path.Join(r.LocalCodePath, ".deepsource.toml")) + if err != nil { + fmt.Printf("Failed to read the .deepsource.toml config. Error: %s", err) + return nil + } + + // Unmarshal the []byte config data into struct + err = toml.Unmarshal(config, &r.DSConfig) + if err != nil { + fmt.Printf("Failed to retrieve the .deepsource.toml config data. Error: %s", err) + return nil + } + return err +} + +/* Configures the analysis_config.json file which is used by analyzer to run analysis since it contains + * metadata about the files to be analyzed, the test files in the project, the excluded files and analyzer meta */ +func (r *AnalysisRun) ConfigureAnalysis() (*AnalysisConfig, error) { + var err error + analyzerConfig := &AnalysisConfig{} + + // Parse DeepSource config (.deepsource.toml) in order to get the + // configured `exclude_patterns` and `test_patterns` + if err = r.parseDeepSourceConfig(); err != nil { + return analyzerConfig, err + } + + // Get the list of all the files present in the CODE_PATH and are to be analyzed + if r.AnalysisFiles, err = readAllFiles(r.LocalCodePath); err != nil { + return analyzerConfig, err + } + + // Gets the list of files to be excluded from analysis and the test files present + // Doesn't return error, just logs it even if the error comes up since it doens't affect the analysis run + if err = r.checkExcludedAndTestFiles(); err != nil { + return analyzerConfig, nil + } + + // Filter out the files to be analyzed by removing the r.ExcludedFiles from them and assign them to r.AnalysisFiles + r.filterAnalysisFiles() + + // Format the analysis config data into LSP format of analysis_config.json + return r.formatAnalysisConfigToLSP(), nil +} diff --git a/analysis/config/format.go b/analysis/config/format.go new file mode 100644 index 00000000..11c814f5 --- /dev/null +++ b/analysis/config/format.go @@ -0,0 +1,36 @@ +package config + +import ( + "github.com/deepsourcelabs/cli/analysis/lsp" +) + +/* Formats the analysis config data in the form of the LSP format as defined in + * the `analysis/types.go` */ +func (r *AnalysisRun) formatAnalysisConfigToLSP() *AnalysisConfig { + anaConfig := AnalysisConfig{ + ExcludePatterns: r.DSConfig.ExcludePatterns, + TestPatterns: r.DSConfig.TestPatterns, + } + + // Store the files, test files and excluded files in the LSP based analysis config + for _, file := range r.AnalysisFiles { + anaConfig.Files = append(anaConfig.Files, lsp.TextDocumentItem{URI: lsp.DocumentURI(file)}) + } + + for _, testFile := range r.TestFiles { + anaConfig.TestFiles = append(anaConfig.TestFiles, lsp.TextDocumentItem{URI: lsp.DocumentURI(testFile)}) + } + + for _, excludedFile := range r.ExcludedFiles { + anaConfig.ExcludedFiles = append(anaConfig.ExcludedFiles, lsp.TextDocumentItem{URI: lsp.DocumentURI(excludedFile)}) + } + + // Read analyzer_meta from DeepSource config (.deepsource.toml) and + // store the one corresponding to the Analyzer whose check is scheduled in analysis_config.json + for _, analyzer := range r.DSConfig.Analyzers { + if analyzer.Name == r.AnalyzerName { + anaConfig.AnalyzerMeta = analyzer.Meta + } + } + return &anaConfig +} diff --git a/analysis/config/types.go b/analysis/config/types.go new file mode 100644 index 00000000..1e8414fb --- /dev/null +++ b/analysis/config/types.go @@ -0,0 +1,103 @@ +package config + +import "github.com/deepsourcelabs/cli/analysis/lsp" + +/////////////////////////////////////////////////////// +// DSConfig is the struct for .deepsource.toml file // +///////////////////////////////////////////////////// + +type DSConfig struct { + Version int `mapstructure:"version,omitempty" json:"version" toml:"version"` + ExcludePatterns []string `mapstructure:"exclude_patterns,omitempty" json:"exclude_patterns,omitempty" toml:"exclude_patterns"` + TestPatterns []string `mapstructure:"test_patterns,omitempty" json:"test_patterns,omitempty" toml:"test_patterns"` + Analyzers []Analyzer `mapstructure:"analyzers,omitempty" json:"analyzers,omitempty" toml:"analyzers"` + Transformers []Transformer `mapstructure:"transformers,omitempty" json:"transformers,omitempty" toml:"transformers"` +} + +type Analyzer struct { + Name string `mapstructure:"name,omitempty" json:"name,omitempty" toml:"name"` + RuntimeVersion string `mapstructure:"runtime_version,omitempty" json:"runtime_version,omitempty" toml:"runtime_version,omitempty"` + Enabled bool `mapstructure:"enabled,omitempty" json:"enabled" toml:"enabled"` + DependencyFilePaths []string `mapstructure:"dependency_file_paths,omitempty" json:"dependency_file_paths,omitempty"` + Meta interface{} `mapstructure:"meta,omitempty" json:"meta,omitempty" toml:"meta"` + Thresholds interface{} `mapstructure:"thresholds,omitempty" json:"thresholds,omitempty"` +} + +type Transformer struct { + Name string `mapstructure:"name,omitempty" json:"name,omitempty" toml:"name"` + Enabled bool `mapstructure:"enabled,omitempty" json:"enabled,omitempty" toml:"enabled"` +} + +/////////////////////////////////////// +// LSP based Analysis Config Types // +///////////////////////////////////// + +type AnalysisConfig struct { + Files []lsp.TextDocumentItem `json:"files"` + TestFiles []lsp.TextDocumentItem `json:"test_files"` + ExcludedFiles []lsp.TextDocumentItem `json:"excluded_files"` + ExcludePatterns []string `json:"exclude_patterns"` + TestPatterns []string `json:"test_patterns"` + AnalyzerMeta interface{} `json:"analyzer_meta"` +} + +////////////////////////////////////// +// LSP based Analysis Result Types // +///////////////////////////////////// + +type Namespace struct { + Key string `json:"key"` + Value float64 `json:"value"` +} + +type Metric struct { + MetricCode string `json:"metric_code"` + Namespaces []Namespace `json:"namespaces"` +} + +type AnalysisResult struct { + Issues []lsp.Diagnostic `json:"issues"` + Metrics []Metric `json:"metrics,omitempty"` + IsPassed bool `json:"is_passed"` + Errors []Error `json:"errors"` + // Errors []lsp.Diagnostic `json:"errors"` + ExtraData interface{} `json:"extra_data"` +} + +//////////////////////////////////// +// Default Analysis Result Types // +/////////////////////////////////// + +type Error struct { + HMessage string `json:"hmessage"` + Level int `json:"level"` +} + +type Coordinate struct { + Line int `json:"line"` + Column int `json:"column"` +} + +type Position struct { + Begin Coordinate `json:"begin"` + End Coordinate `json:"end"` +} + +type Location struct { + Path string `json:"path"` + Position Position `json:"position"` +} + +type Issue struct { + Code string `json:"issue_code"` + Title string `json:"issue_text"` + Location Location `json:"location"` +} + +type DefaultAnalysisResult struct { + Issues []Issue `json:"issues"` + Metrics []Metric `json:"metrics,omitempty"` + IsPassed bool `json:"is_passed"` + Errors []Error `json:"errors"` + ExtraData interface{} `json:"extra_data"` +} diff --git a/analysis/config/utils.go b/analysis/config/utils.go new file mode 100644 index 00000000..4fb360c7 --- /dev/null +++ b/analysis/config/utils.go @@ -0,0 +1,78 @@ +package config + +import ( + "os" + "path" + "path/filepath" + "strings" + + "github.com/gobwas/glob" +) + +/* Walks the `CODE_PATH` directory and returns all the files other than the ones present + * in the .git folder for analysis in the form of a string array */ +func readAllFiles(codePath string) ([]string, error) { + fileCount := 0 + + allFiles := make([]string, 0) + + err := filepath.Walk(codePath, + func(path string, fileInfo os.FileInfo, err error) error { + if err != nil { + return err + } + fileCount++ + + /* Check the following before appending to the list of files: + * Should not be a directory + * The walked file should not be present in .git folder */ + if !fileInfo.IsDir() && !strings.HasPrefix(path, filepath.Join(codePath, ".git")) { + allFiles = append(allFiles, path) + } + return nil + }) + if err != nil { + return allFiles, err + } + return allFiles, nil +} + +// Returns the slice of files matching certain glob patterns +func (r *AnalysisRun) getMatchingFiles(patterns []string) ([]string, error) { + matchedFiles := make([]string, 0) + + // Return all the files if no exclude_patterns are configured + if len(patterns) == 0 { + return matchedFiles, nil + } + + for _, file := range r.AnalysisFiles { + for i := range patterns { + g := glob.MustCompile(path.Join(r.LocalCodePath, patterns[i])) + if g.Match(file) { + matchedFiles = append(matchedFiles, file) + } + } + } + return matchedFiles, nil +} + +// Filters the analysis files and removes the files matching the exclude_patterns from them +// TODO: Improve the logic here +func (r *AnalysisRun) filterAnalysisFiles() { + excluded := false + filteredFiles := []string{} + for _, file := range r.AnalysisFiles { + excluded = false + for _, excludedFile := range r.ExcludedFiles { + if file == excludedFile { + excluded = true + break + } + } + if !excluded { + filteredFiles = append(filteredFiles, file) + } + } + r.AnalysisFiles = filteredFiles +} diff --git a/analysis/lsp/types.go b/analysis/lsp/types.go new file mode 100644 index 00000000..b42ead68 --- /dev/null +++ b/analysis/lsp/types.go @@ -0,0 +1,63 @@ +package lsp + +////////////////////////////// +// Document identity types // +///////////////////////////// + +type DocumentURI string + +type TextDocumentItem struct { + URI DocumentURI `json:"uri"` + LanguageID string `json:"languageID,omitempty"` + Version int `json:"version,omitempty"` + Text string `json:"text,omitempty"` +} + +/////////////////////////////// +// Diagnostic related types // +///////////////////////////// + +type DiagnosticSeverity int + +const ( + Error DiagnosticSeverity = 1 + Warning DiagnosticSeverity = 2 + Information DiagnosticSeverity = 3 + Hint DiagnosticSeverity = 4 +) + +type Position struct { + Line int `json:"line"` + Character int `json:"character"` +} +type Range struct { + Start Position `json:"start"` + End Position `json:"end"` +} + +type Location struct { + URI string `json:"uri"` + Range Range `json:"range"` +} + +type DiagnosticRelatedInformation struct { + Location Location `json:"location"` + Message string `json:"message"` +} + +type Diagnostic struct { + Range Range `json:"range"` + Severity DiagnosticSeverity `json:"severity,omitempty"` + Code string `json:"code,omitempty"` + Source string `json:"source,omitempty"` + Message string `json:"message"` + + /** + * An array of related diagnostic information, e.g. when symbol-names within + * a scope collide all definitions can be marked via this property. + * var a,b + * a := 2 + * Issues in line 1 and 2 are related. + */ + RelatedInformation []DiagnosticRelatedInformation `json:"relatedInformation"` +} diff --git a/analysis/processor/batch_process.go b/analysis/processor/batch_process.go new file mode 100644 index 00000000..00de911d --- /dev/null +++ b/analysis/processor/batch_process.go @@ -0,0 +1,152 @@ +package processor + +import ( + "fmt" + "io/ioutil" + "log" + "sort" + "strings" + + "github.com/deepsourcelabs/cli/types" +) + +var ( + issueIndex int = 0 + batchSize int = 30 + maxIssueDensity int = 100 +) + +type fileContentNode struct { + Filename string + FileContent []string +} + +// While this loop looks like it would have a complexity of len(filesWIssueRange) * len(cachedFiles) * issues * len(processorList) +// it only has a complexity of O(len(report.Issues)). +// When there are a lot of files to be processed, opening all of them one by one takes time, while the CPU waits idly. +// Opening all files and loading them into memory is expensive in terms of space, since there could be a lot of files. +// Hence, opening files concurrently in batches (of, say, 30 files) and then processing all issues in those 30 files one by one +// appears to be the best option. We cannot process each file's issues concurrently, because only the file loading operation is +// IO intensive, and the rest is CPU intensive. +func (p *ReportProcessor) processIssuesBatch(filesWIssueRange []IssueRange, result *types.AnalysisResult, processedIssues *[]types.Issue) { + // Process files in batches of `batchSize` to avoid `too many files open` error + for processedFiles := 0; processedFiles < len(filesWIssueRange); { + filesToProcess := 0 + + // The default batch size is 30. If the number of files is less than this batchsize assign their count + // as the number of files to process, else assign the batchsize as the number of files to be processed in + // this iteration. + if len(filesWIssueRange)-processedFiles < batchSize { + filesToProcess = len(filesWIssueRange) - processedFiles + } else { + filesToProcess = batchSize + } + + // The slice containing the data about cached files to be processed. + cachedFiles := p.cacheFilesToBeProcessed(filesToProcess, processedFiles, filesWIssueRange) + + // Iterate over the cached files data and process the issues present in them. + for j, cachedFile := range cachedFiles { + for issueIndex < len(result.Issues) { + issue := result.Issues[issueIndex] // initialize the loop + // Check if the file is a generated one, this happens if enormous amount of issues are + // reported in a single file on a single line. + if p.isGeneratedFile(processedFiles+j, &cachedFile, filesWIssueRange, result) { + continue + } + + // Check if the issue is for another file. + // If yes, skip this iteration and go to next file. + if cachedFile.Filename != issue.Location.Path { + break + } + + if err := p.runProcessors(cachedFile, &issue, processedIssues); err != nil { + fmt.Println(err.Error()) + } + issueIndex++ + } + } + + // Increase total number of files processed + processedFiles += filesToProcess + } +} + +// runProcessors runs the supported processors on the issue passed as a parameter +func (p *ReportProcessor) runProcessors(cachedFile fileContentNode, issueToProcess *types.Issue, processedIssues *[]types.Issue) (err error) { + // Loop through processors and execute them on the issue passed as a parameter + for _, processor := range p.Processors { + err = processor.Process(cachedFile.FileContent, issueToProcess, processedIssues) + if err != nil { + return fmt.Errorf("failed to execute the processor %s with the following error: %s", processor, err) + } + } + return +} + +// If the number of issues in this file is more than a certain number of issues +// averaged per line, this may be a generated file. Skip processing of further issues +// in this file +func (p *ReportProcessor) isGeneratedFile(fileIndex int, cachedFile *fileContentNode, filesWIssueRange []IssueRange, result *types.AnalysisResult) bool { + linesInThisFile := len(cachedFile.FileContent) | 1 // bitwise op to ensure no divisionbyzero errs + issuesInThisFile := filesWIssueRange[fileIndex].EndIndex - filesWIssueRange[fileIndex].BeginIndex + if (issuesInThisFile / linesInThisFile) > maxIssueDensity { + log.Printf( + "Skipping file %s. Too many issues per line. Lines: %d, issues: %d\n", + cachedFile.Filename, + linesInThisFile, + issuesInThisFile, + ) + result.Errors = append(result.Errors, types.Error{ + HMessage: fmt.Sprintf( + "Skipped file %s because too many issues were raised. "+ + "Is this a generated file that can be added in [exclude_patterns](https://deepsource.io/docs/config/deepsource-toml.html#exclude-patterns)?", + cachedFile.Filename, + ), + Level: 1, + }) + return true + } + return false +} + +// cacheBatchOfFiles receives the count of files to be cached and caches them in a batch by spawning goroutines. +func (p *ReportProcessor) cacheFilesToBeProcessed(totalFiles, processedFiles int, filesWIssueRange []IssueRange) []fileContentNode { + fileContentChannel := make(chan fileContentNode, totalFiles) + for j := 0; j < totalFiles; j++ { + filename := filesWIssueRange[processedFiles+j].Filename + go addFileToCache(fileContentChannel, filename) + } + + cachedFiles := []fileContentNode{} + for j := 0; j < totalFiles; j++ { + cachedFiles = append(cachedFiles, <-fileContentChannel) + } + + // sort the cached files by filename, because our issues are sorted by filename + sort.Slice(cachedFiles, func(i, j int) bool { + return cachedFiles[i].Filename < cachedFiles[j].Filename + }) + return cachedFiles +} + +// addFileToCache reads the file and formats its content into a fileContentNode struct instance +// and passes that to the cachedFilesChannel channel since this function is run on a goroutine. +func addFileToCache(cachedFilesChannel chan fileContentNode, filename string) { + fileContentSlice := []string{} + + fileContentBytes, err := ioutil.ReadFile(filename) + if err != nil { + fmt.Println("Could not process for file: ", filename, ". Err: ", err) + } else if string(fileContentBytes) != "" { + fileContentSlice = strings.Split(string(fileContentBytes), "\n") + } else { + fileContentSlice = []string{} + } + + cachedFilesChannel <- fileContentNode{ + Filename: filename, + FileContent: fileContentSlice, + } +} diff --git a/analysis/processor/process.go b/analysis/processor/process.go new file mode 100644 index 00000000..4467276b --- /dev/null +++ b/analysis/processor/process.go @@ -0,0 +1,56 @@ +package processor + +import ( + "github.com/deepsourcelabs/cli/types" +) + +// Processor interface to receive analysis post-processors. +type IProcessor interface { + String() string + Process([]string, *types.Issue, *[]types.Issue) error +} + +// ReportProcessor struct contains the processor data needed to process the analysis results. +type ReportProcessor struct { + LocalSourcePath string // The local source code path which was analyzed by the Analyzer. + ContainerCodePath string // The codepath set for the Analysis container. + Processors []IProcessor // The list of supported post-analysis processors. + Report types.AnalyzerReport // The report generated by the Analyzer post analysis. +} + +// ProcessAnalyzerReport accepts the result as a byte array and processes the results in the form of a +// AnalyzerReport struct instance. +// It sorts the issues in an alphabetical order of filenames just to ensure that all issues getting +// reported for the same files come together & processes the issues for the various required processors. +// As of now, there are two processors supported: +// - skipcq : Processes the issues and checks if some of them should be ignored since they have +// been ignored by the user through suitable `skipcq` comments. +// - source_code_load : Processes the issues for the source code snippets, highlights the snippets +// and adds them to the Analysis result. +func (p *ReportProcessor) Process() types.AnalysisResult { + // Covert the Analyzer report from LSP based format to the default results format. + analysisResult := p.formatLSPResultsToDefault() + + // Check if there are issues reported actually. + if len(analysisResult.Issues) <= 0 { + return analysisResult + } + + // All the files that appear in the issues are now processed by the processors listed in analyzer conf + // We must cache the files in order to not do file IO for every processor. + p.sortIssuesByFile(&analysisResult) + + // Get the issues to file range data. + filesIndex := createIssueFileRange(analysisResult) + + // Iterate over the filesIndex and read the files in batch and process the issues using the suitable processors. + processedIssues := []types.Issue{} + p.processIssuesBatch(filesIndex, &analysisResult, &processedIssues) + analysisResult.Issues = processedIssues + + // Sort again for consistency (mostly for test to pass). + p.sortIssuesByFile(&analysisResult) + + // Return the processed analysis result. + return analysisResult +} diff --git a/analysis/processor/processors/proc_skip_cq.go b/analysis/processor/processors/proc_skip_cq.go new file mode 100644 index 00000000..529b2ba9 --- /dev/null +++ b/analysis/processor/processors/proc_skip_cq.go @@ -0,0 +1,170 @@ +package processors + +import ( + "fmt" + "regexp" + "strings" + + "github.com/deepsourcelabs/cli/types" +) + +type ProcSkipCQ struct{} + +func isSimilarIssue(fileExt, skipCQTag, probableIssueCode, issueCode string) bool { + // if it is // skipcq: SCC-S1002 or similar plain skipcq tag, return + if skipCQTag == "skipcq" { + if probableIssueCode != "" { + return strings.EqualFold(strings.TrimSpace(probableIssueCode), issueCode) + } else { + return true + } + } + + // if the tag to skip CQ is something else, check here + for _, silencer := range languagesMeta[fileExt].Silencers { + if strings.EqualFold(strings.TrimSpace(skipCQTag), silencer.SilencerCode) { + if len(silencer.Issues) == 0 { + // if the silencer doesn't have any issues in it, ignore all the issues + return true + } + + // similarIssues is a comma separated list of issues corresponding to the raised issue + similarIssues, ok := silencer.Issues[strings.Split(issueCode, "-")[1]] + + // if the issue is not found in the silencer's issue map, do not ignore the issue + if !ok { + return false + } + + if probableIssueCode == "" { + // if there is no specific issue silenced, ignore all issues + return true + } else { + for _, similarIssue := range strings.Split(similarIssues, ",") { + // if the silencer has an issue map that contains this particular issue, ignore it + if similarIssue == strings.ToUpper(probableIssueCode) { + return true + } else { + return false + } + } + } + } + } + return false +} + +func checkSkipCQ(fileExt string, skipCQre regexp.Regexp, line, issueCode string) bool { + matches := skipCQre.FindAllStringSubmatch(line, -1) + skipCQTag := "" + + if matches == nil { + return false + } + + ignoreIssue := true + + for i, name := range skipCQre.SubexpNames() { + for _, match := range matches { + if i != 0 && name != "" { + // note the name of the issue silencer and move on + if name == "skipcq_tag" { + skipCQTag = match[i] + } else if name == "issue_codes" { + if match[i] != "" { + for _, probableIssueCode := range strings.Split(match[i], ",") { + // if an issue is to be ignored in this line of code is same as + // the issue that is raised, we have to ignore the issue + + if isSimilarIssue(fileExt, skipCQTag, probableIssueCode, issueCode) { + ignoreIssue = true + // since we are only dealing with one issue at a time + // break at the first occurrence + return ignoreIssue + } else { + ignoreIssue = false + } + } + } else { + // in case there is no issue code associated, check for just the silencer tag + ignoreIssue = isSimilarIssue(fileExt, skipCQTag, "", issueCode) + } + } + } + } + } + return ignoreIssue +} + +/* Check if a given line of code is eligible to be checked for skip CQ. + * Bare minimum eligibility is that the line should either be empty or + * contain a comment only line. */ +func analyzeLineForSkipCQ(line, fileExt string) bool { + var commentIdentifier string + for _, langMeta := range languagesMeta { + if fileExt == langMeta.Extension { + commentIdentifier = langMeta.CommentIdentifier + break + } + } + + line = strings.TrimSpace(line) + + if line == "" { + return true + } + + match, err := regexp.Match(fmt.Sprintf("^%s", commentIdentifier), []byte(line)) + return err == nil && match +} + +// Returns the name of the processor +func (p ProcSkipCQ) String() string { + return "skip_cq" +} + +// Process checks if the issue passed as an argument should be skipped or not +// If it should be skipped, it is not appended to the processedIssues slice while if it is not skipped, it is appended. +func (p ProcSkipCQ) Process(fileContentSlice []string, issue *types.Issue, processedIssues *[]types.Issue) error { + filePath := issue.Location.Path + lineStart := issue.Location.Position.Begin.Line + issueCode := issue.IssueCode + + if lineStart < 1 || lineStart > len(fileContentSlice) { + return fmt.Errorf("issue position is weird for file of %d lines", len(fileContentSlice)) + } + + shouldSkipCQ := false + line := strings.TrimSpace(fileContentSlice[lineStart-1]) + + fileSupported := false + var fileExt string + var skipCQregex regexp.Regexp + for re := range languagesMeta { + match, err := regexp.Match(re, []byte(filePath)) + if err != nil || !match { + continue + } + fileSupported = true + fileExt = re + break + } + if !fileSupported { + fileExt = "default_extension" + } + skipCQregex = regexMap[fileExt] + + // Skip code quality checks for the given issue code on the lineStart + shouldSkipCQ = checkSkipCQ(fileExt, skipCQregex, line, issueCode) + + // Continue looking only if the previous line is a comment-only line specifying the skipcq condition, + // the lineStart is not the first line of the file and shouldSkipCQ is not already true + for index := lineStart - 2; !shouldSkipCQ && index >= 0 && analyzeLineForSkipCQ(fileContentSlice[index], fileExt); index-- { + shouldSkipCQ = checkSkipCQ(fileExt, skipCQregex, fileContentSlice[index], issueCode) + } + + if !shouldSkipCQ { + *processedIssues = append(*processedIssues, *issue) + } + return nil +} diff --git a/analysis/processor/processors/proc_source_code_load.go b/analysis/processor/processors/proc_source_code_load.go new file mode 100644 index 00000000..7521e376 --- /dev/null +++ b/analysis/processor/processors/proc_source_code_load.go @@ -0,0 +1,225 @@ +package processors + +import ( + "bytes" + "fmt" + "log" + "strings" + + "github.com/alecthomas/chroma/formatters/html" + "github.com/alecthomas/chroma/lexers" + "github.com/alecthomas/chroma/styles" + "github.com/deepsourcelabs/cli/types" +) + +const sourceCodeOffset int = 3 + +type ProcSourceCodeLoad struct{} + +type formattedFile struct { + fileContent []string + highlightedContent []string +} + +var ( + lineStartWithOffset, lineEndWithOffset int + formattedFileCache map[string]formattedFile = make(map[string]formattedFile) +) + +// We cache the past file's iterator, taking advantage of the fact that +// issues are sorted according to filenames +func getFinalFormattedSlice(fileContentSlice []string, issue *types.Issue) formattedFile { + filePath := issue.Location.Path + + // Check if the formatted file data is already present in the cache. + // If yes, return the cached data. + if formattedFileData, ok := formattedFileCache[filePath]; ok { + return formattedFileData + } + + // Else, clear the cache. + for k := range formattedFileCache { + delete(formattedFileCache, k) + } + + /* ============================================================ + * Use alecthomas/chroma to generate syntax highlighted snippet + * ============================================================ */ + fileContentString := strings.Join(fileContentSlice, "\n") + lexer := lexers.Match(filePath) + + /* Case: In case of .vue files, use the `html` lexer since the `vue` lexer + * breaks in certain cases. The `html` lexer provides comparatively better results. + * TODO(SNT): Remove this case if vue lexer is improved in future. */ + if strings.HasSuffix(filePath, ".vue") { + lexer = lexers.Get("html") + } + if lexer == nil { + lexer = lexers.Fallback + } + + // Tokenize the file content. + iterator, err := lexer.Tokenise(nil, fileContentString) + if err != nil { + log.Println("Could not tokenize file ", filePath) + return formattedFile{ + fileContent: []string{}, + highlightedContent: []string{}, + } + } + + // Selecting the chroma format in which we expect the output(html) and use the `monokai` colorscheme to highlight the snippet. + formatter := html.New(html.WithLineNumbers(true), html.PreventSurroundingPre(true), html.WithClasses(true)) + style := styles.Get("monokai") + if style == nil { + style = styles.Fallback + } + + var chromaFormattedBytes bytes.Buffer + var chromaFormattedString string + err = formatter.Format(&chromaFormattedBytes, style, iterator) + if err != nil { + fmt.Println(err) + return formattedFile{ + fileContent: []string{}, + highlightedContent: []string{}, + } + } + + // Convert the generated data in bytes to string and also extract the slice containing + // all the lines as the contents. + chromaFormattedString = chromaFormattedBytes.String() + chromaFormattedSlice := strings.Split(chromaFormattedString, "\n") + + // We need to move the trailing span to the previous line in order. + for i := range chromaFormattedSlice { + if i != 0 && !strings.HasPrefix(chromaFormattedSlice[i], "") { + lineStartIndex := strings.Index(chromaFormattedSlice[i], "") + + if lineStartIndex != -1 { + chromaFormattedSlice[i-1] += chromaFormattedSlice[i][:lineStartIndex] + chromaFormattedSlice[i] = chromaFormattedSlice[i][lineStartIndex:] + } + } + } + + // Highlight all lines in the file. + lexer = lexers.Match(filePath) + if lexer == nil { + lexer = lexers.Fallback + } + iterator, err = lexer.Tokenise(nil, fileContentString) + if err != nil { + fmt.Println("Could not tokenize file ", filePath) + return formattedFile{ + fileContent: []string{}, + highlightedContent: []string{}, + } + } + + // Specifying the highlight range. + lineHighlightRange := [][2]int{{1, len(fileContentSlice)}} + + // Format, style and color the snippet. + formatter = html.New(html.WithLineNumbers(true), html.PreventSurroundingPre(true), html.WithClasses(true), html.HighlightLines(lineHighlightRange)) + style = styles.Get("monokai") + if style == nil { + style = styles.Fallback + } + var chromaHighlightedBytes bytes.Buffer + err = formatter.Format(&chromaHighlightedBytes, style, iterator) + if err != nil { + fmt.Println(err) + return formattedFile{ + fileContent: []string{}, + highlightedContent: []string{}, + } + } + chromaHighlightedString := chromaHighlightedBytes.String() + chromaHighlightedSlice := strings.Split(chromaHighlightedString, "\n") + + /* Correct the span elements in the slice. + * Highlighted lines look like this: + * + * 1import os + * 2import random # noqa: F401 + * 3import this # noqa + * 4import sys */ + + // We need to move the trailing span to the previous line in order for our replacement logic to work. + for i := range chromaHighlightedSlice { + if i != 0 && !strings.HasPrefix(chromaHighlightedSlice[i], "") { + lineStartIndex := strings.Index(chromaHighlightedSlice[i], "") + + if lineStartIndex != -1 { + chromaHighlightedSlice[i-1] += chromaHighlightedSlice[i][:lineStartIndex] + chromaHighlightedSlice[i] = chromaHighlightedSlice[i][lineStartIndex:] + } + } + } + + // Create formattedContent variable of `formattedFile` type and return. + formattedContent := formattedFile{ + fileContent: chromaFormattedSlice, + highlightedContent: chromaHighlightedSlice, + } + formattedFileCache[filePath] = formattedContent + + return formattedContent +} + +// Returns the name of the processor +func (p ProcSourceCodeLoad) String() string { + return "source_code_load" +} + +// Process processes the source code to be highlighted using chroma and writes that into the +// analysis result post highlighting. +func (p ProcSourceCodeLoad) Process(fileContentSlice []string, issue *types.Issue, _ *[]types.Issue) error { + lineStart := issue.Location.Position.Begin.Line + lineEnd := issue.Location.Position.End.Line + + // Count lines in the file + numLines := len(fileContentSlice) + + // Calculate the line number from where the highlighting should start + if lineStart-sourceCodeOffset < 1 { + lineStartWithOffset = 1 + } else { + lineStartWithOffset = lineStart - sourceCodeOffset + } + + // Calculate the line number from where the highlighting should end + if lineEnd+sourceCodeOffset > numLines { + lineEndWithOffset = numLines + } else { + lineEndWithOffset = lineEnd + sourceCodeOffset + } + + formattedFileContent := getFinalFormattedSlice(fileContentSlice, issue) + chromaFormattedSlice := formattedFileContent.fileContent + chromaHighlightedSlice := formattedFileContent.highlightedContent + + finalFormattedSlice := make([]string, 0) + finalFormattedSlice = append(finalFormattedSlice, `
`)
+
+	// Get the file slice to write
+	for i := lineStartWithOffset; i <= lineEndWithOffset; i++ {
+		currentLine := chromaFormattedSlice[i-1]
+
+		// for all lines except the last, append a newline
+		if i < lineEndWithOffset {
+			currentLine = currentLine + "\n"
+		}
+
+		// highlight the lines containing the issue
+		// We need not add a \n at the end of highlighted lines, as chroma does it itself
+		if i >= lineStart && i <= lineEnd {
+			currentLine = chromaHighlightedSlice[i-1]
+		}
+		finalFormattedSlice = append(finalFormattedSlice, currentLine)
+	}
+	finalFormattedSlice = append(finalFormattedSlice, "
") + issue.ProcessedData.SourceCode.Rendered = strings.Join(finalFormattedSlice, "") + return nil +} diff --git a/analysis/processor/processors/silencers.go b/analysis/processor/processors/silencers.go new file mode 100644 index 00000000..7cfc0e5c --- /dev/null +++ b/analysis/processor/processors/silencers.go @@ -0,0 +1,106 @@ +package processors + +import ( + "regexp" + "strings" +) + +var languagesMeta map[string]LanguageMeta + +// for the extension e.g .py, .go, .js etc build a map of compiled +// regex with the corresponding comment identifier in regex. +var regexMap map[string]regexp.Regexp + +// Silencers data structure +type IssueSilencer struct { + PortName string `json:"port_name"` + SilencerCode string `json:"silencer_code"` + Issues map[string]string `json:"issues"` + TagSeparator string `json:"tag_separator"` +} + +type LanguageMeta struct { + Extension string `json:"extension"` + CommentIdentifier string `json:"comment_identifier"` + Silencers []IssueSilencer `json:"issue_silencers"` +} + +// Reads the `silencers.json` file present in `/toolbox` directory +// and makes a LanguageMeta map which helps in processing skipcq +func prepareSilencersMeta() []LanguageMeta { + // Sane default meta to use when failed to read the silencers file. + // Also, appended to the every config + issuesMeta := []LanguageMeta{ + { + Extension: "default_extension", + CommentIdentifier: `(\#|\/\/)`, + Silencers: []IssueSilencer{}, + }, + } + return issuesMeta +} + +// generateRegExp generates the regex expression used for matching the skipcq comment +func generateRegExp(fileExt string) regexp.Regexp { + // Analyzer specific issues silencer tags, with `skipcq` tag + skipCQTags := []string{"skipcq"} + + // Different analyzers may have different ending token for issue silencing. eg. Pylint has `=` while + // most others have `:` + separators := map[string]int{ + ":": 1, // default, for skipcq + } + for _, silencer := range languagesMeta[fileExt].Silencers { + skipCQTags = append(skipCQTags, silencer.SilencerCode) + separators[silencer.TagSeparator] = 1 + } + + separatorsList := []string{} + for k := range separators { + separatorsList = append(separatorsList, k) + } + + commentIdentifier := languagesMeta[fileExt].CommentIdentifier + regex := *regexp.MustCompile( + // case-insensitive + `(?i)` + + + // group the silencer tags with a name, matching one of the many issue silencers (eg, noqa, nosec) + commentIdentifier + `.*?(?P(` + strings.Join(skipCQTags, "|") + `))` + + + // zero or more occurrences of the issue codes, ends at `)?` later + `(` + + + // separators, like `:` and `=` + `?:(` + strings.Join(separatorsList, "|") + `)[\s]?` + + + // create a group named issue_codes, with pattern similar to PYL-W0614 or SA1022 + `(?P([A-Z]*-?[A-Z]*[0-9]+(?:,(\s+)?)?)+)` + + + // zero or one occurrences of `: issuecode1, issuecode2` and so on + `)?`, + ) + return regex +} + +// generateSilencersRegexMap generates the silencers regex expression used for skipcq processing +func GenerateSilencersRegexMap() { + langMeta := make(map[string]LanguageMeta) + silencerRegexMap := make(map[string]regexp.Regexp) + + // Fetch the silencers meta for the Analyzer + // Keeping it as the default silencer for now + silencersData := prepareSilencersMeta() + + // Mapping the silencers data to the file extension + // in the above declared map `langMeta` + for _, silencerData := range silencersData { + langMeta[silencerData.Extension] = silencerData + } + languagesMeta = langMeta + + for ext := range langMeta { + silencerRegexMap[ext] = generateRegExp(ext) + } + regexMap = silencerRegexMap +} diff --git a/analysis/processor/utils.go b/analysis/processor/utils.go new file mode 100644 index 00000000..ad013e6f --- /dev/null +++ b/analysis/processor/utils.go @@ -0,0 +1,112 @@ +package processor + +import ( + "path" + "sort" + "strings" + + "github.com/deepsourcelabs/cli/types" +) + +// sortIssuesByFile sorts the issues in an alphabetical order according to the filenames +// where they got reported. +func (p *ReportProcessor) sortIssuesByFile(result *types.AnalysisResult) { + sort.Slice(result.Issues, func(i, j int) bool { + return result.Issues[i].Location.Path < result.Issues[j].Location.Path + }) +} + +// Prepare a map with unique filenames as key and the issue range for each file as value +// This is done to ensure fewer loops when processing issues. +type IssueRange struct { + Filename string // The file which has the issues + BeginIndex int // Array index in report.Issues where the particular issue starts + EndIndex int // Array index in report.Issues where the particular issue ends +} + +// GenerateIssueRangeSlice generates an array containing the issue ranges with respect to files +// that helps us to go through them and map them to the files where they got reported instead +// of opening the file for each of them. The generated index looks like this: + +// [{analyzer.go 0 0} {autofix_patch.go 1 1} {difftool.go 2 2} {patch.patch 3 5} {proc_skip_cq_test.go 6 6}] + +// Here, the first field if filename and the second and third fields are the index range in which the issues reported +// in these files lie in the sorted AnalyzerReport slice. +func createIssueFileRange(report types.AnalysisResult) []IssueRange { + fileCount := 0 // for 1 file, 0 based indexing + issuesRange := []IssueRange{} + prevFilename := report.Issues[0].Location.Path + + issueRange := IssueRange{ + BeginIndex: 0, + EndIndex: len(report.Issues) - 1, + Filename: prevFilename, + } + issuesRange = append(issuesRange, issueRange) + + // Iterating over the issues and creating an array containing issues with index data about + // the files in which those issues are present + for i := 1; i < len(report.Issues); i++ { + issue := report.Issues[i] + currentFilename := issue.Location.Path + + // TODO: Check when this condition is implied + if issue.Location.Position.End.Line == -1 { + issue.Location.Position.End.Line = issue.Location.Position.Begin.Line + } + + if currentFilename != prevFilename { + fileCount++ + + issueRange = issuesRange[fileCount-1] + issueRange.EndIndex = i - 1 + issuesRange[fileCount-1] = issueRange + + // Create for the new file + issueRange = IssueRange{ + Filename: currentFilename, + BeginIndex: i, + EndIndex: len(report.Issues) - 1, + } + + issuesRange = append(issuesRange, issueRange) + prevFilename = currentFilename + } + } + return issuesRange +} + +// formatLSPResultsToDefault converts the LSP based analysis results into the default format supported by DeepSource. +func (p *ReportProcessor) formatLSPResultsToDefault() types.AnalysisResult { + analysisResult := types.AnalysisResult{} + analysisResult.IsPassed = p.Report.IsPassed + analysisResult.Metrics = append(p.Report.Metrics, analysisResult.Metrics...) + analysisResult.Errors = append(p.Report.Errors, analysisResult.Errors...) + + // Appending the issues to the default format of Analysis report + for _, issue := range p.Report.Issues { + analysisIssue := types.Issue{ + IssueCode: issue.Code, + IssueText: issue.Message, + Location: types.Location{ + Path: p.sanitizeFilePath(issue.RelatedInformation[0].Location.URI), + Position: types.Position{ + Begin: types.Coordinate{ + Line: issue.Range.Start.Line, + Column: issue.Range.Start.Character, + }, + End: types.Coordinate{ + Line: issue.Range.End.Line, + Column: issue.Range.End.Character, + }, + }, + }, + } + analysisResult.Issues = append(analysisResult.Issues, analysisIssue) + } + return analysisResult +} + +func (p *ReportProcessor) sanitizeFilePath(filePath string) string { + return path.Join(p.LocalSourcePath, strings.TrimPrefix(filePath, p.ContainerCodePath)) +} diff --git a/analyzers/backend/docker/build.go b/analyzers/backend/docker/build.go new file mode 100644 index 00000000..fb975c78 --- /dev/null +++ b/analyzers/backend/docker/build.go @@ -0,0 +1,115 @@ +package docker + +import ( + "context" + "crypto/rand" + "fmt" + "io" + "os" + "strings" + "time" + + cliTypes "github.com/deepsourcelabs/cli/types" + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/docker/docker/pkg/archive" +) + +// Timeout for build and container operations (10 minutes) +const buildTimeout = 10 * time.Minute + +type DockerClient struct { + Client *client.Client + ContainerName string + ContainerID string + ImageName string + ImageTag string + ImagePlatform string + DockerfilePath string + AnalysisOpts AnalysisParams + ShowLogs bool +} + +type DockerBuildError struct { + Message string +} + +func (d *DockerBuildError) Error() string { + return d.Message +} + +// SetupClient initializes the Docker client with opts. +func (d *DockerClient) SetupClient() error { + var err error + d.Client, err = client.NewClientWithOpts(client.FromEnv) + if err != nil { + return err + } + + return nil +} + +// BuildAnalyzerDockerImage is the docker image build API used by various CLI commands +func (d *DockerClient) BuildAnalyzerDockerImage() (context.CancelFunc, io.ReadCloser, *DockerBuildError) { + var err error + + cancelFunc, responseReader, err := d.executeImageBuild() + if err != nil { + return cancelFunc, nil, &DockerBuildError{ + Message: err.Error(), + } + } + return cancelFunc, responseReader, nil +} + +// Executes the docker image build +func (d *DockerClient) executeImageBuild() (context.CancelFunc, io.ReadCloser, error) { + ctx, ctxCancelFunc := context.WithTimeout(context.Background(), buildTimeout) + cwd, _ := os.Getwd() + + tarOptions := &archive.TarOptions{ + ExcludePatterns: []string{".git/**"}, + } + tar, err := archive.TarWithOptions(cwd, tarOptions) + if err != nil { + return ctxCancelFunc, nil, err + } + + opts := types.ImageBuildOptions{ + Dockerfile: d.DockerfilePath, + Tags: []string{fmt.Sprintf("%s:%s", d.ImageName, d.ImageTag)}, + Remove: true, + Platform: d.ImagePlatform, + } + res, err := d.Client.ImageBuild(ctx, tar, opts) + if err != nil { + ctxCancelFunc() + return ctxCancelFunc, nil, err + } + return ctxCancelFunc, res.Body, nil +} + +// Returns the docker image details to build +func GetDockerImageDetails(analyzerTOMLData *cliTypes.AnalyzerTOML) (string, string) { + var dockerFilePath, dockerFileName string + dockerFilePath = "Dockerfile" + + // Read config for the value if specified + if analyzerTOMLData.Build.Dockerfile != "" { + dockerFilePath = analyzerTOMLData.Build.Dockerfile + } + + // Removing the @ from the shortcode since docker build doesn't accept it as a valid image name + if analyzerTOMLData.Shortcode != "" { + dockerFileName = strings.TrimPrefix(analyzerTOMLData.Shortcode, "@") + } + return dockerFilePath, dockerFileName +} + +func GenerateImageVersion(length int) string { + b := make([]byte, length) + if _, err := rand.Read(b); err != nil { + panic(err) + } + return fmt.Sprintf("%x", b) +} diff --git a/analyzers/backend/docker/container.go b/analyzers/backend/docker/container.go new file mode 100644 index 00000000..7576e444 --- /dev/null +++ b/analyzers/backend/docker/container.go @@ -0,0 +1,156 @@ +package docker + +import ( + "archive/tar" + "context" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "runtime" + "strings" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/pkg/archive" + v1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +type AnalysisParams struct { + AnalyzerName string + AnalyzerShortcode string + HostCodePath string + HostToolBoxPath string + AnalysisCommand string + ContainerCodePath string + ContainerToolBoxPath string + AnalysisResultsPath string + AnalysisResultsFilename string + AnalysisConfigPath string +} + +const containerRunTimeout = 10 * time.Minute + +/* Creates a Docker container with the volume mount in which the source code to be analyzed and the CMD instruction being the + * analysis command configured by the user. + * Having started the container, streams the logs to STDOUT. On completion of the streaming, + * copies the `analysis_results.json` result file generated in the container to the host directory + */ +func (d *DockerClient) StartDockerContainer() error { + /* ========================================================== + * Prepare the container config with the following data: + * - ImageName + * - CMD instruction + * - Environment variables + * ========================================================== */ + config := container.Config{ + Image: fmt.Sprintf("%s:%s", d.ImageName, d.ImageTag), + Cmd: strings.Split(d.AnalysisOpts.AnalysisCommand, " "), + Env: []string{ + "TOOLBOX_PATH=" + d.AnalysisOpts.ContainerToolBoxPath, + "CODE_PATH=" + d.AnalysisOpts.ContainerCodePath, + }, + } + + /* Host config containing the mounted volumes + * The host machine's temporary code path and toolbox path is mounted in the container */ + hostConfig := container.HostConfig{ + Binds: []string{ + fmt.Sprintf("%s:%s", d.AnalysisOpts.HostCodePath, d.AnalysisOpts.ContainerCodePath), + }, + } + + // Prepare the network config + networkConfig := network.NetworkingConfig{} + + // Configure the platform(mostly architecture) for the container. If specified by the user, use that else + // determine it using runtime.GOARCH. + containerArch := runtime.GOARCH + if d.ImagePlatform != "" { + containerArch = strings.SplitN(d.ImagePlatform, "/", 2)[1] + } + + platform := v1.Platform{ + Architecture: containerArch, + OS: "linux", + } + + /* =============================================================================== + * Create container with the above configs and copy the analysis_config.json to it + * =============================================================================== */ + ctx, cancel := context.WithTimeout(context.Background(), containerRunTimeout) + defer cancel() + containerCreateResp, err := d.Client.ContainerCreate(ctx, &config, &hostConfig, &networkConfig, &platform, d.ContainerName) + if err != nil { + return err + } + d.ContainerID = containerCreateResp.ID + + tr, err := archive.Tar(d.AnalysisOpts.AnalysisConfigPath, archive.Uncompressed) + if err != nil { + return err + } + + opts := types.CopyToContainerOptions{} + if err = d.Client.CopyToContainer(ctx, d.ContainerID, path.Join(d.AnalysisOpts.ContainerToolBoxPath), tr, opts); err != nil { + return err + } + + /* ========================================= + * Start the container + * ========================================= */ + containerOpts := types.ContainerStartOptions{} + err = d.Client.ContainerStart(ctx, containerCreateResp.ID, containerOpts) + if err != nil { + return err + } + + /* =================================================================== + * Stream container logs to STDOUT + * TODO: Check if the logs are needed only in --verbose/--debug mode? + * =================================================================== */ + reader, err := d.Client.ContainerLogs(ctx, containerCreateResp.ID, types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Timestamps: false}) + if err != nil { + return err + } + defer reader.Close() + + _, err = io.Copy(os.Stdout, reader) + if err != nil && err != io.EOF { + return err + } + return nil +} + +/* Fetch analysis results generated after analysis from the container */ +func (d *DockerClient) FetchAnalysisResults() ([]byte, string, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*120) + defer cancel() + + // If no error is found from the above step, copy the analysis results file to the host + contentReader, _, err := d.Client.CopyFromContainer(ctx, d.ContainerID, path.Join(d.AnalysisOpts.ContainerToolBoxPath, d.AnalysisOpts.AnalysisResultsFilename)) + if err != nil { + return nil, "", err + } + defer contentReader.Close() + + tr := tar.NewReader(contentReader) + + // Read the TAR archive returned by docker + result, err := tr.Next() + if err != nil { + if err != io.EOF { + return nil, "", err + } + } + + // Read the contents of the TAR archive into a byte buffer + buf, err := ioutil.ReadAll(tr) + if err != nil { + return nil, "", err + } + return buf, result.Name, nil +} diff --git a/analyzers/backend/docker/pull.go b/analyzers/backend/docker/pull.go new file mode 100644 index 00000000..75fc3610 --- /dev/null +++ b/analyzers/backend/docker/pull.go @@ -0,0 +1,21 @@ +package docker + +import ( + "context" + "io" + + "github.com/docker/docker/api/types" +) + +// PullImage pulls an image from a registry. +func (d *DockerClient) PullImage(imageName string) (context.CancelFunc, io.ReadCloser, error) { + ctx, ctxCancelFunc := context.WithTimeout(context.Background(), buildTimeout) + + reader, err := d.Client.ImagePull(ctx, imageName, types.ImagePullOptions{}) + if err != nil { + ctxCancelFunc() + return ctxCancelFunc, nil, err + } + + return ctxCancelFunc, reader, nil +} diff --git a/analyzers/backend/docker/push.go b/analyzers/backend/docker/push.go new file mode 100644 index 00000000..0d720689 --- /dev/null +++ b/analyzers/backend/docker/push.go @@ -0,0 +1,40 @@ +package docker + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "time" + + "github.com/docker/docker/api/types" +) + +const imagePushTimeout = 10 * time.Minute + +// PushImageToRegistry pushes the Analyzer image to the docker registry post authenticating using the +// credentials passed as arguments. +func (d *DockerClient) PushImageToRegistry(user, token string) (context.CancelFunc, io.ReadCloser, error) { + ctx, ctxCancelFunc := context.WithTimeout(context.Background(), imagePushTimeout) + authConfig := types.AuthConfig{ + Username: user, + Password: token, + } + + // Encode the authentication config as a JSON. + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + return ctxCancelFunc, nil, err + } + // Encode the config to base64. + authStr := base64.URLEncoding.EncodeToString(encodedJSON) + + // Push the image. + imagePushRespReader, err := d.Client.ImagePush(ctx, fmt.Sprintf("%s:%s", d.ImageName, d.ImageTag), types.ImagePushOptions{RegistryAuth: authStr}) + if err != nil { + return ctxCancelFunc, nil, err + } + + return ctxCancelFunc, imagePushRespReader, nil +} diff --git a/analyzers/backend/docker/utils.go b/analyzers/backend/docker/utils.go new file mode 100644 index 00000000..df0a6608 --- /dev/null +++ b/analyzers/backend/docker/utils.go @@ -0,0 +1,90 @@ +package docker + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "io" + "strings" +) + +type ErrorLine struct { + Error string `json:"error"` + ErrorDetail ErrorDetail `json:"errorDetail"` +} + +type ErrorDetail struct { + Message string `json:"message"` +} + +type DockerBuildResponse struct { + Stream string `json:"stream"` +} + +type DockerPullResponse struct { + Status string `json:"status"` + Progress string `json:"progress"` + ID string `json:"id"` +} + +/* Checks the docker build response and prints all the logs if `showAllLogs` is true + * Used in `deepsource analyzer run` and `deepsource analyzer verify` commands */ +func CheckBuildResponse(rd io.Reader, showAllLogs bool) error { // skipcq: RVV-A0005 + var lastLine []byte + count := 0 + var currentStream string + + scanner := bufio.NewScanner(rd) + for scanner.Scan() { + lastLine = scanner.Bytes() + d := &DockerBuildResponse{} + err := json.Unmarshal(lastLine, d) + if err != nil { + return err + } + if d.Stream == "" || d.Stream == "\n" || strings.Contains(d.Stream, "--->") || strings.TrimSuffix(d.Stream, "\n") == currentStream { + continue + } + currentStream = strings.TrimSuffix(d.Stream, "\n") + if showAllLogs { + fmt.Println(currentStream) + } + count++ + } + + errLine := &ErrorLine{} + json.Unmarshal([]byte(lastLine), errLine) + if errLine.Error != "" { + return errors.New(errLine.Error) + } + return scanner.Err() +} + +/* Checks the docker pull response and prints all the logs if `showAllLogs` is true + * Used in `deepsource analyzer dry-run` command. */ +func CheckPullResponse(rd io.Reader, showAllLogs bool) error { // skipcq: RVV-A0005 + var lastLine []byte + count := 0 + + scanner := bufio.NewScanner(rd) + for scanner.Scan() { + lastLine = scanner.Bytes() + d := &DockerPullResponse{} + err := json.Unmarshal(lastLine, d) + if err != nil { + return err + } + if showAllLogs { + fmt.Printf("%s %s\n", d.Status, d.Progress) + } + count++ + } + + errLine := &ErrorLine{} + json.Unmarshal([]byte(lastLine), errLine) + if errLine.Error != "" { + return errors.New(errLine.Error) + } + return scanner.Err() +} diff --git a/analyzers/config/config.go b/analyzers/config/config.go new file mode 100644 index 00000000..8d486e16 --- /dev/null +++ b/analyzers/config/config.go @@ -0,0 +1,132 @@ +package config + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/deepsourcelabs/cli/types" + "github.com/deepsourcelabs/cli/utils" + "github.com/pelletier/go-toml/v2" +) + +var ( + projectRoot string + analyzerTOMLPath string + issuesDirectoryPath string + configFolder string = ".deepsource/analyzer" +) + +func InitAnalyzerConfigurationPaths() (string, string, string) { + resolveAnalyzerConfigurationPaths() + return projectRoot, analyzerTOMLPath, issuesDirectoryPath +} + +// Read the types and read the config +func resolveAnalyzerConfigurationPaths() { + cwd, _ := os.Getwd() + + // Extracting the path of the project root + projectRoot, err := utils.ExtractProjectRootPath() + if err != nil { + projectRoot = cwd + } + + // Configuring the paths of analyzer.toml and issues directory + analyzerTOMLPath = filepath.Join(projectRoot, configFolder, "analyzer.toml") + issuesDirectoryPath = filepath.Join(projectRoot, configFolder, "issues/") +} + +// Verify the paths of analyzer configurations like analyzer.toml and +// issue descriptions +func VerifyAnalyzerConfigs() error { + resolveAnalyzerConfigurationPaths() + + // Check if `analyzer.toml` is present in `.deepsource/analyzer` folder + if _, err := os.Stat(analyzerTOMLPath); err != nil { + if errors.Is(err, os.ErrNotExist) { + return errors.New("the analyzer.toml file doesn't exist\n") + } + } + + // Check if `issues/` directory is present in `.deepsource/analyzer` folder and is not empty. + if _, err := os.Stat(issuesDirectoryPath); err != nil { + if errors.Is(err, os.ErrNotExist) { + return errors.New("the issue descriptions directory doesn't exist\n") + } + } + + // Check if there are any toml files in the `issues/` directory + files, err := ioutil.ReadDir(issuesDirectoryPath) + if err != nil { + return fmt.Errorf("failed to read the files present in the issues directory at %s\n", issuesDirectoryPath) + } + + // Check if its an empty directory + if len(files) < 1 { + return fmt.Errorf("found 0 issues configured in the issues directory at %s\n", issuesDirectoryPath) + } + + tomlPresent := false + // Check if there are TOML files configured in the issues/ directory + for _, file := range files { + if strings.HasSuffix(file.Name(), ".toml") { + tomlPresent = true + break + } + } + if !tomlPresent { + return fmt.Errorf("found no toml files in the issues directory at %s\n", issuesDirectoryPath) + } + return nil +} + +// Get the analyzer.toml data +func GetAnalyzerTOML() (*types.AnalyzerTOML, error) { + resolveAnalyzerConfigurationPaths() + config := types.AnalyzerTOML{} + + // Read the contents of analyzer.toml file + analyzerTOMLContent, err := ioutil.ReadFile(analyzerTOMLPath) + if err != nil { + return &config, errors.New("failed to read analyzer.toml file") + } + + // Unmarshal TOML into config + if err = toml.Unmarshal(analyzerTOMLContent, &config); err != nil { + return &config, err + } + return &config, nil +} + +// Get the list of issue descriptions +func GetIssueDescriptions() (*[]types.AnalyzerIssue, error) { + resolveAnalyzerConfigurationPaths() + issueDescriptions := []types.AnalyzerIssue{} + + issuesList, err := ioutil.ReadDir(issuesDirectoryPath) + if err != nil { + return nil, err + } + for _, issuePath := range issuesList { + issue := types.AnalyzerIssue{} + // Set the issue shortcode as the filename + issue.Shortcode = strings.TrimSuffix(issuePath.Name(), ".toml") + + // Read the contents of issue toml file + issueTOMLContent, err := ioutil.ReadFile(filepath.Join(issuesDirectoryPath, issuePath.Name())) + if err != nil { + return nil, fmt.Errorf("failed to read file: %s", filepath.Join(issuesDirectoryPath, issuePath.Name())) + } + + // Unmarshal TOML into config + if err = toml.Unmarshal(issueTOMLContent, &issue); err != nil { + return nil, err + } + issueDescriptions = append(issueDescriptions, issue) + } + return &issueDescriptions, nil +} diff --git a/analyzers/diagnostics/diagnostics.go b/analyzers/diagnostics/diagnostics.go new file mode 100644 index 00000000..88439e8a --- /dev/null +++ b/analyzers/diagnostics/diagnostics.go @@ -0,0 +1,116 @@ +package diagnostics + +import ( + "fmt" + "os" + "strings" + + "github.com/deepsourcelabs/cli/analyzers/validator" + "github.com/morikuni/aec" +) + +// Diagnostic represents a diagnostics reported by the DeepSource CLI validators. +type Diagnostic struct { + Line int + Codeframe string + ErrorMessage string +} + +// GetDiagnostics returns diagnostics as strings. +func GetDiagnostics(failure validator.ValidationFailure) ([]string, error) { + diagnostics := []string{} + + fileContent, err := readFileContent(failure.File) + if err != nil { + return nil, err + } + + // Get diagnostics using the file's content. + fileDiagnostics := getDiagnosticsFromFile(fileContent, failure.Errors) + + // Pretty-print diagnostics. + for _, diag := range fileDiagnostics { + message := constructDiagnostic(diag) + diagnostics = append(diagnostics, message) + } + + return diagnostics, nil +} + +// constructDiagnostic returns the diagnostic as a pretty-printed string. +func constructDiagnostic(diag Diagnostic) string { + errMsg := "" + errMsg += aec.LightRedF.Apply(fmt.Sprintf("%s\n", diag.ErrorMessage)) + errMsg += diag.Codeframe + errMsg += "\n" + + return errMsg +} + +// readFileContent reads the file and returns its content. +func readFileContent(filename string) (string, error) { + content, err := os.ReadFile(filename) + if err != nil { + return "", err + } + + return string(content), nil +} + +// getDiagnosticsFromFile uses the file content to return diagnostics with metadata like line number, content, etc. +func getDiagnosticsFromFile(fileContent string, errors []validator.ErrorMeta) []Diagnostic { + diagnostics := []Diagnostic{} + + lines := strings.Split(string(fileContent), "\n") + + // Iterate over each error and check line-by-line. + for _, err := range errors { + for lineNum, line := range lines { + // If the line contains the field name, and if it doesn't have a comment prefix, then we can proceed to diagnostic generation. + // TODO(burntcarrot): Replace these conditions with regex. + if strings.HasPrefix(line, err.Field) || + strings.HasPrefix(line, fmt.Sprintf("# %s", err.Field)) || + strings.HasPrefix(line, fmt.Sprintf("#%s", err.Field)) { + // Prepare code frame for the current line. + codeFrame := prepareCodeFrame(lineNum, lines) + + // Generate a diagnostic. + diag := Diagnostic{ + Line: lineNum, + Codeframe: codeFrame, + ErrorMessage: err.Message, + } + + diagnostics = append(diagnostics, diag) + } + } + } + + return diagnostics +} + +// prepareCodeFrame prepares a code frame using the file content. The code frame is meant to be displayed on the console while reporting diagnostics. +// NOTE: lineNum always starts from 0. +func prepareCodeFrame(lineNum int, lines []string) string { + frame := "" + + if lineNum <= 1 { + // Case 1: When the line is near the top of the file. + // Generate a frame with the current and next line only. + frame += aec.LightRedF.Apply(fmt.Sprintf("> %d | %s\n", lineNum+1, lines[lineNum])) + frame += fmt.Sprintf(" %d | %s\n", lineNum+2, lines[lineNum+1]) + + } else if lineNum >= (len(lines) - 1) { + // Case 2: When the line is near the bottom of the file. + // Generate a frame with the current line only. + frame += aec.LightRedF.Apply(fmt.Sprintf("> %d | %s\n", lineNum, lines[lineNum-1])) + } else { + // Case 3: When the line is in between the top and the bottom. + // Generate a frame with the the previous, current and the next line. + frame += fmt.Sprintf(" %d | %s\n", lineNum, lines[lineNum-1]) + frame += aec.LightRedF.Apply(fmt.Sprintf("> %d | %s\n", lineNum+1, lines[lineNum])) + frame += fmt.Sprintf(" %d | %s\n", lineNum+2, lines[lineNum+1]) + } + + return frame +} diff --git a/analyzers/diagnostics/diagnostics_test.go b/analyzers/diagnostics/diagnostics_test.go new file mode 100644 index 00000000..f936f8b7 --- /dev/null +++ b/analyzers/diagnostics/diagnostics_test.go @@ -0,0 +1,176 @@ +package diagnostics + +import ( + "os" + "regexp" + "strings" + "testing" + + "github.com/deepsourcelabs/cli/analyzers/validator" + "github.com/google/go-cmp/cmp" +) + +func TestPrepareCodeFrame(t *testing.T) { + type test struct { + description string + lineNum int // lineNum always starts from 0. + linesFilename string + wantFilename string + } + + tests := []test{ + { + description: "single line", + lineNum: 1, + linesFilename: "./testdata/test_codeframe/single_line/test.toml", + wantFilename: "./testdata/test_codeframe/single_line/test_want.toml", + }, + { + description: "multiple lines", + lineNum: 2, + linesFilename: "./testdata/test_codeframe/multiple_lines/test.toml", + wantFilename: "./testdata/test_codeframe/multiple_lines/test_want.toml", + }, + { + description: "multiple lines at bottom", + lineNum: 5, + linesFilename: "./testdata/test_codeframe/multiple_lines_bottom/test.toml", + wantFilename: "./testdata/test_codeframe/multiple_lines_bottom/test_want.toml", + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + // Read file for getting line content. + fileContentLines, err := os.ReadFile(tc.linesFilename) + if err != nil { + t.Errorf("error raised while running test (%s): %s\n", tc.description, err) + } + linesStr := string(fileContentLines) + lines := strings.Split(linesStr, "\n") + + // Prepare code frame. + got := prepareCodeFrame(tc.lineNum, lines) + + // Strip ANSI escape codes. + got = stripANSI(got) + got = strings.TrimSpace(got) + + fileContent, err := os.ReadFile(tc.wantFilename) + if err != nil { + t.Errorf("error raised while running test (%s): %s\n", tc.description, err) + } + + // Convert file content to string. + want := string(fileContent) + want = strings.TrimSpace(want) + + diff := cmp.Diff(got, want) + if len(diff) != 0 { + t.Errorf("test failed (%s)\ngot != want:\n%s\n", tc.description, diff) + } + }) + } +} + +func TestGetDiagnostics(t *testing.T) { + type test struct { + description string + failure validator.ValidationFailure + wantFilename string + } + + tests := []test{ + { + description: "single error", + failure: validator.ValidationFailure{ + File: "./testdata/test_getdiagnostics/single_error/test.toml", + Errors: []validator.ErrorMeta{ + { + Field: "engine", + Message: "Invalid build engine \"docke\". The following build engines are supported: [docker]", + }, + }, + }, + wantFilename: "./testdata/test_getdiagnostics/single_error/test_want.toml", + }, + { + description: "multiple errors", + failure: validator.ValidationFailure{ + File: "./testdata/test_getdiagnostics/multiple_errors/test.toml", + Errors: []validator.ErrorMeta{ + { + Field: "shortcode", + Message: "Analyzer shortcode should begin with '@'", + }, + { + Field: "engine", + Message: "Invalid build engine \"docke\". The following build engines are supported: [docker]", + }, + }, + }, + wantFilename: "./testdata/test_getdiagnostics/multiple_errors/test_want.toml", + }, + { + description: "no errors", + failure: validator.ValidationFailure{ + File: "./testdata/test_getdiagnostics/no_errors/test.toml", + Errors: []validator.ErrorMeta{}, + }, + wantFilename: "./testdata/test_getdiagnostics/no_errors/test_want.toml", + }, + { + description: "file with less lines", + failure: validator.ValidationFailure{ + File: "./testdata/test_getdiagnostics/less_lines/test.toml", + Errors: []validator.ErrorMeta{ + { + Field: "shortcode", + Message: "Analyzer shortcode should begin with '@'", + }, + }, + }, + wantFilename: "./testdata/test_getdiagnostics/less_lines/test_want.toml", + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + got, err := GetDiagnostics(tc.failure) + if err != nil { + t.Errorf("error raised while running test (%s): %s\n", tc.description, err) + } + + // Prepare a string for comparing. + gotStr := "" + for _, str := range got { + gotStr += str + "\n" + } + gotStr = strings.TrimSpace(gotStr) + + fileContent, err := os.ReadFile(tc.wantFilename) + if err != nil { + t.Errorf("error raised while running test (%s): %s\n", tc.description, err) + } + + // Convert file content to string. + want := string(fileContent) + want = strings.TrimSpace(want) + + // Strip ANSI escape codes. + gotStr = stripANSI(gotStr) + + diff := cmp.Diff(gotStr, want) + if len(diff) != 0 { + t.Errorf("test failed (%s)\ngot != want:\n%s\n", tc.description, diff) + } + }) + } +} + +// Strip ANSI codes for testing. +func stripANSI(str string) string { + ansi := "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" + re := regexp.MustCompile(ansi) + return re.ReplaceAllString(str, "") +} diff --git a/analyzers/diagnostics/testdata/test_codeframe/multiple_lines/test.toml b/analyzers/diagnostics/testdata/test_codeframe/multiple_lines/test.toml new file mode 100644 index 00000000..89acf0c7 --- /dev/null +++ b/analyzers/diagnostics/testdata/test_codeframe/multiple_lines/test.toml @@ -0,0 +1,7 @@ + +[build] + engine = "docke" + dockerfile = "" + +[test] + command = "" diff --git a/analyzers/diagnostics/testdata/test_codeframe/multiple_lines/test_want.toml b/analyzers/diagnostics/testdata/test_codeframe/multiple_lines/test_want.toml new file mode 100644 index 00000000..a549e5ab --- /dev/null +++ b/analyzers/diagnostics/testdata/test_codeframe/multiple_lines/test_want.toml @@ -0,0 +1,3 @@ + 2 | [build] +> 3 | engine = "docke" + 4 | dockerfile = "" diff --git a/analyzers/diagnostics/testdata/test_codeframe/multiple_lines_bottom/test.toml b/analyzers/diagnostics/testdata/test_codeframe/multiple_lines_bottom/test.toml new file mode 100644 index 00000000..c26b1a2f --- /dev/null +++ b/analyzers/diagnostics/testdata/test_codeframe/multiple_lines_bottom/test.toml @@ -0,0 +1,5 @@ + +[build] + engine = "docke" + dockerfile = "" + error = "" diff --git a/analyzers/diagnostics/testdata/test_codeframe/multiple_lines_bottom/test_want.toml b/analyzers/diagnostics/testdata/test_codeframe/multiple_lines_bottom/test_want.toml new file mode 100644 index 00000000..1e5eac35 --- /dev/null +++ b/analyzers/diagnostics/testdata/test_codeframe/multiple_lines_bottom/test_want.toml @@ -0,0 +1 @@ +> 5 | error = "" diff --git a/analyzers/diagnostics/testdata/test_codeframe/single_line/test.toml b/analyzers/diagnostics/testdata/test_codeframe/single_line/test.toml new file mode 100644 index 00000000..65af834b --- /dev/null +++ b/analyzers/diagnostics/testdata/test_codeframe/single_line/test.toml @@ -0,0 +1,3 @@ +name = "" +shortcode = "@aadhav-deepsource/2do-checker" +example = "hello" diff --git a/analyzers/diagnostics/testdata/test_codeframe/single_line/test_want.toml b/analyzers/diagnostics/testdata/test_codeframe/single_line/test_want.toml new file mode 100644 index 00000000..5d698653 --- /dev/null +++ b/analyzers/diagnostics/testdata/test_codeframe/single_line/test_want.toml @@ -0,0 +1,2 @@ +> 2 | shortcode = "@aadhav-deepsource/2do-checker" + 3 | example = "hello" diff --git a/analyzers/diagnostics/testdata/test_getdiagnostics/less_lines/test.toml b/analyzers/diagnostics/testdata/test_getdiagnostics/less_lines/test.toml new file mode 100644 index 00000000..f05c4b7e --- /dev/null +++ b/analyzers/diagnostics/testdata/test_getdiagnostics/less_lines/test.toml @@ -0,0 +1,2 @@ +name = "2do-Checker" +shortcode = "aadhav-deepsource/2do-checker" diff --git a/analyzers/diagnostics/testdata/test_getdiagnostics/less_lines/test_want.toml b/analyzers/diagnostics/testdata/test_getdiagnostics/less_lines/test_want.toml new file mode 100644 index 00000000..893a2ea4 --- /dev/null +++ b/analyzers/diagnostics/testdata/test_getdiagnostics/less_lines/test_want.toml @@ -0,0 +1,3 @@ +Analyzer shortcode should begin with '@' +> 2 | shortcode = "aadhav-deepsource/2do-checker" + 3 | diff --git a/analyzers/diagnostics/testdata/test_getdiagnostics/multiple_errors/test.toml b/analyzers/diagnostics/testdata/test_getdiagnostics/multiple_errors/test.toml new file mode 100644 index 00000000..c1cdc1d9 --- /dev/null +++ b/analyzers/diagnostics/testdata/test_getdiagnostics/multiple_errors/test.toml @@ -0,0 +1,20 @@ +name = "2do-Checker" +shortcode = "aadhav-deepsource/2do-checker" +description = "Checks for TODO." +tags = ["todo", "TODO", "checker"] +repository = "https://github.com/siddhant-deepsource/2do-checker" +documentation = "" +bug_tracker = "" + +[environment_variables] + +[analysis] + command = "/app/todo-checker" + +[build] + engine = "docke" + dockerfile = "" + script = "" + +[test] + command = "" diff --git a/analyzers/diagnostics/testdata/test_getdiagnostics/multiple_errors/test_want.toml b/analyzers/diagnostics/testdata/test_getdiagnostics/multiple_errors/test_want.toml new file mode 100644 index 00000000..21b0918d --- /dev/null +++ b/analyzers/diagnostics/testdata/test_getdiagnostics/multiple_errors/test_want.toml @@ -0,0 +1,9 @@ +Analyzer shortcode should begin with '@' +> 2 | shortcode = "aadhav-deepsource/2do-checker" + 3 | description = "Checks for TODO." + + +Invalid build engine "docke". The following build engines are supported: [docker] + 14 | [build] +> 15 | engine = "docke" + 16 | dockerfile = "" diff --git a/analyzers/diagnostics/testdata/test_getdiagnostics/no_errors/test.toml b/analyzers/diagnostics/testdata/test_getdiagnostics/no_errors/test.toml new file mode 100644 index 00000000..12aa7fe3 --- /dev/null +++ b/analyzers/diagnostics/testdata/test_getdiagnostics/no_errors/test.toml @@ -0,0 +1,20 @@ +name = "2do-Checker" +shortcode = "@aadhav-deepsource/2do-checker" +description = "Checks for TODO." +tags = ["todo", "TODO", "checker"] +repository = "https://github.com/siddhant-deepsource/2do-checker" +documentation = "" +bug_tracker = "" + +[environment_variables] + +[analysis] + command = "/app/todo-checker" + +[build] + engine = "docker" + dockerfile = "" + script = "" + +[test] + command = "" diff --git a/analyzers/diagnostics/testdata/test_getdiagnostics/no_errors/test_want.toml b/analyzers/diagnostics/testdata/test_getdiagnostics/no_errors/test_want.toml new file mode 100644 index 00000000..e69de29b diff --git a/analyzers/diagnostics/testdata/test_getdiagnostics/single_error/test.toml b/analyzers/diagnostics/testdata/test_getdiagnostics/single_error/test.toml new file mode 100644 index 00000000..c1cdc1d9 --- /dev/null +++ b/analyzers/diagnostics/testdata/test_getdiagnostics/single_error/test.toml @@ -0,0 +1,20 @@ +name = "2do-Checker" +shortcode = "aadhav-deepsource/2do-checker" +description = "Checks for TODO." +tags = ["todo", "TODO", "checker"] +repository = "https://github.com/siddhant-deepsource/2do-checker" +documentation = "" +bug_tracker = "" + +[environment_variables] + +[analysis] + command = "/app/todo-checker" + +[build] + engine = "docke" + dockerfile = "" + script = "" + +[test] + command = "" diff --git a/analyzers/diagnostics/testdata/test_getdiagnostics/single_error/test_want.toml b/analyzers/diagnostics/testdata/test_getdiagnostics/single_error/test_want.toml new file mode 100644 index 00000000..1aa78552 --- /dev/null +++ b/analyzers/diagnostics/testdata/test_getdiagnostics/single_error/test_want.toml @@ -0,0 +1,4 @@ +Invalid build engine "docke". The following build engines are supported: [docker] + 14 | [build] +> 15 | engine = "docke" + 16 | dockerfile = "" diff --git a/analyzers/validator/analyzer_toml.go b/analyzers/validator/analyzer_toml.go new file mode 100644 index 00000000..9e2fb921 --- /dev/null +++ b/analyzers/validator/analyzer_toml.go @@ -0,0 +1,86 @@ +package validator + +import ( + "fmt" + "strings" + + "github.com/deepsourcelabs/cli/types" + validate "github.com/go-playground/validator/v10" +) + +var supportedEngines []string = []string{"docker"} + +func validateAnalyzerTOMLFields(config *types.AnalyzerTOML, filePath string) (*ValidationFailure, error) { + var supportedEnginesString string + analyzerTOMLValidationErrors := ValidationFailure{} + + // Validate analyzer.toml fields based on type and sanity checks + v := validate.New() + // Custom validators for shortcode and engine + v.RegisterValidation("shortcode", ValidateShortcode) + v.RegisterValidation("engine", ValidateEngine) + + // Start the validation + if err := v.Struct(config); err != nil { + // List the missing required fields + missingRequiredFields := getMissingRequiredFields(err, *config) + analyzerTOMLValidationErrors = ValidationFailure{ + File: filePath, + } + + // Find any missing "required" fields from analyzer.toml + for _, missingField := range missingRequiredFields { + analyzerTOMLValidationErrors.Errors = append(analyzerTOMLValidationErrors.Errors, ErrorMeta{ + Level: Error, + Field: missingField, + Message: fmt.Sprintf("Missing required field: %s", missingField), + }, + ) + } + + // Check if the shortcode begins with @ and the right build engine is configured + errs := err.(validate.ValidationErrors) + for _, err := range errs { + if err.Tag() == "shortcode" { + analyzerTOMLValidationErrors.Errors = append(analyzerTOMLValidationErrors.Errors, ErrorMeta{ + Level: Error, + Field: "shortcode", + Message: "Analyzer shortcode should begin with '@'", + }) + } + + if err.Tag() == "engine" { + if len(supportedEngines) > 1 { + supportedEnginesString = strings.Join(supportedEngines, ", ") + } else { + supportedEnginesString = supportedEngines[0] + } + + analyzerTOMLValidationErrors.Errors = append(analyzerTOMLValidationErrors.Errors, ErrorMeta{ + Level: Error, + Field: "engine", + Message: fmt.Sprintf("Invalid build engine \"%s\". The following build engines are supported: [%s]", config.Build.Engine, supportedEnginesString), + }) + } + } + } + if len(analyzerTOMLValidationErrors.Errors) > 0 { + return &analyzerTOMLValidationErrors, nil + } + return nil, nil +} + +// Validates if the shortcode begins with `@` +func ValidateShortcode(fl validate.FieldLevel) bool { + return strings.HasPrefix(fl.Field().String(), "@") +} + +// Validates the supported engines. As of now, only docker is supported. +func ValidateEngine(fl validate.FieldLevel) bool { + for _, supportedEngine := range supportedEngines { + if fl.Field().String() == supportedEngine { + return true + } + } + return false +} diff --git a/analyzers/validator/issues.go b/analyzers/validator/issues.go new file mode 100644 index 00000000..38372d78 --- /dev/null +++ b/analyzers/validator/issues.go @@ -0,0 +1,72 @@ +package validator + +import ( + "fmt" + "strings" + + "github.com/deepsourcelabs/cli/types" + validate "github.com/go-playground/validator/v10" +) + +var supportedIssueCategories = []string{ + "bug-risk", + "antipattern", + "security", + "style", + "performance", + "doc", + "typecheck", + "coverage", +} + +func validateIssueTOML(config *types.AnalyzerIssue, issuePath string) *ValidationFailure { + issueValidationError := ValidationFailure{} + // Validate the issue data + v := validate.New() + v.RegisterValidation("category", ValidateCategory) + if err := v.Struct(config); err != nil { + // validationFailed = true + missingRequiredFields := getMissingRequiredFields(err, *config) + issueValidationError = ValidationFailure{ + File: issuePath, + } + + // TODO: Tweak this to accomodate other error types. + for _, missingField := range missingRequiredFields { + issueValidationError.Errors = append(issueValidationError.Errors, ErrorMeta{ + Level: Error, + Field: missingField, + Message: fmt.Sprintf("Missing required field: %s", missingField), + }, + ) + } + + // Check if the category is supported + errs := err.(validate.ValidationErrors) + for _, err := range errs { + if err.Tag() == "category" { + supportedCategories := strings.Join(supportedIssueCategories, ", ") + issueValidationError.Errors = append(issueValidationError.Errors, ErrorMeta{ + Level: Error, + Field: "category", + Message: fmt.Sprintf("Invalid issue category \"%s\". The following issue categories are supported: [%s]", config.Category, supportedCategories), + }) + } + } + } + + // Return nil if no validation errors found + if len(issueValidationError.Errors) > 0 { + return &issueValidationError + } + return nil +} + +func ValidateCategory(fl validate.FieldLevel) bool { + for _, supportedCategory := range supportedIssueCategories { + if fl.Field().String() == supportedCategory { + return true + } + } + return false +} diff --git a/analyzers/validator/testdata/analyzer1/.deepsource/analyzer/analyzer.toml b/analyzers/validator/testdata/analyzer1/.deepsource/analyzer/analyzer.toml new file mode 100644 index 00000000..858d23e6 --- /dev/null +++ b/analyzers/validator/testdata/analyzer1/.deepsource/analyzer/analyzer.toml @@ -0,0 +1,14 @@ +name = "todo comments checker" +shortcode = "@deepsource/demo-python" +description = "finds the todo comments in codebase." +tags = ["documentation","todo"] + +[environment_variables] + CODE_PATH = "/code" + +repository = "https://github.com/deepsourcelabs/2do-checker" +documentation = "https://deepsource.io/docs" +bug_tracker = "https://bugtracker.deepsource.io" + +[analysis] + command = "/app/todo-checker" diff --git a/analyzers/validator/testdata/analyzer1/.deepsource/analyzer/issues/PTC-W001.toml b/analyzers/validator/testdata/analyzer1/.deepsource/analyzer/issues/PTC-W001.toml new file mode 100644 index 00000000..01cdbe26 --- /dev/null +++ b/analyzers/validator/testdata/analyzer1/.deepsource/analyzer/issues/PTC-W001.toml @@ -0,0 +1,6 @@ +# shortcode = "PTC-W001" +# title = "Potential hardcoded credential detected" +# category = "security" +description = """ +Potential hardcoded credential. Sensitive information like password shouldn't be hardcoded. Use an environment variable instead. +""" diff --git a/analyzers/validator/testdata/analyzer1/.deepsource/analyzer/issues/PTC-W002.toml b/analyzers/validator/testdata/analyzer1/.deepsource/analyzer/issues/PTC-W002.toml new file mode 100644 index 00000000..f38fb4f2 --- /dev/null +++ b/analyzers/validator/testdata/analyzer1/.deepsource/analyzer/issues/PTC-W002.toml @@ -0,0 +1,5 @@ +# shortcode = "PTC-W001" +category = "security" +description = """ +Potential hardcoded credential. Sensitive information like password shouldn't be hardcoded. Use an environment variable instead. +""" diff --git a/analyzers/validator/testdata/analyzer2/.deepsource/analyzer/analyzer.toml b/analyzers/validator/testdata/analyzer2/.deepsource/analyzer/analyzer.toml new file mode 100644 index 00000000..0c6d13c6 --- /dev/null +++ b/analyzers/validator/testdata/analyzer2/.deepsource/analyzer/analyzer.toml @@ -0,0 +1,16 @@ +name = "Todo comments checker" +# shortcode = "@deepsource/demo-python" # test invalid config +description = "Finds the TODO comments in codebase." +# category = "doc" +tags = ["documentation","todo"] + +[environment_variables] + CODE_PATH = "/code" + +[urls] + source = "https://github.com/deepsourcelabs/2do-checker" + documentation = "https://deepsource.io/docs" + bug_tracker = "https://bugtracker.deepsource.io" + +[analysis] + command = "/app/todo-checker" diff --git a/analyzers/validator/testdata/analyzer2/.deepsource/analyzer/issues/JS-001.toml b/analyzers/validator/testdata/analyzer2/.deepsource/analyzer/issues/JS-001.toml new file mode 100644 index 00000000..d7272ebd --- /dev/null +++ b/analyzers/validator/testdata/analyzer2/.deepsource/analyzer/issues/JS-001.toml @@ -0,0 +1,4 @@ +category = "security" +description = """ +Potential hardcoded credential. Sensitive information like password shouldn't be hardcoded. Use an environment variable instead. +""" diff --git a/analyzers/validator/testdata/analyzer2/.deepsource/analyzer/issues/JS-002.toml b/analyzers/validator/testdata/analyzer2/.deepsource/analyzer/issues/JS-002.toml new file mode 100644 index 00000000..e69de29b diff --git a/analyzers/validator/testdata/analyzer3/.deepsource/analyzer/analyzer.toml b/analyzers/validator/testdata/analyzer3/.deepsource/analyzer/analyzer.toml new file mode 100644 index 00000000..3e95d2d9 --- /dev/null +++ b/analyzers/validator/testdata/analyzer3/.deepsource/analyzer/analyzer.toml @@ -0,0 +1,14 @@ +name = "Todo comments checker" +shortcode = "deepsource/demo-python" +description = "Finds the TODO comments in codebase." +tags = ["documentation","todo"] + +[environment_variables] + CODE_PATH = "/code" + +repository = "https://github.com/deepsourcelabs/2do-checker" +documentation = "https://deepsource.io/docs" +bug_tracker = "https://bugtracker.deepsource.io" + +[analysis] + command = "/app/todo-checker" diff --git a/analyzers/validator/utils.go b/analyzers/validator/utils.go new file mode 100644 index 00000000..7fa29172 --- /dev/null +++ b/analyzers/validator/utils.go @@ -0,0 +1,103 @@ +package validator + +import ( + "errors" + "fmt" + "path" + "reflect" + "strings" + + validate "github.com/go-playground/validator/v10" + "github.com/pelletier/go-toml/v2" +) + +// Returns the list of required fields from the error message returned by the `go-playground/validator` library +func getMissingRequiredFields(err error, config interface{}) []string { + missingRequiredFields := []string{} + errs := err.(validate.ValidationErrors) + for _, err := range errs { + if err.Tag() == "required" { + c := reflect.ValueOf(config) + for i := 0; i < c.Type().NumField(); i++ { + if err.Field() == c.Type().Field(i).Name { + missingRequiredFields = append(missingRequiredFields, c.Type().Field(i).Tag.Get("toml")) + } + } + } + } + return missingRequiredFields +} + +// Handle decoding errors reported by go-toml +func handleTOMLDecodeErrors(err error, filePath string) *ValidationFailure { + var usefulResponse, expectedType, receivedType, fieldName, decodeErrorMessage string + + // Get the DecodeError exported by go-toml + // Ref: https://pkg.go.dev/github.com/pelletier/go-toml/v2#DecodeError + var decodeErr *toml.DecodeError + if !errors.As(err, &decodeErr) { + decodeErrorMessage = err.Error() + + // Handle strict mode error when some alien fields are added in the user configured TOML + if strings.HasPrefix(err.Error(), "strict mode") { + decodeErrorMessage = fmt.Sprintf("Failed to parse %s. Invalid fields detected.", path.Base(filePath)) + } + validationError := ValidationFailure{ + File: filePath, + Errors: []ErrorMeta{ + { + Level: DecodeErr, + Field: "", + Message: decodeErrorMessage, + }, + }, + } + return &validationError + } + + /* ================================================= + * Extract the data about the decoding failure and return + * a validation failure response + * ================================================= */ + + errorMessage := decodeErr.Error() + // Error case 1: `toml: cannot decode TOML integer into struct field types.AnalyzerTOML.Name of type string"` + if strings.HasPrefix(errorMessage, "toml: cannot decode TOML") { + + usefulResponse = strings.TrimPrefix(errorMessage, "toml: cannot decode TOML ") + responseArray := strings.Split(usefulResponse, " ") + + expectedType = responseArray[len(responseArray)-1] + receivedType = responseArray[0] + fieldData := responseArray[len(responseArray)-4] + index := strings.LastIndex(fieldData, ".") + fieldName = strings.ToLower(fieldData[index:]) + // Framing the decoding failure error message + decodeErrorMessage = fmt.Sprintf("expected the field \"%s\" of type %s. Got %s.", fieldName, expectedType, receivedType) + + } else if strings.HasPrefix(errorMessage, "toml: cannot store TOML") { + + // Error case 2: `toml: cannot store TOML string into a Go slice` + usefulResponse = strings.TrimPrefix(errorMessage, "toml: cannot store TOML ") + responseArray := strings.Split(usefulResponse, " ") + + expectedType = responseArray[len(responseArray)-1] + receivedType = responseArray[0] + decodeErrorMessage = fmt.Sprintf("expected type for one of the fields : %s. Received: %s.", expectedType, receivedType) + } else { + decodeErrorMessage = errorMessage + fieldName = "" + } + + validationError := ValidationFailure{ + File: filePath, + Errors: []ErrorMeta{ + { + Level: DecodeErr, + Field: fieldName, + Message: decodeErrorMessage, + }, + }, + } + return &validationError +} diff --git a/analyzers/validator/validator.go b/analyzers/validator/validator.go new file mode 100644 index 00000000..d88d6611 --- /dev/null +++ b/analyzers/validator/validator.go @@ -0,0 +1,157 @@ +package validator + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/deepsourcelabs/cli/types" + "github.com/pelletier/go-toml/v2" +) + +/* ================================================== + * Types used to report validation failure error data + * ================================================== */ +type ErrLevel int + +const ( + DecodeErr ErrLevel = iota + Error + Warning + Information +) + +type ErrorMeta struct { + Level ErrLevel + Field string + Message string +} + +type ValidationFailure struct { + File string + Errors []ErrorMeta +} + +// CheckForAnalyzerConfig receives the path of the `analyzer.toml` and issue descriptions and +// checks if they are actually present +func CheckForAnalyzerConfig(analyzerTOMLPath, issuesDirectoryPath string) (err error) { + // Check if `analyzer.toml` is present in `.deepsource/analyzer` folder + if _, err := os.Stat(analyzerTOMLPath); err != nil { + if errors.Is(err, os.ErrNotExist) { + return errors.New("the analyzer.toml file doesn't exist") + } + } + + // Check if `issues/` directory is present in `.deepsource/analyzer` folder and is not empty. + if _, err := os.Stat(issuesDirectoryPath); err != nil { + if errors.Is(err, os.ErrNotExist) { + return errors.New("the issue descriptions directory doesn't exist") + } + } + + // Check if there are any toml files in the `issues/` directory + files, err := ioutil.ReadDir(issuesDirectoryPath) + if err != nil { + return fmt.Errorf("failed to read the files present in the issues directory at %s", issuesDirectoryPath) + } + + // Check if its an empty directory + if len(files) < 1 { + return fmt.Errorf("found 0 issues configured in the issues directory at %s", issuesDirectoryPath) + } + + tomlPresent := false + // Check if there are TOML files configured in the issues/ directory + for _, file := range files { + if strings.HasSuffix(file.Name(), ".toml") { + tomlPresent = true + break + } + } + if !tomlPresent { + return fmt.Errorf("found no toml files in the issues directory at %s", issuesDirectoryPath) + } + + return +} + +// ValidateAnalyzerTOML receives the path of analyzer.toml and reads as well as validates it for +// the type checks, required fields etc. Returns the analyzer.toml content and the validation failures +// if any in the form of ValidationFailure struct. +func ValidateAnalyzerTOML(analyzerTOMLPath string) (*types.AnalyzerTOML, *ValidationFailure, error) { + config := types.AnalyzerTOML{} + + // Read the contents of analyzer.toml file + analyzerTOMLContent, err := ioutil.ReadFile(analyzerTOMLPath) + if err != nil { + return nil, nil, errors.New("failed to read analyzer.toml file") + } + + // Decode the TOML into the struct + d := toml.NewDecoder(bytes.NewBuffer(analyzerTOMLContent)) + d.DisallowUnknownFields() + if err := d.Decode(&config); err != nil { + decodeErrorResp := handleTOMLDecodeErrors(err, analyzerTOMLPath) + if decodeErrorResp != nil { + return nil, decodeErrorResp, nil + } + return nil, nil, err + } + + // Validate the analyzer.toml fields for default/custom type checks, required fields + analyzerTOMLValidationErrors, err := validateAnalyzerTOMLFields(&config, analyzerTOMLPath) + if err != nil { + return nil, nil, err + } + return &config, analyzerTOMLValidationErrors, nil +} + +// ValidateIssueDescriptions receives the path of issues directory for reading and validating them +// for type checks and required fields. Returns an array of ValidationFailure struct containing +// validation errors for each issue TOML file. +func ValidateIssueDescriptions(issuesDirectoryPath string) (*[]ValidationFailure, error) { + issueValidationErrors := []ValidationFailure{} + + // TODO: List only TOML files here + issuesList, err := ioutil.ReadDir(issuesDirectoryPath) + if err != nil { + return nil, err + } + + // Iterate over the issues one by one, read and decode them, validate the fields and return the + // validation result. + for _, issuePath := range issuesList { + // Set the issue shortcode as the filename + config := types.AnalyzerIssue{} + config.Shortcode = strings.TrimSuffix(issuePath.Name(), ".toml") + + // Read the contents of issue toml file + issueTOMLContent, err := ioutil.ReadFile(filepath.Join(issuesDirectoryPath, issuePath.Name())) + if err != nil { + return nil, fmt.Errorf("failed to read file: %s", filepath.Join(issuesDirectoryPath, issuePath.Name())) + } + + // Decode the TOML content into the AnalyzerIssue struct object + d := toml.NewDecoder(bytes.NewBuffer(issueTOMLContent)) + d.DisallowUnknownFields() + if err = d.Decode(&config); err != nil { + decodeErrorResp := handleTOMLDecodeErrors(err, issuePath.Name()) + if decodeErrorResp != nil { + // Append the error to the array created for reporting issue validation errors and return it + issueValidationErrors = append(issueValidationErrors, *decodeErrorResp) + continue + } + } + + // Validate the analyzer.toml fields for default/custom type checks, required fields + issueValidationError := validateIssueTOML(&config, issuePath.Name()) + if issueValidationError != nil { + issueValidationErrors = append(issueValidationErrors, *issueValidationError) + } + } + return &issueValidationErrors, nil +} diff --git a/analyzers/validator/validator_test.go b/analyzers/validator/validator_test.go new file mode 100644 index 00000000..26104e5b --- /dev/null +++ b/analyzers/validator/validator_test.go @@ -0,0 +1,65 @@ +package validator + +import ( + "testing" +) + +func TestValidateAnalyzerToml(t *testing.T) { + type test struct { + tomlPath string + validTOML bool + } + + tests := []test{ + { + tomlPath: "./testdata/analyzer1/.deepsource/analyzer/analyzer.toml", + validTOML: true, + }, + { + tomlPath: "./testdata/analyzer2/.deepsource/analyzer/analyzer.toml", + validTOML: false, + }, + { + tomlPath: "./testdata/analyzer3/.deepsource/analyzer/analyzer.toml", + validTOML: false, + }, + } + + for _, tc := range tests { + _, validationErr, _ := ValidateAnalyzerTOML(tc.tomlPath) + if validationErr != nil && tc.validTOML { + t.Errorf("Expected valid TOML for %s. Got: %v", tc.tomlPath, validationErr) + } + if validationErr == nil && !tc.validTOML { + t.Errorf("Expected invalid TOML for %s. Got: %v", tc.tomlPath, validationErr) + } + } +} + +func TestValidateIssueDescriptions(t *testing.T) { + type test struct { + issuesDirPath string + validIssues bool + } + + tests := []test{ + { + issuesDirPath: "./testdata/analyzer1/.deepsource/analyzer/issues/", + validIssues: false, + }, + { + issuesDirPath: "./testdata/analyzer2/.deepsource/analyzer/issues/", + validIssues: false, + }, + } + + for _, tc := range tests { + validationErrors, err := ValidateIssueDescriptions(tc.issuesDirPath) + if validationErrors != nil && tc.validIssues { + t.Errorf("Expected valid TOML for %s. Got: %v", tc.issuesDirPath, err) + } + if validationErrors == nil && !tc.validIssues { + t.Errorf("Expected invalid TOML for %s. Got: %v", tc.issuesDirPath, err) + } + } +} diff --git a/command/analyzer/analyzer.go b/command/analyzer/analyzer.go new file mode 100644 index 00000000..b5d9dd8f --- /dev/null +++ b/command/analyzer/analyzer.go @@ -0,0 +1,26 @@ +package analyzer + +import ( + "github.com/spf13/cobra" + + dryrun "github.com/deepsourcelabs/cli/command/analyzer/dryrun" + initialize "github.com/deepsourcelabs/cli/command/analyzer/initialize" + push "github.com/deepsourcelabs/cli/command/analyzer/push" + verify "github.com/deepsourcelabs/cli/command/analyzer/verify" +) + +// Options holds the metadata. +type Options struct{} + +// NewCmdVersion returns the current version of cli being used +func NewCmdAnalyzer() *cobra.Command { + cmd := &cobra.Command{ + Use: "analyzer", + Short: "Operations related to DeepSource Analyzers", + } + cmd.AddCommand(dryrun.NewCmdAnalyzerRun()) + cmd.AddCommand(verify.NewCmdAnalyzerVerify()) + cmd.AddCommand(initialize.NewCmdAnalyzerInit()) + cmd.AddCommand(push.NewCmdAnalyzerPush()) + return cmd +} diff --git a/command/analyzer/dryrun/client.go b/command/analyzer/dryrun/client.go new file mode 100644 index 00000000..de015f5f --- /dev/null +++ b/command/analyzer/dryrun/client.go @@ -0,0 +1,46 @@ +package dryrun + +import ( + "strings" + + "github.com/deepsourcelabs/cli/analyzers/backend/docker" + "github.com/deepsourcelabs/cli/analyzers/config" +) + +// Reads the analyzer.toml data and environment variables and creates a docker client +// to be used in the docker related ops by the run command +func (a *AnalyzerDryRun) createDockerClient() error { + // Get the Analyzer.toml contents + analyzerTOMLData, err := config.GetAnalyzerTOML() + if err != nil { + return err + } + + // Fetch environment variables set by the user + fetchEnvironmentVariables() + + // Extracting the docker file and path details + dockerFilePath, dockerFileName := docker.GetDockerImageDetails(analyzerTOMLData) + analyzerName := strings.Split(dockerFileName, "/")[1] + + /* ====================================== */ + // Create a Docker Client + /* ====================================== */ + + a.Client = &docker.DockerClient{ + ImageName: dockerFileName, + ImagePlatform: a.DockerImagePlatform, + ImageTag: docker.GenerateImageVersion(7), + ContainerName: analyzerName + "-" + docker.GenerateImageVersion(7), + DockerfilePath: dockerFilePath, + AnalysisOpts: docker.AnalysisParams{ + AnalyzerName: analyzerTOMLData.Name, + AnalyzerShortcode: analyzerTOMLData.Shortcode, + AnalysisCommand: analyzerTOMLData.Analysis.Command, + ContainerCodePath: containerCodePath, + ContainerToolBoxPath: containerToolBoxPath, + AnalysisResultsFilename: analysisResultsName + analysisResultsExt, + }, + } + return nil +} diff --git a/command/analyzer/dryrun/config.go b/command/analyzer/dryrun/config.go new file mode 100644 index 00000000..71587b7d --- /dev/null +++ b/command/analyzer/dryrun/config.go @@ -0,0 +1,42 @@ +package dryrun + +import ( + "encoding/json" + "os" + "path" + + analysis_config "github.com/deepsourcelabs/cli/analysis/config" +) + +// Prepares the analysis config and writes it to TOOLBOX_PATH +func (a *AnalyzerDryRun) prepareAnalysisConfig() (err error) { + /* Prepare the analysis_config.json here and mount into the container at `TOOLBOX_PATH/analysis_config.json` + * The analysis_config.json will have path prepended with the CODE_PATH of the container and not local CODE_PATH */ + analysisRun := analysis_config.AnalysisRun{ + AnalyzerName: a.Client.AnalysisOpts.AnalyzerName, + LocalCodePath: a.Client.AnalysisOpts.HostCodePath, + ContainerCodePath: containerCodePath, + } + + if a.AnalysisConfig, err = analysisRun.ConfigureAnalysis(); err != nil { + return err + } + return nil +} + +// Writes the analysis_config.json into a temporary directory which shall be mounted as TOOLBOX directory in the container +func (a *AnalyzerDryRun) writeAnalysisConfig() (err error) { + // Modify the paths of analysis_config.json file to use the container based CODE_PATH instead + // of the local CODE_PATH + modifyAnalysisConfigFilepaths(a.AnalysisConfig, a.Client.AnalysisOpts.HostCodePath, a.Client.AnalysisOpts.ContainerCodePath) + + // Marshal the analysis_config data into JSON + analysisConfigJSON, err := json.Marshal(a.AnalysisConfig) + if err != nil { + return err + } + a.Client.AnalysisOpts.AnalysisConfigPath = path.Join(a.TempToolBoxDirectory, analysisConfigName+analysisConfigExt) + + // Create a temporary directory + return os.WriteFile(path.Join(a.TempToolBoxDirectory, analysisConfigName+analysisConfigExt), analysisConfigJSON, 0o644) +} diff --git a/command/analyzer/dryrun/process.go b/command/analyzer/dryrun/process.go new file mode 100644 index 00000000..81a2b922 --- /dev/null +++ b/command/analyzer/dryrun/process.go @@ -0,0 +1,42 @@ +package dryrun + +import ( + "encoding/json" + + "github.com/deepsourcelabs/cli/analysis/processor" + "github.com/deepsourcelabs/cli/analysis/processor/processors" + "github.com/deepsourcelabs/cli/types" +) + +// processAnalyzerReport processes the analysis report generated by the Analyzer +func (a *AnalyzerDryRun) processAnalyzerReport(reportBytes []byte) (types.AnalysisResult, error) { + report := types.AnalyzerReport{} + + // Creating instances of skipcq and source code highlighting processors + skipCQProcessor := processors.ProcSkipCQ{} + sourceCodeHighlightingProcessor := processors.ProcSourceCodeLoad{} + + // Initializing the processors using the IProcessor interface provided by the `processor` package + var skip_cq, source_code_load processor.IProcessor + + // Assigning the instances to the IProcessor interface + skip_cq = skipCQProcessor + source_code_load = sourceCodeHighlightingProcessor + + // Start the processors workflow + processor := processor.ReportProcessor{ + LocalSourcePath: a.SourcePath, + ContainerCodePath: a.Client.AnalysisOpts.ContainerCodePath, + Processors: []processor.IProcessor{source_code_load, skip_cq}, + } + + // Generate the silencers regexMap. + processors.GenerateSilencersRegexMap() + if err := json.Unmarshal(reportBytes, &report); err != nil { + return types.AnalysisResult{}, err + } + + processor.Report = report + analysisResult := processor.Process() + return analysisResult, nil +} diff --git a/command/analyzer/dryrun/render/category.go b/command/analyzer/dryrun/render/category.go new file mode 100644 index 00000000..82c6f573 --- /dev/null +++ b/command/analyzer/dryrun/render/category.go @@ -0,0 +1,26 @@ +package render + +// fetchIssueCategoryData creates a map of issue category to issue occurences count of that category. +func (r *ResultRenderOpts) fetchIssueCategoryData() { + // Iterate over the map and then keep adding the issue counts. + issueCategoryMap := make(map[string]int) + + // Creating a map of issue categories present to their count. + for _, occurenceData := range r.AnalysisResultData.IssuesOccurenceMap { + if _, ok := issueCategoryMap[occurenceData.IssueMeta.Category]; !ok { + issueCategoryMap[occurenceData.IssueMeta.Category] = len(occurenceData.Occurences) + continue + } + issueCategoryMap[occurenceData.IssueMeta.Category] = issueCategoryMap[occurenceData.IssueMeta.Category] + len(occurenceData.Occurences) + } + + // Add remaining categories to the map other than what are reported in the issues by the Analyzer since + // need to render all the categories. + for categoryShortcode := range r.IssueCategoryNameMap { + if _, ok := issueCategoryMap[categoryShortcode]; !ok { + issueCategoryMap[categoryShortcode] = 0 + continue + } + } + r.AnalysisResultData.IssueCategoryCountMap = issueCategoryMap +} diff --git a/command/analyzer/dryrun/render/metrics.go b/command/analyzer/dryrun/render/metrics.go new file mode 100644 index 00000000..25fcf920 --- /dev/null +++ b/command/analyzer/dryrun/render/metrics.go @@ -0,0 +1,14 @@ +package render + +// fetchIssueMetricsData fetches the metrics data to be rendered. +func (r *ResultRenderOpts) fetchIssueMetricsData() { + metricsMap := make(map[string]float64) + for _, metric := range r.AnalysisResultData.AnalysisResult.Metrics { + if _, ok := r.MetricNameMap[metric.MetricCode]; !ok { + continue + } + metricName := r.MetricNameMap[metric.MetricCode] + metricsMap[metricName] = metric.Namespaces[0].Value + } + r.AnalysisResultData.MetricsMap = metricsMap +} diff --git a/command/analyzer/dryrun/render/occurences.go b/command/analyzer/dryrun/render/occurences.go new file mode 100644 index 00000000..b91ca293 --- /dev/null +++ b/command/analyzer/dryrun/render/occurences.go @@ -0,0 +1,87 @@ +package render + +import ( + "fmt" + "os" + "strings" +) + +// fetchIssueOccurencesData collects all the occurence related data. +func (r *ResultRenderOpts) fetchIssueOccurencesData(cwd string) { + // Create a map of occurences of the issues. + issueOccurenceMap := make(map[string]OccurenceData) + + // Iterate over the analysis result issues. + for _, issue := range r.AnalysisResultData.AnalysisResult.Issues { + currentOccurence := OccurenceData{} + + // Fix path of the issues(remove cwd prefix from them). + issue.Location.Path = strings.TrimPrefix(issue.Location.Path, r.AnalysisResultData.SourcePath+string(os.PathSeparator)) + + if _, ok := issueOccurenceMap[issue.IssueCode]; !ok { + // Fetch issue meta for the issue code raised. + issueMeta, err := getIssueMeta(cwd, issue.IssueCode) + if err != nil { + fmt.Println("Couldn't resolve issue meta for the issue:", issue.IssueCode) + continue + } + currentOccurence = OccurenceData{ + IssueMeta: issueMeta, + } + currentOccurence.Occurences = append(currentOccurence.Occurences, issue) + currentOccurence.Files = append(currentOccurence.Files, issue.Location.Path) + issueOccurenceMap[issue.IssueCode] = currentOccurence + continue + } + + // Get past occurences and append to it since maps don't allow direct append to a slice value. + pastOccurences := issueOccurenceMap[issue.IssueCode] + currentOccurence.IssueMeta = pastOccurences.IssueMeta + currentOccurence.Occurences = append(pastOccurences.Occurences, issue) + currentOccurence.Files = append(pastOccurences.Files, issue.Location.Path) + issueOccurenceMap[issue.IssueCode] = currentOccurence + } + + // Remove duplicates from the files array. + for issueCode, occurenceData := range issueOccurenceMap { + filesMap := make(map[string]int, 0) + uniqueFiles := make([]string, 0) + + // Setting the map value to 1 for the files in order to identify unique files. + for _, file := range occurenceData.Files { + filesMap[file] = 1 + } + + // Creating a slice of unique files. + for file := range filesMap { + uniqueFiles = append(uniqueFiles, file) + } + + // Assign the unique files slice to the map. + issueOccurence := issueOccurenceMap[issueCode] + issueOccurence.Files = uniqueFiles + } + + // Create the files information string. + for issueCode, occurenceData := range issueOccurenceMap { + switch len(occurenceData.Files) - 1 { + case 0: + occurenceData.FilesInfo = fmt.Sprintf("Found in %s", occurenceData.Files[0]) + case 1: + occurenceData.FilesInfo = fmt.Sprintf("Found in %s and %d other file", occurenceData.Files[0], len(occurenceData.Files)-1) + default: + occurenceData.FilesInfo = fmt.Sprintf("Found in %s and %d other files", occurenceData.Files[0], len(occurenceData.Files)-1) + } + issueOccurenceMap[issueCode] = occurenceData + } + // Assign the value of local IssueOccurenceMap to global struct field. + r.AnalysisResultData.IssuesOccurenceMap = issueOccurenceMap + + // Find out total number of occurences of all the issues. + for _, v := range issueOccurenceMap { + r.AnalysisResultData.TotalOccurences = r.AnalysisResultData.TotalOccurences + len(v.Occurences) + } + + // Finds the unique issues count(the length of the occurences map since its mapped by issue codes which are unique). + r.AnalysisResultData.UniqueIssuesCount = len(r.AnalysisResultData.IssuesOccurenceMap) +} diff --git a/command/analyzer/dryrun/render/render.go b/command/analyzer/dryrun/render/render.go new file mode 100644 index 00000000..3eef16d8 --- /dev/null +++ b/command/analyzer/dryrun/render/render.go @@ -0,0 +1,88 @@ +package render + +import ( + "context" + "embed" + "fmt" + "html/template" + "io/fs" + "net/http" + "os" + "os/signal" + "time" + + "github.com/cli/browser" + "github.com/pterm/pterm" +) + +/* Embedding the required files in views folder + into the binary using go embed */ + +//go:embed views/*.html views/assets +var tmplFS embed.FS + +// renderResultsOnBrowser renders the results on the browser through a local server, +// go template and an awesome frontend. +func (r *ResultRenderOpts) RenderResultsOnBrowser(server IRenderServer) (err error) { + // Collect all other data to be rendered. + r.collectResultToBeRendered() + + // In order to serve the static css files, this creates a handler to serve any static assets stored under + // `views/` at `/static/assets/*`. + fsys, err := fs.Sub(tmplFS, "views") + if err != nil { + return err + } + + // Parse the HTML templates. + r.Template = template.Must(template.ParseFS(tmplFS, "views/*.html")) + + // Define the routes using echo and start the server. + r.EchoServer = server.GetEchoContext() + server.DeclareRoutes(http.FS((fsys))) + serverPort := getServerPort() + + // Spawn the server in a goroutine. + go func() { + if err := r.EchoServer.Start(fmt.Sprintf(":%s", serverPort)); err != nil && err != http.ErrServerClosed { + r.EchoServer.Logger.Fatal("Shutting down the server") + } + }() + pterm.Success.Printf("Analysis results live at http://localhost:%s..\n", serverPort) + + // Having received the user code, open the browser at the localhost. + browser.OpenURL(fmt.Sprintf("http://localhost:%s", serverPort)) + + // Wait for interrupt signal to gracefully shutdown the server with a timeout of 10 seconds. + // Use a buffered channel to avoid missing signals as recommended for signal.Notify + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + return r.EchoServer.Shutdown(ctx) +} + +// collectResultToBeRendered formats all the result received after post-processing and then adds the +// extra data required for rendering on the browser +func (r *ResultRenderOpts) collectResultToBeRendered() { + cwd, _ := os.Getwd() + + // Fetch the run summary data. + r.Summary.RunDuration, r.Summary.TimeSinceRun = fetchRunSummary(r.Summary.AnalysisStartTime, r.Summary.AnalysisEndTime) + + // Inject the Analyzer VCS information. + r.VCSInfo.Branch, r.VCSInfo.CommitSHA = fetchVCSDetails(cwd) + + // Fetch the data as to status of Analyzer w.r.t the latest version/tag. + r.VCSInfo.VersionDiff = fetchAnalyzerVCSData(cwd) + + // Get occurence data. + r.fetchIssueOccurencesData(cwd) + + // Get the category data. + r.fetchIssueCategoryData() + + // Fetch metrics data. + r.fetchIssueMetricsData() +} diff --git a/command/analyzer/dryrun/render/route.go b/command/analyzer/dryrun/render/route.go new file mode 100644 index 00000000..dde9008b --- /dev/null +++ b/command/analyzer/dryrun/render/route.go @@ -0,0 +1,78 @@ +package render + +import ( + "fmt" + "html/template" + "net/http" + + "github.com/labstack/echo/v4" +) + +type IRenderServer interface { + GetEchoContext() *echo.Echo + DeclareRoutes(http.FileSystem) +} + +/* Declared Routes: + * / + * /issues + * /issue/{issue_code}/occurences + * /issues?category=all + * /issues?category={issue_category} */ + +// getEchoContext returns a new Echo server instance. +func (*ResultRenderOpts) GetEchoContext() *echo.Echo { + e := echo.New() + e.HideBanner = true + return e +} + +// declareRoutes declares routes for various incoming requests to the Analyzer dry run local server. +func (r *ResultRenderOpts) DeclareRoutes(staticFS http.FileSystem) { + // Issues page containing all the reported issues. + r.EchoServer.GET("/", r.IssuesHandler) + r.EchoServer.GET("/issues", r.IssuesHandler) + + // Handle serving static assets. + assetHandler := http.FileServer(staticFS) + r.EchoServer.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler))) + + // Handle showing issues for a certain category. + r.EchoServer.GET("/issue/:issue_code/occurences", r.IssueOccurencesHandler) +} + +// IssuesHandler handles serving the list of issues reported +func (r *ResultRenderOpts) IssuesHandler(c echo.Context) error { + // Check URL query parameters + qParams := c.QueryParams() + + if qParams.Has("category") { + r.SelectedCategory = qParams.Get("category") + } else { + r.SelectedCategory = "all" + } + + err := r.Template.ExecuteTemplate(c.Response().Writer, "index.html", *r) + if err != nil { + fmt.Println(err) + return c.String(http.StatusInternalServerError, err.Error()) + } + return c.NoContent(http.StatusOK) +} + +// IssuesOccurencesHandler handles serving the issue occurences. +func (r *ResultRenderOpts) IssueOccurencesHandler(c echo.Context) error { + // Fetch the issue code from URI. + r.SelectedIssueCode = c.Param("issue_code") + + issueOccurences := r.AnalysisResultData.IssuesOccurenceMap[r.SelectedIssueCode] + for _, occurence := range issueOccurences.Occurences { + r.AnalysisResultData.RenderedSourceCode = append(r.AnalysisResultData.RenderedSourceCode, template.HTML(occurence.ProcessedData.SourceCode.Rendered)) // skipcq: GSC-G203 + } + err := r.Template.ExecuteTemplate(c.Response().Writer, "occurence.html", *r) + if err != nil { + fmt.Println(err) + return c.String(http.StatusInternalServerError, err.Error()) + } + return c.NoContent(http.StatusOK) +} diff --git a/command/analyzer/dryrun/render/types.go b/command/analyzer/dryrun/render/types.go new file mode 100644 index 00000000..e6340fb9 --- /dev/null +++ b/command/analyzer/dryrun/render/types.go @@ -0,0 +1,54 @@ +package render + +import ( + "html/template" + "time" + + "github.com/deepsourcelabs/cli/types" + "github.com/labstack/echo/v4" +) + +type RunSummary struct { + RunDuration string // Time taken to complete analysis. + TimeSinceRun string // Time elapsed since the completion of the analysis run. + AnalysisStartTime time.Time + AnalysisEndTime time.Time +} + +type VCSInfo struct { + Branch string // VCS branch of the Analyzer. + CommitSHA string // The latest commit SHA of the Analyzer. + VersionDiff string // The string specifying the status of Analyzer w.r.t previous version. +} + +type OccurenceData struct { + IssueMeta types.AnalyzerIssue // Contains the data stored in issue TOMLs for the respective issue. + Files []string // Files where this issue has been reported. + FilesInfo string // The string containing the data of which files the issue has been reported in. + Occurences []types.Issue // The slice of occurences for a certain issue code. +} + +type ResultData struct { + UniqueIssuesCount int // The unique issues count. + TotalOccurences int // Total issues reported by the Analyzer. + SourcePath string // The path where the source code to be analyzer is stored. + IssuesOccurenceMap map[string]OccurenceData // The map of issue code to its occurences data. + IssueCategoryCountMap map[string]int // The map of issue category to the count of the issues of that category. + AnalysisResult types.AnalysisResult // The analysis result post running processors. + MetricsMap map[string]float64 // The map of metric names to their values. + RenderedSourceCode []template.HTML // The slice containing the source code snippets for each occurence. +} + +type ResultRenderOpts struct { + EchoServer *echo.Echo // The Echo server instance to run the renderer server. + Template *template.Template // The go template field so that it can be accessible in `route.go` as well. + PageTitle string // The title of the HTML page. + AnalyzerShortcode string // The shortcode of the Analyzer. + VCSInfo VCSInfo // The VCS information of the Analyzer. + Summary RunSummary // The run summary. + AnalysisResultData ResultData // The analysis result data. + SelectedIssueCode string // The field used to recognize which issue code the user has clicked on to check its occurences. + SelectedCategory string // The field used to recognize which category the user has clicked to filter the issues based on it. + IssueCategoryNameMap map[string]string // The map used to route category names to their codes. Eg: `Documentation`->`doc`. + MetricNameMap map[string]string // The map of metrics shortcodes with their names. +} diff --git a/command/analyzer/dryrun/render/utils.go b/command/analyzer/dryrun/render/utils.go new file mode 100644 index 00000000..9b30fa93 --- /dev/null +++ b/command/analyzer/dryrun/render/utils.go @@ -0,0 +1,217 @@ +package render + +import ( + "bytes" + "fmt" + "html/template" + "net" + "os" + "path/filepath" + "strings" + "time" + + "github.com/deepsourcelabs/cli/types" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/hako/durafmt" + "github.com/pelletier/go-toml/v2" + "github.com/yuin/goldmark" +) + +// fetchRunSummary fetches the data for the run summary section involving the time since latest run +// and the run duration. +func fetchRunSummary(startTime, endTime time.Time) (string, string) { + // Find the time elapsed since the analysis run. + timeSinceRun := fmt.Sprintf("%s ago", durafmt.Parse(time.Since(startTime)).LimitFirstN(1).String()) + + // Find the run duration i.e. the time between the analysis start and end time. + runDuration := durafmt.Parse(endTime.Sub(startTime)).LimitFirstN(1).String() + return runDuration, timeSinceRun +} + +// fetchHeadManually fetches the latest commit hash using the command `git rev-parse HEAD` +// through go-git. +func fetchHeadManually(directoryPath string) (string, error) { + gitOpts := &git.PlainOpenOptions{ + DetectDotGit: true, + } + + // Open a new repository targeting the given path (the .git folder) + repo, err := git.PlainOpenWithOptions(directoryPath, gitOpts) + if err != nil { + return "", err + } + + // Resolve revision into a sha1 commit + commitHash, err := repo.ResolveRevision(plumbing.Revision("HEAD")) + if err != nil { + return "", err + } + return commitHash.String(), nil +} + +// fetchVCSDetails fetches the VCS details to be shown on the dashboard. +func fetchVCSDetails(dir string) (string, string) { + branch := "" + latestCommitHash := "" + + repo, err := git.PlainOpen(dir) + if err != nil { + return "", "" + } + + // Fetch the repository HEAD reference. + headRef, _ := repo.Head() + + // Fetch the commit SHA of the latest commit + latestCommitHash, _ = fetchHeadManually(dir) + + // Fetch the branch name. + branchData := headRef.String() + branch = branchData[strings.LastIndex(branchData, "/")+1:] + + return branch, latestCommitHash[:7] +} + +// fetchAnalyzerVCSDetails fetches Analyzer VCS details like how many commits is the Analyzer +// ahead of the latest git tag. +func fetchAnalyzerVCSData(dir string) string { + // Open the Analyzer's git directory. + repo, err := git.PlainOpenWithOptions(dir, &git.PlainOpenOptions{ + DetectDotGit: true, + }) + if err != nil { + fmt.Println(err) + return "" + } + + // Fetch the repo tags list. + tagReferences, _ := repo.Tags() + currentTagRef := []string{} + if err = tagReferences.ForEach(func(t *plumbing.Reference) error { + currentTagRef = append(currentTagRef, t.Name().String()) + return nil + }); err != nil { + fmt.Println(err) + return "" + } + + // currentTagRef slice is empty if there are not tags in the Analyzer git directory. + if len(currentTagRef) == 0 { + return "" + } + + // Convert refs/tags/v0.2.1 -> v0.2.1 + latestTag := strings.TrimPrefix(currentTagRef[len(currentTagRef)-1], "refs/tags/") + + // Fetch the iterator to the tag objects latest git tag. + tagsIter, _ := repo.TagObjects() + + // Fetch the current tag and the commit pointed by the current tag. + currentTag := "" + var currentCommitSHA plumbing.Hash + var currentTagPushTime time.Time + if err = tagsIter.ForEach(func(t *object.Tag) (err error) { + if t.Name != latestTag { + return nil + } + currentTag = t.Name + commit, err := t.Commit() + if err != nil { + fmt.Println(err) + return err + } + + // Finds the hash of the commit and the timestamp of when the commit was pushed. + currentCommitSHA = commit.Hash + currentTagPushTime = commit.Author.When + return nil + }); err != nil { + fmt.Println(err) + return "" + } + + // Retrieve the commit history from the current tag. + commitIter, err := repo.Log(&git.LogOptions{ + Order: git.LogOrderCommitterTime, + Since: ¤tTagPushTime, + }) + if err != nil { + fmt.Println(err) + return "" + } + + // Just iterates over the commits and finds the count of how many commits have been + // made since the current git tag. + commitsSinceCurrentTag := 0 + if err = commitIter.ForEach(func(c *object.Commit) error { + if c.Hash == currentCommitSHA { + return nil + } + commitsSinceCurrentTag++ + return nil + }); err != nil { + fmt.Println(err) + return "" + } + + // Return the Analyzer diff info. + switch commitsSinceCurrentTag { + case 0: + return fmt.Sprintf("This Analyzer is up to date with %s", currentTag) + case 1: + return fmt.Sprintf("This Analyzer is %d commit ahead of %s", commitsSinceCurrentTag, currentTag) + } + + return fmt.Sprintf("This Analyzer is %d commits ahead of %s", commitsSinceCurrentTag, currentTag) +} + +// getServerPort returns the port used to render the server. +func getServerPort() string { + serverPort := ":8080" + + // Check if the default port(8080) is available. + listener, err := net.Listen("tcp", serverPort) + if err == nil { + // Close the listener if it starts to listen on the default port. + listener.Close() + return strings.TrimPrefix(serverPort, ":") + } + + // If the port is busy, get a new port. + listener, _ = net.Listen("tcp", ":0") + // Close the listener if it starts to listen on the default port. + serverPort = strings.TrimPrefix(listener.Addr().String(), "[::]:") + listener.Close() + return serverPort +} + +// getIssueMeta receives the issuecode that is raised and it reads the TOML of that issue and returns +// its details configured in the TOML like title, description and category. +func getIssueMeta(cwd, issueCode string) (types.AnalyzerIssue, error) { + analyzerIssue := types.AnalyzerIssue{} + // Read the toml file of the issue in .deepsource/analyzer/issues directory + issueFilePath := filepath.Join(cwd, ".deepsource/analyzer/issues", fmt.Sprintf("%s.toml", issueCode)) + + // Read the issue and populate the data of issue category and description + issueData, err := os.ReadFile(issueFilePath) + if err != nil { + return analyzerIssue, err + } + + // Unmarshal the data from the issue TOMLs into the struct + if err = toml.Unmarshal(issueData, &analyzerIssue); err != nil { + return analyzerIssue, err + } + + // Parsing the markdown issue description and passing it as an HTML string. + var buf bytes.Buffer + if err := goldmark.Convert([]byte(analyzerIssue.Description), &buf); err != nil { + return types.AnalyzerIssue{}, err + } + + // Goldmark already provides a secure HTML. Ref: https://github.com/yuin/goldmark#security + analyzerIssue.HTMLDescription = template.HTML(buf.String()) // skipcq: GSC-G203 + return analyzerIssue, nil +} diff --git a/command/analyzer/dryrun/render/views/assets/cli.css b/command/analyzer/dryrun/render/views/assets/cli.css new file mode 100644 index 00000000..268cfeb0 --- /dev/null +++ b/command/analyzer/dryrun/render/views/assets/cli.css @@ -0,0 +1,285 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.button { + @apply text-xs h-full py-2 px-4 gap-2 flex items-center rounded-sm; +} + +.sidebar { + @apply text-sm p-2 h-8 gap-2 flex items-center rounded-sm hover:bg-ink-300 w-full text-vanilla-400 hover:text-vanilla-100; +} + +.sidebar.selected { + @apply bg-ink-200 hover:bg-ink-300 text-vanilla-100 hover:text-vanilla-100; +} + +.sidebar .tag { + @apply px-2 text-xs rounded-full py-1 leading-none bg-ink-200 justify-self-end; +} + +.sidebar .label { + @apply leading-none flex-grow text-left; +} + +.info { + @apply flex items-center gap-1 text-sm text-vanilla-400; +} + +.highlight .hll, +.highlight .hl { + background-color: #21242c; + display: block; +} +.highlight .hlr { + background-color: #582c27; + display: inherit; +} +.highlight .hlg { + background-color: #004f41; + display: inherit; +} +.highlight .c { + color: #75715e; +} /* Comment */ +.highlight .err { + color: #960050; + background-color: #1e0010; +} /* Error */ +.highlight .k { + color: #66d9ef; +} /* Keyword */ +.highlight .l { + color: #ae81ff; +} /* Literal */ +.highlight .n { + color: #f8f8f2; +} /* Name */ +.highlight .o { + color: #f92672; +} /* Operator */ +.highlight .p { + color: #f8f8f2; +} /* Punctuation */ +.highlight .cm { + color: #75715e; +} /* Comment.Multiline */ +.highlight .cp { + color: #75715e; +} /* Comment.Preproc */ +.highlight .c1, +.highlight .ch { + color: #75715e; +} /* Comment.Single */ +.highlight .cs { + color: #75715e; +} /* Comment.Special */ +.highlight .ge { + font-style: italic; +} /* Generic.Emph */ +.highlight .gs { + font-weight: bold; +} /* Generic.Strong */ +.highlight .kc { + color: #66d9ef; +} /* Keyword.Constant */ +.highlight .kd { + color: #66d9ef; +} /* Keyword.Declaration */ +.highlight .kn { + color: #f92672; +} /* Keyword.Namespace */ +.highlight .kp { + color: #66d9ef; +} /* Keyword.Pseudo */ +.highlight .kr { + color: #66d9ef; +} /* Keyword.Reserved */ +.highlight .kt { + color: #66d9ef; +} /* Keyword.Type */ +.highlight .ld { + color: #e6db74; +} /* Literal.Date */ +.highlight .m { + color: #ae81ff; +} /* Literal.Number */ +.highlight .s { + color: #e6db74; +} /* Literal.String */ +.highlight .na { + color: #a6e22e; +} /* Name.Attribute */ +.highlight .nb { + color: #f8f8f2; +} /* Name.Builtin */ +.highlight .nc { + color: #a6e22e; +} /* Name.Class */ +.highlight .no { + color: #66d9ef; +} /* Name.Constant */ +.highlight .nd { + color: #a6e22e; +} /* Name.Decorator */ +.highlight .ni { + color: #f8f8f2; +} /* Name.Entity */ +.highlight .ne { + color: #a6e22e; +} /* Name.Exception */ +.highlight .nf { + color: #a6e22e; +} /* Name.Function */ +.highlight .fm { + color: #a6e22e; +} /* Name.SpecialFunction */ +.highlight .nl { + color: #f8f8f2; +} /* Name.Label */ +.highlight .nn { + color: #f8f8f2; +} /* Name.Namespace */ +.highlight .nx { + color: #a6e22e; +} /* Name.Other */ +.highlight .py { + color: #f8f8f2; +} /* Name.Property */ +.highlight .nt { + color: #f92672; +} /* Name.Tag */ +.highlight .nv { + color: #f8f8f2; +} /* Name.Variable */ +.highlight .ow { + color: #f92672; +} /* Operator.Word */ +.highlight .w { + color: #f8f8f2; +} /* Text.Whitespace */ +.highlight .mf { + color: #ae81ff; +} /* Literal.Number.Float */ +.highlight .mh { + color: #ae81ff; +} /* Literal.Number.Hex */ +.highlight .mi { + color: #ae81ff; +} /* Literal.Number.Integer */ +.highlight .mo { + color: #ae81ff; +} /* Literal.Number.Oct */ +.highlight .sb { + color: #e6db74; +} /* Literal.String.Backtick */ +.highlight .sc { + color: #e6db74; +} /* Literal.String.Char */ +.highlight .sd { + color: #e6db74; +} /* Literal.String.Doc */ +.highlight .s2 { + color: #e6db74; +} /* Literal.String.Double */ +.highlight .se { + color: #ae81ff; +} /* Literal.String.Escape */ +.highlight .sh { + color: #e6db74; +} /* Literal.String.Heredoc */ +.highlight .si { + color: #e6db74; +} /* Literal.String.Interpol */ +.highlight .sx { + color: #e6db74; +} /* Literal.String.Other */ +.highlight .sr { + color: #e6db74; +} /* Literal.String.Regex */ +.highlight .s1 { + color: #e6db74; +} /* Literal.String.Single */ +.highlight .ss { + color: #e6db74; +} /* Literal.String.Symbol */ +.highlight .bp { + color: #f8f8f2; +} /* Name.Builtin.Pseudo */ +.highlight .vc { + color: #f8f8f2; +} /* Name.Variable.Class */ +.highlight .vg { + color: #f8f8f2; +} /* Name.Variable.Global */ +.highlight .vi { + color: #f8f8f2; +} /* Name.Variable.Instance */ +.highlight .il { + color: #ae81ff; +} /* Literal.Number.Integer.Long */ +.highlight .vm { + color: #a6e22e; +} + +.highlight .gh { +} /* Generic Heading & Diff Header */ +.highlight .gu { + color: #75715e; +} /* Generic.Subheading & Diff Unified/Comment? */ +.highlight .gd { + color: #f92672; +} /* Generic.Deleted & Diff Deleted */ +.highlight .gi { + color: #a6e22e; +} /* Generic.Inserted & Diff Inserted */ + +.highlight .ln { + padding-right: 0.75rem; + padding-left: 0.75rem; + color: #6a737d; +} + +.highlight .hl .ln { + color: #c0c1c3; +} + +.highlight .hl .ln, +.highlight .hll .ln { + background-color: #21242c; +} +.highlight .hlg .ln { + background-color: #004f41; +} +.highlight .hlr .ln { + background-color: #582c27; +} + +.highlight pre { + font-size: 13px; + padding-top: 0.75rem; + padding-bottom: 0.5rem; + color: #ccc; + overflow-x: auto; + position: relative; +} + +.highlight :not(.prose) pre { + background-color: #16181d !important; +} + +.highlighttable { + background: #16181d; + width: 100%; +} +.highlighttable td.code { + width: 100%; + padding-left: 0.75rem; +} +.highlighttable .linenos { + padding-left: 0.75rem; + color: #6a737d; +} +.highlighttable .linenos pre { + font-size: 13px; +} diff --git a/command/analyzer/dryrun/render/views/assets/deepsource-cli.css b/command/analyzer/dryrun/render/views/assets/deepsource-cli.css new file mode 100644 index 00000000..1d1623d3 --- /dev/null +++ b/command/analyzer/dryrun/render/views/assets/deepsource-cli.css @@ -0,0 +1,2326 @@ +/* +! tailwindcss v3.1.4 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: currentColor; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +*/ + +html { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input:-ms-input-placeholder, textarea:-ms-input-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::-webkit-backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.prose { + color: #ffffff; + max-width: 65ch; +} + +.prose :where([class~="lead"]):not(:where([class~="not-prose"] *)) { + color: #c0c1c3; + font-size: 1.25em; + line-height: 1.6; + margin-top: 1.2em; + margin-bottom: 1.2em; +} + +.prose :where(a):not(:where([class~="not-prose"] *)) { + color: #33cb9a; + -webkit-text-decoration: normal; + text-decoration: normal; + font-weight: 500; +} + +.prose :where(strong):not(:where([class~="not-prose"] *)) { + color: #ffffff; + font-weight: 600; +} + +.prose :where(ol):not(:where([class~="not-prose"] *)) { + list-style-type: decimal; + padding-left: 1.625em; +} + +.prose :where(ol[type="A"]):not(:where([class~="not-prose"] *)) { + list-style-type: upper-alpha; +} + +.prose :where(ol[type="a"]):not(:where([class~="not-prose"] *)) { + list-style-type: lower-alpha; +} + +.prose :where(ol[type="A" s]):not(:where([class~="not-prose"] *)) { + list-style-type: upper-alpha; +} + +.prose :where(ol[type="a" s]):not(:where([class~="not-prose"] *)) { + list-style-type: lower-alpha; +} + +.prose :where(ol[type="I"]):not(:where([class~="not-prose"] *)) { + list-style-type: upper-roman; +} + +.prose :where(ol[type="i"]):not(:where([class~="not-prose"] *)) { + list-style-type: lower-roman; +} + +.prose :where(ol[type="I" s]):not(:where([class~="not-prose"] *)) { + list-style-type: upper-roman; +} + +.prose :where(ol[type="i" s]):not(:where([class~="not-prose"] *)) { + list-style-type: lower-roman; +} + +.prose :where(ol[type="1"]):not(:where([class~="not-prose"] *)) { + list-style-type: decimal; +} + +.prose :where(ul):not(:where([class~="not-prose"] *)) { + list-style-type: none; + padding-left: 1.625em; + list-style: none; +} + +.prose :where(ol > li):not(:where([class~="not-prose"] *))::marker { + font-weight: 400; + color: var(--tw-prose-counters); +} + +.prose :where(ul > li):not(:where([class~="not-prose"] *))::marker { + color: var(--tw-prose-bullets); +} + +.prose :where(hr):not(:where([class~="not-prose"] *)) { + border-color: #2a2e37; + border-top-width: 1px; + margin-top: 3em; + margin-bottom: 3em; +} + +.prose :where(blockquote):not(:where([class~="not-prose"] *)) { + font-weight: 500; + font-style: normal; + color: inherit; + border-left-width: 0.25rem; +} + +.prose :where(blockquote):not(:where([class~="not-prose"] *)) borderLeftColor { + 100: #b5ecda; + 150: #a5e8d2; + 200: #94e4ca; + 300: #74dcba; + 400: #53d4aa; + 500: #33cb9a; + 600: #2eb78b; + -d-e-f-a-u-l-t: #33cb9a; +} + +.prose :where(blockquote):not(:where([class~="not-prose"] *)) { + quotes: "\201C""\201D""\2018""\2019"; + margin-top: 1.6em; + margin-bottom: 1.6em; + padding-left: 1em; + line-height: 1.5; + font-size: 1.125rem; + font-size: [object Object]; +} + +.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"] *))::before { + content: open-quote; +} + +.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"] *))::after { + content: close-quote; +} + +.prose :where(h1):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 700; + font-size: 2.25em; + margin-top: 0; + margin-bottom: 0.8888889em; + line-height: 1.1111111; +} + +.prose :where(h1 strong):not(:where([class~="not-prose"] *)) { + font-weight: 900; +} + +.prose :where(h2):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + font-size: 1.5em; + margin-top: 2em; + margin-bottom: 1em; + line-height: 1.3333333; +} + +.prose :where(h2 strong):not(:where([class~="not-prose"] *)) { + font-weight: 800; +} + +.prose :where(h3):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + font-size: 1.25em; + margin-top: 1.6em; + margin-bottom: 0.6em; + line-height: 1.6; +} + +.prose :where(h3 strong):not(:where([class~="not-prose"] *)) { + font-weight: 700; +} + +.prose :where(h4):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + margin-top: 1.5em; + margin-bottom: 0.5em; + line-height: 1.5; +} + +.prose :where(h4 strong):not(:where([class~="not-prose"] *)) { + font-weight: 700; +} + +.prose :where(figure > *):not(:where([class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} + +.prose :where(figcaption):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-captions); + font-size: 0.875em; + line-height: 1.4285714; + margin-top: 0.8571429em; +} + +.prose :where(code):not(:where([class~="not-prose"] *)) { + color: #eeeeee; + font-weight: 600; + font-size: 0.875em; +} + +.prose :where(code):not(:where([class~="not-prose"] *))::before { + content: "`"; +} + +.prose :where(code):not(:where([class~="not-prose"] *))::after { + content: "`"; +} + +.prose :where(a code):not(:where([class~="not-prose"] *)) { + color: #33cb9a; +} + +.prose :where(pre):not(:where([class~="not-prose"] *)) { + color: #c0c1c3; + background-color: #23262e; + overflow-x: auto; + font-weight: 400; + font-size: 0.875em; + line-height: 1.7142857; + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; + border-radius: 0.25rem; + padding-top: 0.8571429em; + padding-right: 1.1428571em; + padding-bottom: 0.8571429em; + padding-left: 1.1428571em; +} + +.prose :where(pre code):not(:where([class~="not-prose"] *)) { + background-color: transparent; + border-width: 0; + border-radius: 0; + padding: 0; + font-weight: 400; + color: inherit; + font-size: inherit; + font-family: inherit; + line-height: inherit; +} + +.prose :where(pre code):not(:where([class~="not-prose"] *))::before { + content: none; +} + +.prose :where(pre code):not(:where([class~="not-prose"] *))::after { + content: none; +} + +.prose :where(table):not(:where([class~="not-prose"] *)) { + width: 100%; + table-layout: auto; + text-align: left; + margin-top: 2em; + margin-bottom: 2em; + font-size: 0.875em; + line-height: 1.7142857; +} + +.prose :where(thead):not(:where([class~="not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: var(--tw-prose-th-borders); + color: #c0c1c3; +} + +.prose :where(thead th):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 500; + vertical-align: bottom; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; +} + +.prose :where(tbody tr):not(:where([class~="not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: var(--tw-prose-td-borders); +} + +.prose :where(tbody tr:last-child):not(:where([class~="not-prose"] *)) { + border-bottom-width: 0; +} + +.prose :where(tbody td):not(:where([class~="not-prose"] *)) { + vertical-align: top; + padding-top: 0.5714286em; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; +} + +.prose { + --tw-prose-body: #374151; + --tw-prose-headings: #111827; + --tw-prose-lead: #4b5563; + --tw-prose-links: #111827; + --tw-prose-bold: #111827; + --tw-prose-counters: #6b7280; + --tw-prose-bullets: #d1d5db; + --tw-prose-hr: #e5e7eb; + --tw-prose-quotes: #111827; + --tw-prose-quote-borders: #e5e7eb; + --tw-prose-captions: #6b7280; + --tw-prose-code: #111827; + --tw-prose-pre-code: #e5e7eb; + --tw-prose-pre-bg: #1f2937; + --tw-prose-th-borders: #d1d5db; + --tw-prose-td-borders: #e5e7eb; + --tw-prose-invert-body: #d1d5db; + --tw-prose-invert-headings: #fff; + --tw-prose-invert-lead: #9ca3af; + --tw-prose-invert-links: #fff; + --tw-prose-invert-bold: #fff; + --tw-prose-invert-counters: #9ca3af; + --tw-prose-invert-bullets: #4b5563; + --tw-prose-invert-hr: #374151; + --tw-prose-invert-quotes: #f3f4f6; + --tw-prose-invert-quote-borders: #374151; + --tw-prose-invert-captions: #9ca3af; + --tw-prose-invert-code: #fff; + --tw-prose-invert-pre-code: #d1d5db; + --tw-prose-invert-pre-bg: rgb(0 0 0 / 50%); + --tw-prose-invert-th-borders: #4b5563; + --tw-prose-invert-td-borders: #374151; + font-size: 1rem; + line-height: 1.75; +} + +.prose :where(p):not(:where([class~="not-prose"] *)) { + margin-top: 1.25em; + margin-bottom: 1.25em; +} + +.prose :where(img):not(:where([class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(video):not(:where([class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(figure):not(:where([class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(h2 code):not(:where([class~="not-prose"] *)) { + font-size: 0.875em; +} + +.prose :where(h3 code):not(:where([class~="not-prose"] *)) { + font-size: 0.9em; +} + +.prose :where(li):not(:where([class~="not-prose"] *)) { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +.prose :where(ol > li):not(:where([class~="not-prose"] *)) { + padding-left: 0.375em; + position: relative; +} + +.prose :where(ul > li):not(:where([class~="not-prose"] *)) { + padding-left: 0.375em; + position: relative; +} + +.prose > :where(ul > li p):not(:where([class~="not-prose"] *)) { + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.prose > :where(ul > li > *:first-child):not(:where([class~="not-prose"] *)) { + margin-top: 1.25em; +} + +.prose > :where(ul > li > *:last-child):not(:where([class~="not-prose"] *)) { + margin-bottom: 1.25em; +} + +.prose > :where(ol > li > *:first-child):not(:where([class~="not-prose"] *)) { + margin-top: 1.25em; +} + +.prose > :where(ol > li > *:last-child):not(:where([class~="not-prose"] *)) { + margin-bottom: 1.25em; +} + +.prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"] *)) { + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.prose :where(hr + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h2 + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h3 + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h4 + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(thead th:first-child):not(:where([class~="not-prose"] *)) { + padding-left: 0; +} + +.prose :where(thead th:last-child):not(:where([class~="not-prose"] *)) { + padding-right: 0; +} + +.prose :where(tbody td:first-child):not(:where([class~="not-prose"] *)) { + padding-left: 0; +} + +.prose :where(tbody td:last-child):not(:where([class~="not-prose"] *)) { + padding-right: 0; +} + +.prose > :where(:first-child):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose > :where(:last-child):not(:where([class~="not-prose"] *)) { + margin-bottom: 0; +} + +.prose :where(a:hover):not(:where([class~="not-prose"] *)) { + text-decoration: underline; +} + +.prose :where(ol > li):not(:where([class~="not-prose"] *))::before { + content: counter(list-item) "."; + position: absolute; + font-weight: 400; + color: #c0c1c3; +} + +.prose :where(ul > li):not(:where([class~="not-prose"] *))::before { + content: "—"; + margin-right: 0.5rem; + width: 0; + top: 0; + left: 0; + height: 0; + position: absolute; +} + +.prose :where(ul > li):not(:where([class~="not-prose"] *))::before color { + 100: #b5ecda; + 150: #a5e8d2; + 200: #94e4ca; + 300: #74dcba; + 400: #53d4aa; + 500: #33cb9a; + 600: #2eb78b; + -d-e-f-a-u-l-t: #33cb9a; +} + +.prose :where(h1, h2, h3, h4, h5):not(:where([class~="not-prose"] *)) { + color: #ffffff; +} + +.prose :where(h3, h4, h5):not(:where([class~="not-prose"] *)) { + font-weight: 500; +} + +.prose :where(figure figcaption):not(:where([class~="not-prose"] *)) { + color: #c0c1c3; + text-align: center; +} + +@media (min-width: 640px) { + .prose :where(figure figcaption):not(:where([class~="not-prose"] *)) { + text-align: left; + } +} + +.prose :where(figure img):not(:where([class~="not-prose"] *)) { + border-radius: 0.25rem; +} + +.prose :where(code::before, code):not(:where([class~="not-prose"] *))::after { + content: ""; +} + +.prose :where(pre code::before, pre code):not(:where([class~="not-prose"] *))::after { + content: ""; +} + +.prose :where(thead, tbody tr):not(:where([class~="not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: #2a2e37; +} + +.prose-sm { + font-size: 0.875rem; + line-height: 1.7142857; +} + +.prose-sm :where(p):not(:where([class~="not-prose"] *)) { + margin-top: 1.1428571em; + margin-bottom: 1.1428571em; +} + +.prose-sm :where([class~="lead"]):not(:where([class~="not-prose"] *)) { + font-size: 1.2857143em; + line-height: 1.5555556; + margin-top: 0.8888889em; + margin-bottom: 0.8888889em; +} + +.prose-sm :where(blockquote):not(:where([class~="not-prose"] *)) { + margin-top: 1.3333333em; + margin-bottom: 1.3333333em; + padding-left: 1.1111111em; +} + +.prose-sm :where(h1):not(:where([class~="not-prose"] *)) { + font-size: 2.1428571em; + margin-top: 0; + margin-bottom: 0.8em; + line-height: 1.2; +} + +.prose-sm :where(h2):not(:where([class~="not-prose"] *)) { + font-size: 1.4285714em; + margin-top: 1.6em; + margin-bottom: 0.8em; + line-height: 1.4; +} + +.prose-sm :where(h3):not(:where([class~="not-prose"] *)) { + font-size: 1.2857143em; + margin-top: 1.5555556em; + margin-bottom: 0.4444444em; + line-height: 1.5555556; +} + +.prose-sm :where(h4):not(:where([class~="not-prose"] *)) { + margin-top: 1.4285714em; + margin-bottom: 0.5714286em; + line-height: 1.4285714; +} + +.prose-sm :where(img):not(:where([class~="not-prose"] *)) { + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; +} + +.prose-sm :where(video):not(:where([class~="not-prose"] *)) { + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; +} + +.prose-sm :where(figure):not(:where([class~="not-prose"] *)) { + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; +} + +.prose-sm :where(figure > *):not(:where([class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} + +.prose-sm :where(figcaption):not(:where([class~="not-prose"] *)) { + font-size: 0.8571429em; + line-height: 1.3333333; + margin-top: 0.6666667em; +} + +.prose-sm :where(code):not(:where([class~="not-prose"] *)) { + font-size: 0.8571429em; +} + +.prose-sm :where(h2 code):not(:where([class~="not-prose"] *)) { + font-size: 0.9em; +} + +.prose-sm :where(h3 code):not(:where([class~="not-prose"] *)) { + font-size: 0.8888889em; +} + +.prose-sm :where(pre):not(:where([class~="not-prose"] *)) { + font-size: 0.8571429em; + line-height: 1.6666667; + margin-top: 1.6666667em; + margin-bottom: 1.6666667em; + border-radius: 0; + padding-top: 0.6666667em; + padding-right: 1em; + padding-bottom: 0.6666667em; + padding-left: 1em; + color: #c0c1c3; + background-color: #23262e; + overflow-x: auto; +} + +.prose-sm :where(ol):not(:where([class~="not-prose"] *)) { + padding-left: 1.5714286em; +} + +.prose-sm :where(ul):not(:where([class~="not-prose"] *)) { + padding-left: 1.5714286em; +} + +.prose-sm :where(li):not(:where([class~="not-prose"] *)) { + margin-top: 0.2857143em; + margin-bottom: 0.2857143em; +} + +.prose-sm :where(ol > li):not(:where([class~="not-prose"] *)) { + padding-left: 0.4285714em; +} + +.prose-sm :where(ul > li):not(:where([class~="not-prose"] *)) { + padding-left: 0.4285714em; +} + +.prose-sm > :where(ul > li p):not(:where([class~="not-prose"] *)) { + margin-top: 0.5714286em; + margin-bottom: 0.5714286em; +} + +.prose-sm > :where(ul > li > *:first-child):not(:where([class~="not-prose"] *)) { + margin-top: 1.1428571em; +} + +.prose-sm > :where(ul > li > *:last-child):not(:where([class~="not-prose"] *)) { + margin-bottom: 1.1428571em; +} + +.prose-sm > :where(ol > li > *:first-child):not(:where([class~="not-prose"] *)) { + margin-top: 1.1428571em; +} + +.prose-sm > :where(ol > li > *:last-child):not(:where([class~="not-prose"] *)) { + margin-bottom: 1.1428571em; +} + +.prose-sm :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"] *)) { + margin-top: 0.5714286em; + margin-bottom: 0.5714286em; +} + +.prose-sm :where(hr):not(:where([class~="not-prose"] *)) { + margin-top: 2.8571429em; + margin-bottom: 2.8571429em; +} + +.prose-sm :where(hr + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose-sm :where(h2 + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose-sm :where(h3 + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose-sm :where(h4 + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose-sm :where(table):not(:where([class~="not-prose"] *)) { + font-size: 0.8571429em; + line-height: 1.5; +} + +.prose-sm :where(thead th):not(:where([class~="not-prose"] *)) { + padding-right: 1em; + padding-bottom: 0.6666667em; + padding-left: 1em; +} + +.prose-sm :where(thead th:first-child):not(:where([class~="not-prose"] *)) { + padding-left: 0; +} + +.prose-sm :where(thead th:last-child):not(:where([class~="not-prose"] *)) { + padding-right: 0; +} + +.prose-sm :where(tbody td):not(:where([class~="not-prose"] *)) { + padding-top: 0.6666667em; + padding-right: 1em; + padding-bottom: 0.6666667em; + padding-left: 1em; +} + +.prose-sm :where(tbody td:first-child):not(:where([class~="not-prose"] *)) { + padding-left: 0; +} + +.prose-sm :where(tbody td:last-child):not(:where([class~="not-prose"] *)) { + padding-right: 0; +} + +.prose-sm > :where(:first-child):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose-sm > :where(:last-child):not(:where([class~="not-prose"] *)) { + margin-bottom: 0; +} + +.prose-sm :where(ul > li):not(:where([class~="not-prose"] *))::before { + content: "—"; + margin-right: 0.5rem; + width: 0; + top: 0; + left: 0; + height: 0; + position: absolute; +} + +.prose-sm :where(ul > li):not(:where([class~="not-prose"] *))::before color { + 100: #b5ecda; + 150: #a5e8d2; + 200: #94e4ca; + 300: #74dcba; + 400: #53d4aa; + 500: #33cb9a; + 600: #2eb78b; + -d-e-f-a-u-l-t: #33cb9a; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.sticky { + position: -webkit-sticky; + position: sticky; +} + +.top-12 { + top: 3rem; +} + +.top-0 { + top: 0px; +} + +.top-36 { + top: 9rem; +} + +.z-20 { + z-index: 20; +} + +.z-50 { + z-index: 50; +} + +.col-span-2 { + grid-column: span 2 / span 2; +} + +.row-span-2 { + grid-row: span 2 / span 2; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.mr-4 { + margin-right: 1rem; +} + +.block { + display: block; +} + +.flex { + display: flex; +} + +.table { + display: table; +} + +.grid { + display: grid; +} + +.list-item { + display: list-item; +} + +.h-24 { + height: 6rem; +} + +.h-full { + height: 100%; +} + +.h-12 { + height: 3rem; +} + +.h-auto { + height: auto; +} + +.h-\[calc\(100vh-9rem\)\] { + height: calc(100vh - 9rem); +} + +.min-h-screen { + min-height: 100vh; +} + +.w-full { + width: 100%; +} + +.w-28 { + width: 7rem; +} + +.max-w-xs { + max-width: 20rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-shrink-0 { + flex-shrink: 0; +} + +.flex-grow { + flex-grow: 1; +} + +.list-none { + list-style-type: none; +} + +.auto-rows-min { + grid-auto-rows: -webkit-min-content; + grid-auto-rows: min-content; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.grid-cols-\[48px_1fr\] { + grid-template-columns: 48px 1fr; +} + +.grid-cols-\[16rem_1fr_20rem\] { + grid-template-columns: 16rem 1fr 20rem; +} + +.grid-cols-\[1fr_10rem\] { + grid-template-columns: 1fr 10rem; +} + +.grid-cols-\[16rem_1fr\] { + grid-template-columns: 16rem 1fr; +} + +.grid-cols-\[1fr_20rem\] { + grid-template-columns: 1fr 20rem; +} + +.flex-row { + flex-direction: row; +} + +.flex-col { + flex-direction: column; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.place-content-center { + place-content: center; +} + +.items-center { + align-items: center; +} + +.items-baseline { + align-items: baseline; +} + +.justify-between { + justify-content: space-between; +} + +.gap-5 { + gap: 1.25rem; +} + +.gap-4 { + gap: 1rem; +} + +.gap-2\.5 { + gap: 0.625rem; +} + +.gap-2 { + gap: 0.5rem; +} + +.gap-1 { + gap: 0.25rem; +} + +.gap-x-4 { + -moz-column-gap: 1rem; + column-gap: 1rem; +} + +.gap-y-2\.5 { + row-gap: 0.625rem; +} + +.gap-y-2 { + row-gap: 0.5rem; +} + +.space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} + +.space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.divide-x > :not([hidden]) ~ :not([hidden]) { + --tw-divide-x-reverse: 0; + border-right-width: calc(1px * var(--tw-divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse))); +} + +.divide-ink-200 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(35 38 46 / var(--tw-divide-opacity)); +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-x-scroll { + overflow-x: scroll; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.rounded-sm { + border-radius: 0.125rem; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.border { + border-width: 1px; +} + +.border-b { + border-bottom-width: 1px; +} + +.border-t { + border-top-width: 1px; +} + +.border-dashed { + border-style: dashed; +} + +.border-ink-200 { + --tw-border-opacity: 1; + border-color: rgb(35 38 46 / var(--tw-border-opacity)); +} + +.bg-ink-400 { + --tw-bg-opacity: 1; + background-color: rgb(22 24 29 / var(--tw-bg-opacity)); +} + +.bg-ink-300 { + --tw-bg-opacity: 1; + background-color: rgb(26 29 35 / var(--tw-bg-opacity)); +} + +.bg-ink-200 { + --tw-bg-opacity: 1; + background-color: rgb(35 38 46 / var(--tw-bg-opacity)); +} + +.p-6 { + padding: 1.5rem; +} + +.p-4 { + padding: 1rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-3 { + padding: 0.75rem; +} + +.py-16 { + padding-top: 4rem; + padding-bottom: 4rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.pt-5 { + padding-top: 1.25rem; +} + +.pl-4 { + padding-left: 1rem; +} + +.pr-2 { + padding-right: 0.5rem; +} + +.text-center { + text-align: center; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.font-medium { + font-weight: 500; +} + +.font-normal { + font-weight: 400; +} + +.font-semibold { + font-weight: 600; +} + +.uppercase { + text-transform: uppercase; +} + +.leading-tight { + line-height: 1.25; +} + +.leading-none { + line-height: 1; +} + +.tracking-wider { + letter-spacing: 0.05em; +} + +.tracking-wide { + letter-spacing: 0.025em; +} + +.text-vanilla-100 { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.text-vanilla-400 { + --tw-text-opacity: 1; + color: rgb(192 193 195 / var(--tw-text-opacity)); +} + +.underline { + -webkit-text-decoration-line: underline; + text-decoration-line: underline; +} + +.button { + display: flex; + height: 100%; + align-items: center; + gap: 0.5rem; + border-radius: 0.125rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + padding-right: 1rem; + font-size: 0.75rem; + line-height: 1rem; +} + +.sidebar { + display: flex; + height: 2rem; + width: 100%; + align-items: center; + gap: 0.5rem; + border-radius: 0.125rem; + padding: 0.5rem; + font-size: 0.875rem; + line-height: 1.25rem; + --tw-text-opacity: 1; + color: rgb(192 193 195 / var(--tw-text-opacity)); +} + +.sidebar:hover { + --tw-bg-opacity: 1; + background-color: rgb(26 29 35 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.sidebar.selected { + --tw-bg-opacity: 1; + background-color: rgb(35 38 46 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.sidebar.selected:hover { + --tw-bg-opacity: 1; + background-color: rgb(26 29 35 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.sidebar .tag { + justify-self: end; + border-radius: 9999px; + --tw-bg-opacity: 1; + background-color: rgb(35 38 46 / var(--tw-bg-opacity)); + padding-left: 0.5rem; + padding-right: 0.5rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + font-size: 0.75rem; + line-height: 1rem; + line-height: 1; +} + +.sidebar .label { + flex-grow: 1; + text-align: left; + line-height: 1; +} + +.info { + display: flex; + align-items: center; + gap: 0.25rem; + font-size: 0.875rem; + line-height: 1.25rem; + --tw-text-opacity: 1; + color: rgb(192 193 195 / var(--tw-text-opacity)); +} + +.highlight .hll, +.highlight .hl { + background-color: #21242c; + display: block; +} + +.highlight .hlr { + background-color: #582c27; + display: inherit; +} + +.highlight .hlg { + background-color: #004f41; + display: inherit; +} + +.highlight .c { + color: #75715e; +} + +/* Comment */ + +.highlight .err { + color: #960050; + background-color: #1e0010; +} + +/* Error */ + +.highlight .k { + color: #66d9ef; +} + +/* Keyword */ + +.highlight .l { + color: #ae81ff; +} + +/* Literal */ + +.highlight .n { + color: #f8f8f2; +} + +/* Name */ + +.highlight .o { + color: #f92672; +} + +/* Operator */ + +.highlight .p { + color: #f8f8f2; +} + +/* Punctuation */ + +.highlight .cm { + color: #75715e; +} + +/* Comment.Multiline */ + +.highlight .cp { + color: #75715e; +} + +/* Comment.Preproc */ + +.highlight .c1, +.highlight .ch { + color: #75715e; +} + +/* Comment.Single */ + +.highlight .cs { + color: #75715e; +} + +/* Comment.Special */ + +.highlight .ge { + font-style: italic; +} + +/* Generic.Emph */ + +.highlight .gs { + font-weight: bold; +} + +/* Generic.Strong */ + +.highlight .kc { + color: #66d9ef; +} + +/* Keyword.Constant */ + +.highlight .kd { + color: #66d9ef; +} + +/* Keyword.Declaration */ + +.highlight .kn { + color: #f92672; +} + +/* Keyword.Namespace */ + +.highlight .kp { + color: #66d9ef; +} + +/* Keyword.Pseudo */ + +.highlight .kr { + color: #66d9ef; +} + +/* Keyword.Reserved */ + +.highlight .kt { + color: #66d9ef; +} + +/* Keyword.Type */ + +.highlight .ld { + color: #e6db74; +} + +/* Literal.Date */ + +.highlight .m { + color: #ae81ff; +} + +/* Literal.Number */ + +.highlight .s { + color: #e6db74; +} + +/* Literal.String */ + +.highlight .na { + color: #a6e22e; +} + +/* Name.Attribute */ + +.highlight .nb { + color: #f8f8f2; +} + +/* Name.Builtin */ + +.highlight .nc { + color: #a6e22e; +} + +/* Name.Class */ + +.highlight .no { + color: #66d9ef; +} + +/* Name.Constant */ + +.highlight .nd { + color: #a6e22e; +} + +/* Name.Decorator */ + +.highlight .ni { + color: #f8f8f2; +} + +/* Name.Entity */ + +.highlight .ne { + color: #a6e22e; +} + +/* Name.Exception */ + +.highlight .nf { + color: #a6e22e; +} + +/* Name.Function */ + +.highlight .fm { + color: #a6e22e; +} + +/* Name.SpecialFunction */ + +.highlight .nl { + color: #f8f8f2; +} + +/* Name.Label */ + +.highlight .nn { + color: #f8f8f2; +} + +/* Name.Namespace */ + +.highlight .nx { + color: #a6e22e; +} + +/* Name.Other */ + +.highlight .py { + color: #f8f8f2; +} + +/* Name.Property */ + +.highlight .nt { + color: #f92672; +} + +/* Name.Tag */ + +.highlight .nv { + color: #f8f8f2; +} + +/* Name.Variable */ + +.highlight .ow { + color: #f92672; +} + +/* Operator.Word */ + +.highlight .w { + color: #f8f8f2; +} + +/* Text.Whitespace */ + +.highlight .mf { + color: #ae81ff; +} + +/* Literal.Number.Float */ + +.highlight .mh { + color: #ae81ff; +} + +/* Literal.Number.Hex */ + +.highlight .mi { + color: #ae81ff; +} + +/* Literal.Number.Integer */ + +.highlight .mo { + color: #ae81ff; +} + +/* Literal.Number.Oct */ + +.highlight .sb { + color: #e6db74; +} + +/* Literal.String.Backtick */ + +.highlight .sc { + color: #e6db74; +} + +/* Literal.String.Char */ + +.highlight .sd { + color: #e6db74; +} + +/* Literal.String.Doc */ + +.highlight .s2 { + color: #e6db74; +} + +/* Literal.String.Double */ + +.highlight .se { + color: #ae81ff; +} + +/* Literal.String.Escape */ + +.highlight .sh { + color: #e6db74; +} + +/* Literal.String.Heredoc */ + +.highlight .si { + color: #e6db74; +} + +/* Literal.String.Interpol */ + +.highlight .sx { + color: #e6db74; +} + +/* Literal.String.Other */ + +.highlight .sr { + color: #e6db74; +} + +/* Literal.String.Regex */ + +.highlight .s1 { + color: #e6db74; +} + +/* Literal.String.Single */ + +.highlight .ss { + color: #e6db74; +} + +/* Literal.String.Symbol */ + +.highlight .bp { + color: #f8f8f2; +} + +/* Name.Builtin.Pseudo */ + +.highlight .vc { + color: #f8f8f2; +} + +/* Name.Variable.Class */ + +.highlight .vg { + color: #f8f8f2; +} + +/* Name.Variable.Global */ + +.highlight .vi { + color: #f8f8f2; +} + +/* Name.Variable.Instance */ + +.highlight .il { + color: #ae81ff; +} + +/* Literal.Number.Integer.Long */ + +.highlight .vm { + color: #a6e22e; +} + +.highlight .gh { +} + +/* Generic Heading & Diff Header */ + +.highlight .gu { + color: #75715e; +} + +/* Generic.Subheading & Diff Unified/Comment? */ + +.highlight .gd { + color: #f92672; +} + +/* Generic.Deleted & Diff Deleted */ + +.highlight .gi { + color: #a6e22e; +} + +/* Generic.Inserted & Diff Inserted */ + +.highlight .ln { + padding-right: 0.75rem; + padding-left: 0.75rem; + color: #6a737d; +} + +.highlight .hl .ln { + color: #c0c1c3; +} + +.highlight .hl .ln, +.highlight .hll .ln { + background-color: #21242c; +} + +.highlight .hlg .ln { + background-color: #004f41; +} + +.highlight .hlr .ln { + background-color: #582c27; +} + +.highlight pre { + font-size: 13px; + padding-top: 0.75rem; + padding-bottom: 0.5rem; + color: #ccc; + overflow-x: auto; + position: relative; +} + +.highlight :not(.prose) pre { + background-color: #16181d !important; +} + +.highlighttable { + background: #16181d; + width: 100%; +} + +.highlighttable td.code { + width: 100%; + padding-left: 0.75rem; +} + +.highlighttable .linenos { + padding-left: 0.75rem; + color: #6a737d; +} + +.highlighttable .linenos pre { + font-size: 13px; +} + +.hover\:bg-ink-300:hover { + --tw-bg-opacity: 1; + background-color: rgb(26 29 35 / var(--tw-bg-opacity)); +} + +.hover\:bg-ink-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(42 46 55 / var(--tw-bg-opacity)); +} + +.hover\:text-vanilla-200:hover { + --tw-text-opacity: 1; + color: rgb(245 245 245 / var(--tw-text-opacity)); +} + +@media (min-width: 640px) { + .sm\:w-auto { + width: auto; + } + + .sm\:space-y-0 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0px * var(--tw-space-y-reverse)); + } +} + +@media (min-width: 768px) { + .md\:flex { + display: flex; + } + + .md\:items-start { + align-items: flex-start; + } + + .md\:space-x-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1rem * var(--tw-space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); + } +} + +@media (min-width: 1280px) { + .xl\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } +} diff --git a/command/analyzer/dryrun/render/views/assets/images/deepsource.svg b/command/analyzer/dryrun/render/views/assets/images/deepsource.svg new file mode 100644 index 00000000..29c007d4 --- /dev/null +++ b/command/analyzer/dryrun/render/views/assets/images/deepsource.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/command/analyzer/dryrun/render/views/assets/images/favicon-dark.svg b/command/analyzer/dryrun/render/views/assets/images/favicon-dark.svg new file mode 100644 index 00000000..fe8167c4 --- /dev/null +++ b/command/analyzer/dryrun/render/views/assets/images/favicon-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/command/analyzer/dryrun/render/views/assets/images/favicon.svg b/command/analyzer/dryrun/render/views/assets/images/favicon.svg new file mode 100644 index 00000000..c41e1dcf --- /dev/null +++ b/command/analyzer/dryrun/render/views/assets/images/favicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/command/analyzer/dryrun/render/views/assets/images/feather-sprite.svg b/command/analyzer/dryrun/render/views/assets/images/feather-sprite.svg new file mode 100644 index 00000000..f958c114 --- /dev/null +++ b/command/analyzer/dryrun/render/views/assets/images/feather-sprite.svg @@ -0,0 +1,1587 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/command/analyzer/dryrun/render/views/header.html b/command/analyzer/dryrun/render/views/header.html new file mode 100644 index 00000000..58000188 --- /dev/null +++ b/command/analyzer/dryrun/render/views/header.html @@ -0,0 +1,96 @@ +{{ define "header" }} +
+ + +

+ {{ .AnalyzerShortcode }} +

+
+ {{ if not (eq .AnalysisResultData.UniqueIssuesCount 0) }} +
+ + + + + {{ .AnalysisResultData.UniqueIssuesCount }} + {{ if eq .AnalysisResultData.UniqueIssuesCount 1 }} + issue + {{ else }} + issues + {{ end }} + +
+ {{ end }} + {{ if not (eq .VCSInfo.Branch "") }} +
+ + + + {{ .VCSInfo.Branch }} +
+ {{ end }} + + {{ if not (eq .VCSInfo.CommitSHA "") }} +
+ + + + {{ .VCSInfo.CommitSHA }} +
+ {{ end }} + + {{ if not (eq .VCSInfo.VersionDiff "") }} +
+ + + + {{ .VCSInfo.VersionDiff }} +
+ {{ end }} +
+
+{{ end }} \ No newline at end of file diff --git a/command/analyzer/dryrun/render/views/index.html b/command/analyzer/dryrun/render/views/index.html new file mode 100644 index 00000000..022ce193 --- /dev/null +++ b/command/analyzer/dryrun/render/views/index.html @@ -0,0 +1,83 @@ + + + + + {{ .PageTitle }} + {{ template "links" }} + + {{ $defaultIssueCategory := "all" }} + {{ $selectedCategory := .SelectedCategory }} + {{ $categoryMap := .IssueCategoryNameMap }} + + {{ template "navbar" . }} + {{ template "header" . }} +
+ {{ template "sidebar" . }} +
+ {{ if eq (len .AnalysisResultData.IssuesOccurenceMap) 0 }} +
No issues to show
+ {{ else }} + {{ range $key, $value := .AnalysisResultData.IssuesOccurenceMap }} + {{ if or (eq $selectedCategory $value.IssueMeta.Category) (eq $selectedCategory $defaultIssueCategory) }} + +
+

{{ $value.IssueMeta.Title }}

+
+
+ + + + {{ index $categoryMap $value.IssueMeta.Category }} +
+
+ + + + {{ $value.FilesInfo }} +
+
+
+
{{ len $value.Occurences }}
+
+ {{ end }} + {{ end }} + {{ end }} +
+ +
+ + diff --git a/command/analyzer/dryrun/render/views/links.html b/command/analyzer/dryrun/render/views/links.html new file mode 100644 index 00000000..0f0ed761 --- /dev/null +++ b/command/analyzer/dryrun/render/views/links.html @@ -0,0 +1,13 @@ +{{ define "links" }} + + + + + + + + +{{ end }} \ No newline at end of file diff --git a/command/analyzer/dryrun/render/views/nav.html b/command/analyzer/dryrun/render/views/nav.html new file mode 100644 index 00000000..4ac6a5f9 --- /dev/null +++ b/command/analyzer/dryrun/render/views/nav.html @@ -0,0 +1,57 @@ +{{ define "navbar" }} + +{{ end }} \ No newline at end of file diff --git a/command/analyzer/dryrun/render/views/occurence.html b/command/analyzer/dryrun/render/views/occurence.html new file mode 100644 index 00000000..ba59709f --- /dev/null +++ b/command/analyzer/dryrun/render/views/occurence.html @@ -0,0 +1,142 @@ + + + + + {{ .PageTitle }} + {{ template "links" . }} + +{{ $issueData := (index .AnalysisResultData.IssuesOccurenceMap .SelectedIssueCode)}} + + {{ template "navbar" . }} {{ template "header" . }} +
+ {{ template "sidebar" . }} +
+
+ +
+
+
+
+ + {{ $issueData.IssueMeta.Title }} + + {{ .SelectedIssueCode }} +
+ +
+
+
+ + + + + {{ $categoryMap := .IssueCategoryNameMap }} + {{ index $categoryMap $issueData.IssueMeta.Category }} + +
+
+ + + + + {{ $issueData.FilesInfo }} + +
+
+
+
+
+
+
+
+ {{ $src := .AnalysisResultData.RenderedSourceCode }} + {{ range $index, $occurence := $issueData.Occurences}} +
+
+
+ {{ $occurence.IssueText }} +
+
+ + + + + {{ $occurence.Location.Path }} + +
+
+
+
+ {{ index $src $index}} +
+
+
+ {{ end }} +
+
+
+
+ Description +
+
+ {{ $issueData := (index .AnalysisResultData.IssuesOccurenceMap + .SelectedIssueCode)}} {{ $issueData.IssueMeta.HTMLDescription }} +
+
+
+
+
+ + diff --git a/command/analyzer/dryrun/render/views/package.json b/command/analyzer/dryrun/render/views/package.json new file mode 100644 index 00000000..5de32eaf --- /dev/null +++ b/command/analyzer/dryrun/render/views/package.json @@ -0,0 +1,14 @@ +{ + "name": "dry-run", + "version": "1.0.0", + "main": "index.js", + "author": "Shivam Mishra ", + "scripts": { + "dev": "npx tailwindcss -i ./assets/cli.css -o ./assets/deepsource-cli.css --watch" + }, + "license": "MIT", + "devDependencies": { + "@tailwindcss/typography": "^0.5.2", + "tailwindcss": "^3.1.4" + } +} diff --git a/command/analyzer/dryrun/render/views/sidebar.html b/command/analyzer/dryrun/render/views/sidebar.html new file mode 100644 index 00000000..62bb00b7 --- /dev/null +++ b/command/analyzer/dryrun/render/views/sidebar.html @@ -0,0 +1,95 @@ +{{ define "sidebar" }} +{{ $defaultIssueCategory := "all" }} +{{ $selectedCategory := .SelectedCategory }} + +{{ end }} \ No newline at end of file diff --git a/command/analyzer/dryrun/render/views/tailwind.config.js b/command/analyzer/dryrun/render/views/tailwind.config.js new file mode 100644 index 00000000..42dd8501 --- /dev/null +++ b/command/analyzer/dryrun/render/views/tailwind.config.js @@ -0,0 +1,157 @@ +const typographyConfig = require('./typography.js') + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["*.{html,js}"], + theme: { + fontFamily: { + sans: [ + 'Inter', + 'ui-sans-serif', + 'system-ui', + '-apple-system', + 'BlinkMacSystemFont', + '"Segoe UI"', + 'Roboto', + '"Helvetica Neue"', + 'Arial', + '"Noto Sans"', + 'sans-serif', + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + '"Noto Color Emoji"' + ], + serif: ['Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'], + mono: [ + 'ui-monospace', + 'Menlo', + 'Monaco', + 'Consolas', + '"Liberation Mono"', + '"Courier New"', + 'monospace' + ], + }, + colors: { + transparent: 'transparent', + current: 'currentColor', + black: '#000000', + slate: '#52575c', + github: '#414141', + gitlab: '#6753b5', + bitbucket: '#1e54c5', + lavender: '#7a97fa', + lilac: '#c97bd4', + 'sea-glass': '#49f9cf', + pink: '#f977ff', + 'light-juniper': '#2eb78b', // legacy + 'light-cherry': '#d6435b', // legacy + juniper: { + DEFAULT: '#33cb9a', + 600: '#2eb78b', + 500: '#33cb9a', + 400: '#53d4aa', + 300: '#74dcba', + 200: '#94e4ca', + 150: '#a5e8d2', + 100: '#b5ecda' + }, + robin: { + DEFAULT: '#4568dc', + 600: '#2449c1', + 500: '#4568dc', + 400: '#6784e2', + 200: '#acbcef', + 300: '#8aa0e9', + 150: '#bdcaf3', + 100: '#ced8f6' + }, + cherry: { + DEFAULT: '#da5565', + 600: '#d6435b', + 500: '#da5565', + 400: '#e17783', + 300: '#e998a2', + 200: '#f0bac0', + 150: '#f4cacf', + 100: '#f7dbde' + }, + honey: { + DEFAULT: '#f6d87c', + 600: '#f0bf28', + 500: '#f6d87c', + 400: '#f9e3a2', + 300: '#fbefc8', + 200: '#fefaef', + 150: '#ffffff', + 100: '#ffffff' + }, + aqua: { + DEFAULT: '#23c4f8', + 600: '#07aade', + 500: '#23c4f8', + 400: '#4bcff9', + 300: '#72d9fb', + 200: '#9ae4fc', + 150: '#ade9fc', + 100: '#c1eefd' + }, + ink: { + 50: '#303540', + 100: '#2a2e37', + 200: '#23262e', + 300: '#1a1d23', + 400: '#16181d' + }, + vanilla: { + 100: '#ffffff', + 200: '#f5f5f5', + 300: '#eeeeee', + 400: '#c0c1c3' + } + }, + extend: { + typography: (theme) => ({ + DEFAULT: { + css: { + ...typographyConfig.DEFAULT(theme) + } + }, + muted: { + css: { + ...typographyConfig.MUTED(theme) + } + }, + rte: { + css: { + ...typographyConfig.RTE(theme) + } + }, + sm: { + css: { + ...typographyConfig.SMALL_SCREEN_CSS(theme) + } + }, + lg: { + css: { + ...typographyConfig.LARGE_SCREEN_CSS(theme) + } + }, + xl: { + css: { + ...typographyConfig.LARGE_SCREEN_CSS(theme) + } + }, + '2xl': { + css: { + ...typographyConfig.LARGE_SCREEN_CSS(theme) + } + } + }) + } + }, + plugins: [ + require('@tailwindcss/typography') + ], +} \ No newline at end of file diff --git a/command/analyzer/dryrun/render/views/typography.js b/command/analyzer/dryrun/render/views/typography.js new file mode 100644 index 00000000..dc48cd8a --- /dev/null +++ b/command/analyzer/dryrun/render/views/typography.js @@ -0,0 +1,288 @@ +const BEFORE_LIST_ITEM = function (theme, muted = false) { + return { + content: '"—"', + marginRight: theme('spacing.2'), + width: '0', + top: '0', + left: '0', + height: '0', + position: 'absolute', + color: muted ? theme('colors.vanilla.400') : theme('colors.juniper') + } +} + +const DEFAULT = function (theme) { + return { + color: theme('colors.vanilla.100'), + '[class~="lead"]': { + color: theme('colors.vanilla.400') + }, + a: { + color: theme('colors.juniper.DEFAULT'), + textDecoration: 'normal', + fontWeight: theme('fontWeight.medium') + }, + 'a:hover': { + textDecoration: 'underline' + }, + strong: { + color: theme('colors.vanilla.100'), + fontWeight: theme('fontWeight.semibold') + }, + 'ol > li': { + position: 'relative' + }, + 'ol > li::before': { + content: 'counter(list-item) "."', + position: 'absolute', + fontWeight: theme('fontWeight.normal'), + color: theme('colors.vanilla.400') + }, + ul: { + listStyleType: 'none', + listStyle: 'none' + }, + 'ul > li': { + position: 'relative' + }, + 'ul > li::before': { + ...BEFORE_LIST_ITEM(theme) + }, + hr: { + borderColor: theme('colors.ink.100'), + borderTopWidth: 1 + }, + blockquote: { + fontWeight: theme('fontWeight.medium'), + lineHeight: theme('lineHeight.normal'), + fontSize: theme('fontSize.lg'), + fontStyle: 'normal', + color: 'inherit', + borderLeftWidth: '0.25rem', + borderLeftColor: theme('colors.juniper'), + quotes: '"\\201C""\\201D""\\2018""\\2019"' + }, + 'blockquote p:first-of-type::before': { + content: 'open-quote' + }, + 'blockquote p:last-of-type::after': { + content: 'close-quote' + }, + 'h1, h2, h3, h4, h5': { + color: theme('colors.vanilla.100') + }, + h1: { + fontWeight: theme('fontWeight.bold') + }, + h2: { + fontWeight: theme('fontWeight.semibold') + }, + 'h3, h4, h5': { + fontWeight: theme('fontWeight.medium') + }, + 'figure figcaption': { + color: theme('colors.vanilla.400'), + textAlign: 'center', + [`@media (min-width: ${theme('screens.sm')})`]: { + textAlign: 'left' + } + }, + 'figure img': { + borderRadius: `${theme('spacing.1')}` + }, + code: { + color: theme('colors.vanilla.300'), + fontWeight: theme('fontWeight.semibold') + }, + 'code::before, code::after': { + content: '""' + }, + 'a code': { + color: theme('colors.juniper.DEFAULT') + }, + pre: { + color: theme('colors.vanilla.400'), + backgroundColor: theme('colors.ink.200'), + overflowX: 'auto', + borderRadius: `${theme('spacing.1')}` + }, + 'pre code': { + backgroundColor: 'transparent', + borderWidth: '0', + borderRadius: '0', + padding: '0', + fontWeight: theme('fontWeight.normal'), + color: 'inherit', + fontSize: 'inherit', + fontFamily: 'inherit', + lineHeight: 'inherit' + }, + 'pre code::before, pre code::after': { + content: '""' + }, + table: { + width: '100%', + tableLayout: 'auto', + textAlign: 'left' + }, + thead: { + color: theme('colors.vanilla.400') + }, + 'thead th': { + fontWeight: theme('fontWeight.medium'), + verticalAlign: 'bottom' + }, + 'thead, tbody tr': { + borderBottomWidth: theme('spacing.px'), + borderBottomColor: theme('colors.ink.100') + }, + 'tbody tr:last-child': { + borderBottomWidth: '0' + }, + 'tbody td': { + verticalAlign: 'top' + } + } +} + +const SMALL_SCREEN_CSS = function (theme) { + return { + 'ul > li::before': { + ...BEFORE_LIST_ITEM(theme) + }, + pre: { + color: theme('colors.vanilla.400'), + backgroundColor: theme('colors.ink.200'), + overflowX: 'auto', + borderRadius: '0' + } + } +} + +const LARGE_SCREEN_CSS = function (theme) { + return { + 'ul > li::before': { + ...BEFORE_LIST_ITEM(theme) + }, + 'figure img': { + borderRadius: `${theme('spacing.1')}` + } + } +} + +const MUTED = function (theme) { + return { + 'ul > li::before': { + ...BEFORE_LIST_ITEM(theme, true) + }, + a: { + color: theme('colors.vanilla.400'), + textDecoration: 'normal', + fontWeight: theme('fontWeight.medium') + } + } +} + +const RTE = function (theme) { + return { + p: { + fontSize: theme('fontSize.sm'), + lineHeight: theme('lineHeight.relaxed'), + marginBottom: 0, + marginTop: 0 + }, + '*:not(p)': { + marginBottom: 0, + marginTop: 0 + }, + '*:not(p) + *:not(p), *:not(p) + p': { + marginBottom: '10px', + marginTop: '8px' + }, + 'p + p': { + marginBottom: '10px', + marginTop: '10px' + }, + 'li + li': { + marginBottom: 0, + marginTop: 0 + }, + h1: { + fontSize: theme('fontSize.xl'), + fontWeight: theme('fontWeight.medium'), + lineHeight: theme('lineHeight["9"]') + }, + h2: { + fontSize: theme('fontSize.lg'), + fontWeight: theme('fontWeight.medium'), + lineHeight: theme('lineHeight["7"]') + }, + 'h1::before, h2::before, h3::before, h4::before, h5::before': { + content: '""', + marginRight: theme('spacing.0') + }, + code: { + backgroundColor: theme('colors.ink.200'), + fontSize: theme('fontSize.sm'), + padding: theme('padding["1"]'), + borderRadius: theme('borderRadius.md'), + fontWeight: theme('fontWeight.normal') + }, + 'code::before, code::after': { + content: '""' + }, + a: { + fontWeight: theme('fontWeight.normal'), + color: theme('colors.juniper.DEFAULT') + }, + blockquote: { + fontWeight: theme('fontWeight.normal'), + borderLeftColor: theme('colors.vanilla.400'), + paddingLeft: theme('padding["2"]') + }, + 'blockquote p:first-of-type::before, blockquote p:last-of-type::after': { + content: 'none' + }, + 'ol > li > :last-child, ul > li > :last-child': { + marginBottom: 0 + }, + 'ol > li > :first-child, ul > li > :first-child': { + marginTop: 0 + }, + 'ol > li::before': { + color: 'inherit', + marginTop: theme('margin["-px"]'), + fontSize: theme('fontSize.sm') + }, + 'ol > li': { + paddingLeft: theme('padding["6"]') + }, + 'ul > li::before': { + color: 'inherit', + content: 'none' + }, + 'ul > li': { + paddingLeft: 0 + }, + 'ul > li > p': { + paddingLeft: theme('padding["2"]'), + margin: 0 + }, + ul: { + listStyleType: 'disc', + paddingLeft: theme('padding["4"]') + }, + 'ul ul, ul ol, ol ul, ol ol': { + margin: 0 + } + } +} + +module.exports = { + BEFORE_LIST_ITEM, + DEFAULT, + SMALL_SCREEN_CSS, + LARGE_SCREEN_CSS, + MUTED, + RTE +} diff --git a/command/analyzer/dryrun/render/views/yarn.lock b/command/analyzer/dryrun/render/views/yarn.lock new file mode 100644 index 00000000..03a03bfc --- /dev/null +++ b/command/analyzer/dryrun/render/views/yarn.lock @@ -0,0 +1,449 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@tailwindcss/typography@^0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.2.tgz#24b069dab24d7a2467d01aca0dd432cb4b29f0ee" + integrity sha512-coq8DBABRPFcVhVIk6IbKyyHUt7YTEC/C992tatFB+yEx5WGBQrCgsSFjxHUr8AWXphWckadVJbominEduYBqw== + dependencies: + lodash.castarray "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.merge "^4.6.2" + +acorn-node@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.0.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +color-name@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ== + +detective@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" + integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== + dependencies: + acorn-node "^1.8.2" + defined "^1.0.0" + minimist "^1.2.6" + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +fast-glob@^3.2.11: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +lilconfig@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" + integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== + +lodash.castarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115" + integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +postcss-import@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" + integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00" + integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== + dependencies: + camelcase-css "^2.0.1" + +postcss-load-config@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" + integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== + dependencies: + lilconfig "^2.0.5" + yaml "^1.10.2" + +postcss-nested@5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" + integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== + dependencies: + postcss-selector-parser "^6.0.6" + +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.6: + version "6.0.10" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" + integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.4.14: + version "8.4.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" + integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +resolve@^1.1.7, resolve@^1.22.0: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tailwindcss@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.1.4.tgz#64b09059805505902139fa805d97046080bd90b9" + integrity sha512-NrxbFV4tYsga/hpWbRyUfIaBrNMXDxx5BsHgBS4v5tlyjf+sDsgBg5m9OxjrXIqAS/uR9kicxLKP+bEHI7BSeQ== + dependencies: + arg "^5.0.2" + chokidar "^3.5.3" + color-name "^1.1.4" + detective "^5.2.1" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.11" + glob-parent "^6.0.2" + is-glob "^4.0.3" + lilconfig "^2.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.14" + postcss-import "^14.1.0" + postcss-js "^4.0.0" + postcss-load-config "^3.1.4" + postcss-nested "5.0.6" + postcss-selector-parser "^6.0.10" + postcss-value-parser "^4.2.0" + quick-lru "^5.1.1" + resolve "^1.22.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +xtend@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yaml@^1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== diff --git a/command/analyzer/dryrun/result.go b/command/analyzer/dryrun/result.go new file mode 100644 index 00000000..8ce5afb1 --- /dev/null +++ b/command/analyzer/dryrun/result.go @@ -0,0 +1,35 @@ +package dryrun + +import ( + "errors" + "os" + "path" + "strings" +) + +// Writes the analysis results into a file with the name specified in the `fileName` parameter +func (a *AnalyzerDryRun) writeAnalysisResults(buf []byte, fileName string) (err error) { + analysisResultsPath := path.Join(a.Client.AnalysisOpts.AnalysisResultsPath, fileName) + + // Ref: https://deepsource.io/directory/analyzers/go/issues/GSC-G305 + if !strings.Contains(string(buf), "..") { + // Check if the results file already exists + if _, err := os.Stat(analysisResultsPath); err != nil { + if errors.Is(err, os.ErrNotExist) { + // Create the file and allocate permissions to it + _, err := os.Create(analysisResultsPath) + if err != nil { + return err + } + if err = os.Chmod(analysisResultsPath, 0o600); err != nil { + return err + } + } + } + + if err = os.WriteFile(path.Join(a.Client.AnalysisOpts.AnalysisResultsPath, fileName), buf, 0o600); err != nil { + return err + } + } + return nil +} diff --git a/command/analyzer/dryrun/run.go b/command/analyzer/dryrun/run.go new file mode 100644 index 00000000..83a8a0ab --- /dev/null +++ b/command/analyzer/dryrun/run.go @@ -0,0 +1,249 @@ +package dryrun + +import ( + "fmt" + "os" + "path" + "runtime" + "time" + + "github.com/fatih/color" + "github.com/morikuni/aec" + "github.com/spf13/cobra" + + "github.com/deepsourcelabs/cli/analysis/config" + "github.com/deepsourcelabs/cli/analyzers/backend/docker" + "github.com/deepsourcelabs/cli/command/analyzer/dryrun/render" + "github.com/deepsourcelabs/cli/types" + "github.com/deepsourcelabs/cli/utils" +) + +// Variables to hold the value of CODE_PATH and TOOLBOX_PATH to be injected +// into the analysis container. +var ( + containerCodePath string + containerToolBoxPath string + analysisConfigName string = "analysis_config" + analysisResultsName string = "analysis_results" + analysisConfigExt string = ".json" + analysisResultsExt string = ".json" +) + +// The params required while running the Analysis locally. +type AnalyzerDryRun struct { + Client *docker.DockerClient // The client to be used for all docker related ops. + DockerImageName string // The docker image supplied by the user that needs to be pulled and run. + DockerImagePlatform string // The platform for which the Docker image is to be built. + RemoteSource bool // True if the source to be analyzed is a remote VCS repository. + SourcePath string // The path of the directory of source code to be analyzed. + TempCloneDirectory string // The temporary directory where the source of the remote VCS will be cloned to. + TempToolBoxDirectory string // The temporary directory where the analysis_config is present. + AnalysisFiles []string // The list of analysis files. + AnalysisConfig *config.AnalysisConfig // The analysis_config.json file containing the meta for analysis. + AnalysisResult types.AnalysisResult // The analysis result received after post processing Analyzer report. + Spinner *utils.SpinnerUtils // The spinner command line utility. + RenderOpts render.ResultRenderOpts +} + +func NewCmdAnalyzerRun() *cobra.Command { + // Setting the current working directory as the default path of the source to be analyzed. + cwd, _ := os.Getwd() + + // Initializing the run params and setting defaults. + opts := AnalyzerDryRun{ + Spinner: &utils.SpinnerUtils{}, + SourcePath: cwd, + RemoteSource: false, + } + + cmd := &cobra.Command{ + Use: "dry-run", + Short: "Dry run the DeepSource Analyzer locally", + Args: utils.MaxNArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + if len(args) > 0 { + opts.SourcePath = args[0] + } + if err := opts.AnalyzerRun(); err != nil { + return fmt.Errorf("Failed to run the Analyzer. Error: %s", err) + } + return nil + }, + } + + // --output-file/ -o flag. + cmd.Flags().StringVarP(&opts.TempToolBoxDirectory, "output-file", "o", "", "The path of analysis results") + + // --image flag. + cmd.Flags().StringVar(&opts.DockerImageName, "image", "", "The Analyzer docker image to build and run") + + // --platform flag; used for explicitly setting up the build platform for the Docker image. Defaults to linux/ if not provided. + defaultPlatform := fmt.Sprintf("linux/%s", runtime.GOARCH) + cmd.Flags().StringVar(&opts.DockerImagePlatform, "platform", defaultPlatform, "Explicitly set build platform for Docker image.") + + return cmd +} + +// AnalyzerRun runs the Analyzer locally on a certain directory or repository. +func (a *AnalyzerDryRun) AnalyzerRun() (err error) { + err = a.createDockerClient() + if err != nil { + return err + } + + err = a.Client.SetupClient() + if err != nil { + return err + } + + if a.DockerImageName != "" { + a.Spinner.StartSpinnerWithLabel(fmt.Sprintf("Pulling Analyzer image (%s)...", a.DockerImageName), fmt.Sprintf("Pulled Analyzer image (%s)", a.DockerImageName)) + + // Pull image from registry. + ctxCancelFunc, pullRespReader, pullErr := a.Client.PullImage(a.DockerImageName) + + // Cancel context and close the reader before exiting. + if pullRespReader != nil { + defer pullRespReader.Close() + } + if ctxCancelFunc != nil { + defer ctxCancelFunc() + } + + if pullErr != nil { + a.Spinner.StopSpinnerWithError("Failed to pull the Analyzer image", pullErr) + return pullErr + } + + // Check the docker pull response. + if err = docker.CheckPullResponse(pullRespReader, false); err != nil { + a.Spinner.StopSpinnerWithError("Failed to pull the Analyzer image", err) + return err + } + + // Setup image name and image tag. The image name and image tag are used while building the container. + a.Client.ImageName, a.Client.ImageTag = a.parseImageName() + + // Notify the user that we use the latest tag. + // TODO: Setting the suffix doesn't work. + if a.Client.ImageTag == "latest" { + a.Spinner.SetSuffix("(no image tag found, using latest)") + } + a.Spinner.StopSpinner() + } else { + // Building the Analyzer image. + a.Spinner.StartSpinnerWithLabel("Building Analyzer image...", "Built Analyzer image") + ctxCancelFunc, buildRespReader, buildError := a.Client.BuildAnalyzerDockerImage() + + // Cancel the build context and close the reader before exiting this function. + if buildRespReader != nil { + defer buildRespReader.Close() + } + defer ctxCancelFunc() + if buildError != nil { + a.Spinner.StopSpinnerWithError("Failed to build the Analyzer image", err) + return buildError + } + + // Check the docker build response. + if err = docker.CheckBuildResponse(buildRespReader, false); err != nil { + a.Spinner.StopSpinnerWithError("Failed to build the Analyzer image", err) + return err + } + a.Spinner.StopSpinner() + } + + // Create temporary toolbox directory to store analysis config and later analyis results. + // If already passed through --output-file flag, use that one. + a.Spinner.StartSpinnerWithLabel("Creating temporary toolbox directory...", "Temporary toolbox directory created") + if err = a.createTemporaryToolBoxDir(); err != nil { + a.Spinner.StopSpinnerWithError("Failed to create temporary toolbox directory", err) + return err + } + a.Spinner.StopSpinner() + + // Resolve the path of source code to be analyzed based on the user input. + a.Spinner.StartSpinnerWithLabel("Resolving the path of source code to be analyzed...", "") + if a.Client.AnalysisOpts.HostCodePath, err = a.resolveAnalysisCodePath(); err != nil { + a.Spinner.StopSpinnerWithError("Failed to resolve path of source code to be analyzed", err) + return err + } + a.Spinner.StopSpinner() + + // Generate the analysis_config.json file. + // Also, write the analysis_config data into a temp /toolbox directory to be mounted into the container. + a.Spinner.StartSpinnerWithLabel("Generating analysis config...", fmt.Sprint("Analysis config (analysis_config.json) generated at ", + path.Join(a.TempToolBoxDirectory, "analysis_config.json"))) + if err = a.prepareAnalysisConfig(); err != nil { + a.Spinner.StopSpinnerWithError("Failed to generate analysis_config.json", err) + return err + } + a.Spinner.StopSpinner() + + // Write the analysis_config.json to local toolbox directory. + if err = a.writeAnalysisConfig(); err != nil { + a.Spinner.StopSpinnerWithError("Failed to write analysis_config.json", err) + return err + } + a.Spinner.StopSpinner() + + // Starts the Docker container which analyzes the code and stores the analysis results + // in a variable. + fmt.Println(aec.Apply("[+] Starting the Analysis container", aec.LightYellowF)) + analysisStartTime := time.Now() + if err = a.Client.StartDockerContainer(); err != nil { + return err + } + + // Fetch the analysis results. + a.Spinner.StartSpinnerWithLabel("Fetching Analyzer report...", "Successfully fetched Analyzer report") + analysisResultBuf, analysisResultFileName, err := a.Client.FetchAnalysisResults() + if err != nil { + a.Spinner.StopSpinnerWithError("Failed to fetch Analyzer report", err) + return err + } + a.Spinner.StopSpinner() + analysisEndTime := time.Now() + + // Write the analysis results to the file. + a.Spinner.StartSpinnerWithLabel("Writing Analyzer report...", fmt.Sprintf("Analyzer report written to %s", + path.Join(a.Client.AnalysisOpts.AnalysisResultsPath, analysisResultFileName))) + if err = a.writeAnalysisResults(analysisResultBuf, analysisResultFileName); err != nil { + a.Spinner.StopSpinnerWithError("Failed to write Analyzer report", err) + return err + } + a.Spinner.StopSpinner() + + // Process the analyzer report once it is received. + a.Spinner.StartSpinnerWithLabel("Processing Analyzer report...", "Successfully processed Analyzer report") + if a.AnalysisResult, err = a.processAnalyzerReport(analysisResultBuf); err != nil { + a.Spinner.StopSpinnerWithError("Failed to process Analyzer report", err) + return err + } + a.Spinner.StopSpinner() + fmt.Println(aec.Apply(fmt.Sprintf("[✔] Issues after processing: %d", len(a.AnalysisResult.Issues)), aec.LightGreenF)) + + // Prompt the user to press return in order to check results on browser. + c := color.New(color.FgCyan, color.Bold) + c.Printf("Press enter to view the analysis results in the browser...") + fmt.Scanln() + + // Showcase the analysis results on the browser. + // Initialize `ResultRenderOpts` with some of the initial data that needs to be rendered. + a.RenderOpts = render.ResultRenderOpts{ + PageTitle: fmt.Sprintf("Issues | %s", a.Client.AnalysisOpts.AnalyzerShortcode), + AnalyzerShortcode: a.Client.AnalysisOpts.AnalyzerShortcode, + AnalysisResultData: render.ResultData{ + AnalysisResult: a.AnalysisResult, + SourcePath: a.SourcePath, + }, + SelectedCategory: "all", + IssueCategoryNameMap: types.IssueCategoryMap, + MetricNameMap: types.MetricMap, + Summary: render.RunSummary{ + AnalysisStartTime: analysisStartTime, + AnalysisEndTime: analysisEndTime, + }, + } + return a.RenderOpts.RenderResultsOnBrowser(&a.RenderOpts) +} diff --git a/command/analyzer/dryrun/source.go b/command/analyzer/dryrun/source.go new file mode 100644 index 00000000..d4d88d1b --- /dev/null +++ b/command/analyzer/dryrun/source.go @@ -0,0 +1,50 @@ +package dryrun + +import ( + "bytes" + "fmt" + "path/filepath" + + "github.com/go-git/go-git/v5" +) + +/* Resolves the code to be analyzed by the Analyzer. + * The user passes it as an argument to the command `deepsource analyzer run ` + * Parse the argument and check if its a URL, if not then resolve the local directory path */ +func (a *AnalyzerDryRun) resolveAnalysisCodePath() (string, error) { + // Check if the source path is a valid VCS URL + if isValidUrl(a.SourcePath) { + tempCloneDir, err := a.cloneRemoteSource() + if err != nil { + return "", err + } + a.SourcePath = tempCloneDir + } else { + // Resolve the path if it is a relative path + a.SourcePath, _ = filepath.Abs(a.SourcePath) + } + return a.SourcePath, nil +} + +// Clones the remote repository which is to be analyzed +func (a *AnalyzerDryRun) cloneRemoteSource() (string, error) { + var err error + buf := bytes.NewBuffer(nil) + a.RemoteSource = true + + a.Spinner.SetSuffix(fmt.Sprintf("Creating temporary directory to clone %s", a.SourcePath)) + if a.TempCloneDirectory, err = createTemporaryDirectory("code"); err != nil { + return "", err + } + + // Clone the repository to a temporary directory + a.Spinner.SetSuffix(fmt.Sprintf("Cloning %s to %s", a.SourcePath, a.TempCloneDirectory)) + if _, err := git.PlainClone(a.TempCloneDirectory, false, &git.CloneOptions{ + URL: a.SourcePath, + Depth: 1, + Progress: buf, + }); err != nil { + return "", err + } + return a.TempCloneDirectory, nil +} diff --git a/command/analyzer/dryrun/source_test.go b/command/analyzer/dryrun/source_test.go new file mode 100644 index 00000000..c5c56150 --- /dev/null +++ b/command/analyzer/dryrun/source_test.go @@ -0,0 +1,140 @@ +package dryrun + +import ( + "bytes" + "encoding/json" + "os" + "strings" + "testing" + + "github.com/deepsourcelabs/cli/analyzers/backend/docker" + "github.com/deepsourcelabs/cli/utils" +) + +func TestPrepareAnalysisConfig(t *testing.T) { + type TestCase struct { + CodePath string + ToolBoxPath string + AnalyzerName string + } + + testCases := []TestCase{ + { + CodePath: "testdata/project1/", + ToolBoxPath: "testdata/toolbox/project1/", + AnalyzerName: "acme", + }, + { + CodePath: "testdata/project2/", + ToolBoxPath: "testdata/toolbox/project2/", + AnalyzerName: "vim", + }, + { + CodePath: "testdata/project3/", + ToolBoxPath: "testdata/toolbox/project3/", + AnalyzerName: "emacs", + }, + { + CodePath: "testdata/project4/", + ToolBoxPath: "testdata/toolbox/project4/", + AnalyzerName: "nano", + }, + } + containerCodePath := "/code" + + for _, tc := range testCases { + dc := docker.DockerClient{ + AnalysisOpts: docker.AnalysisParams{ + AnalyzerName: tc.AnalyzerName, + HostCodePath: tc.CodePath, + ContainerCodePath: containerCodePath, + }, + } + + opts := AnalyzerDryRun{ + Client: &dc, + TempToolBoxDirectory: tc.ToolBoxPath, + } + + err := opts.prepareAnalysisConfig() + if err != nil { + t.Errorf("Failed to verify analysis config generation. Error:%s", err) + } + modifyAnalysisConfigFilepaths(opts.AnalysisConfig, opts.Client.AnalysisOpts.HostCodePath, opts.Client.AnalysisOpts.ContainerCodePath) + + receivedAnalysisConfig, err := json.Marshal(opts.AnalysisConfig) + if err != nil { + t.Errorf("Failed to marshal the received config to JSON. Error:%s", err) + } + + expectedAnalysisConfig, err := os.ReadFile(tc.ToolBoxPath + "analysis_config.json") + if err != nil { + t.Errorf("Failed to read the expected analysis config. Error:%s", err) + } + + if !bytes.Equal(receivedAnalysisConfig, expectedAnalysisConfig) { + t.Errorf("Received invalid analysis config. Expected %s\nGot %s\n", string(expectedAnalysisConfig), string(receivedAnalysisConfig)) + } + + } +} + +func TestResolveAnalysisCodePath(t *testing.T) { + type TestCase struct { + Path string + Response string + RemoteSource bool + } + + testCases := []TestCase{ + { + Path: "/Users/phoenix/codes/", + Response: "/Users/phoenix/codes", + RemoteSource: false, + }, + { + Path: "/tmp/helloworld/", + Response: "/tmp/helloworld", + RemoteSource: false, + }, + { + Path: "https://github.com/deepsourcelabs/shifty", + Response: "TEMPDIR", + RemoteSource: true, + }, + { + Path: "https://gitlab.com/deepsourcelabs/demo-go", + Response: "TEMPDIR", + RemoteSource: true, + }, + { + Path: "github.com/deepsourcelabs/cli", + Response: "TEMPDIR", + RemoteSource: false, + }, + } + + for _, tc := range testCases { + opts := AnalyzerDryRun{ + SourcePath: tc.Path, + Spinner: &utils.SpinnerUtils{}, + } + resolvedPath, err := opts.resolveAnalysisCodePath() + if err != nil && err.Error() != "authentication required" { + t.Errorf("Failed to resolve the analysis source path. Error:%s", err) + } + + if opts.RemoteSource != tc.RemoteSource { + t.Errorf("Expected RemoteSource for %s to be %v. Got %v", tc.Path, tc.RemoteSource, opts.RemoteSource) + } + if resolvedPath != tc.Response { + if tc.Response != "TEMPDIR" { + t.Errorf("Failed to resolve the local analysis source path. Wanted %s. Got %s", tc.Response, resolvedPath) + } + + if !strings.HasPrefix(resolvedPath, os.Getenv("TEMPDIR")) { + t.Errorf("Failed to resolve the remote analysis source path. Wanted a temp directory. Got %s", resolvedPath) + } + } + } +} diff --git a/command/analyzer/dryrun/test/expected_analysis_results.json b/command/analyzer/dryrun/test/expected_analysis_results.json new file mode 100644 index 00000000..99743779 --- /dev/null +++ b/command/analyzer/dryrun/test/expected_analysis_results.json @@ -0,0 +1 @@ +{"issues":[{"range":{"start":{"line":4,"character":0},"end":{"line":4,"character":0}},"code":"I001","message":"Found a TODO comment","relatedInformation":[{"location":{"uri":"analyzer.toml","range":{"start":{"line":4,"character":0},"end":{"line":4,"character":0}}},"message":"Found a TODO comment"}]},{"range":{"start":{"line":1,"character":0},"end":{"line":1,"character":0}},"code":"I001","message":"Found a TODO comment","relatedInformation":[{"location":{"uri":"I001.toml","range":{"start":{"line":1,"character":0},"end":{"line":1,"character":0}}},"message":"Found a TODO comment"}]},{"range":{"start":{"line":4,"character":0},"end":{"line":4,"character":0}},"code":"I001","message":"Found a TODO comment","relatedInformation":[{"location":{"uri":"I001.toml","range":{"start":{"line":4,"character":0},"end":{"line":4,"character":0}}},"message":"Found a TODO comment"}]},{"range":{"start":{"line":48,"character":0},"end":{"line":48,"character":0}},"code":"I001","message":"Found a TODO comment","relatedInformation":[{"location":{"uri":"main.go","range":{"start":{"line":48,"character":0},"end":{"line":48,"character":0}}},"message":"Found a TODO comment"}]},{"range":{"start":{"line":15,"character":0},"end":{"line":15,"character":0}},"code":"I001","message":"Found a TODO comment","relatedInformation":[{"location":{"uri":"utils.go","range":{"start":{"line":15,"character":0},"end":{"line":15,"character":0}}},"message":"Found a TODO comment"}]},{"range":{"start":{"line":32,"character":0},"end":{"line":32,"character":0}},"code":"I001","message":"Found a TODO comment","relatedInformation":[{"location":{"uri":"utils.go","range":{"start":{"line":32,"character":0},"end":{"line":32,"character":0}}},"message":"Found a TODO comment"}]},{"range":{"start":{"line":54,"character":0},"end":{"line":54,"character":0}},"code":"I001","message":"Found a TODO comment","relatedInformation":[{"location":{"uri":"utils.go","range":{"start":{"line":54,"character":0},"end":{"line":54,"character":0}}},"message":"Found a TODO comment"}]}],"is_passed":true,"errors":null,"extra_data":null} \ No newline at end of file diff --git a/command/analyzer/dryrun/test/run_test.go b/command/analyzer/dryrun/test/run_test.go new file mode 100644 index 00000000..adf44d5d --- /dev/null +++ b/command/analyzer/dryrun/test/run_test.go @@ -0,0 +1,117 @@ +package dryrun + +import ( + "bytes" + "fmt" + "log" + "os" + "os/exec" + "path" + "testing" + + "github.com/fsnotify/fsnotify" + "github.com/google/go-cmp/cmp" +) + +// Execute the command from there +func TestAnalyzerRun(t *testing.T) { + var analysisCmd *exec.Cmd + /* ============================================================================= + // Copying the todo-checker directory to $APP_PATH for the integration tests + /* ============================================================================= */ + cwd, _ := os.Getwd() + + analyzerPath := path.Join(cwd, "todo-checker") + appPath := os.Getenv("APP_PATH") + if appPath == "" { + appPath = "/app/" + } + + // Copy all the files to APP_PATH + cmd := exec.Command("cp", "-a", analyzerPath+"/.", ".") + cmd.Dir = appPath + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + // Run the command + err := cmd.Run() + _, outErr := stdout.String(), stderr.String() + if err != nil { + t.Fatalf("Failed to copy the analyzer code to %s. Error:%s\n%s", appPath, err, outErr) + } + + // Create the file to watch. + os.Create(fmt.Sprintf("%s/analysis_results.json", appPath)) + + // Watch for the output file for any changes. Once the file is written, kill the dry-run process. + watcher, err := fsnotify.NewWatcher() + if err != nil { + t.Error(err) + } + defer watcher.Close() + + done := make(chan bool) + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + t.Log("Event:", event) + if event.Op&fsnotify.Write == fsnotify.Write { + t.Log("Modified file:", event.Name) + + // Kill the process once the file has been modified(the results have been received). + log.Println("KILLING THE PROCESS") + analysisCmd.Process.Kill() + + /* ============================================================================= + // Compare the results + /* ============================================================================= */ + receivedAnalysisResults, err := os.ReadFile(path.Join(appPath, "analysis_results.json")) + if err != nil { + t.Errorf("Failed to read the received analysis result. Error:%s", err) + } + expectedAnalysisResults, err := os.ReadFile(path.Join(cwd, "expected_analysis_results.json")) + if err != nil { + t.Errorf("Failed to read the expected analysis result. Error:%s", err) + } + if !bytes.Equal(receivedAnalysisResults, expectedAnalysisResults) { + diff := cmp.Diff(receivedAnalysisResults, expectedAnalysisResults) + t.Errorf("Failed to verify analysis results. Expected: %s\n==========\nGot: %s\n=========\nDiff:%s", expectedAnalysisResults, receivedAnalysisResults, diff) + } + done <- true + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("Error:", err) + } + } + }() + + err = watcher.Add(fmt.Sprintf("%s/analysis_results.json", appPath)) + if err != nil { + t.Error(err) + } + + /* ============================================================================= + * Run the analyzer dry-run command on the testdata/todo-checker directory + * ============================================================================= */ + + analysisCmd = exec.Command("/tmp/deepsource", "analyzer", "dry-run", analyzerPath, "--output-file", appPath) + analysisCmd.Dir = appPath + analysisCmd.Env = []string{"TOOLBOX_PATH=/toolbox", "CODE_PATH=/code"} + + var stdout1, stderr1 bytes.Buffer + analysisErr := analysisCmd.Run() + _, outErr1 := stdout1.String(), stderr1.String() + if err != nil { + t.Errorf("Failed to run the analyzer. Error:%s\n%s", analysisErr, outErr1) + } + <-done +} diff --git a/command/analyzer/dryrun/test/todo-checker/.deepsource/analyzer/analyzer.toml b/command/analyzer/dryrun/test/todo-checker/.deepsource/analyzer/analyzer.toml new file mode 100644 index 00000000..056b137e --- /dev/null +++ b/command/analyzer/dryrun/test/todo-checker/.deepsource/analyzer/analyzer.toml @@ -0,0 +1,24 @@ +version = "1" +name = "Todo comments checker" +shortcode = "@siddhant-deepsource/todo-comments-checker" +description = "Finds the TODO comments in codebase." +category = "language" +tags = [] + +[urls] + source = "" + documentation = "" + bug_tracker = "" + +[environment_variables] + +[analysis] + command = "/app/todo-checker" + +[build] + builder = "" + dockerfile = "" + script = "" + +[test] + command = "" diff --git a/command/analyzer/dryrun/test/todo-checker/.deepsource/analyzer/issues/I001.toml b/command/analyzer/dryrun/test/todo-checker/.deepsource/analyzer/issues/I001.toml new file mode 100644 index 00000000..99057c2c --- /dev/null +++ b/command/analyzer/dryrun/test/todo-checker/.deepsource/analyzer/issues/I001.toml @@ -0,0 +1,5 @@ +title = "Found a TODO comment" +category = "doc" +description = """ +Here's a TODO comment you might wanna check. +""" diff --git a/command/analyzer/dryrun/test/todo-checker/Dockerfile b/command/analyzer/dryrun/test/todo-checker/Dockerfile new file mode 100644 index 00000000..1b84531d --- /dev/null +++ b/command/analyzer/dryrun/test/todo-checker/Dockerfile @@ -0,0 +1,27 @@ +FROM golang:1.17.6-alpine3.15 + +USER root + +RUN mkdir -p /toolbox /app /code /artifacts /macrocode + +ENV CODE_PATH=/code +ENV TOOLBOX_PATH=/toolbox +RUN apk add --no-cache openssh shadow git grep + +COPY . /app + +WORKDIR /app +RUN git config --global url.git@github.com:.insteadOf https://github.com/ +RUN go env -w GOPRIVATE=github.com/siddhant-deepsource/* + +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/todo-checker . + +# delete any user with uid 1000, then create the runner user with uid 1000 +RUN adduser -D -u 1000 runner runner && \ + mkdir -p /.cache /code /home/runner/go && \ + chmod -R ugo+rwx /toolbox /.cache /code /home/runner && \ + chown -R runner:runner /toolbox /.cache /code /home/runner + +WORKDIR /app + +USER runner diff --git a/command/analyzer/dryrun/test/todo-checker/config.go b/command/analyzer/dryrun/test/todo-checker/config.go new file mode 100644 index 00000000..065ad554 --- /dev/null +++ b/command/analyzer/dryrun/test/todo-checker/config.go @@ -0,0 +1,22 @@ +package main + +import ( + "encoding/json" + "os" + "path" +) + +func readAnalysisConfig() (*AnalysisConfig, error) { + analysisConfig := AnalysisConfig{} + + data, err := os.ReadFile(path.Join(toolboxPath, "analysis_config.json")) + if err != nil { + return nil, err + } + + err = json.Unmarshal(data, &analysisConfig) + if err != nil { + return nil, err + } + return &analysisConfig, nil +} diff --git a/command/analyzer/dryrun/test/todo-checker/go.mod b/command/analyzer/dryrun/test/todo-checker/go.mod new file mode 100644 index 00000000..6783f05e --- /dev/null +++ b/command/analyzer/dryrun/test/todo-checker/go.mod @@ -0,0 +1,5 @@ +module github.com/siddhant-deepsource/2do-checker + +go 1.17 + +require github.com/karrick/godirwalk v1.16.1 diff --git a/command/analyzer/dryrun/test/todo-checker/go.sum b/command/analyzer/dryrun/test/todo-checker/go.sum new file mode 100644 index 00000000..c2c11623 --- /dev/null +++ b/command/analyzer/dryrun/test/todo-checker/go.sum @@ -0,0 +1,2 @@ +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= diff --git a/command/analyzer/dryrun/test/todo-checker/main.go b/command/analyzer/dryrun/test/todo-checker/main.go new file mode 100644 index 00000000..93d486a9 --- /dev/null +++ b/command/analyzer/dryrun/test/todo-checker/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" +) + +var ( + issues []Diagnostic + analysisConf *AnalysisConfig + toolboxPath string = os.Getenv("TOOLBOX_PATH") +) + +func main() { + var err error + + cmd := exec.Command("ls", "-ltr") + cmd.Dir = toolboxPath + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err = cmd.Run() + outStr, _ := stdout.String(), stderr.String() + if err != nil { + fmt.Println(err) + } + fmt.Println(outStr) + + log.Println("Parsing analysis_config.json...") + if analysisConf, err = readAnalysisConfig(); err != nil { + log.Fatalln(err) + } + + for _, path := range analysisConf.Files { + content, err := ioutil.ReadFile(string(path.URI)) + if err != nil { + log.Fatal(err) + } + lines := strings.Split(string(content), "\n") + for lineNumber, line := range lines { + if strings.Contains(line, "TODO") { + createIssue(string(path.URI), lineNumber, 0) + } + } + } + macroAnalysisResult := prepareResult() + + if writeError := writeMacroResult(¯oAnalysisResult); writeError != nil { + log.Fatalln("Error occured while writing results:", writeError) + } +} diff --git a/command/analyzer/dryrun/test/todo-checker/types.go b/command/analyzer/dryrun/test/todo-checker/types.go new file mode 100644 index 00000000..5c91dd0f --- /dev/null +++ b/command/analyzer/dryrun/test/todo-checker/types.go @@ -0,0 +1,94 @@ +package main + +/////////////////////////////// +// Diagnostic related types // +///////////////////////////// + +type DiagnosticSeverity int + +const ( + Error DiagnosticSeverity = 1 + Warning DiagnosticSeverity = 2 + Information DiagnosticSeverity = 3 + Hint DiagnosticSeverity = 4 +) + +type Position struct { + Line int `json:"line"` + Character int `json:"character"` +} +type Range struct { + Start Position `json:"start"` + End Position `json:"end"` +} + +type Location struct { + URI string `json:"uri"` + Range Range `json:"range"` +} + +type DiagnosticRelatedInformation struct { + Location Location `json:"location"` + Message string `json:"message"` +} + +type Diagnostic struct { + Range Range `json:"range"` + Severity DiagnosticSeverity `json:"severity,omitempty"` + Code string `json:"code,omitempty"` + Source string `json:"source,omitempty"` + Message string `json:"message"` + + /** + * An array of related diagnostic information, e.g. when symbol-names within + * a scope collide all definitions can be marked via this property. + * var a,b + * a := 2 + * Issues in line 1 and 2 are related. + */ + RelatedInformation []DiagnosticRelatedInformation `json:"relatedInformation"` +} + +type Namespace struct { + Key string `json:"key"` + Value float64 `json:"value"` +} + +type Metric struct { + MetricCode string `json:"metric_code"` + Namespaces []Namespace `json:"namespaces"` +} + +type AnalysisResult struct { + Issues []Diagnostic `json:"issues"` + Metrics []Metric `json:"metrics,omitempty"` + IsPassed bool `json:"is_passed"` + Errors []Diagnostic `json:"errors"` + ExtraData interface{} `json:"extra_data"` +} + +//////////////////////////// +// Analysis Config Types // +//////////////////////////// + +type AnalysisConfig struct { + Files []TextDocumentItem `json:"files"` + TestFiles []TextDocumentItem `json:"test_files"` + ExcludedFiles []TextDocumentItem `json:"excluded_files"` + ExcludePatterns []string `json:"exclude_patterns"` + TestPatterns []string `json:"test_patterns"` + AnalyzerMeta interface{} `json:"analyzer_meta"` +} + +////////////////////////////// +// Document identity types // +///////////////////////////// + +type DocumentURI string + +type TextDocumentItem struct { + URI DocumentURI `json:"uri"` + LanguageID string `json:"languageID,omitempty"` + Version int `json:"version,omitempty"` + Text string `json:"text,omitempty"` +} diff --git a/command/analyzer/dryrun/test/todo-checker/utils.go b/command/analyzer/dryrun/test/todo-checker/utils.go new file mode 100644 index 00000000..c888fb64 --- /dev/null +++ b/command/analyzer/dryrun/test/todo-checker/utils.go @@ -0,0 +1,80 @@ +package main + +import ( + "encoding/json" + "os" + "path" +) + +func createIssue(filePath string, lineNumber, _ int) { + vcsPath := path.Base(filePath) + actualLineNumber := lineNumber + 1 + + // issue := Diagnostic{ + // Code: "I001", + // Title: "Possible TODO comment found", + // Location: Location{ + // Path: vcsPath, + // Position: Position{ + // Begin: Coordinate{ + // Line: actualLineNumber, + // Column: column, + // }, + // End: Coordinate{ + // Line: actualLineNumber, + // }, + // }, + // }, + // } + + issue := Diagnostic{ + Code: "I001", + Message: "Found a TODO comment", + Range: Range{ + Start: Position{ + Line: actualLineNumber, + }, + End: Position{ + Line: actualLineNumber, + }, + }, + RelatedInformation: []DiagnosticRelatedInformation{ + { + Location: Location{ + URI: vcsPath, + Range: Range{ + Start: Position{ + Line: actualLineNumber, + }, + End: Position{ + Line: actualLineNumber, + }, + }, + }, + Message: "Found a TODO comment", + }, + }, + } + issues = append(issues, issue) +} + +func prepareResult() AnalysisResult { + result := AnalysisResult{} + result.Issues = issues + result.IsPassed = false + + if len(issues) > 0 { + result.IsPassed = true + } + + return result +} + +func writeMacroResult(result *AnalysisResult) error { + resultJSON, err := json.Marshal(result) + if err != nil { + return err + } + + return os.WriteFile(path.Join(toolboxPath, "analysis_results.json"), resultJSON, 0o777) +} diff --git a/command/analyzer/dryrun/testdata/project1/.deepsource.toml b/command/analyzer/dryrun/testdata/project1/.deepsource.toml new file mode 100644 index 00000000..e6ec67e5 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/.deepsource.toml @@ -0,0 +1,30 @@ +version = 1 + +exclude_patterns = ["bind.*", "unsafe_*.*"] + +test_patterns = ["*_test.go"] + +[[analyzers]] +name = "go" +enabled = true + + [analyzers.meta] + import_paths = ["github.com/deepsourcelabs/demo-go"] + +[[analyzers]] +name = "docker" +enabled = true + + [analyzers.meta] + dockerfile_paths = [ + "./env/Dockerfile_dev", + "./env/Dockerfile_prod" + ] + +[[transformers]] +name = "gofmt" +enabled = true + +[[analyzers]] +name = "secrets" +enabled = true diff --git a/command/analyzer/dryrun/testdata/project1/.gitignore b/command/analyzer/dryrun/testdata/project1/.gitignore new file mode 100644 index 00000000..f80df8dd --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/.gitignore @@ -0,0 +1 @@ +demo-go diff --git a/command/analyzer/dryrun/testdata/project1/README.md b/command/analyzer/dryrun/testdata/project1/README.md new file mode 100644 index 00000000..80eb7283 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/README.md @@ -0,0 +1,31 @@ +

+ +

+ +

+ Documentation | + Get Started | + Discuss +

+ +

+ DeepSource helps you ship good quality code. +

+ +

+ +--- + +# Demo repository - Go + +This repository demonstrates sample issues in Go code raised by DeepSource. + +[![DeepSource](https://deepsource.io/gh/deepsourcelabs/demo-go.svg/?label=active+issues&show_trend=true)](https://deepsource.io/gh/deepsourcelabs/demo-go/?ref=repository-badge) + +### Report + +[https://deepsource.io/gh/deepsourcelabs/demo-go/issues/](https://deepsource.io/gh/deepsourcelabs/demo-go/issues/) + +### Documentation + +[https://deepsource.io/docs/analyzer/go.html](https://deepsource.io/docs/analyzer/go.html) diff --git a/command/analyzer/dryrun/testdata/project1/bind.go b/command/analyzer/dryrun/testdata/project1/bind.go new file mode 100644 index 00000000..611cca39 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/bind.go @@ -0,0 +1,20 @@ +package main + +import ( + "log" + "net" + + "golang.org/x/crypto/ssh" +) + +func connect() { + l, err := net.Listen("tcp", "0.0.0.0:2000") + if err != nil { + log.Fatal(err) + } + defer l.Close() +} + +func sshConfigure() { + _ = ssh.InsecureIgnoreHostKey() +} diff --git a/command/analyzer/dryrun/testdata/project1/cmd.go b/command/analyzer/dryrun/testdata/project1/cmd.go new file mode 100644 index 00000000..495140b6 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/cmd.go @@ -0,0 +1,108 @@ +package main + +import ( + "bytes" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" + "sync" + "time" +) + +func runCmd(command string, args []string, env []string, cmdDir string) (string, string, error) { + cmd := exec.Command(command, args...) + cmd.Dir = cmdDir + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, env...) + + var stdoutBuf, stderrBuf bytes.Buffer + stdoutIn, _ := cmd.StdoutPipe() + stderrIn, _ := cmd.StderrPipe() + + stdout := io.MultiWriter(os.Stdout, &stdoutBuf) + stderr := io.MultiWriter(os.Stderr, &stderrBuf) + + startTime := time.Now() + + err := cmd.Start() + if err != nil { + return string(stdoutBuf.Bytes()), string(stderrBuf.Bytes()), err + } + + log.Println("==> EXEC COMMAND " + command) + log.Println("-> ARGS: " + strings.Join(args[:], " ")) + log.Println("-> DIR: " + cmdDir) + log.Println("-> STDOUT/STDERR: ..") + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + _, _ = io.Copy(stdout, stdoutIn) + wg.Done() + }() + + _, _ = io.Copy(stderr, stderrIn) + wg.Wait() + + err = cmd.Wait() + + if err != nil { + log.Println("-> ERROR: ", err.Error()) + if exitError, ok := err.(*exec.ExitError); ok { + log.Println("-> EXIT CODE: ", exitError.ExitCode()) + + log.Println("-> TIME TAKEN: ", time.Now().Sub(startTime)/time.Millisecond) + log.Println("==> END EXEC COMMAND " + command + "\n") + + return string(stdoutBuf.Bytes()), string(stderrBuf.Bytes()), err + } + } + + log.Println("-> TIME TAKEN: ", time.Now().Sub(startTime)/time.Millisecond) + log.Println("=> END EXEC COMMAND " + command + "\n") + + if bytes.Compare(stdoutBuf.Bytes(), stderrBuf.Bytes()) == 0 { + log.Println("Both stdout and stderr have same value") + } + + return string(stdoutBuf.Bytes()), string(stderrBuf.Bytes()), nil +} + +func ExampleOpenFile() { + f, err := os.OpenFile("notes.txt", os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + log.Fatal(err) + } + + if err := f.Close(); err != nil { + log.Fatal(err) + } +} + +func createTempFile() { + tmpFile, _ := os.Create("emptyFile.txt") + log.Println(tmpFile) +} + +func ExampleTempFile() { + err := ioutil.WriteFile("/tmp/demo-go", []byte("deepsource-for-go"), 0644) + if err != nil { + panic(err) + } +} + +func ExampleCreate() { + f, _ := os.Create("exampe.txt") + f.Close() +} + +func EmptyPath(path string) bool { + if len(path) == 0 { + return true + } + return false +} diff --git a/command/analyzer/dryrun/testdata/project1/code.go b/command/analyzer/dryrun/testdata/project1/code.go new file mode 100644 index 00000000..ab19040f --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/code.go @@ -0,0 +1,161 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "strings" +) + +type GenericInterface interface { + DeliversTo(string) bool +} + +type Address struct { + City string `json:"city"` + PostalCode string `json:"postal_code"` + FirstLine string `json:"first_line"` + SecondLine string `json:"second_line"` +} + +type Seller struct { + Name string `json:"name"` + Address Address `json:"address"` + IsVerified bool `json:"is_verified"` +} + +type Dealer struct { + Name string `json:"dealer_name"` + Address Address `json:"dealer_address"` + IsVerified bool `json:"dealer_is_verified"` +} + +func PromoteSellerToDealer(seller Seller) Dealer { + dealer := Dealer{ + Name: seller.Name, + Address: seller.Address, + IsVerified: seller.IsVerified, + } + return dealer +} + +func (seller Seller) DeliversTo(city string) bool { + return city == seller.Address.City +} + +func HasAnySellersFromCity(sellers []Seller, city string) { + city = city + + for i := range sellers { + if sellers[i].Address.City == city { + fmt.Printf("Found seller %s in %s city", sellers[i].Name, city) + } + if sellers[i].IsVerified == true { + fmt.Printf("This seller is verified\n") + } + deliveryPostalRange := "5600" + if strings.Index(sellers[i].Address.PostalCode, deliveryPostalRange) != -1 { + fmt.Printf("This seller does not deliver to the given postal code range") + } + break + } + + allSellers := make([]Seller, len(sellers)) + + if allSellers != nil && len(allSellers) == 0 { + fmt.Println("allSellers is empty") + } + + for i, x := range sellers { + allSellers[i] = x + } + + combinedSellers := []Seller{} + + for _, x := range allSellers { + combinedSellers = append(sellers, x) + } + for _, x := range combinedSellers { + fmt.Println(x) + } +} + +type Product struct { + Name string `json:"name"` + Price int `json:"price"` + Description string `json:"description"` + Seller Seller `json:"seller"` +} + +func (product Product) DeliversTo(city string) bool { + delivers := product.Seller.DeliversTo(city) + if delivers { + return true + } else { + return false + } +} + +func NewProduct(name string, price int, description string, seller Seller) Product { + return Product{ + Name: name, + Price: price, + Description: description, + Seller: seller, + } +} + +func (product Product) Update(updatedProduct Product) { + product.Name = updatedProduct.Name + product.Price = updatedProduct.Price + product.Description = updatedProduct.Description + product.Seller = updatedProduct.Seller +} + +func LoadProducts(jsonPath string) ([]Product, error) { + productBytes, err := ioutil.ReadFile(jsonPath) + products := []Product{} + err = json.Unmarshal(productBytes, &products) + + if nil != err { + fmt.Println(err) + return products, err + } + + return products, nil +} + +func WriteProducts(productsSold []Product, productsLeft []Product, jsonPath string) error { + allProducts := []Product{} + + for _, product := range productsSold { + allProducts = append(allProducts, product) + } + + for i := range productsLeft { + productsLeft = append(allProducts, productsLeft[i]) + } + + fmt.Println(allProducts[:]) + + if len(allProducts) == 0 { + return errors.New(fmt.Sprintf("%d products found. This is an error.", len(allProducts))) + } + + return nil +} + +func traverseProducts() { + var Products [2048]byte + for _, product := range Products { + fmt.Println(product) + } + + for index := 0; index < len(Products); index++ { + productMap := make([][1024]byte, index) + for product, productIndex := range productMap { + fmt.Println(product, "indexed as", productIndex) + } + } +} diff --git a/command/analyzer/dryrun/testdata/project1/go.mod b/command/analyzer/dryrun/testdata/project1/go.mod new file mode 100644 index 00000000..ce0e09ea --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/go.mod @@ -0,0 +1,5 @@ +module github.com/deepsourcelabs/demo-go + +go 1.13 + +require golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582 diff --git a/command/analyzer/dryrun/testdata/project1/go.sum b/command/analyzer/dryrun/testdata/project1/go.sum new file mode 100644 index 00000000..caf21c9d --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/go.sum @@ -0,0 +1,10 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582 h1:0WDrJ1E7UolDk1KhTXxxw3Fc8qtk5x7dHP431KHEJls= +golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582/go.mod h1:tCqSYrHVcf3i63Co2FzBkTCo2gdF6Zak62921dSfraU= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201113234701-d7a72108b828 h1:htWEtQEuEVJ4tU/Ngx7Cd/4Q7e3A5Up1owgyBtVsTwk= +golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/command/analyzer/dryrun/testdata/project1/hex_example.go b/command/analyzer/dryrun/testdata/project1/hex_example.go new file mode 100644 index 00000000..a637bcdf --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/hex_example.go @@ -0,0 +1,9 @@ +package main + +func HexLiteral() bool { + x := 0xFff + y := 0xFFF + z := 0xfff + + return (x == y) && (y == z) +} diff --git a/command/analyzer/dryrun/testdata/project1/html.go b/command/analyzer/dryrun/testdata/project1/html.go new file mode 100644 index 00000000..1ee059d5 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/html.go @@ -0,0 +1,18 @@ +package main + +import ( + "html/template" + "os" +) + +const tmpl = "" + +func badHTMLTemplate() { + a := "something from another place" + t := template.Must(template.New("ex").Parse(tmpl)) + v := map[string]interface{}{ + "Title": "Test World", + "Body": template.HTML(a), + } + t.Execute(os.Stdout, v) +} diff --git a/command/analyzer/dryrun/testdata/project1/main.go b/command/analyzer/dryrun/testdata/project1/main.go new file mode 100644 index 00000000..da032eac --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/main.go @@ -0,0 +1,36 @@ +package main + +import "fmt" + +const ( + productPath string = "/tmp/products.json" + sellerPath string = "/tmp/sellers.json" +) + +func main() { + s := Seller{ + Name: "SomeSeller", + Address: Address{ + City: "Gotham", + }, + } + var i GenericInterface + i = &s + + givenCity := "Gotham" + + switch i.(type) { + case interface{}: + fmt.Println("What to do") + case Product: + fmt.Println(i.(Product).DeliversTo(givenCity)) + case Seller: + fmt.Println(i.(Seller).DeliversTo(givenCity)) + } +} + +func appendData() { + var s []string + s = append(s, productPath) + s = append(s, sellerPath) +} diff --git a/command/analyzer/dryrun/testdata/project1/sync_example.go b/command/analyzer/dryrun/testdata/project1/sync_example.go new file mode 100644 index 00000000..e19d3e16 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/sync_example.go @@ -0,0 +1,24 @@ +package main + +import ( + "sync" + "testing" +) + +var dataLarge []byte + +const size = 64 * 1024 // 65536 + +func benchmarkLargeSizePool(b *testing.B) { + bytePool := sync.Pool{ + New: func() interface{} { + b := make([]byte, size) + return b + }, + } + for i := 0; i < b.N; i++ { + dataLarge = bytePool.Get().([]byte) + _ = dataLarge + bytePool.Put(dataLarge) + } +} diff --git a/command/analyzer/dryrun/testdata/project1/unsafe_example.go b/command/analyzer/dryrun/testdata/project1/unsafe_example.go new file mode 100644 index 00000000..71b02463 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/unsafe_example.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "unsafe" +) + +type Fake struct{} + +func (Fake) Good() {} + +func unsafeCode() { + unsafeM := Fake{} + unsafeM.Good() + intArray := [...]int{1, 2} + fmt.Printf("\nintArray: %v\n", intArray) + intPtr := &intArray[0] + fmt.Printf("\nintPtr=%p, *intPtr=%d.\n", intPtr, *intPtr) + addressHolder := uintptr(unsafe.Pointer(intPtr)) + unsafe.Sizeof(intArray[0]) + intPtr = (*int)(unsafe.Pointer(addressHolder)) + fmt.Printf("\nintPtr=%p, *intPtr=%d.\n\n", intPtr, *intPtr) +} diff --git a/command/analyzer/dryrun/testdata/project1/unsafe_header.go b/command/analyzer/dryrun/testdata/project1/unsafe_header.go new file mode 100644 index 00000000..7ebf7979 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/unsafe_header.go @@ -0,0 +1,18 @@ +package main + +import "net/http" + +func exposeHeader(url string) error { + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return err + } + req.Header.Set("Server", "Apache/2.4.1 (Unix)") + + _, err = http.DefaultClient.Do(req) + if err != nil { + return err + } + + return nil +} diff --git a/command/analyzer/dryrun/testdata/project1/weak_crypto.go b/command/analyzer/dryrun/testdata/project1/weak_crypto.go new file mode 100644 index 00000000..32739427 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project1/weak_crypto.go @@ -0,0 +1,24 @@ +package main + +import ( + "crypto/md5" + "crypto/rand" + "crypto/rsa" + "fmt" + "os" +) + +func makeMD5Hash() { + for _, arg := range os.Args { + fmt.Printf("%x - %s\n", md5.Sum([]byte(arg)), arg) + } +} + +func generateRSAKey() { + // Generate Private Key + pvk, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + fmt.Println(err) + } + fmt.Println(pvk) +} diff --git a/command/analyzer/dryrun/testdata/project2/.deepsource.toml b/command/analyzer/dryrun/testdata/project2/.deepsource.toml new file mode 100644 index 00000000..21806759 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project2/.deepsource.toml @@ -0,0 +1,9 @@ +version = 1 + +[[analyzers]] +name = "ruby" +enabled = true + +[[analyzers]] +name = "secrets" +enabled = true diff --git a/command/analyzer/dryrun/testdata/project2/.rubocop.yml b/command/analyzer/dryrun/testdata/project2/.rubocop.yml new file mode 100644 index 00000000..48a6a58a --- /dev/null +++ b/command/analyzer/dryrun/testdata/project2/.rubocop.yml @@ -0,0 +1,2 @@ +require: + - rubocop-performance diff --git a/command/analyzer/dryrun/testdata/project2/README.md b/command/analyzer/dryrun/testdata/project2/README.md new file mode 100644 index 00000000..bc14dbea --- /dev/null +++ b/command/analyzer/dryrun/testdata/project2/README.md @@ -0,0 +1,31 @@ +

+ +

+ +

+ Documentation | + Get Started | + Discuss +

+ +

+ DeepSource helps you ship good quality code. +

+ +

+ +--- + +# Demo repository - Ruby + +This repository demonstrates sample issues in Ruby code raised by DeepSource. + +[![DeepSource](https://deepsource.io/gh/deepsourcelabs/demo-ruby.svg/?label=active+issues&show_trend=true&token=GUiSpAVeA3v_yYb35jVt0I1V)](https://deepsource.io/gh/deepsourcelabs/demo-ruby/?ref=repository-badge) + +### Report + +[https://deepsource.io/gh/deepsourcelabs/demo-ruby/issues/](https://deepsource.io/gh/deepsourcelabs/demo-ruby/issues/) + +### Documentation + +[https://deepsource.io/docs/analyzer/ruby.html](https://deepsource.io/docs/analyzer/ruby.html) diff --git a/command/analyzer/dryrun/testdata/project2/code.rb b/command/analyzer/dryrun/testdata/project2/code.rb new file mode 100644 index 00000000..462b5476 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project2/code.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +# Raises "bad ordering of magic comments" +# frozen_string_literal: true +# encoding: ascii + +# Raises "use of deprecated BigDecimal.new" +a = { 'hello' => 'world', 'testing' => BigDecimal.new(123.456, 3) } + +# Raises "`while`/`until` detected in `begin` block" +begin + do_something +end while a == b + +# Raises "multiple comparison detected" +x < y < z +10 <= x <= 20 + +# Raises "empty rescue block detected" +begin + bar +rescue +end + +# Raises "redundant `else`-clause detected" +if bar +else +end + +# Raises "unused method arguments detected" +def some_method(bar) + puts 'Hello' +end + +# Raises "unreachable code detected" +def some_method + return + do_something +end + +# Raises "top level return with argument detected" +return 1 + +# Raises "duplicate elsif block detected" +if x == 1 + do_something +elsif x == 1 + do_something_else +end + +# Raises "Deprecated way of initializing OpenSSL::Cipher and OpenSSL::Digest" +OpenSSL::Cipher::AES.new(128, :GCM) + +# Raises "put empty method definitions on a single line" +# Also raises "multiple methods with same name in the same scope" as we have a +# method with same name above +def some_method +end + +# Raises "Invalid annotation keyword format detected" +def foo + # TODO Replace this with bar + do_something +end + +# Raises "Use `Range#cover?` instead of `Range#include?`" +(1..9).include?(5) + +my_hash = {} +# Raises "Hash merging can be replaced by hash key assignment" +my_hash.merge!('key': value) + + +# Raises "Use `size` instead of `count`" +[1, 2, 3].count diff --git a/command/analyzer/dryrun/testdata/project3/.deepsource.toml b/command/analyzer/dryrun/testdata/project3/.deepsource.toml new file mode 100644 index 00000000..066c4f06 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project3/.deepsource.toml @@ -0,0 +1,9 @@ +version = 1 + +[[analyzers]] +name = "secrets" +enabled = true + +[[analyzers]] +name = "rust" +enabled = true diff --git a/command/analyzer/dryrun/testdata/project3/.gitignore b/command/analyzer/dryrun/testdata/project3/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project3/.gitignore @@ -0,0 +1 @@ +/target diff --git a/command/analyzer/dryrun/testdata/project3/Cargo.lock b/command/analyzer/dryrun/testdata/project3/Cargo.lock new file mode 100644 index 00000000..0d6ba639 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project3/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "demo-rust" +version = "0.1.0" diff --git a/command/analyzer/dryrun/testdata/project3/Cargo.toml b/command/analyzer/dryrun/testdata/project3/Cargo.toml new file mode 100644 index 00000000..a2eccfc6 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project3/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "demo-rust" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/command/analyzer/dryrun/testdata/project3/README.md b/command/analyzer/dryrun/testdata/project3/README.md new file mode 100644 index 00000000..6f4d6b7f --- /dev/null +++ b/command/analyzer/dryrun/testdata/project3/README.md @@ -0,0 +1,29 @@ +

+ +

+ +

+ Documentation | + Get Started | + Discuss +

+ +

+ DeepSource helps you ship good quality code. +

+ +--- + +# Demo repository - Rust + +This repository demonstrates sample issues in Rust code raised by DeepSource. + +[![DeepSource](https://deepsource.io/gh/deepsourcelabs/demo-rust.svg/?label=active+issues&show_trend=true&token=altPfxyH5fAdAyqUZ7zDX7dx)](https://deepsource.io/gh/deepsourcelabs/demo-rust/?ref=repository-badge) + +### Report + +[https://deepsource.io/gh/deepsourcelabs/demo-rust/issues/](https://deepsource.io/gh/deepsourcelabs/demo-rust/issues/) + +### Documentation + +[https://deepsource.io/docs/analyzer/rust.html](https://deepsource.io/docs/analyzer/rust.html) diff --git a/command/analyzer/dryrun/testdata/project3/src/main.rs b/command/analyzer/dryrun/testdata/project3/src/main.rs new file mode 100644 index 00000000..8fd50214 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project3/src/main.rs @@ -0,0 +1,101 @@ +//! A two player tic-tac-toe game to showcase some of +//! the issues caught by DeepSource's Rust analyzer + +use std::{convert::identity as id, {fmt}}; + +#[derive(Debug, PartialEq, Copy, Clone)] +enum Player { + PlayerX, + PlayerO, + EmptyPlayer, +} + +impl fmt::Display for Player { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::PlayerX => write!(f, "X"), + Self::PlayerO => write!(f, "O"), + Self::EmptyPlayer => write!(f, "E"), + } + } +} + +#[derive(Debug, PartialEq)] +struct Board { + cells: [[Player; 3]; 3], +} + +impl fmt::Display for Board { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for row in &self.cells { + for col in row { + write!(f, " {} ", col)?; + } + write!(f, "\n")?; + } + Ok(()) + } +} + +impl Board { + pub fn new() -> Self { + let cells = [[Player::EmptyPlayer; 03]; 03]; + Self { cells } + } + + #[must_use] + pub fn update_state(&mut self, player: Player, position: usize) { + let position = position - 1; + let (x, y) = (position / 3, position % 3); + self.cells[x][y] = player; + } + + pub fn get_state(&self, position: usize) -> Player { + let position = position - 1; + let (x, y) = (position / 3, position % 3); + self.cells[x][y] + } + + pub fn is_winner(&self, target: Player) -> bool { + let rows = [[1, 2, 3], [4, 5, 6], [7, 8, 9]].iter(); + let columns = [[1, 4, 7], [2, 5, 8], [3, 6, 9]].iter(); + let diags = [[1, 5, 9], [3, 5, 7]].iter(); + + rows.chain(columns) + .chain(diags) + .map(|triple| { + triple + .iter() + .map(|&i| self.get_state(i)) + .fold(true, |acc, x| acc && x == target) + }) + .any(id) + } +} + +fn main() { + let mut board = Board::new(); + + // a sample tic-tac-toe game run + // O|O|O + // X|O|E + // X|E|X + board.update_state(Player::PlayerO, 5); + board.update_state(Player::PlayerX, 4); + board.update_state(Player::PlayerO, 3); + board.update_state(Player::PlayerX, 7); + board.update_state(Player::PlayerO, 1); + board.update_state(Player::PlayerX, 9); + board.update_state(Player::PlayerO, 2); + + println!("{}", board); + + let winner = if board.is_winner(Player::PlayerO) { + "O" + } else if !board.is_winner(Player::PlayerX) { + "Draw" + } else { + "X" + }; + println!("Winner: {}", winner); +} diff --git a/command/analyzer/dryrun/testdata/project4/.deepsource.toml b/command/analyzer/dryrun/testdata/project4/.deepsource.toml new file mode 100644 index 00000000..a0c44f91 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/.deepsource.toml @@ -0,0 +1,21 @@ +version = 1 + +[[analyzers]] +name = "python" +enabled = true + + [analyzers.meta] + runtime_version = "3.x.x" + type_checker = "mypy" + +[[transformers]] +name = "black" +enabled = true + +[[analyzers]] +name = "test-coverage" +enabled = true + +[[analyzers]] +name = "secrets" +enabled = true diff --git a/command/analyzer/dryrun/testdata/project4/.github/workflows/CI.yml b/command/analyzer/dryrun/testdata/project4/.github/workflows/CI.yml new file mode 100644 index 00000000..f6151ad2 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/.github/workflows/CI.yml @@ -0,0 +1,36 @@ +name: CI + +on: + pull_request: + push: + branches: + - master + +jobs: + run-tests: + runs-on: [ubuntu-latest] + + env: + DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Set up python3.9 + uses: actions/setup-python@v2 + with: + python-version: '3.9' + + - name: Run tests + run: | + pip install pytest-cov django + pytest --cov=./ --cov-report=xml + + - name: Report test coverage to DeepSource + run: | + curl https://deepsource.io/cli | sh + ./bin/deepsource report --analyzer test-coverage --key python --value-file ./coverage.xml diff --git a/command/analyzer/dryrun/testdata/project4/.gitignore b/command/analyzer/dryrun/testdata/project4/.gitignore new file mode 100644 index 00000000..f7174864 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/.gitignore @@ -0,0 +1,125 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/command/analyzer/dryrun/testdata/project4/Pipfile b/command/analyzer/dryrun/testdata/project4/Pipfile new file mode 100644 index 00000000..b5846df1 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/Pipfile @@ -0,0 +1,11 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] + +[requires] +python_version = "3.8" diff --git a/command/analyzer/dryrun/testdata/project4/README.md b/command/analyzer/dryrun/testdata/project4/README.md new file mode 100644 index 00000000..51bf367c --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/README.md @@ -0,0 +1,31 @@ +

+ +

+ +

+ Documentation | + Get Started | + Discuss +

+ +

+ DeepSource helps you ship good quality code. +

+ +

+ +--- + +# Demo repository - Python + +This repository demonstrates sample issues in Python code raised by DeepSource. + +[![DeepSource](https://deepsource.io/gh/deepsourcelabs/demo-python.svg/?label=active+issues&show_trend=true)](https://deepsource.io/gh/deepsourcelabs/demo-python/?ref=repository-badge) + +### Report + +[https://deepsource.io/gh/deepsourcelabs/demo-python/issues/](https://deepsource.io/gh/deepsourcelabs/demo-python/issues/) + +### Documentation + +[https://deepsource.io/docs/analyzer/python.html](https://deepsource.io/docs/analyzer/python.html) diff --git a/command/analyzer/dryrun/testdata/project4/assignment.py b/command/analyzer/dryrun/testdata/project4/assignment.py new file mode 100644 index 00000000..f401a7da --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/assignment.py @@ -0,0 +1,3 @@ +*FIRST = [1, 2, 3] +(*FIRST,) = [1, 2, 3] +*FIRST, a, b = [1, 2, 3] diff --git a/command/analyzer/dryrun/testdata/project4/demo_code.py b/command/analyzer/dryrun/testdata/project4/demo_code.py new file mode 100644 index 00000000..68313e68 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/demo_code.py @@ -0,0 +1,141 @@ +import random +import pdb +import sys as sys +import os +import subprocess +import ssl + + +# from django.db.models.expressions import RawSQL + +AWS_SECRET_KEY = "d6s$f9g!j8mg7hw?n&2" + +class BaseNumberGenerator: + """Declare a method -- `get_number`.""" + + def __init__(self): + self.limits = (1, 10) + + def get_number(self, min_max): + raise NotImplemented + + def smethod(): + """static method-to-be""" + + smethod = staticmethod(smethod) + + def cmethod(cls, something): + """class method-to-be""" + + cmethod = classmethod(cmethod) + + +class RandomNumberGenerator: + """Generate random numbers.""" + + def limits(self): + return self.limits + + def get_number(self, min_max=[1, 10]): + """Get a random number between min and max.""" + assert all([isinstance(i, int) for i in min_max]) + return random.randint(*min_max) + + +class ImaginaryNumber: + """Class to represent an imaginary number.""" + def __init__(self): + self.real = 0 + self.imaginary = 1 + + def __getattr__(self, key): + return key + + +def main(options: dict = {}) -> str: + pdb.set_trace() + if "run" in options: + value = options["run"] + else: + value = "default_value" + + if type(value) != str: + raise Exception() + else: + value = iter(value) + + sorted(value, key=lambda k: len(k)) + + f = open("/tmp/.deepsource.toml", "r") + f.write("config file.") + f.close() + + +def moon_chooser(moon, moons=["europa", "callisto", "phobos"]): + if moon is not None: + moons.append(moon) + + return random.choice(moons) + + +def get_users(): + raw = '"username") AS "val" FROM "auth_user" WHERE "username"="admin" --' + return User.objects.annotate(val=RawSQL(raw, [])) + + +def tar_something(): + context = ssl._create_stdlib_context() + os.tempnam("dir1") + subprocess.Popen("/bin/chown *", shell=True) + o.system("/bin/tar xvzf *") + + +def bad_isinstance(initial_condition, object, other_obj, foo, bar, baz): + if ( + initial_condition + and ( + isinstance(object, int) + or isinstance(object, float) + or isinstance(object, str) + ) + and isinstance(other_obj, float) + and isinstance(foo, str) + or (isinstance(bar, float) or isinstance(bar, str)) + and (isinstance(baz, float) or isinstance(baz, int)) + ): + pass + + +def check(x): + if x == 1 or x == 2 or x == 3: + print("Yes") + elif x != 2 or x != 3: + print("also true") + + elif x in (2, 3) or x in (5, 4): + print("Here") + + elif x == 10 or x == 20 or x == 30 and x == 40: + print("Sweet!") + + elif x == 10 or x == 20 or x == 30: + print("Why even?") + + +def chained_comparison(): + a = 1 + b = 2 + c = 3 + return a < b and b < c + +def wrong_callable(): + number = ImaginaryNumber() + if hasattr(number, '__call__'): + return number() + +if __name__ == "__main__": + args = ["--disable", "all"] + for i in range(len(args)): + has_truthy = True if args[i] else False + if has_truthy: + break diff --git a/command/analyzer/dryrun/testdata/project4/django_issues.py b/command/analyzer/dryrun/testdata/project4/django_issues.py new file mode 100644 index 00000000..a0d19177 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/django_issues.py @@ -0,0 +1,10 @@ +import datetime + +from django.http import HttpResponse +from django.views.decorators.http import require_http_methods + +@require_http_methods(["GET", "POST"]) # Sensitive +def current_datetime(request): + now = datetime.datetime.now() + html = "It is %s." % now + return HttpResponse(html) diff --git a/command/analyzer/dryrun/testdata/project4/duplicate_bases_class.py b/command/analyzer/dryrun/testdata/project4/duplicate_bases_class.py new file mode 100644 index 00000000..ad1dfe8b --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/duplicate_bases_class.py @@ -0,0 +1,19 @@ +import abc + + +class Base: + def __init__(self): + self.base = 1 + + +class BaseOne: + def __init__(self): + self.base_one = 2 + + +class Child(Base, BaseOne, Base, BaseOne): + """Some Child class""" + + +class ChildOne(Base, BaseOne, Base, BaseOne, abc.ABC, abc.ABCMeta, abc.ABCMeta): + """Class with duplicate bases""" diff --git a/command/analyzer/dryrun/testdata/project4/miscellaneous.py b/command/analyzer/dryrun/testdata/project4/miscellaneous.py new file mode 100644 index 00000000..a3b4fa7e --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/miscellaneous.py @@ -0,0 +1,23 @@ +from utils import get_next, render_to_frontend, render_bg + +class Orange: + """Represents the fruit orange.""" + orange = "#FFA500" + + # Other class implementations + + def get_orange(self): + return self.orange + +def render(): + fruit = Orange() + render_to_frontend(fruit.orange) # Rendering a color, but one can get confused with the fruit + render_bg(fruit.get_orange) + +def play_with_magic_numbers(): + magic_numbers = {0, 1, 1, 2, 3, 5} + + for elem in magic_numbers: + magic_numbers.add(get_next(elem)) + return magic_numbers + diff --git a/command/analyzer/dryrun/testdata/project4/return_not_implemented.py b/command/analyzer/dryrun/testdata/project4/return_not_implemented.py new file mode 100644 index 00000000..c9d7e580 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/return_not_implemented.py @@ -0,0 +1,25 @@ +class RealNumber: + """Represents a real number.""" + def __init__(self, val): + self.val = val + + def __add__(self, other): + raise NotImplementedError + +class ComplexNumber: + """Represents an imaginary number.""" + def __init__(self, x, y): + self.x = x + self.y = y + + def __add__(self, other): + return self.val + other.val + + def __radd__(self, other): + res = (self.x + other.val, self.y) + return res + +if __name__ == "__main__": + complex_num = ComplexNumber(2, 5) + real_num = RealNumber(32) + return real_num + complex_num diff --git a/command/analyzer/dryrun/testdata/project4/security.py b/command/analyzer/dryrun/testdata/project4/security.py new file mode 100644 index 00000000..1916df29 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/security.py @@ -0,0 +1,32 @@ +import sqlite3 +import requests + +class ResidentsDb: + def __init__(self, table_name, mapping_function, duration): + """Set location on disk data cache will reside. + Also sets the table name and refresh duration + """ + self.table_name = table_name + self.mapping_function = mapping_function + self.disk_location = DISK_LOCATION_DEFAULT + self.duration = duration + self.conn = None + self.cursor = None + + def open(self): + """ Opens connection to sqlite database.""" + self.conn = sqlite3.connect(self.dbname) + self.cursor = self.conn.cursor() + + def get_id_from_name(self, name): + """Get id of resident from name.""" + data = self.cursor.execute("SELECT id FROM userdata WHERE Name ={};".format(name)) + self.conn.commit() + return data + +def fetch_version(request): + """Fetch verison of bgmi.""" + version = requests.get( + "https://pypi.python.org/pypi/bgmi/json", verify=False + ).json()["info"]["version"] + return version diff --git a/command/analyzer/dryrun/testdata/project4/tests/__init__.py b/command/analyzer/dryrun/testdata/project4/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/command/analyzer/dryrun/testdata/project4/tests/test_code.py b/command/analyzer/dryrun/testdata/project4/tests/test_code.py new file mode 100644 index 00000000..159c78df --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/tests/test_code.py @@ -0,0 +1,11 @@ +import unittest +from demo_code import RandomNumberGenerator + + +def test_random_number_generator(): + """Test random number generator.""" + assert RandomNumberGenerator().get_number() + +class Tests(unittest.TestCase): + def my_test(self, arg1, arg2): + self.assertEquals(arg1, arg2) diff --git a/command/analyzer/dryrun/testdata/project4/type_checks.py b/command/analyzer/dryrun/testdata/project4/type_checks.py new file mode 100644 index 00000000..811da988 --- /dev/null +++ b/command/analyzer/dryrun/testdata/project4/type_checks.py @@ -0,0 +1,7 @@ +def greet_all(names: list[str]) -> None: + for name in names: + print('Hello ' + name) + +if __name__ == "__main__": + heights = [5.5, 6, 5.9] + greet_all(heights) diff --git a/command/analyzer/dryrun/testdata/toolbox/project1/analysis_config.json b/command/analyzer/dryrun/testdata/toolbox/project1/analysis_config.json new file mode 100644 index 00000000..c40f9464 --- /dev/null +++ b/command/analyzer/dryrun/testdata/toolbox/project1/analysis_config.json @@ -0,0 +1 @@ +{"files":[{"uri":"/code/.deepsource.toml"},{"uri":"/code/README.md"},{"uri":"/code/cmd.go"},{"uri":"/code/code.go"},{"uri":"/code/go.mod"},{"uri":"/code/go.sum"},{"uri":"/code/hex_example.go"},{"uri":"/code/html.go"},{"uri":"/code/main.go"},{"uri":"/code/sync_example.go"},{"uri":"/code/weak_crypto.go"}],"test_files":null,"excluded_files":[{"uri":"/code/bind.go"},{"uri":"/code/unsafe_example.go"},{"uri":"/code/unsafe_header.go"}],"exclude_patterns":["bind.*","unsafe_*.*"],"test_patterns":["*_test.go"],"analyzer_meta":null} \ No newline at end of file diff --git a/command/analyzer/dryrun/testdata/toolbox/project2/analysis_config.json b/command/analyzer/dryrun/testdata/toolbox/project2/analysis_config.json new file mode 100644 index 00000000..b0b85435 --- /dev/null +++ b/command/analyzer/dryrun/testdata/toolbox/project2/analysis_config.json @@ -0,0 +1 @@ +{"files":[{"uri":"/code/.deepsource.toml"},{"uri":"/code/.rubocop.yml"},{"uri":"/code/README.md"},{"uri":"/code/code.rb"}],"test_files":null,"excluded_files":null,"exclude_patterns":null,"test_patterns":null,"analyzer_meta":null} \ No newline at end of file diff --git a/command/analyzer/dryrun/testdata/toolbox/project3/analysis_config.json b/command/analyzer/dryrun/testdata/toolbox/project3/analysis_config.json new file mode 100644 index 00000000..5fe311ff --- /dev/null +++ b/command/analyzer/dryrun/testdata/toolbox/project3/analysis_config.json @@ -0,0 +1 @@ +{"files":[{"uri":"/code/.deepsource.toml"},{"uri":"/code/Cargo.lock"},{"uri":"/code/Cargo.toml"},{"uri":"/code/README.md"},{"uri":"/code/src/main.rs"}],"test_files":null,"excluded_files":null,"exclude_patterns":null,"test_patterns":null,"analyzer_meta":null} \ No newline at end of file diff --git a/command/analyzer/dryrun/testdata/toolbox/project4/analysis_config.json b/command/analyzer/dryrun/testdata/toolbox/project4/analysis_config.json new file mode 100644 index 00000000..8d917402 --- /dev/null +++ b/command/analyzer/dryrun/testdata/toolbox/project4/analysis_config.json @@ -0,0 +1 @@ +{"files":[{"uri":"/code/.deepsource.toml"},{"uri":"/code/Pipfile"},{"uri":"/code/README.md"},{"uri":"/code/assignment.py"},{"uri":"/code/demo_code.py"},{"uri":"/code/django_issues.py"},{"uri":"/code/duplicate_bases_class.py"},{"uri":"/code/miscellaneous.py"},{"uri":"/code/return_not_implemented.py"},{"uri":"/code/security.py"},{"uri":"/code/tests/__init__.py"},{"uri":"/code/tests/test_code.py"},{"uri":"/code/type_checks.py"}],"test_files":null,"excluded_files":null,"exclude_patterns":null,"test_patterns":null,"analyzer_meta":null} \ No newline at end of file diff --git a/command/analyzer/dryrun/types.go b/command/analyzer/dryrun/types.go new file mode 100644 index 00000000..f4ef9800 --- /dev/null +++ b/command/analyzer/dryrun/types.go @@ -0,0 +1,11 @@ +package dryrun + +type AnalysisConfig struct { + Files []string `json:"files"` + ExcludePatterns []string `json:"exclude_patterns"` + ExcludeFiles []string `json:"exclude_files"` + TestFiles []string `json:"test_files"` + TestPatterns []string `json:"test_patterns"` + AnalyzerMeta interface{} `json:"analyzer_meta"` +} + diff --git a/command/analyzer/dryrun/utils.go b/command/analyzer/dryrun/utils.go new file mode 100644 index 00000000..21cbe6f8 --- /dev/null +++ b/command/analyzer/dryrun/utils.go @@ -0,0 +1,96 @@ +package dryrun + +import ( + "net/url" + "os" + "path" + "strings" + + analysis "github.com/deepsourcelabs/cli/analysis/config" + "github.com/deepsourcelabs/cli/analysis/lsp" +) + +// isValidUrl tests a string to determine if it is a well-structured url or not. +func isValidUrl(toTest string) bool { + _, err := url.ParseRequestURI(toTest) + if err != nil { + return false + } + + u, err := url.Parse(toTest) + if err != nil || u.Scheme == "" || u.Host == "" { + return false + } + + return true +} + +// Modify the filepaths to use the container CODE_PATH and not the local CODE_PATH +// since the file will be mounted on the container and there, the container's path would +// only be resolvable +func modifyAnalysisConfigFilepaths(analysisConfig *analysis.AnalysisConfig, localCodePath, containerCodePath string) { + for idx, file := range analysisConfig.Files { + filePath := strings.TrimPrefix(string(file.URI), localCodePath) + analysisConfig.Files[idx].URI = lsp.DocumentURI(path.Join(containerCodePath, filePath)) + } + + for idx, file := range analysisConfig.TestFiles { + filePath := strings.TrimPrefix(string(file.URI), localCodePath) + analysisConfig.TestFiles[idx].URI = lsp.DocumentURI(path.Join(containerCodePath, filePath)) + } + + for idx, file := range analysisConfig.ExcludedFiles { + filePath := strings.TrimPrefix(string(file.URI), localCodePath) + analysisConfig.ExcludedFiles[idx].URI = lsp.DocumentURI(path.Join(containerCodePath, filePath)) + } +} + +// Creates a temporary directory with the name supplied as the parameter +func createTemporaryDirectory(directoryName string) (string, error) { + return os.MkdirTemp("", directoryName) +} + +/* Fetches environment variables like CODE_PATH and TOOLBOX_PATH set by the user + * Check if the user supplied CODE_PATH and TOOLBOX_PATH, if not + * use the default values of CODE_PATH and TOOLBOX_PATH */ +func fetchEnvironmentVariables() { + // Set the value of container's code path + if _, envVarPresent := os.LookupEnv("CODE_PATH"); envVarPresent { + containerCodePath = os.Getenv("CODE_PATH") + } else { + containerCodePath = "/code" + } + + // Set the value of container's toolbox path + if _, envVarPresent := os.LookupEnv("TOOLBOX_PATH"); envVarPresent { + containerToolBoxPath = os.Getenv("TOOLBOX_PATH") + } else { + containerToolBoxPath = "/toolbox" + } +} + +func (a *AnalyzerDryRun) createTemporaryToolBoxDir() (err error) { + if a.TempToolBoxDirectory == "" { + // Create a temporary directory + if a.TempToolBoxDirectory, err = createTemporaryDirectory("toolbox"); err != nil { + return err + } + } + + /* Assign the path of temporary local toolbox directory to the HostToolBoxPath(which shall be mounted into the container) + * and also use it to write the analysis_results.json file locally to the temporary */ + a.Client.AnalysisOpts.HostToolBoxPath = a.TempToolBoxDirectory + a.Client.AnalysisOpts.AnalysisResultsPath = a.TempToolBoxDirectory + return nil +} + +// parseImageName returns the image name and the image tag from the complete image name. +func (a *AnalyzerDryRun) parseImageName() (string, string) { + components := strings.Split(a.DockerImageName, ":") + + if len(components) > 1 { + return components[0], components[1] + } else { + return components[0], "latest" + } +} diff --git a/command/analyzer/initialize/init.go b/command/analyzer/initialize/init.go new file mode 100644 index 00000000..5395f0b0 --- /dev/null +++ b/command/analyzer/initialize/init.go @@ -0,0 +1,271 @@ +package initialize + +import ( + "bytes" + "fmt" + "os" + "strings" + + "github.com/pelletier/go-toml" + "github.com/pterm/pterm" + "github.com/spf13/cobra" + + "github.com/deepsourcelabs/cli/analyzers/config" + "github.com/deepsourcelabs/cli/types" + "github.com/deepsourcelabs/cli/utils" +) + +type SDKResponse struct { + SDKRequired bool + SDKLanguage string +} + +type InputPrompt interface { + ConfirmFromUser(string, string) (bool, error) + GetSingleLineInput(string, string, string) (string, error) + SelectFromOptions(string, string, []string) (string, error) +} + +type AnalyzerInitOpts struct { + Namespace string + AnalyzerName string + SDKInput SDKResponse + ProjectRootPath string + AnalyzerTOMLPath string + IssuesDirectoryPath string + AnalyzerShortcodeArg string + AnalyzerTOMLData types.AnalyzerTOML + PromptUtils InputPrompt +} + +/* ============================================================= + * $ deepsource analyzer init + * + * Helps in initializing the config for a new DeepSource Analyzer + * ============================================================== */ +func NewCmdAnalyzerInit() *cobra.Command { + cwd, _ := os.Getwd() + + opts := AnalyzerInitOpts{ + PromptUtils: utils.UserInputPrompt{}, + AnalyzerShortcodeArg: "", + } + + // Fetch the project root path and analyzer.toml path + opts.ProjectRootPath, opts.AnalyzerTOMLPath, opts.IssuesDirectoryPath = config.InitAnalyzerConfigurationPaths() + + cmd := &cobra.Command{ + Use: "init", + Short: "Initialize DeepSource Analyzer", + Args: utils.MaxNArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + // Check if the analyzer.toml already exists. + // If yes, display that the analyzer already initialized at `.deepsource/analyzer/analyzer.toml` + if _, err := os.Stat(opts.AnalyzerTOMLPath); err == nil { + pterm.Info.Printf("Analyzer already initialized at %s. Exiting...\n", strings.TrimPrefix(opts.AnalyzerTOMLPath, cwd+"/")) + return nil + } + + // Check for the shortcode argument (if any) + if len(args) > 0 { + opts.AnalyzerShortcodeArg = args[0] + if !strings.HasPrefix(opts.AnalyzerShortcodeArg, "@") { + opts.AnalyzerShortcodeArg = strings.ToLower("@" + opts.AnalyzerShortcodeArg) + } + } + + analysisConfigBytes, err := opts.initAnalyzer() + if err != nil { + return fmt.Errorf("Analyzer initialization failed. Error: %s", err) + } + if err = opts.writeAnalyzerTOMLConfig(analysisConfigBytes); err != nil { + return fmt.Errorf("Analyzer initialization failed. Error: %s", err) + } + pterm.Success.Printf("Analyzer %s initialized successfully!\n", opts.AnalyzerTOMLData.Shortcode) + return nil + }, + } + return cmd +} + +// initAnalyzer helps the Analyzer authors initialize a new Analyzer +// in an interactive way with the help of suitable prompts +func (a *AnalyzerInitOpts) initAnalyzer() (*bytes.Buffer, error) { + // If the user didn't add an Analyzer shortcode argument to the command and + // just ran `deepsource analyzer init`, input the Namespace and Analyzer name from the user + // and create the Analyzer shortcode in the format : @/ + if a.AnalyzerShortcodeArg == "" { + if err := a.fetchNamespaceAndAnalyzerName(); err != nil { + return nil, err + } + } + + a.AnalyzerTOMLData.Shortcode = strings.ToLower(a.AnalyzerShortcodeArg) + pterm.Info.Printf("Initializing analyzer %s...\n", a.AnalyzerTOMLData.Shortcode) + + // Collect Analyzer name and description + if err := a.fetchAnalyzerDisplayNameAndDescription(); err != nil { + return nil, err + } + + // Collect Analyzer tags + if err := a.fetchTags(); err != nil { + return nil, err + } + + // Collect the required URLs (git repository URL for example) + if err := a.fetchURLs(); err != nil { + return nil, err + } + + // Collect the required analysis and test commands + if err := a.fetchCommands(); err != nil { + return nil, err + } + + // Collect the SDKs to use + if err := a.fetchSDKInput(); err != nil { + return nil, err + } + + // Setting build.engine as "docker" + // TODO(SNT): Remove this once more than one build engines are supported. + a.AnalyzerTOMLData.Build.Engine = "docker" + + // Encoding the struct to TOML + // and storing in GeneratedConfig of Options struct + var buf bytes.Buffer + if err := toml.NewEncoder(&buf).Order(toml.OrderPreserve).Encode(a.AnalyzerTOMLData); err != nil { + return nil, err + } + return &buf, nil +} + +/* Input the namespace and Analyzer name from the user in case the user didn't mention it as an + * argument to the `deepsource analyzer init` command */ +func (a *AnalyzerInitOpts) fetchNamespaceAndAnalyzerName() (err error) { + // TODO(SNT): Change this to use a public API to show a list of namespaces the user has access to. + msg := "Namespace" + helpText := "The namespace to which the Analyzer will be associated." + if a.Namespace, err = a.PromptUtils.GetSingleLineInput(msg, helpText, ""); err != nil { + return err + } + + // Check for empty namespace and return error + if a.Namespace == "" { + return fmt.Errorf("namespace cannot be empty") + } + + // Sanitize the namespace + a.Namespace = sanitizeInput(a.Namespace) + + // Input shortcode of the Analyzer. + msg = "Name of the Analyzer" + helpText = "Name of the Analyzer. This is to be used to access the Analyzer as: @/." + if a.AnalyzerName, err = a.PromptUtils.GetSingleLineInput(msg, helpText, ""); err != nil { + return err + } + + // Check for empty shortcode and return error. + if a.AnalyzerName == "" { + return fmt.Errorf("Analyzer name cannot be empty") + } + + // Sanitize the Analyzer name + a.AnalyzerName = sanitizeInput(a.AnalyzerName) + + a.AnalyzerShortcodeArg = strings.ToLower("@" + a.Namespace + "/" + a.AnalyzerName) + return +} + +// fetchAnalyzerDisplayNameAndDescription collects input for the Analyzer display name and description from the Analyzer author +func (a *AnalyzerInitOpts) fetchAnalyzerDisplayNameAndDescription() (err error) { + // Fetch the default analyzer name from the shortcode + // Eg: @deepsource/armory -> Armory + defaultAnalyzerName := strings.Title(strings.Split(a.AnalyzerTOMLData.Shortcode, "/")[1]) // skipcq: SCC-SA1019 + + // Collect name of the Analyzer + msg := "Display name of the Analyzer" + helpText := "The name of the Analyzer which will be displayed on the dashboard." + if a.AnalyzerTOMLData.Name, err = a.PromptUtils.GetSingleLineInput(msg, helpText, defaultAnalyzerName); err != nil { + return err + } + + // Collect description of the Analyzer + msg = "Description of the Analyzer" + helpText = "Explain what the Analyzer does and the kinds of issues it detects." + if a.AnalyzerTOMLData.Description, err = a.PromptUtils.GetSingleLineInput(msg, helpText, ""); err != nil { + return err + } + return nil +} + +// fetchTags collects input for any tags/keywords that the Analyzer author would like to +// configure for the Analyzer +func (a *AnalyzerInitOpts) fetchTags() error { + msg := "Tags for the Analyzer (comma or space separated)" + helpText := "Some keywords related to the Analyzer. Use commas/spaces to separate the keywords." + analyzerTags, err := a.PromptUtils.GetSingleLineInput(msg, helpText, "") + if err != nil { + return err + } + + // Parse tags from the user input + a.AnalyzerTOMLData.Tags = processAnalyzerTags(analyzerTags) + return nil +} + +// fetchURLs collects the git repository URL of the Analyzer +func (a *AnalyzerInitOpts) fetchURLs() error { + defaultRemoteURL, err := fetchRemoteURL() + if err != nil { + defaultRemoteURL = "" + } + msg := "Git repository URL of the Analyzer?" + helpText := "The remote repository URL of the Analyzer." + if a.AnalyzerTOMLData.Repository, err = a.PromptUtils.GetSingleLineInput(msg, helpText, strings.TrimRight(defaultRemoteURL, "\n")); err != nil { + return err + } + return nil +} + +// fetchCommands fetches the analysis and test command that the Analyzer author wants to configure +func (a *AnalyzerInitOpts) fetchCommands() (err error) { + // Collect the analysis command of the Analyzer + msg := "Analysis command for the Analyzer" + helpText := "The command used to execute the Analyzer" + if a.AnalyzerTOMLData.Analysis.Command, err = a.PromptUtils.GetSingleLineInput(msg, helpText, ""); err != nil { + return err + } + + // Collect the test command of the Analyzer + msg = "Test command for the Analyzer" + helpText = "The command used to run tests on the Analyzer" + if a.AnalyzerTOMLData.Test.Command, err = a.PromptUtils.GetSingleLineInput(msg, helpText, ""); err != nil { + return err + } + return +} + +// fetchSDKInput fetches the user input for the DeepSource Analyzer SDKs the Analyzer author wants to +// use for the Analyzer +func (a *AnalyzerInitOpts) fetchSDKInput() (err error) { + supportedSDKS := getSupportedSDKs() + if len(supportedSDKS) > 0 { + // Check if DeepSource SDK is needed or not? + msg := "Would you like to use DeepSource Analyzer SDK to build your Analyzer?" + helpText := "DeepSource SDKs help you to easily create an Analyzer" + if a.SDKInput.SDKRequired, err = a.PromptUtils.ConfirmFromUser(msg, helpText); err != nil { + return err + } + + if a.SDKInput.SDKRequired { + msg := "Which language do you want the SDK for:" + helpText := "Choose the language for which the SDK will be generated" + if a.SDKInput.SDKLanguage, err = a.PromptUtils.SelectFromOptions(msg, helpText, supportedSDKS); err != nil { + return err + } + } + } + return +} diff --git a/command/analyzer/initialize/init_test.go b/command/analyzer/initialize/init_test.go new file mode 100644 index 00000000..a066b40b --- /dev/null +++ b/command/analyzer/initialize/init_test.go @@ -0,0 +1,133 @@ +package initialize + +import ( + "reflect" + "strings" + "testing" +) + +type MockInputPrompt struct { + AnalyzerShortcode string + AnalyzerName string + AnalyzerDescription string + AnalyzerTags string + Repository string + AnalysisCommand string + TestCommand string + SDKRequired bool + SDKLanguage string + ExpectedConfig string +} + +func (m MockInputPrompt) GetSingleLineInput(msg, _, _ string) (string, error) { + if strings.Contains(msg, "Display name") { + return m.AnalyzerName, nil + } + if strings.Contains(msg, "Description") { + return m.AnalyzerDescription, nil + } + if strings.Contains(msg, "Tags") { + return m.AnalyzerTags, nil + } + if strings.Contains(msg, "Git repository") { + return m.Repository, nil + } + if strings.Contains(msg, "Analysis command") { + return m.AnalysisCommand, nil + } + if strings.Contains(msg, "Test command") { + return m.TestCommand, nil + } + return "", nil +} + +func (m MockInputPrompt) ConfirmFromUser(_, _ string) (bool, error) { + return m.SDKRequired, nil +} + +func (m MockInputPrompt) SelectFromOptions(_, _ string, _ []string) (string, error) { + return m.SDKLanguage, nil +} + +func TestInitAnalyzer(t *testing.T) { + testCases := []MockInputPrompt{ + { + AnalyzerShortcode: "@deepsource/demo-python", + AnalyzerName: "Python Analyzer", + AnalyzerDescription: "This is a python analyzer", + AnalyzerTags: "python, analyzer, static-analysis", + Repository: "https://github.com/deepsourcelabs/demo-python", + AnalysisCommand: "./analysis.sh", + TestCommand: "./test-command.sh", + SDKRequired: false, + SDKLanguage: "", + ExpectedConfig: `name = "Python Analyzer" +shortcode = "@deepsource/demo-python" +description = "This is a python analyzer" +tags = ["python", "analyzer", "static-analysis"] +repository = "https://github.com/deepsourcelabs/demo-python" +documentation = "" +bug_tracker = "" + +[environment_variables] + +[analysis] + command = "./analysis.sh" + +[build] + engine = "docker" + dockerfile = "" + script = "" + +[test] + command = "./test-command.sh" +`, + }, + { + AnalyzerShortcode: "@deepsource/demo-go", + AnalyzerName: "Go Analyzer", + AnalyzerDescription: "This is a golang analyzer", + AnalyzerTags: "golang go analyzer static-analysis", + Repository: "https://gitlab.com/deepsourcelabs/demo-go", + AnalysisCommand: "", + TestCommand: "", + SDKRequired: true, + SDKLanguage: "Go", + ExpectedConfig: `name = "Go Analyzer" +shortcode = "@deepsource/demo-go" +description = "This is a golang analyzer" +tags = ["golang", "go", "analyzer", "static-analysis"] +repository = "https://gitlab.com/deepsourcelabs/demo-go" +documentation = "" +bug_tracker = "" + +[environment_variables] + +[analysis] + command = "" + +[build] + engine = "docker" + dockerfile = "" + script = "" + +[test] + command = "" +`, + }, + } + + for _, tc := range testCases { + opts := AnalyzerInitOpts{ + PromptUtils: tc, + } + opts.AnalyzerShortcodeArg = tc.AnalyzerShortcode + buf, err := opts.initAnalyzer() + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(tc.ExpectedConfig, buf.String()) { + t.Errorf("Expected %s\n====\nGot %s", tc.ExpectedConfig, buf.String()) + } + } +} diff --git a/command/analyzer/initialize/utils.go b/command/analyzer/initialize/utils.go new file mode 100644 index 00000000..ff23ea69 --- /dev/null +++ b/command/analyzer/initialize/utils.go @@ -0,0 +1,104 @@ +package initialize + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +// Contains the list of supported SDKs for building DeepSource Analyzers +var supportedAnalyzerSDKs = []string{} + +func processAnalyzerTags(userInput string) []string { + re := regexp.MustCompile("[ ,]") + return delete_empty(re.Split(userInput, -1)) +} + +func delete_empty(s []string) []string { + var r []string + for _, str := range s { + if str != "" { + r = append(r, str) + } + } + return r +} + +func fetchRemoteURL() (string, error) { + var remoteRepoURL string + + // TODO: Add support for fetching URLs for other remotes here as well + remoteURL, err := exec.Command("git", "config", "--get", "remote.origin.url").Output() + if err != nil { + return "", err + } + remoteRepoURL = strings.TrimRight(string(remoteURL), "\000\n") + + // Return the remoteURL if it already begins with `https://` + if strings.HasPrefix(remoteRepoURL, "https://") { + return remoteRepoURL, nil + } + + // If the URL doesn't begin with `git@` don't move further and send an empty default remoteURL + if !strings.HasPrefix(remoteRepoURL, "git@") { + return "", fmt.Errorf("couldn't parse the default remote URL") + } + + // If the URL begins with `git@`, extract the substring till the colon(:) in the remote URL + colonIndex := strings.Index(remoteRepoURL, ":") + if colonIndex == -1 { + return remoteRepoURL, fmt.Errorf("couldn't parse the default remote URL") + } + + vcsURL := remoteRepoURL[:colonIndex] + switch vcsURL { + case "git@github.com": + urlComponent := strings.TrimPrefix(remoteRepoURL, "git@github.com:") + remoteRepoURL = "https://github.com/" + urlComponent + case "git@gitlab.com": + urlComponent := strings.TrimPrefix(remoteRepoURL, "git@gitlab.com:") + remoteRepoURL = "https://gitlab.com/" + urlComponent + case "git@bitbucket.org": + urlComponent := strings.TrimPrefix(remoteRepoURL, "git@bitbucket.org:") + remoteRepoURL = "https://bitbucket.org/" + urlComponent + } + return strings.TrimSuffix(remoteRepoURL, ".git"), nil +} + +// Returns the list of supported SDKs +func getSupportedSDKs() []string { + return supportedAnalyzerSDKs +} + +// Sanitize the input of namespace and Analyzer shortcode for any spaces +// or special characters and replace them with hyphen +func sanitizeInput(input string) string { + re := regexp.MustCompile("[^a-zA-Z0-9_]+") + return re.ReplaceAllString(input, "-") +} + +// Writes the Analyzer TOML data to the file +func (a *AnalyzerInitOpts) writeAnalyzerTOMLConfig(buf *bytes.Buffer) (err error) { + // Create the .deepsource/analyzer directory and issues/ directory + directoriesToCreate := []string{".deepsource", ".deepsource/analyzer", ".deepsource/analyzer/issues/"} + + // Create the required directories mentioned above + for _, dir := range directoriesToCreate { + if _, err := os.Stat(filepath.Join(a.ProjectRootPath, dir)); errors.Is(err, os.ErrNotExist) { + if err = os.Mkdir(filepath.Join(a.ProjectRootPath, dir), 0o750); err != nil { + return err + } + } + } + + // Write the input data to analyzer.toml + if err = os.WriteFile(a.AnalyzerTOMLPath, buf.Bytes(), 0o750); err != nil { + return err + } + return +} diff --git a/command/analyzer/push/push.go b/command/analyzer/push/push.go new file mode 100644 index 00000000..6c95a198 --- /dev/null +++ b/command/analyzer/push/push.go @@ -0,0 +1,198 @@ +package push + +import ( + "fmt" + "strings" + + "github.com/deepsourcelabs/cli/analyzers/backend/docker" + "github.com/deepsourcelabs/cli/command/analyzer/verify" + "github.com/deepsourcelabs/cli/config" + "github.com/deepsourcelabs/cli/types" + "github.com/deepsourcelabs/cli/utils" + "github.com/pterm/pterm" + "github.com/spf13/cobra" +) + +var ( + defaultRegistryURL string = "registry.deepsource.io" + analyzerImagePlatform string = "linux/amd64" +) + +type AnalyzerPushOpts struct { + RegistryURL string + AnalyzerTOMLData types.AnalyzerTOML + IssuesData *[]types.AnalyzerIssue + DockerClient *docker.DockerClient + Spinner *utils.SpinnerUtils // The spinner command line utility. + cliConfig *config.CLIConfig +} + +/* ============================================================= + * $ deepsource analyzer push + * + * Pushes the Analyzer image and configuration to DeepSource. + * ============================================================== */ +func NewCmdAnalyzerPush() *cobra.Command { + cmd := &cobra.Command{ + Use: "push", + Short: "Push the Analyzer image and configuration to DeepSource", + Args: utils.MaxNArgs(1), + PreRunE: verify.NewCmdAnalyzerVerify().RunE, + RunE: func(_ *cobra.Command, _ []string) error { + // Initialize the push utilities. + a := AnalyzerPushOpts{ + Spinner: &utils.SpinnerUtils{}, + } + + if err := a.pushAndSyncAnalyzer(); err != nil { + return fmt.Errorf("Failed to push the Analyzer changes to DeepSource.") + } + pterm.Success.Println("Analyzer push successful.") + return nil + }, + } + return cmd +} + +func (a *AnalyzerPushOpts) pushAndSyncAnalyzer() (err error) { + if err = a.checkAuthentication(); err != nil { + return err + } + if err = a.pushAnalyzer(); err != nil { + return err + } + if err = a.syncAnalyzerData(); err != nil { + return err + } + return nil +} + +func (a *AnalyzerPushOpts) checkAuthentication() (err error) { + // Get the DeepSource CLI config contents. + a.Spinner.StartSpinnerWithLabel("Verifying CLI authentication...", "Verified CLI authentication") + a.cliConfig, err = config.GetConfig() + if err != nil { + a.Spinner.StopSpinnerWithError("Failed to read the CLI authentication config", err) + return fmt.Errorf("error while reading DeepSource CLI config : %v", err) + } + + // Verify the DeepSource CLI authentication of the user. + if err = a.cliConfig.VerifyAuthentication(); err != nil { + a.Spinner.StopSpinnerWithError("Failed to verify authentication status", err) + return err + } + a.Spinner.StopSpinner() + return nil +} + +// pushAnalyzer contains the pipeline to build the Analyzer image and then push it to +// the registry post authentication. +func (a *AnalyzerPushOpts) pushAnalyzer() (err error) { + // Fetch docker registry URL and the data required to build the Analyzer image. + a.Spinner.StartSpinnerWithLabel("Fetching Analyzer registry URL and image data...", "Fetched the Analyzer registry URL and image data") + a.RegistryURL = getImageRegistryURL() + if a.AnalyzerTOMLData, a.AnalyzerTOMLData.AnalyzerVersion, err = getAnalyzerData(); err != nil { + a.Spinner.StopSpinnerWithError("Failed to fetch the image data", err) + return err + } + a.Spinner.StopSpinner() + + // Build the Analyzer image using the received version. + analyzerImageName := fmt.Sprintf("%s/%s:%s", a.RegistryURL, strings.TrimPrefix(a.AnalyzerTOMLData.Shortcode, "@"), a.AnalyzerTOMLData.AnalyzerVersion) + a.Spinner.StartSpinnerWithLabel(fmt.Sprintf("Building the Analyzer image with the name %s", analyzerImageName), "Built the Analyzer image") + if err = a.buildAnalyzerImage(a.AnalyzerTOMLData.AnalyzerVersion, &a.AnalyzerTOMLData); err != nil { + a.Spinner.StopSpinnerWithError("Failed to build the image", err) + return err + } + a.Spinner.StopSpinner() + + // Push the Analyzer image to the registry. + a.Spinner.StartSpinnerWithLabel(fmt.Sprintf("Pushing the Analyzer image to DeepSource with the name %s", analyzerImageName), "Pushed the Analyzer image to DeepSource") + if err = a.pushAnalyzerImage(a.cliConfig.User, a.cliConfig.Token); err != nil { + a.Spinner.StopSpinnerWithError("Failed to push the image to DeepSource", err) + return err + } + a.Spinner.StopSpinner() + return nil +} + +func (a *AnalyzerPushOpts) syncAnalyzerData() (err error) { + // Run the data sync mutation. + a.Spinner.StartSpinnerWithLabel(fmt.Sprintf("Syncing the latest changes in %s:%s", a.AnalyzerTOMLData.Shortcode, a.AnalyzerTOMLData.AnalyzerVersion), "Synced the latest changes") + syncSuccessful, syncWarnings, err := a.syncAnalyzer(a.cliConfig.Host, a.cliConfig.Token) + if err != nil { + a.Spinner.StopSpinnerWithError("Failed to sync the Analyzer", err) + return err + } + + // Check if the Analyzer sync passed or failed. + if !syncSuccessful { + a.Spinner.StopSpinnerWithError("Failed to sync the Analyzer", err) + } else { + a.Spinner.StopSpinner() + } + + // Check if there are any warnings in the response and handle print them accordingly. + if len(syncWarnings) != 0 { + for _, warning := range syncWarnings { + warningMessage := utils.GetBulletMessage(warning, "yellow") + fmt.Printf(" %s\n", warningMessage) + } + } + return nil +} + +// buildAnalyzerImage builds the Analyzer image and tags it with the latest git tag or commit (if tag not found). +func (a *AnalyzerPushOpts) buildAnalyzerImage(analyzerVersion string, analyzerTOMLData *types.AnalyzerTOML) error { + // Fetch the details of the dockerfile to build and also the name of the docker image + dockerFilePath, dockerImageName := docker.GetDockerImageDetails(analyzerTOMLData) + + // Initialize the builder with the above fetched details + a.DockerClient = &docker.DockerClient{ + ImageName: fmt.Sprintf("%s/%s", a.RegistryURL, dockerImageName), + DockerfilePath: dockerFilePath, + ImageTag: analyzerVersion, + ImagePlatform: analyzerImagePlatform, + } + + err := a.DockerClient.SetupClient() + if err != nil { + return err + } + + // Build the Analyzer docker image. + ctxCancelFunc, buildRespReader, buildErr := a.DockerClient.BuildAnalyzerDockerImage() + + // Cancel the build context and close the reader before exiting this function. + if buildRespReader != nil { + defer buildRespReader.Close() + } + if ctxCancelFunc != nil { + defer ctxCancelFunc() + } + if buildErr != nil { + return buildErr + } + + // Read the docker build response. + // Passing `verboseMode` as false since its not required as of now in the `push` command. + return docker.CheckBuildResponse(buildRespReader, false) +} + +// pushAnalyzerImage pushes the Analyzer image to the registry. +func (a *AnalyzerPushOpts) pushAnalyzerImage(user, token string) error { + ctxCancelFunc, pushResponseReader, pushErr := a.DockerClient.PushImageToRegistry(user, token) + if pushResponseReader != nil { + defer pushResponseReader.Close() + } + if ctxCancelFunc != nil { + defer ctxCancelFunc() + } + if pushErr != nil { + return pushErr + } + + // Read the docker push response. + // Passing `verboseMode` as false since its not required as of now in the `push` command. + return docker.CheckBuildResponse(pushResponseReader, false) +} diff --git a/command/analyzer/push/push_test.go b/command/analyzer/push/push_test.go new file mode 100644 index 00000000..a2c8f793 --- /dev/null +++ b/command/analyzer/push/push_test.go @@ -0,0 +1,153 @@ +package push + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + "github.com/deepsourcelabs/cli/analyzers/backend/docker" + "github.com/deepsourcelabs/cli/analyzers/config" + cliConfig "github.com/deepsourcelabs/cli/config" + "github.com/deepsourcelabs/cli/types" + "github.com/deepsourcelabs/cli/utils" +) + +func TestPushAnalyzer(t *testing.T) { + /* ---------------------------------------------------------------------------- + // 1. Run the registry locally. + /* ---------------------------------------------------------------------------- */ + appPath := os.Getenv("APP_PATH") + if appPath == "" { + appPath = "/app/" + } + + // Set REGISTRY_URL as localhost:5000 otherwise it will use the default value which is registry.deepsource.io. + os.Setenv("REGISTRY_URL", "localhost:5000") + + /* ---------------------------------------------------------------------------- + // Setup the local docker registry for testing at localhost:5000. + /* ---------------------------------------------------------------------------- */ + if err := runCmd("docker", appPath, "run", "-d", "-p", "5000:5000", "--restart=always", "--name", "registry", "registry:2"); err != nil { + t.Error(err) + } + + /* ---------------------------------------------------------------------------- + // Copying the todo-checker directory to $APP_PATH for the integration tests + /* ---------------------------------------------------------------------------- */ + analyzerPath, _ := filepath.Abs("../dryrun/test/todo-checker") + + // Copy all the files to APP_PATH + if err := runCmd("cp", appPath, "-a", "-f", analyzerPath+"/.", "."); err != nil { + t.Error(err) + } + + /* ---------------------------------------------------------------------------- + // Initialize appPath as git repo and add a commit to it + /* ---------------------------------------------------------------------------- */ + + if err := runCmd("git", appPath, "init"); err != nil { + t.Error(err) + } + + if err := runCmd("git", appPath, "add", "."); err != nil { + t.Error(err) + } + + if err := runCmd("git", appPath, "commit", "--no-gpg-sign", "-m", "hello"); err != nil { + t.Error(err) + } + /* ------------------------------------------------------------------------------------- + // Fetch the analyzer.toml data and the issue descriptions of the todo-checker Analyzer + /* ------------------------------------------------------------------------------------- */ + os.Chdir(appPath) + analyzerTOMLData, err := config.GetAnalyzerTOML() + if err != nil { + t.Error(err) + } + + issuesData, err := config.GetIssueDescriptions() + if err != nil { + t.Error(err) + } + + /* ------------------------------------------------------------------------------------- + // Create test cases. + /* ------------------------------------------------------------------------------------- */ + type fields struct { + RegistryURL string + AnalyzerTOMLData types.AnalyzerTOML + IssuesData *[]types.AnalyzerIssue + DockerClient *docker.DockerClient + Spinner *utils.SpinnerUtils + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "Test Analyzer push to the registry", + fields: fields{ + RegistryURL: "localhost:5000", + AnalyzerTOMLData: *analyzerTOMLData, + IssuesData: issuesData, + DockerClient: &docker.DockerClient{}, + Spinner: &utils.SpinnerUtils{}, + }, + wantErr: false, + }, + } + + cliConf := cliConfig.CLIConfig{ + Host: "deepsource.io", + Token: "dsp_2c14cf8d5c46485e8b811687760caf526a4", + TokenExpiresIn: time.Unix(1<<63-1, 0), + User: "test@deepsource.io", + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &AnalyzerPushOpts{ + RegistryURL: tt.fields.RegistryURL, + AnalyzerTOMLData: tt.fields.AnalyzerTOMLData, + IssuesData: tt.fields.IssuesData, + DockerClient: tt.fields.DockerClient, + Spinner: tt.fields.Spinner, + cliConfig: &cliConf, + } + if err := a.pushAnalyzer(); (err != nil) != tt.wantErr { + t.Errorf("AnalyzerPushOpts.pushAnalyzer() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } + + /* Stop the registry. */ + if err := runCmd("docker", appPath, "container", "stop", "registry"); err != nil { + t.Log(err) + } + if err := runCmd("docker", appPath, "container", "rm", "-v", "registry"); err != nil { + t.Log(err) + } +} + +func runCmd(command, directory string, cmdArgs ...string) (err error) { + // Stop the running registry instance. + cmd := exec.Command(command, cmdArgs...) + cmd.Dir = directory + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + // Run the command + err = cmd.Run() + _, outErr := stdout.String(), stderr.String() + if err != nil { + return fmt.Errorf("Failed to run the command. Error:%s\n", outErr) + } + return nil +} diff --git a/command/analyzer/push/sync.go b/command/analyzer/push/sync.go new file mode 100644 index 00000000..28a8ab69 --- /dev/null +++ b/command/analyzer/push/sync.go @@ -0,0 +1,34 @@ +package push + +import ( + "context" + + analyzerConfig "github.com/deepsourcelabs/cli/analyzers/config" + "github.com/deepsourcelabs/cli/deepsource" +) + +// syncAnalyzer uses the asgard public API to sync the Analyzer data with DeepSource. +func (a *AnalyzerPushOpts) syncAnalyzer(host, token string) (bool, []string, error) { + var err error + + // Fetch the issues data. + if a.IssuesData, err = analyzerConfig.GetIssueDescriptions(); err != nil { + return false, []string{}, err + } + + // Use the SDK for syncing the Analyzer through the public API. + deepsource, err := deepsource.New(deepsource.ClientOpts{ + Token: token, + HostName: host, + }) + if err != nil { + return false, []string{}, err + } + + ctx := context.Background() + syncResponse, err := deepsource.SyncAnalyzer(ctx, &a.AnalyzerTOMLData, a.IssuesData) + if err != nil { + return false, []string{}, err + } + return syncResponse.Ok, syncResponse.Warnings, nil +} diff --git a/command/analyzer/push/utils.go b/command/analyzer/push/utils.go new file mode 100644 index 00000000..38fb3059 --- /dev/null +++ b/command/analyzer/push/utils.go @@ -0,0 +1,77 @@ +package push + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + analyzerConfig "github.com/deepsourcelabs/cli/analyzers/config" + "github.com/deepsourcelabs/cli/types" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" +) + +// getAnalyzerVersion fetches the latest git tag version of the Analyzer. +func getAnalyzerVersion(analyzerDir string) (string, error) { + analyzerDir, _ = filepath.Abs(analyzerDir) + + // Open the Analyzer's git directory. + repo, err := git.PlainOpenWithOptions(analyzerDir, &git.PlainOpenOptions{ + DetectDotGit: true, + }) + if err != nil { + return "", err + } + + // Fetch the repo tags list. + tagReferences, _ := repo.Tags() + currentTagRef := []string{} + if err = tagReferences.ForEach(func(t *plumbing.Reference) error { + currentTagRef = append(currentTagRef, t.Name().String()) + return nil + }); err != nil { + return "", err + } + + if len(currentTagRef) != 0 { + // Convert refs/tags/v0.2.1 -> v0.2.1 + return strings.TrimPrefix(currentTagRef[len(currentTagRef)-1], "refs/tags/"), nil + } + + // currentTagRef slice is empty if there are not tags in the Analyzer git directory. + headRef, err := repo.Head() + if err != nil { + return "", fmt.Errorf("couldn't find the latest git tag or commit") + } + return headRef.Hash().String()[:7], nil +} + +// getImageRegistryURL fetches the registry URL to which the image has to be pushed. +func getImageRegistryURL() string { + if envRegistryURL, envConfigured := os.LookupEnv("REGISTRY_URL"); envConfigured { + return envRegistryURL + } + return defaultRegistryURL +} + +// getAnalyzerData fetches the data required to build and push the image: +// - The analyzer.toml data which contains the Analyzer shortcode which would be the image name. +// - The latest git tag or the git commit if the tag doesn't exist which will be used to tag the image. +func getAnalyzerData() (types.AnalyzerTOML, string, error) { + cwd, err := os.Getwd() + if err != nil { + return types.AnalyzerTOML{}, "", err + } + + analyzerTOML, err := analyzerConfig.GetAnalyzerTOML() + if err != nil { + return types.AnalyzerTOML{}, "", err + } + + analyzerVersion, err := getAnalyzerVersion(cwd) + if err != nil { + return types.AnalyzerTOML{}, "", err + } + return *analyzerTOML, analyzerVersion, nil +} diff --git a/command/analyzer/push/utils_test.go b/command/analyzer/push/utils_test.go new file mode 100644 index 00000000..ac0f7855 --- /dev/null +++ b/command/analyzer/push/utils_test.go @@ -0,0 +1,34 @@ +package push + +import ( + "os" + "testing" +) + +func TestGetImageRegistryURL(t *testing.T) { + tests := []struct { + registryURL string + want string + }{ + { + registryURL: "us.gcr.io/deepsource-dev", + want: "us.gcr.io/deepsource-dev", + }, + { + registryURL: "", + want: "registry.deepsource.io", + }, + } + + for _, tt := range tests { + t.Run(tt.registryURL, func(t *testing.T) { + if tt.registryURL != "" { + os.Setenv("REGISTRY_URL", tt.registryURL) + } + if got := getImageRegistryURL(); got != tt.want { + t.Errorf("Incorrect registry URL. Got = %v, want %v", got, tt.want) + } + os.Unsetenv("REGISTRY_URL") + }) + } +} diff --git a/command/analyzer/verify/build.go b/command/analyzer/verify/build.go new file mode 100644 index 00000000..4cd4757d --- /dev/null +++ b/command/analyzer/verify/build.go @@ -0,0 +1,100 @@ +package verify + +import ( + "errors" + "fmt" + "os" + + build "github.com/deepsourcelabs/cli/analyzers/backend/docker" + "github.com/deepsourcelabs/cli/utils" + "github.com/mgutz/ansi" +) + +// Verify the docker image build of the Analyzer +func (a *AnalyzerVerifyOpts) verifyAnalyzerDockerBuild() (err error) { + // Fetch the details of the dockerfile to build and also the name of the docker image + a.Build.DockerFilePath, a.Build.DockerImageName = build.GetDockerImageDetails(a.AnalyzerTOMLData) + + /* In case of verbose mode (when the user supplies a `--verbose` flag), do not use spinner since it doesn't allow showing multiline + * output at the same time as rendering a spinner. Can be done but would require some string concatenation magic. Can be picked up later. */ + switch a.Build.VerboseMode { + case true: + arrowIcon := ansi.Color("> [verbose]", "yellow") + fmt.Printf("%s Building Analyzer image with the name \"%s\"\n", arrowIcon, a.Build.DockerImageName) + case false: + a.Spinner.StartSpinnerWithLabel(fmt.Sprintf("Building Analyzer image with the name \"%s\"", a.Build.DockerImageName), "Successfully built the Analyzer image") + } + + /* Specifying the source to build + * Check for the presence of `build.Dockerfile` or if not a `Dockerfile` in the current working directory */ + if _, err := os.Stat(a.Build.DockerFilePath); err != nil { + if errors.Is(err, os.ErrNotExist) { + if a.Build.VerboseMode { + msg := utils.GetFailureMessage("Failed to build the image", fmt.Sprintf("%s not found", a.Build.DockerFilePath)) + fmt.Println(msg) + return fmt.Errorf(msg) + } + a.Spinner.StopSpinnerWithError("Failed to build the image", fmt.Errorf("%s not found", a.Build.DockerFilePath)) + return err + } + } + + /* =================================== + * Analyzer docker build verification + * =================================== */ + + /* Initialize the builder with the above fetched details + * Use the `GenerateImageVersion` utility to generate a random string of length 7 to tag the image */ + analyzerBuilder := build.DockerClient{ + ImageName: a.Build.DockerImageName, + ImagePlatform: a.Build.DockerImagePlatform, + DockerfilePath: a.Build.DockerFilePath, + ImageTag: build.GenerateImageVersion(7), + ShowLogs: a.Build.VerboseMode, + } + + // Setup client for the builder. + err = analyzerBuilder.SetupClient() + if err != nil { + return err + } + + // Build the docker image. + ctxCancelFunc, buildRespReader, buildErr := analyzerBuilder.BuildAnalyzerDockerImage() + + // Cancel the build context and close the reader before exiting this function + if buildRespReader != nil { + defer buildRespReader.Close() + } + if ctxCancelFunc != nil { + defer ctxCancelFunc() + } + + if buildErr != nil { + a.handleBuildError(buildErr) + return buildErr + } + + // Read the docker build response + if err = build.CheckBuildResponse(buildRespReader, a.Build.VerboseMode); err != nil { + a.handleBuildError(err) + return err + } + + // In case of verbose mode, no need to stop the spinner + if a.Build.VerboseMode { + fmt.Print(utils.GetSuccessMessage("Successfully built the Analyzer image\n")) + return nil + } + a.Spinner.StopSpinner() + return nil +} + +// Utility to handle the output in case of build errors for different modes. +func (a *AnalyzerVerifyOpts) handleBuildError(buildError error) { + if a.Build.VerboseMode { + fmt.Printf("%s\n", utils.GetFailureMessage("Failed to build the image", buildError.Error())) + return + } + a.Spinner.StopSpinnerWithError("Failed to build the image", fmt.Errorf(buildError.Error())) +} diff --git a/command/analyzer/verify/verify.go b/command/analyzer/verify/verify.go new file mode 100644 index 00000000..af1be3f1 --- /dev/null +++ b/command/analyzer/verify/verify.go @@ -0,0 +1,164 @@ +package verify + +import ( + "errors" + "fmt" + "path/filepath" + "runtime" + + "github.com/deepsourcelabs/cli/analyzers/diagnostics" + "github.com/deepsourcelabs/cli/analyzers/validator" + "github.com/deepsourcelabs/cli/types" + "github.com/deepsourcelabs/cli/utils" + "github.com/pterm/pterm" + "github.com/spf13/cobra" +) + +var ( + analyzerTOMLPath string + issuesDirPath string + configFolder string = ".deepsource/analyzer" +) + +type AnalyzerBuild struct { + DockerFilePath string + DockerImageName string + DockerImagePlatform string + VerboseMode bool +} + +type AnalyzerVerifyOpts struct { + AnalyzerTOMLData *types.AnalyzerTOML + Build AnalyzerBuild + Spinner *utils.SpinnerUtils +} + +/* ====================================== + * $ deepsource analyzer verify + * ====================================== */ + +func NewCmdAnalyzerVerify() *cobra.Command { + opts := AnalyzerVerifyOpts{ + Spinner: &utils.SpinnerUtils{}, + Build: AnalyzerBuild{ + VerboseMode: false, + }, + } + cmd := &cobra.Command{ + Use: "verify", + Short: "Verify DeepSource Analyzers configuration", + Args: utils.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + if err := opts.verifyAnalyzer(); err != nil { + return errors.New("Analyzer verification failed. Exiting.") + } + pterm.Success.Println("Analyzer verification successful!") + return nil + }, + } + + // --verbose flag. On being set, the build logs as well as the verification diagnostics are visible to the user + cmd.Flags().BoolVar(&opts.Build.VerboseMode, "verbose", false, "Output build logs and diagnostics related to verification failures") + + // --platform flag; used for explicitly setting up the build platform for the Docker image. Defaults to linux/ if not provided. + defaultPlatform := fmt.Sprintf("linux/%s", runtime.GOARCH) + cmd.Flags().StringVar(&opts.Build.DockerImagePlatform, "platform", defaultPlatform, "Explicitly set build platform for Docker image.") + + return cmd +} + +/* ==================================== + * Analyzer configuration verification + * ==================================== */ + +func (a *AnalyzerVerifyOpts) verifyAnalyzer() (err error) { + var analyzerTOMLValidationErrors *validator.ValidationFailure + var issuesValidationErrors *[]validator.ValidationFailure + configurationValid := true + + // Configuring the paths of analyzer.toml and issues directory + projectRoot, err := utils.ExtractProjectRootPath() + if err != nil { + fmt.Printf("Couldn't find the root directory of the project. Error: %s\n", err) + } + analyzerTOMLPath = filepath.Join(projectRoot, configFolder, "analyzer.toml") + issuesDirPath = filepath.Join(projectRoot, configFolder, "issues/") + + /* ================================================================================== + * Checks for the presence of .deepsource/analyzer directory, + * the analyzer.toml file and issues present in .deepsource/analyzer/issues directory + * ================================================================================== */ + + a.Spinner.StartSpinnerWithLabel("Checking for presence of analyzer.toml and issue descriptions...", "Found analyzer.toml and issue descriptions") + if err = validator.CheckForAnalyzerConfig(analyzerTOMLPath, issuesDirPath); err != nil { + configurationValid = false + a.Spinner.StopSpinnerWithError("Failed to locate analyzer configurations", err) + return + } + a.Spinner.StopSpinner() + + /* ============================== + * Read and verify analyzer.toml + * ============================== */ + + a.Spinner.StartSpinnerWithLabel("Validating analyzer.toml...", "Verified analyzer.toml") + a.AnalyzerTOMLData, analyzerTOMLValidationErrors, err = validator.ValidateAnalyzerTOML(analyzerTOMLPath) + // TODO: Handle err here + if err != nil { + configurationValid = false + a.Spinner.StopSpinnerWithError("Failed to verify analyzer.toml", err) + } + + // Check for validation errors in analyzer.toml and display them (if any) + if analyzerTOMLValidationErrors != nil && len(analyzerTOMLValidationErrors.Errors) > 0 { + configurationValid = false + a.Spinner.StopSpinnerWithError("Failed to verify analyzer.toml", err) + + // Get diagnostics. + reportedDiagnostics, err := diagnostics.GetDiagnostics(*analyzerTOMLValidationErrors) + if err != nil { + a.Spinner.StopSpinnerWithError("Failed to verify analyzer.toml", err) + } + + // Print diagnostics to the console. + for _, diagnostic := range reportedDiagnostics { + fmt.Printf("%s\n", diagnostic) + } + } + a.Spinner.StopSpinner() + + /* ==================================== + * Read and verify issue descriptions + * ==================================== */ + + a.Spinner.StartSpinnerWithLabel("Validating issue descriptions...", "Verified issue descriptions") + if issuesValidationErrors, err = validator.ValidateIssueDescriptions(issuesDirPath); err != nil { + configurationValid = false + a.Spinner.StopSpinnerWithError("Failed to validate the issues", err) + } + + // Check for validation errors in analyzer issues and display them (if any) + if issuesValidationErrors != nil && len(*issuesValidationErrors) > 0 { + configurationValid = false + a.Spinner.StopSpinnerWithError("Failed to validate the following issues", err) + for _, validationError := range *issuesValidationErrors { + fmt.Printf(" > %s\n", validationError.File) + for _, err := range validationError.Errors { + failureMsg := utils.GetBulletMessage(err.Message, "red") + fmt.Printf(" %s\n", failureMsg) + } + } + } + a.Spinner.StopSpinner() + + // Do not proceed to building the image if the configuration verification fails + if !configurationValid { + return fmt.Errorf("verification failed") + } + + /* ==================================================== + * Verify the local docker image build of the Analyzer + * ==================================================== */ + + return a.verifyAnalyzerDockerBuild() +} diff --git a/command/auth/login/login.go b/command/auth/login/login.go index fb8d1b1b..279379ee 100644 --- a/command/auth/login/login.go +++ b/command/auth/login/login.go @@ -19,6 +19,7 @@ type LoginOptions struct { HostName string Interactive bool PAT string + Prompt utils.UserInputPrompt } // NewCmdLogin handles the login functionality for the CLI @@ -41,6 +42,7 @@ func NewCmdLogin() *cobra.Command { TokenExpired: true, User: "", HostName: "", + Prompt: utils.UserInputPrompt{}, } cmd := &cobra.Command{ @@ -92,7 +94,7 @@ func (opts *LoginOptions) Run() (err error) { if !opts.TokenExpired { // The user is already logged in, confirm re-authentication. msg := fmt.Sprintf("You're already logged into DeepSource as %s. Do you want to re-authenticate?", opts.User) - response, err := utils.ConfirmFromUser(msg, "") + response, err := opts.Prompt.ConfirmFromUser(msg, "") if err != nil { return fmt.Errorf("Error in fetching response. Please try again.") } @@ -121,13 +123,13 @@ func (opts *LoginOptions) handleInteractiveLogin() error { hostPromptHelpText := "The hostname of the DeepSource instance to authenticate with" // Display prompt to user - loginType, err := utils.SelectFromOptions(loginPromptMessage, loginPromptHelpText, accountTypes) + loginType, err := opts.Prompt.SelectFromOptions(loginPromptMessage, loginPromptHelpText, accountTypes) if err != nil { return err } // Prompt the user for hostname only in the case of on-premise if loginType == "DeepSource Enterprise" { - opts.HostName, err = utils.GetSingleLineInput(hostPromptMessage, hostPromptHelpText) + opts.HostName, err = opts.Prompt.GetSingleLineInput(hostPromptMessage, hostPromptHelpText, "") if err != nil { return err } diff --git a/command/auth/login/login_flow.go b/command/auth/login/login_flow.go index cd8b7a0c..87f7ff75 100644 --- a/command/auth/login/login_flow.go +++ b/command/auth/login/login_flow.go @@ -101,10 +101,7 @@ func fetchPAT(ctx context.Context, deviceRegistrationData *auth.Device) (*auth.P if err != nil { hostName = defaultHostName } - if len(hostName) > 256 { - hostName = hostName[:256] - } - userDescription := fmt.Sprintf("CLI : %s@%s", userName, hostName) + userDescription := fmt.Sprintf("CLI PAT for %s@%s", userName, hostName) // Fetching DeepSource client in order to interact with SDK deepsource, err := deepsource.New(deepsource.ClientOpts{ diff --git a/command/auth/login/pat_login_flow.go b/command/auth/login/pat_login_flow.go index edefaf8d..69132a1d 100644 --- a/command/auth/login/pat_login_flow.go +++ b/command/auth/login/pat_login_flow.go @@ -7,7 +7,7 @@ import ( ) // Starts the login flow for the CLI (using PAT) -func (opts *LoginOptions) startPATLoginFlow(cfg *config.CLIConfig, token string) error { +func (*LoginOptions) startPATLoginFlow(cfg *config.CLIConfig, token string) error { // set personal access token (PAT) cfg.Token = token diff --git a/command/auth/logout/logout.go b/command/auth/logout/logout.go index 6d9e3e82..3ba19952 100644 --- a/command/auth/logout/logout.go +++ b/command/auth/logout/logout.go @@ -10,7 +10,9 @@ import ( "github.com/spf13/cobra" ) -type LogoutOptions struct{} +type LogoutOptions struct { + Prompt utils.UserInputPrompt +} // NewCmdLogout handles the logout functionality for the CLI func NewCmdLogout() *cobra.Command { @@ -18,8 +20,10 @@ func NewCmdLogout() *cobra.Command { Use: "logout", Short: "Logout of your active DeepSource account", Args: utils.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - opts := LogoutOptions{} + RunE: func(cmd *cobra.Command, _ []string) error { + opts := LogoutOptions{ + Prompt: utils.UserInputPrompt{}, + } return opts.Run() }, } @@ -39,7 +43,7 @@ func (opts *LogoutOptions) Run() error { // Confirm from the user if they want to logout logoutConfirmationMsg := "Are you sure you want to log out of DeepSource account?" - response, err := utils.ConfirmFromUser(logoutConfirmationMsg, "") + response, err := opts.Prompt.ConfirmFromUser(logoutConfirmationMsg, "") if err != nil { return err } diff --git a/command/auth/refresh/refresh.go b/command/auth/refresh/refresh.go index b71592d3..c54bccbb 100644 --- a/command/auth/refresh/refresh.go +++ b/command/auth/refresh/refresh.go @@ -39,7 +39,7 @@ func NewCmdRefresh() *cobra.Command { return cmd } -func (opts *RefreshOptions) Run() error { +func (*RefreshOptions) Run() error { // Fetch config cfg, err := config.GetConfig() if err != nil { diff --git a/command/auth/status/status.go b/command/auth/status/status.go index 40af9ce4..72d2aa5f 100644 --- a/command/auth/status/status.go +++ b/command/auth/status/status.go @@ -34,7 +34,7 @@ func NewCmdStatus() *cobra.Command { return cmd } -func (opts *AuthStatusOptions) Run() error { +func (*AuthStatusOptions) Run() error { // Fetch config cfg, err := config.GetConfig() if err != nil { diff --git a/command/config/generate/analyzers_input.go b/command/config/generate/analyzers_input.go index 5a2cbbe1..72e082c5 100644 --- a/command/config/generate/analyzers_input.go +++ b/command/config/generate/analyzers_input.go @@ -26,7 +26,7 @@ func (o *Options) collectAnalyzerInput() (err error) { analyzerPromptMsg := "Which languages/tools does your project use?" analyzerPromptHelpText := "Analyzers will find issues in your code. Add an analyzer by selecting a language you've written your code in." - o.ActivatedAnalyzers, err = utils.SelectFromMultipleOptions(analyzerPromptMsg, analyzerPromptHelpText, utils.AnalyzersData.AnalyzerNames) + o.ActivatedAnalyzers, err = o.Prompt.SelectFromMultipleOptions(analyzerPromptMsg, analyzerPromptHelpText, utils.AnalyzersData.AnalyzerNames) if err != nil { return err } @@ -60,7 +60,7 @@ func (o *Options) inputAnalyzerMeta(requiredFieldsData map[string][]AnalyzerMeta switch metaFields[i].Type { case "boolean": metaFields[i].UserInput = "true" - res, err := utils.ConfirmFromUser(metaFields[i].Title, metaFields[i].Description) + res, err := o.Prompt.ConfirmFromUser(metaFields[i].Title, metaFields[i].Description) if err != nil { return err } @@ -68,12 +68,12 @@ func (o *Options) inputAnalyzerMeta(requiredFieldsData map[string][]AnalyzerMeta metaFields[i].UserInput = "false" } case "enum": - metaFields[i].UserInput, err = utils.SelectFromOptions(metaFields[i].Title, metaFields[i].Description, metaFields[i].Options) + metaFields[i].UserInput, err = o.Prompt.SelectFromOptions(metaFields[i].Title, metaFields[i].Description, metaFields[i].Options) if err != nil { return err } default: - metaFields[i].UserInput, err = utils.GetSingleLineInput(metaFields[i].Title, metaFields[i].Description) + metaFields[i].UserInput, err = o.Prompt.GetSingleLineInput(metaFields[i].Title, metaFields[i].Description, "") if err != nil { return err } diff --git a/command/config/generate/generate.go b/command/config/generate/generate.go index 4c719dbe..da17860f 100644 --- a/command/config/generate/generate.go +++ b/command/config/generate/generate.go @@ -22,11 +22,14 @@ type Options struct { ExcludePatterns []string TestPatterns []string GeneratedConfig string + Prompt utils.UserInputPrompt } // NewCmdConfigGenerate handles the generation of DeepSource config based on user inputs func NewCmdConfigGenerate() *cobra.Command { - o := Options{} + o := Options{ + Prompt: utils.UserInputPrompt{}, + } home, _ := os.UserHomeDir() doc := heredoc.Docf(` diff --git a/command/config/generate/generic_input.go b/command/config/generate/generic_input.go index bb147319..806c199e 100644 --- a/command/config/generate/generic_input.go +++ b/command/config/generate/generic_input.go @@ -6,7 +6,6 @@ import ( "path/filepath" "github.com/AlecAivazis/survey/v2" - "github.com/deepsourcelabs/cli/utils" ) // ========== @@ -17,7 +16,7 @@ func (o *Options) collectExcludePatterns() error { helpMsg := "Glob patterns of files that should not be analyzed such as auto-generated files, migrations, compatibility files." // Confirm from the user if they want to add an exclude pattern - response, err := utils.ConfirmFromUser(excludePatternsMsg, helpMsg) + response, err := o.Prompt.ConfirmFromUser(excludePatternsMsg, helpMsg) if err != nil { return err } @@ -40,7 +39,7 @@ func (o *Options) collectTestPatterns() error { helpMsg := "Glob patterns of the test files. This helps us reduce false positives." // Confirm from the user (y/N) if he/she wants to add test patterns - response, err := utils.ConfirmFromUser(testPatternsMsg, helpMsg) + response, err := o.Prompt.ConfirmFromUser(testPatternsMsg, helpMsg) if err != nil { return err } @@ -87,7 +86,7 @@ func (o *Options) inputFilePatterns(field, msg, helpMsg string) error { // Iterating this until user says no // Here field contains : "test"/"exclude" depending upon the invoking confirmationMsg := fmt.Sprintf("Add more %s patterns?", field) - response, err := utils.ConfirmFromUser(confirmationMsg, "") + response, err := o.Prompt.ConfirmFromUser(confirmationMsg, "") if err != nil { return err } diff --git a/command/config/generate/transformers_input.go b/command/config/generate/transformers_input.go index 6e5a2468..91ac3e8a 100644 --- a/command/config/generate/transformers_input.go +++ b/command/config/generate/transformers_input.go @@ -9,7 +9,7 @@ func (o *Options) collectTransformersInput() (err error) { transformerPromptMsg := "Would you like to activate any Transformers for any languages?" transformerPromptHelpText := "DeepSource Transformers automatically help to achieve auto-formatting of code. Add a transformer by selecting the code formatting tool of your choice." - o.ActivatedTransformers, err = utils.SelectFromMultipleOptions(transformerPromptMsg, transformerPromptHelpText, utils.TransformersData.TransformerNames) + o.ActivatedTransformers, err = o.Prompt.SelectFromMultipleOptions(transformerPromptMsg, transformerPromptHelpText, utils.TransformersData.TransformerNames) if err != nil { return err } diff --git a/command/config/validate/validate.go b/command/config/validate/validate.go index 500cb50a..0fdadf22 100644 --- a/command/config/validate/validate.go +++ b/command/config/validate/validate.go @@ -38,7 +38,7 @@ func NewCmdValidate() *cobra.Command { } // Run executes the command. -func (o *Options) Run() error { +func (*Options) Run() error { // Fetch config cfg, err := config.GetConfig() if err != nil { diff --git a/command/issues/list/utils.go b/command/issues/list/utils.go index cbdd86f3..3e2d4867 100644 --- a/command/issues/list/utils.go +++ b/command/issues/list/utils.go @@ -166,7 +166,7 @@ func convertSARIF(issueData []issues.Issue) *sarif.Report { // Adding the tools data to the SARIF report corresponding to the number of analyzers activated for _, issue := range issueData { if !shortcodes[issue.Analyzer.Shortcode].exists { - driverName := "DeepSource " + strings.Title(issue.Analyzer.Shortcode) + " Analyzer" + driverName := "DeepSource " + strings.Title(issue.Analyzer.Shortcode) + " Analyzer" // skipcq: SCC-SA1019 informationURI := "https://deepsource.io/directory/analyzers/" + string(issue.Analyzer.Shortcode) tool := sarif.Tool{ diff --git a/command/report/tests/init_test.go b/command/report/tests/init_test.go index 8f203e10..6d9dd401 100644 --- a/command/report/tests/init_test.go +++ b/command/report/tests/init_test.go @@ -30,7 +30,7 @@ func startMockAPIServer() { go func() { err := srv.ListenAndServe() if err != nil { - log.Fatalln("Error starting HTTP mock server") + log.Fatalln("Error starting HTTP mock server") // skipcq: RVV-A0003 } }() } diff --git a/command/report/tests/report_workflow_test.go b/command/report/tests/report_workflow_test.go index fea34303..af240131 100644 --- a/command/report/tests/report_workflow_test.go +++ b/command/report/tests/report_workflow_test.go @@ -25,7 +25,6 @@ const ( func graphQLAPIMock(w http.ResponseWriter, r *http.Request) { req, _ := ioutil.ReadAll(r.Body) - log.Println(string(req)) // Read test graphql request body artifact file requestBodyData, err := ioutil.ReadFile("./dummy/report_graphql_request_body.json") @@ -51,7 +50,7 @@ func graphQLAPIMock(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") - if string(requestBodyData) == string(req) { + if bytes.Equal(requestBodyData, req) { w.Write([]byte(successResponseBodyData)) } else { w.Write([]byte(errorResponseBodyData)) @@ -86,10 +85,6 @@ func TestReportKeyValueWorkflow(t *testing.T) { cmd.Stderr = &stderr err = cmd.Run() - - outStr, errStr := stdout.String(), stderr.String() - log.Printf("== Run deepsource CLI command ==\n%s\n%s\n", outStr, errStr) - if err != nil { t.Errorf("Error executing deepsource CLI command: %v", err) } @@ -116,13 +111,7 @@ func TestReportKeyValueFileWorkflow(t *testing.T) { cmd.Stderr = &stderr err := cmd.Run() - - outStr, errStr := stdout.String(), stderr.String() - log.Printf("== Run deepsource CLI command ==\n%s\n%s\n", outStr, errStr) - if err != nil { - log.Println(outStr) - log.Println(errStr) t.Errorf("Error executing deepsource CLI command: %v", err) } } diff --git a/command/root.go b/command/root.go index 73e323b3..123eb891 100644 --- a/command/root.go +++ b/command/root.go @@ -1,6 +1,7 @@ package command import ( + "github.com/deepsourcelabs/cli/command/analyzer" "github.com/deepsourcelabs/cli/command/auth" "github.com/deepsourcelabs/cli/command/config" "github.com/deepsourcelabs/cli/command/issues" @@ -29,6 +30,7 @@ Login into DeepSource using the command : deepsource auth login`, cmd.AddCommand(repo.NewCmdRepo()) cmd.AddCommand(issues.NewCmdIssues()) cmd.AddCommand(report.NewCmdReport()) + cmd.AddCommand(analyzer.NewCmdAnalyzer()) return cmd } diff --git a/config/config.go b/config/config.go index d6746cb6..4d021c5b 100644 --- a/config/config.go +++ b/config/config.go @@ -17,7 +17,7 @@ var ( const ( ConfigDirName = "/.deepsource/" ConfigFileName = "/config.toml" - DefaultHostName = "deepsource.io" + DefaultHostName = "deepsource.icu" ) type CLIConfig struct { @@ -37,10 +37,7 @@ func (cfg *CLIConfig) SetTokenExpiry(str string) { } // Checks if the token has expired or not -func (cfg CLIConfig) IsExpired() bool { - if cfg.TokenExpiresIn.IsZero() { - return true - } +func (cfg *CLIConfig) IsExpired() bool { return time.Now().After(cfg.TokenExpiresIn) } @@ -54,7 +51,7 @@ func (CLIConfig) configDir() (string, error) { } // configPath returns the file path to the config file. -func (cfg CLIConfig) configPath() (string, error) { +func (cfg *CLIConfig) configPath() (string, error) { home, err := cfg.configDir() if err != nil { return "", err @@ -87,6 +84,7 @@ func (cfg *CLIConfig) ReadConfigFile() error { return nil } +// GetConfig returns the CLI config after reading it. func GetConfig() (*CLIConfig, error) { if Cfg.Token != "" { return &Cfg, nil @@ -147,10 +145,10 @@ func (cfg *CLIConfig) VerifyAuthentication() error { return errors.New("You are not logged into DeepSource. Run \"deepsource auth login\" to authenticate.") } - // // Check if the token has already expired - // if cfg.IsExpired() { - // return errors.New("The authentication has expired. Run \"deepsource auth refresh\" to refresh the credentials.") - // } + // Check if the token has already expired + if cfg.IsExpired() { + return errors.New("The authentication has expired. Run \"deepsource auth refresh\" to refresh the credentials.") + } return nil } diff --git a/config/config_test.go b/config/config_test.go index 62002b6b..05991eed 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -56,6 +56,7 @@ func TestGetConfig(t *testing.T) { func TestVerifyAuthentication(t *testing.T) { t.Run("must return nil when token is provided", func(t *testing.T) { + cfg.TokenExpiresIn = time.Date(2023, time.Month(2), 13, 1, 10, 30, 0, time.UTC) err := cfg.VerifyAuthentication() assert.Nil(t, err) }) diff --git a/deepsource/analyzers/mutations/sync_analyzer.go b/deepsource/analyzers/mutations/sync_analyzer.go new file mode 100644 index 00000000..4d3be38b --- /dev/null +++ b/deepsource/analyzers/mutations/sync_analyzer.go @@ -0,0 +1,66 @@ +package analyzers + +import ( + "context" + "fmt" + + "github.com/deepsourcelabs/cli/deepsource/analyzers" + "github.com/deepsourcelabs/cli/types" + "github.com/deepsourcelabs/graphql" +) + +// GraphQL query to sync a custom Analyzer with DeepSource. +const syncAnalyzerMutation = ` +mutation SyncAnalyzer($input:SyncAnalyzerInput!) { + syncAnalyzer(input:$input) { + ok + warnings + } +}` + +type AnalyzerInput struct { + Name string `json:"name"` + Version string `json:"version"` + Shortcode string `json:"shortcode"` + Description string `json:"description,omitempty"` + Tags []string `json:"tags,omitempty"` + RepositoryUrl string `json:"repositoryUrl,omitempty"` + DocumentationUrl string `json:"documentationUrl,omitempty"` + BugTrackerUrl string `json:"bugtrackerUrl,omitempty"` + AnalysisCommand string `json:"analysisCommand"` + AutofixCommand string `json:"autofixCommand,omitempty"` +} + +type SyncAnalyzerInput struct { + Analyzer AnalyzerInput `json:"analyzer"` + Issues []types.AnalyzerIssue `json:"issues"` +} + +type SyncAnalyzerRequest struct { + Input SyncAnalyzerInput +} + +// GraphQL interface of API client. +type IGQLClient interface { + GQL() *graphql.Client + GetToken() string +} + +func (s SyncAnalyzerRequest) Do(ctx context.Context, client IGQLClient) (*analyzers.SyncResponse, error) { + req := graphql.NewRequest(syncAnalyzerMutation) + // Add request variables. + req.Var("input", s.Input) + + // Set header fields. + req.Header.Set("Cache-Control", "no-cache") + tokenHeader := fmt.Sprintf("Bearer %s", client.GetToken()) + req.Header.Add("Authorization", tokenHeader) + + // Run the mutation and capture the response. + var respData analyzers.SyncResponse + if err := client.GQL().Run(ctx, req, &respData); err != nil { + return nil, err + } + fmt.Println(respData) + return &respData, nil +} diff --git a/deepsource/analyzers/queries/get_analyzers.go b/deepsource/analyzers/queries/get_analyzers.go index 04e496d2..b2be4a89 100644 --- a/deepsource/analyzers/queries/get_analyzers.go +++ b/deepsource/analyzers/queries/get_analyzers.go @@ -42,7 +42,7 @@ type IGQLClient interface { GetToken() string } -func (a AnalyzersRequest) Do(ctx context.Context, client IGQLClient) ([]analyzers.Analyzer, error) { +func (AnalyzersRequest) Do(ctx context.Context, client IGQLClient) ([]analyzers.Analyzer, error) { req := graphql.NewRequest(listAnalyzersQuery) // set header fields diff --git a/deepsource/analyzers/sync.go b/deepsource/analyzers/sync.go new file mode 100644 index 00000000..3187fee8 --- /dev/null +++ b/deepsource/analyzers/sync.go @@ -0,0 +1,10 @@ +package analyzers + +type SyncResponse struct { + SyncAnalyzer `json:"syncAnalyzer"` +} + +type SyncAnalyzer struct { + Ok bool `json:"ok"` + Warnings []string `json:"warnings"` +} diff --git a/deepsource/auth/mutations/register_device.go b/deepsource/auth/mutations/register_device.go index 57292521..04f502c7 100644 --- a/deepsource/auth/mutations/register_device.go +++ b/deepsource/auth/mutations/register_device.go @@ -29,7 +29,7 @@ type IGQLClient interface { GQL() *graphql.Client } -func (r RegisterDeviceRequest) Do(ctx context.Context, client IGQLClient) (*auth.Device, error) { +func (RegisterDeviceRequest) Do(ctx context.Context, client IGQLClient) (*auth.Device, error) { req := graphql.NewRequest(registerDeviceMutation) req.Header.Set("Cache-Control", "no-cache") diff --git a/deepsource/client.go b/deepsource/client.go index 75264ad4..74befb7f 100644 --- a/deepsource/client.go +++ b/deepsource/client.go @@ -6,19 +6,22 @@ import ( "fmt" "github.com/deepsourcelabs/cli/deepsource/analyzers" - analyzerQuery "github.com/deepsourcelabs/cli/deepsource/analyzers/queries" "github.com/deepsourcelabs/cli/deepsource/auth" - authmut "github.com/deepsourcelabs/cli/deepsource/auth/mutations" "github.com/deepsourcelabs/cli/deepsource/issues" - issuesQuery "github.com/deepsourcelabs/cli/deepsource/issues/queries" "github.com/deepsourcelabs/cli/deepsource/repository" - repoQuery "github.com/deepsourcelabs/cli/deepsource/repository/queries" "github.com/deepsourcelabs/cli/deepsource/transformers" - transformerQuery "github.com/deepsourcelabs/cli/deepsource/transformers/queries" + "github.com/deepsourcelabs/cli/types" "github.com/deepsourcelabs/graphql" + + analyzerMutation "github.com/deepsourcelabs/cli/deepsource/analyzers/mutations" + analyzerQuery "github.com/deepsourcelabs/cli/deepsource/analyzers/queries" + authMutation "github.com/deepsourcelabs/cli/deepsource/auth/mutations" + issuesQuery "github.com/deepsourcelabs/cli/deepsource/issues/queries" + repoQuery "github.com/deepsourcelabs/cli/deepsource/repository/queries" + transformerQuery "github.com/deepsourcelabs/cli/deepsource/transformers/queries" ) -var defaultHostName = "deepsource.io" +var defaultHostName = "deepsource.icu" type ClientOpts struct { Token string @@ -56,7 +59,7 @@ func getAPIClientURL(hostName string) string { // Check if the domain is different from the default domain (In case of Enterprise users) if hostName != defaultHostName { - apiClientURL = fmt.Sprintf("https://%s/api/graphql/", hostName) + apiClientURL = fmt.Sprintf("http://%s/graphql/", hostName) } return apiClientURL } @@ -64,7 +67,7 @@ func getAPIClientURL(hostName string) string { // Registers the device and allots it a device code which is further used for fetching // the PAT and other authentication data func (c Client) RegisterDevice(ctx context.Context) (*auth.Device, error) { - req := authmut.RegisterDeviceRequest{} + req := authMutation.RegisterDeviceRequest{} res, err := req.Do(ctx, c) if err != nil { return nil, err @@ -74,8 +77,8 @@ func (c Client) RegisterDevice(ctx context.Context) (*auth.Device, error) { // Logs in the client using the deviceCode and the user Code and returns the PAT and data which is required for authentication func (c Client) Login(ctx context.Context, deviceCode, description string) (*auth.PAT, error) { - req := authmut.RequestPATRequest{ - Params: authmut.RequestPATParams{ + req := authMutation.RequestPATRequest{ + Params: authMutation.RequestPATParams{ DeviceCode: deviceCode, Description: description, }, @@ -90,8 +93,8 @@ func (c Client) Login(ctx context.Context, deviceCode, description string) (*aut // Refreshes the authentication credentials. Takes the refreshToken as a parameter. func (c Client) RefreshAuthCreds(ctx context.Context, token string) (*auth.PAT, error) { - req := authmut.RefreshTokenRequest{ - Params: authmut.RefreshTokenParams{ + req := authMutation.RefreshTokenRequest{ + Params: authMutation.RefreshTokenParams{ Token: token, }, } @@ -187,3 +190,42 @@ func (c Client) GetIssuesForFile(ctx context.Context, owner, repoName, provider, } return res, nil } + +// SyncAnalyzer syncs the local custom Analyzer with DeepSource. +func (c Client) SyncAnalyzer(ctx context.Context, analyzerTOMLData *types.AnalyzerTOML, issuesData *[]types.AnalyzerIssue) (*analyzers.SyncResponse, error) { + req := analyzerMutation.SyncAnalyzerRequest{ + Input: analyzerMutation.SyncAnalyzerInput{ + Analyzer: analyzerMutation.AnalyzerInput{ + Name: analyzerTOMLData.Name, + Version: analyzerTOMLData.AnalyzerVersion, + Shortcode: analyzerTOMLData.Shortcode, + Description: analyzerTOMLData.Description, + Tags: analyzerTOMLData.Tags, + RepositoryUrl: analyzerTOMLData.Repository, + DocumentationUrl: analyzerTOMLData.DocumentationURL, + BugTrackerUrl: analyzerTOMLData.BugTrackerURL, + AnalysisCommand: analyzerTOMLData.Analysis.Command, + // TODO(SNT): Activate this when Autofix is supported for custom Analyzers. + // AutofixCommand: analyzerTOMLData.AutofixCommand, + }, + }, + } + + // Making a duplicate instance of the issuesData slice since the category needs to be modified to + // match the enum values expected by the syncAnalyzer mutation. + issuesList := *issuesData + + // Assigning the enum value of issue category. + for i := 0; i < len(issuesList); i++ { + issuesList[i].Category = types.IssueCategoryEnumMap[issuesList[i].Category] + } + + // Appending issues to the parameters. + req.Input.Issues = append(req.Input.Issues, issuesList...) + + res, err := req.Do(ctx, c) + if err != nil { + return &analyzers.SyncResponse{}, err + } + return res, nil +} diff --git a/deepsource/transformers/queries/get_transformers.go b/deepsource/transformers/queries/get_transformers.go index b173e0ce..5f68896f 100644 --- a/deepsource/transformers/queries/get_transformers.go +++ b/deepsource/transformers/queries/get_transformers.go @@ -40,7 +40,7 @@ type IGQLClient interface { GetToken() string } -func (t TransformersRequest) Do(ctx context.Context, client IGQLClient) ([]transformers.Transformer, error) { +func (TransformersRequest) Do(ctx context.Context, client IGQLClient) ([]transformers.Transformer, error) { req := graphql.NewRequest(listTransformersQuery) // set header fields diff --git a/go.mod b/go.mod index dd808246..e18e9f7b 100644 --- a/go.mod +++ b/go.mod @@ -3,58 +3,106 @@ module github.com/deepsourcelabs/cli go 1.18 require ( - github.com/AlecAivazis/survey/v2 v2.2.12 + github.com/AlecAivazis/survey/v2 v2.3.5 github.com/Jeffail/gabs/v2 v2.6.1 github.com/MakeNowJust/heredoc v1.0.0 + github.com/alecthomas/chroma v0.10.0 + github.com/briandowns/spinner v1.18.1 github.com/cli/browser v1.1.0 github.com/deepsourcelabs/graphql v0.2.2 - github.com/fatih/color v1.12.0 - github.com/getsentry/sentry-go v0.6.0 - github.com/owenrumney/go-sarif/v2 v2.1.0 - github.com/pelletier/go-toml v1.9.2 - github.com/pterm/pterm v0.12.23 - github.com/spf13/cobra v1.1.3 - github.com/spf13/viper v1.7.1 - github.com/stretchr/testify v1.7.0 + github.com/docker/docker v20.10.17+incompatible + github.com/fatih/color v1.13.0 + github.com/fsnotify/fsnotify v1.5.4 + github.com/getsentry/sentry-go v0.13.0 + github.com/go-git/go-git/v5 v5.4.2 + github.com/go-playground/validator/v10 v10.11.0 + github.com/gobwas/glob v0.2.3 + github.com/google/go-cmp v0.5.8 + github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b + github.com/labstack/echo/v4 v4.7.2 + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d + github.com/morikuni/aec v1.0.0 + github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 + github.com/owenrumney/go-sarif/v2 v2.1.2 + github.com/pelletier/go-toml v1.9.5 + github.com/pelletier/go-toml/v2 v2.0.2 + github.com/pterm/pterm v0.12.42 + github.com/spf13/cobra v1.5.0 + github.com/spf13/viper v1.12.0 + github.com/stretchr/testify v1.8.0 github.com/xeipuuv/gojsonschema v1.2.0 + github.com/yuin/goldmark v1.4.12 ) require ( - github.com/atomicgo/cursor v0.0.1 // indirect + atomicgo.dev/cursor v0.1.1 // indirect + atomicgo.dev/keyboard v0.2.8 // indirect + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/Microsoft/hcsshim v0.9.3 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect + github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/containerd/cgroups v1.0.3 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/containerd/containerd v1.6.6 // indirect + github.com/containerd/continuity v0.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fsnotify/fsnotify v1.4.7 // indirect - github.com/google/go-cmp v0.5.5 // indirect - github.com/gookit/color v1.4.2 // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.3.1 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/gookit/color v1.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/kr/pretty v0.3.0 // indirect - github.com/magiconair/properties v1.8.1 // indirect - github.com/matryer/is v1.4.0 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect + github.com/labstack/gommon v0.3.1 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/lithammer/fuzzysearch v1.1.5 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/sys/mount v0.3.3 // indirect + github.com/moby/sys/mountinfo v0.6.2 // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/runc v1.1.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.8.0 // indirect - github.com/spf13/afero v1.1.2 // indirect - github.com/spf13/cast v1.3.0 // indirect - github.com/spf13/jwalterweatherman v1.0.0 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.2.0 // indirect + github.com/subosito/gotenv v1.3.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.1 // indirect + github.com/xanzy/ssh-agent v0.3.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect - golang.org/x/crypto v0.0.0-20211202192323-5770296d904e // indirect - golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect + go.opencensus.io v0.23.0 // indirect + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect + golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/ini.v1 v1.51.0 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.3.0 // indirect ) diff --git a/go.sum b/go.sum index d7f2af69..98859808 100644 --- a/go.sum +++ b/go.sum @@ -1,376 +1,924 @@ +atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4= +atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= +atomicgo.dev/keyboard v0.2.8 h1:Di09BitwZgdTV1hPyX/b9Cqxi8HVuJQwWivnZUEqlj4= +atomicgo.dev/keyboard v0.2.8/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AlecAivazis/survey/v2 v2.2.12 h1:5a07y93zA6SZ09gOa9wLVLznF5zTJMQ+pJ3cZK4IuO8= -github.com/AlecAivazis/survey/v2 v2.2.12/go.mod h1:6d4saEvBsfSHXeN1a5OA5m2+HJ2LuVokllnC77pAIKI= -github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ= +github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= -github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= github.com/Jeffail/gabs/v2 v2.6.1 h1:wwbE6nTQTwIMsMxzi6XFQQYRZ6wDc1mSdxoAN+9U4Gk= github.com/Jeffail/gabs/v2 v2.6.1/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI= -github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= +github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= +github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= +github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= +github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= +github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= +github.com/MarvinJWendt/testza v0.4.2 h1:Vbw9GkSB5erJI2BPnBL9SVGV9myE+XmUSFahBGUhW2Q= +github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= +github.com/Microsoft/hcsshim v0.9.3/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/atomicgo/cursor v0.0.1 h1:xdogsqa6YYlLfM+GyClC/Lchf7aiMerFiZQn7soTOoU= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= -github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/briandowns/spinner v1.18.1 h1:yhQmQtM1zsqFsouh09Bk/jCjd50pC3EOGsh28gLVvwY= +github.com/briandowns/spinner v1.18.1/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/cli/browser v1.1.0 h1:xOZBfkfY9L9vMBgqb1YwRirGu6QFaQ5dP/vXt5ENSOY= github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= +github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= +github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= +github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deepsourcelabs/graphql v0.2.2 h1:6CtKGvVSIY6Jnf72VyfXB77AaaoHATBzXsrh64irtVQ= github.com/deepsourcelabs/graphql v0.2.2/go.mod h1:2hqi4vS0LxP9wMjbkbMOdR/fap2zwDlzqGGO8WEgyBA= -github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE= +github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= -github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/getsentry/sentry-go v0.6.0 h1:kPd+nr+dlXmaarUBg7xlC/qn+7wyMJL6PMsSn5fA+RM= -github.com/getsentry/sentry-go v0.6.0/go.mod h1:0yZBuzSvbZwBnvaF9VwZIMen3kXscY8/uasKtAX1qG8= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getsentry/sentry-go v0.13.0 h1:20dgTiUSfxRB/EhMPtxcL9ZEbM1ZdR+W/7f7NWD+xWo= +github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= +github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw= +github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= +github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= -github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= -github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= -github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= -github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= -github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= -github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= -github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= -github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= -github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI= +github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= +github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c= +github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= -github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs= +github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= -github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= +github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= -github.com/owenrumney/go-sarif/v2 v2.1.0 h1:Xp4RUbbQc5O2/t5YYZ+1QzJ8FZVdJwXeiWssiF5vQrA= -github.com/owenrumney/go-sarif/v2 v2.1.0/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/owenrumney/go-sarif/v2 v2.1.2 h1:PMDK7tXShJ9zsB7bfvlpADH5NEw1dfA9xwU8Xtdj73U= +github.com/owenrumney/go-sarif/v2 v2.1.2/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.2 h1:7NiByeVF4jKSG1lDF3X8LTIkq2/bu+1uYbIm1eS5tzk= -github.com/pelletier/go-toml v1.9.2/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= +github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/pterm/pterm v0.12.23 h1:+PL0YqmmT0QiDLOpevE3e2HPb5UIDBxh6OlLm8jDhxg= -github.com/pterm/pterm v0.12.23/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= +github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= +github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= +github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= +github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= +github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= +github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= +github.com/pterm/pterm v0.12.42 h1:hDxPyaPHJalzI+uJ+Cnh7tk8GKFkTUHcRmH7FuGcWfc= +github.com/pterm/pterm v0.12.42/go.mod h1:hJgLlBafm45w/Hr0dKXxY//POD7CgowhePaG1sdPNBg= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= +github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= -github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= -github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0= +github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8= -golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -380,144 +928,467 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= -golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/types/analyzer.go b/types/analyzer.go new file mode 100644 index 00000000..c19e2b08 --- /dev/null +++ b/types/analyzer.go @@ -0,0 +1,80 @@ +/* ============================================================= + * Contains type definitions with respect to Analyzers, Issues + * and their configuration. + * ============================================================= */ +package types + +import "html/template" + +/* ==================== + * Analyzer TOML Types + * ==================== */ + +type Analysis struct { + Command string `toml:"command" json:"command" validate:"required"` +} + +// TODO: Remove the comment when we start support for Autofix. +// type Autofix struct { +// Command string `toml:"command" json:"command" analyzertoml:"required"` +// } + +type Build struct { + Engine string `toml:"engine" json:"engine" validate:"omitempty,engine"` + Dockerfile string `toml:"dockerfile" json:"dockerfile,omitempty" validate:"omitempty"` + Script string `toml:"script,multiline" json:"script" validate:"omitempty"` +} + +type Test struct { + Command string `toml:"command" json:"command" validate:"omitempty"` +} + +// analyzer.toml type +type AnalyzerTOML struct { + // Analyzer specific data + Name string `toml:"name" json:"name" validate:"required"` + Shortcode string `toml:"shortcode" json:"shortcode" validate:"required,shortcode"` + AnalyzerVersion string `toml:",omitempty" json:",omitempty"` + Description string `toml:"description" json:"description" validate:"required"` + Tags []string `toml:"tags" json:"tags,omitempty" validate:"omitempty"` + Repository string `toml:"repository" json:"repository" validate:"omitempty,url"` + DocumentationURL string `toml:"documentation" json:"documentation,omitempty" validate:"omitempty,url"` + BugTrackerURL string `toml:"bug_tracker" json:"bug_tracker" validate:"omitempty,url"` + + EnvironmentVariables map[string]string `toml:"environment_variables" json:"environment_variables,omitempty" validate:"omitempty"` + + // Analyzer, Autofix and Transformer config + Analysis Analysis `toml:"analysis" json:"analysis" validate:"required"` + // Autofix Autofix `toml:"autofix" json:"autofix" validate:"omitempty"` + + // Build steps needed to build the analyzer + Build Build `toml:"build" json:"build" validate:"omitempty"` + + // Test command for running the testing workflow + Test Test `toml:"test" json:"test" validate:"omitempty"` +} + +/* ========================= + * Analyzer Issue TOML Types + * ========================= */ + +// Analyzer issue type. +type AnalyzerIssue struct { + Shortcode string `json:"shortcode" validate:"omitempty"` + HTMLDescription template.HTML `json:",omitempty"` + Title string `toml:"title" json:"title" validate:"required"` + Description string `toml:"description" json:"description" validate:"required"` + Category string `toml:"category" json:"category" validate:"required,category"` +} + +// Issue category enum used in +var IssueCategoryEnumMap = map[string]string{ + "bug-risk": "BUG_RISK", + "antipattern": "ANTI_PATTERN", + "performance": "PERFORMANCEW", + "style": "STYLE", + "security": "SECURITY", + "coverage": "COVERAGE", + "typecheck": "TYPECHECK", + "doc": "DOCUMENTATION", +} diff --git a/types/constants.go b/types/constants.go new file mode 100644 index 00000000..b06185a0 --- /dev/null +++ b/types/constants.go @@ -0,0 +1,24 @@ +package types + +// Issue category maps. +var IssueCategoryMap = map[string]string{ + "bug-risk": "Bug risk", + "antipattern": "Anti-pattern", + "performance": "Performance", + "style": "Style", + "security": "Security", + "coverage": "Coverage", + "typecheck": "Type check", + "doc": "Documentation", +} + +// Default supported metrics map. +var MetricMap = map[string]string{ + "BCV": "Branch coverage", + "ADC": "API documentation coverage", + "TDC": "Test documentation coverage", + "IDP": "Indirect dependencies", + "TCV": "Test coverage", + "DDP": "Direct dependencies", + "DCV": "Application documentation coverage", +} diff --git a/types/result.go b/types/result.go new file mode 100644 index 00000000..e571bd09 --- /dev/null +++ b/types/result.go @@ -0,0 +1,75 @@ +package types + +import "github.com/deepsourcelabs/cli/analysis/lsp" + +/////////////////////////////////////// +// LSP based Analysis Report Types // +///////////////////////////////////// + +type AnalyzerReport struct { + Issues []lsp.Diagnostic `json:"issues"` + Metrics []Metric `json:"metrics,omitempty"` + IsPassed bool `json:"is_passed"` + Errors []Error `json:"errors"` + // Errors []lsp.Diagnostic `json:"errors"` + ExtraData interface{} `json:"extra_data"` +} + +///////////////////////////// +// Final Analysis Result // +/////////////////////////// + +type AnalysisResult struct { + Issues []Issue `json:"issues"` + Metrics []Metric `json:"metrics,omitempty"` + IsPassed bool `json:"is_passed"` + Errors []Error `json:"errors"` +} + +type SourceCode struct { + Rendered string `json:"rendered"` +} + +type ProcessedData struct { + SourceCode SourceCode `json:"source_code,omitempty"` +} + +type Issue struct { + IssueCode string `json:"issue_code"` + IssueText string `json:"issue_text"` + IssueTitle string + IssueCategory string + IssueDescription string + Location Location `json:"location"` + ProcessedData ProcessedData `json:"processed_data,omitempty"` +} + +type Error struct { + HMessage string `json:"hmessage"` + Level int `json:"level"` +} + +type Namespace struct { + Key string `json:"key"` + Value float64 `json:"value"` +} + +type Metric struct { + MetricCode string `json:"metric_code"` + Namespaces []Namespace `json:"namespaces"` +} + +type Coordinate struct { + Line int `json:"line"` + Column int `json:"column"` +} + +type Position struct { + Begin Coordinate `json:"begin"` + End Coordinate `json:"end"` +} + +type Location struct { + Path string `json:"path"` + Position Position `json:"position"` +} diff --git a/utils/config.go b/utils/config.go new file mode 100644 index 00000000..082c8b73 --- /dev/null +++ b/utils/config.go @@ -0,0 +1,20 @@ +package utils + +import ( + "os/exec" + "strings" +) + +// Extracts the path of the config sent as a parameter in the user repo +// Checks in the current working directory as well as the root directory +// of the project +func ExtractProjectRootPath() (string, error) { + // Fetch the top-level directory + output, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() + if err != nil { + return "", err + } + + // Removing trailing null characters + return strings.TrimRight(string(output), "\000\n"), nil +} diff --git a/utils/prompt.go b/utils/prompt.go index 976cc059..5e8e3b6e 100644 --- a/utils/prompt.go +++ b/utils/prompt.go @@ -2,17 +2,21 @@ package utils import ( "errors" + "strings" "github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2/terminal" + "github.com/morikuni/aec" ) +type UserInputPrompt struct{} + // ========== // Useful APIs of survey library // ========== // Used for (Yes/No) questions -func ConfirmFromUser(msg, helpText string) (bool, error) { +func (UserInputPrompt) ConfirmFromUser(msg, helpText string) (bool, error) { response := false confirmPrompt := &survey.Confirm{ Renderer: survey.Renderer{}, @@ -20,7 +24,6 @@ func ConfirmFromUser(msg, helpText string) (bool, error) { Default: true, Help: helpText, } - err := survey.AskOne(confirmPrompt, &response) if err != nil { return true, checkInterrupt(err) @@ -33,7 +36,7 @@ func ConfirmFromUser(msg, helpText string) (bool, error) { // > * 1 // * 2 // * 3 -func SelectFromOptions(msg, helpText string, opts []string) (string, error) { +func (UserInputPrompt) SelectFromOptions(msg, helpText string, opts []string) (string, error) { var result string prompt := &survey.Select{ Renderer: survey.Renderer{}, @@ -46,17 +49,17 @@ func SelectFromOptions(msg, helpText string, opts []string) (string, error) { if err != nil { return "", checkInterrupt(err) } - return result, nil + return strings.TrimSpace(result), nil } // Used for Single Line Text Input // Being used for getting "Import root" of user for configuring meta of Go analyzer -func GetSingleLineInput(msg, helpText string) (string, error) { +func (UserInputPrompt) GetSingleLineInput(msg, helpText, defaultValue string) (string, error) { response := "" prompt := &survey.Input{ Renderer: survey.Renderer{}, Message: msg, - Default: "", + Default: defaultValue, Help: helpText, } @@ -64,7 +67,7 @@ func GetSingleLineInput(msg, helpText string) (string, error) { if err != nil { return "", checkInterrupt(err) } - return response, nil + return strings.TrimSpace(response), nil } // Used for multiple inputs from the displayed options @@ -75,7 +78,7 @@ func GetSingleLineInput(msg, helpText string) (string, error) { // [ ] Test Coverage // [ ] Python // [ ] Go -func SelectFromMultipleOptions(msg, helpText string, options []string) ([]string, error) { +func (UserInputPrompt) SelectFromMultipleOptions(msg, helpText string, options []string) ([]string, error) { response := make([]string, 0) // Extracting languages and tools being used in the project for Analyzers analyzerPrompt := &survey.MultiSelect{ @@ -99,3 +102,25 @@ func checkInterrupt(err error) error { } return err } + +func GetSuccessMessage(msg string) string { + greenTickMark := aec.Apply("[✔]", aec.LightGreenF) + return greenTickMark + " " + msg +} + +func GetFailureMessage(msg, errorMsg string) string { + if errorMsg != "" { + return aec.Apply("[✗]"+" "+msg+". "+"Error: "+errorMsg, aec.LightRedF) + } + return aec.Apply("[✗]"+" "+msg, aec.LightRedF) +} + +func GetBulletMessage(msg, color string) string { + switch color { + case "red": + return aec.Apply("•"+" "+msg, aec.LightRedF) + case "yellow": + return aec.Apply("•"+" "+msg, aec.LightYellowF) + } + return "" +} diff --git a/utils/remote_resolver.go b/utils/remote_resolver.go index a43933c7..925379d4 100644 --- a/utils/remote_resolver.go +++ b/utils/remote_resolver.go @@ -54,7 +54,8 @@ func ResolveRemote(repoArg string) (*RemoteData, error) { promptOpts = append(promptOpts, value[3]) } - selectedRemote, err := SelectFromOptions("Please select the repository:", "", promptOpts) + prompt := UserInputPrompt{} + selectedRemote, err := prompt.SelectFromOptions("Please select the repository:", "", promptOpts) if err != nil { return nil, err } diff --git a/utils/spinner.go b/utils/spinner.go new file mode 100644 index 00000000..45d37834 --- /dev/null +++ b/utils/spinner.go @@ -0,0 +1,78 @@ +package utils + +import ( + "fmt" + "os" + "sync" + "time" + + "github.com/briandowns/spinner" +) + +type SpinnerUtils struct { + mu sync.Mutex + Spinner *spinner.Spinner +} + +// Starts the spinner with the passed label. Final message is also set which is shown once the Spinner +// is stopped without an error. +func (s *SpinnerUtils) StartSpinnerWithLabel(label, finalMessage string) { + s.mu.Lock() + defer s.mu.Unlock() + + sp := spinner.New(spinner.CharSets[11], + 120*time.Millisecond, + spinner.WithWriter(os.Stdout), + spinner.WithWriter(os.Stderr), + ) // Build our new spinner + + if label != "" { + sp.Suffix = " " + label + " " + } + if finalMessage != "" { + sp.FinalMSG = fmt.Sprintf("%s\n", GetSuccessMessage(finalMessage)) + } + sp.Start() // Start the spinner + s.Spinner = sp +} + +// Stops the spinner +func (s *SpinnerUtils) StopSpinner() { + s.mu.Lock() + defer s.mu.Unlock() + + if s.Spinner == nil { + return + } + s.Spinner.Stop() + s.Spinner = nil +} + +// Sets a suffix to the spinner. +func (s *SpinnerUtils) SetSuffix(suffix string) { + s.mu.Lock() + defer s.mu.Unlock() + + if s.Spinner == nil { + return + } + s.Spinner.Suffix = " " + suffix + " " +} + +// Stops the spinner and displays an error message with a cross emoji +func (s *SpinnerUtils) StopSpinnerWithError(msg string, errorMessage error) { + s.mu.Lock() + defer s.mu.Unlock() + + if errorMessage != nil { + s.Spinner.FinalMSG = GetFailureMessage(msg, errorMessage.Error()) + "\n" + } else { + s.Spinner.FinalMSG = GetFailureMessage(msg, "") + "\n" + } + + if s.Spinner == nil { + return + } + s.Spinner.Stop() + s.Spinner = nil +}