Skip to content

Commit

Permalink
Merge pull request #20 from odair-pedro/feature/decorate-cmd
Browse files Browse the repository at this point in the history
Implementing decorate command
  • Loading branch information
odair-pedro authored Oct 26, 2020
2 parents aae1acc + b1476d9 commit 0b706e7
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 169 deletions.
116 changes: 116 additions & 0 deletions cmd/decorate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package cmd

import (
"github.com/spf13/cobra"
"log"
"os"
"sonarci/connection/http"
"sonarci/decoration"
decorationFactory "sonarci/decoration/factory"
templateFactory "sonarci/decoration/template/factory"
"sonarci/sonar"
"time"
)

const (
flagDecorateProject = "project"
flagDecorateProjectShort = "p"
flagDecorateProjectUsage = "SonarQube projects key"
)

const (
flagDecoratePullRequest = "pull-request"
flagDecoratePullRequestShort = "r"
flagDecoratePullRequestUsage = "Pull request ID"
)

func NewDecorateCmd() *cobra.Command {
decorateCmd := &cobra.Command{
Use: "decorate",
Short: "Decorate pull request with the quality gate report",
Long: "Decorate pull request with the SonarQube's quality gate report.",
Run: decorate,
}

decorateCmd.Flags().StringP(flagDecorateProject, flagDecorateProjectShort, "", flagDecorateProjectUsage)
_ = decorateCmd.MarkFlagRequired(flagDecorateProject)

decorateCmd.Flags().StringP(flagDecoratePullRequest, flagDecoratePullRequestShort, "", flagDecoratePullRequestUsage)
_ = decorateCmd.MarkFlagRequired(flagDecoratePullRequest)

return decorateCmd
}

func decorate(cmd *cobra.Command, _ []string) {
pr, _ := cmd.Flags().GetString(flagDecoratePullRequest)
if !validateFlag(flagDecoratePullRequest, pr) {
return
}

project, _ := cmd.Flags().GetString(flagDecorateProject)
if !validateFlag(flagDecorateProject, project) {
return
}

pFlags := getPersistentFlagsFromCmd(cmd)
if pFlags == nil {
return
}

api := createSonarApi(pFlags.Server, pFlags.Token, pFlags.Timeout)
qualityGate, err := api.GetPullRequestQualityGate(project, pr)
if err != nil {
log.Fatal(err)
}

decoratePullRequest(qualityGate, pFlags.Timeout)
checkQualityGate(qualityGate)
}

func decoratePullRequest(qualityGate sonar.QualityGate, timeout time.Duration) {
const (
decoratorTypeEnv = "SONARCI_DECORATION_TYPE"
projectEnv = "SONARCI_DECORATION_PROJECT"
repositoryEnv = "SONARCI_DECORATION_REPOSITORY"
tokenEnv = "SONARCI_DECORATION_TOKEN"
)

decoratorType := os.Getenv(decoratorTypeEnv)
if decoratorType == "" {
log.Printf("Failed decoration, decorator type has not been found")
return
}

project := os.Getenv(projectEnv)
if project == "" {
log.Printf("Failed decoration, project information has not been found")
return
}

repository := os.Getenv(repositoryEnv)
if repository == "" {
log.Printf("Failed decoration, repository information has not been found")
return
}

token := os.Getenv(tokenEnv)
if token == "" {
log.Printf("Failed decoration, token information has not been found")
return
}

engine := templateFactory.CreateDummyTemplateEngine()
decorator, err := decorationFactory.CreatePullRequestDecorator(decoratorType, project, repository, engine,
func(server string) decoration.Connection {
return http.NewConnection(server, token, timeout)
})
if err != nil {
log.Printf(err.Error())
return
}

err = decorator.CommentQualityGate(qualityGate)
if err != nil {
log.Print("Failure on pull request decoration: ", err.Error())
}
}
10 changes: 10 additions & 0 deletions cmd/decorate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package cmd

import "testing"

func Test_NewDecorateCmd_CheckReturn(t *testing.T) {
cmd := NewDecorateCmd()
if cmd == nil {
t.Errorf("NewDecorateCmd() = nil")
}
}
78 changes: 78 additions & 0 deletions cmd/qualityGate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cmd

import (
"fmt"
"log"
"sonarci/sonar"
"sort"
"strconv"
"strings"
)

func checkQualityGate(qualityGate sonar.QualityGate) bool {
const banner = " ____ ____ ___ \n" +
"/ ___| ___ _ __ __ _ _ __ / ___|_ _|\n" +
"\\___ \\ / _ \\| '_ \\ / _ | '__| | | | | \n" +
" ___) | (_) | | | | (_| | | | |___ | | \n" +
"|____/ \\___/|_| |_|\\__,_|_| \\____|___|\n\n"

log.Print(banner)
log.Println(genQualityReport(qualityGate))
log.Printf("\nSee more details in %s", qualityGate.LinkDetail)

return qualityGate.HasPassed()
}

func genQualityReport(qualityGate sonar.QualityGate) string {
const (
metricColW = 28
comparatorColW = 10
errorColW = 15
valueColW = 12
statusColW = 6
)

header := "+------------------------------+------------+-----------------+--------------+--------+\n" +
"| METRIC | COMPARATOR | ERROR THRESHOLD | ACTUAL VALUE | STATUS |\n" +
"+------------------------------+------------+-----------------+--------------+--------+\n"
footer := "+------------------------------+------------+-----------------+--------------+--------+\n" +
fmt.Sprintf("| | | | QUALITY GATE | %s |\n",
colorful(qualityGate.Status, padRight(qualityGate.Status, " ", statusColW))) +
"+------------------------------+------------+-----------------+--------------+--------+"

keys := make([]string, 0)
for k := range qualityGate.Conditions {
keys = append(keys, k)
}

sort.Strings(keys)
var rows string
for _, key := range keys {
metric := qualityGate.Conditions[key]
rows += fmt.Sprintf("| %s | %s | %s | %s | %s |\n",
colorful(metric.Status, padRight(metric.Description, " ", metricColW)),
colorful(metric.Status, padRight(metric.Comparator, " ", comparatorColW)),
colorful(metric.Status, padRight(strconv.FormatFloat(float64(metric.ErrorThreshold), 'f', 5, 32), " ", errorColW)),
colorful(metric.Status, padRight(strconv.FormatFloat(float64(metric.Value), 'f', 5, 32), " ", valueColW)),
colorful(metric.Status, padRight(metric.Status, " ", statusColW)))
}

return header + rows + footer
}

func colorful(status string, value string) string {
const (
colorReset = "\033[0m"
colorRed = "\033[31m"
colorGreen = "\033[32m"
)

status = strings.ToUpper(strings.Trim(status, " "))
if status == "OK" {
return fmt.Sprint(colorGreen, value, colorReset)
}
if status == "ERROR" {
return fmt.Sprint(colorRed, value, colorReset)
}
return value
}
49 changes: 49 additions & 0 deletions cmd/qualityGate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cmd

import (
"sonarci/sonar"
"testing"
)

func Test_genQualityReport(t *testing.T) {
const want = "+------------------------------+------------+-----------------+--------------+--------+\n" +
"| METRIC | COMPARATOR | ERROR THRESHOLD | ACTUAL VALUE | STATUS |\n" +
"+------------------------------+------------+-----------------+--------------+--------+\n" +
"| New Reliability Rating | GT | 30.00000 | 56.90000 | STATUS |\n" +
"| New Security Rating | GT | 15.00000 | 20.50000 | STATUS |\n" +
"| New Vulnerabilities | GT | 0.00000 | 3.00000 | STATUS |\n" +
"+------------------------------+------------+-----------------+--------------+--------+\n" +
"| | | | QUALITY GATE | STATUS |\n" +
"+------------------------------+------------+-----------------+--------------+--------+"

qualityGate := sonar.QualityGate{
Status: "STATUS",
LinkDetail: "http://link-detail",
Conditions: map[string]sonar.QualityGateCondition{
"new_reliability_rating": {Status: "STATUS", Description: "New Reliability Rating", Value: 56.9, ErrorThreshold: 30, Comparator: "GT"},
"new_security_rating": {Status: "STATUS", Description: "New Security Rating", Value: 20.5, ErrorThreshold: 15, Comparator: "GT"},
"new_vulnerabilities": {Status: "STATUS", Description: "New Vulnerabilities", Value: 3, ErrorThreshold: 0, Comparator: "GT"},
},
}

report := genQualityReport(qualityGate)
if report != want {
t.Errorf("genQualityReport() = \n%s \n\nwant: \n%s", report, want)
} else {
t.Logf("\n%s", report)
}
}

func Test_checkQualityGate_ReturnTrue(t *testing.T) {
result := checkQualityGate(sonar.QualityGate{Status: "OK"})
if !result {
t.Errorf("checkQualityGate() = false, want true")
}
}

func Test_checkQualityGate_ReturnFalse(t *testing.T) {
result := checkQualityGate(sonar.QualityGate{Status: "ERROR"})
if result {
t.Errorf("checkQualityGate() = true, want false")
}
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func NewRootCmd() *cobra.Command {
rootCmd.AddCommand(NewServerVersionCmd())
rootCmd.AddCommand(NewSearchCmd())
rootCmd.AddCommand(NewValidateCmd())
rootCmd.AddCommand(NewDecorateCmd())

return rootCmd
}
Expand Down
Loading

0 comments on commit 0b706e7

Please sign in to comment.