+
+
+
+
Detail tutorials: [DOCS.md](DOCS.md).
### Build process
build go binary file:
-`GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o drone-sonar`
+`GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o harness-sonar`
build docker image
-`docker build -t aosapps/drone-sonar-plugin .`
+`docker build -t diegokoala/harness-cie-sonarqube-scanner .`
### Testing the docker image:
@@ -18,17 +25,40 @@ docker run --rm \
-e PLUGIN_SOURCES=. \
-e SONAR_HOST=http://localhost:9000 \
-e SONAR_TOKEN=60878847cea1a31d817f0deee3daa7868c431433 \
- aosapps/drone-sonar-plugin
+ -e PLUGIN_SONAR_KEY=project-sonar \
+ -e PLUGIN_SONAR_NAME=project-sonar \
+ diegokoala/harness-cie-sonarqube-scanner
```
### Pipeline example
```yaml
-steps
-- name: code-analysis
- image: aosapps/drone-sonar-plugin
- settings:
- sonar_host:
- from_secret: sonar_host
- sonar_token:
- from_secret: sonar_token
+- step:
+ type: Plugin
+ name: "Check Sonar "
+ identifier: Check_Sonar
+ spec:
+ connectorRef: account.DockerHubDiego
+ image: diegokoala/harness-cie-sonarqube-scanner:master
+ privileged: false
+ settings:
+ sonar_host: http://34.100.11.50
+ sonar_token: 60878847cea1a31d817f0deee3daa7868c431433
+ sources: "."
+ binaries: "."
+ sonar_name: harness-cie-sonarqube-scanner
+ sonar_key: harness-cie-sonarqube-scanner
+- step:
+ type: Run
+ name: Sonar Show Results
+ identifier: Sonar_Results
+ spec:
+ connectorRef: account.DockerHubDiego
+ image: maven:3.6.3-jdk-8
+ command: ls sonarResults.xml
+ privileged: false
+ reports:
+ type: JUnit
+ spec:
+ paths:
+ - sonarResults.xml
```
diff --git a/Sonar-CIE.png b/Sonar-CIE.png
new file mode 100644
index 0000000..974858c
Binary files /dev/null and b/Sonar-CIE.png differ
diff --git a/SonarResult.png b/SonarResult.png
new file mode 100644
index 0000000..dbe76d7
Binary files /dev/null and b/SonarResult.png differ
diff --git a/SonarResultConsole.png b/SonarResultConsole.png
new file mode 100644
index 0000000..12bafbe
Binary files /dev/null and b/SonarResultConsole.png differ
diff --git a/backup.old.md b/backup.old.md
new file mode 100644
index 0000000..d12cef8
--- /dev/null
+++ b/backup.old.md
@@ -0,0 +1,284 @@
+package main
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+
+ "github.com/pelletier/go-toml"
+ "github.com/sirupsen/logrus"
+)
+
+var netClient *http.Client
+
+type (
+ Config struct {
+ Key string
+ Name string
+ Host string
+ Token string
+
+ Version string
+ Branch string
+ Sources string
+ Timeout string
+ Inclusions string
+ Exclusions string
+ Level string
+ ShowProfiling string
+ BranchAnalysis bool
+ UsingProperties bool
+ Binaries string
+ Quality string
+ Settings string
+ Language string
+ Profile string
+ Encoding string
+ Remote string
+ }
+ Plugin struct {
+ Config Config
+ }
+ // SonarReport it is the representation of .scannerwork/report-task.txt
+ SonarReport struct {
+ ProjectKey string `toml:"projectKey"`
+ ServerURL string `toml:"serverUrl"`
+ DashboardURL string `toml:"dashboardUrl"`
+ CeTaskID string `toml:"ceTaskId"`
+ CeTaskURL string `toml:"ceTaskUrl"`
+ }
+ // TaskResponse Give Compute Engine task details such as type, status, duration and associated component.
+ TaskResponse struct {
+ Task struct {
+ ID string `json:"id"`
+ Type string `json:"type"`
+ ComponentID string `json:"componentId"`
+ ComponentKey string `json:"componentKey"`
+ ComponentName string `json:"componentName"`
+ AnalysisID string `json:"analysisId"`
+ Status string `json:"status"`
+ } `json:"task"`
+ }
+ // ProjectStatusResponse Get the quality gate status of a project or a Compute Engine task
+ ProjectStatusResponse struct {
+ ProjectStatus struct {
+ Status string `json:"status"`
+ } `json:"projectStatus"`
+ }
+)
+
+func init() {
+ netClient = &http.Client{
+ Timeout: time.Second * 10,
+ Transport: &http.Transport{
+ Dial: (&net.Dialer{
+ Timeout: 5 * time.Second,
+ }).Dial,
+ TLSHandshakeTimeout: 5 * time.Second,
+ },
+ }
+}
+
+func (p Plugin) Exec() error {
+ args := []string{
+ "-Dsonar.host.url=" + p.Config.Host,
+ "-Dsonar.login=" + p.Config.Token,
+ }
+
+ if !p.Config.UsingProperties {
+ argsParameter := []string{
+ "-Dsonar.projectKey=" + strings.Replace(p.Config.Key, "/", ":", -1),
+ "-Dsonar.projectName=" + p.Config.Name,
+ "-Dsonar.projectVersion=" + p.Config.Version,
+ "-Dsonar.sources=" + p.Config.Sources,
+ "-Dsonar.ws.timeout=" + p.Config.Timeout,
+ "-Dsonar.inclusions=" + p.Config.Inclusions,
+ "-Dsonar.exclusions=" + p.Config.Exclusions,
+ "-Dsonar.log.level=" + p.Config.Level,
+ "-Dsonar.showProfiling=" + p.Config.ShowProfiling,
+ "-Dsonar.scm.provider=git",
+ "-Dsonar.java.binaries=" + p.Config.Binaries,
+ }
+ args = append(args, argsParameter...)
+ }
+
+ if p.Config.BranchAnalysis {
+ args = append(args, "-Dsonar.branch.name="+p.Config.Branch)
+ }
+
+ cmd := exec.Command("sonar-scanner", args...)
+ // fmt.Printf("==> Executing: %s\n", strings.Join(cmd.Args, " "))
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ fmt.Printf("==> Code Analysis Result:\n")
+ err := cmd.Run()
+ if err != nil {
+ return err
+ }
+
+ return nil
+ /*
+
+ report, err := staticScan(&p)
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Unable to scan")
+ }
+
+ logrus.WithFields(logrus.Fields{
+ "job url": report.CeTaskURL,
+ }).Info("Job url")
+
+ task, err := waitForSonarJob(report)
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Unable to get Job state")
+ }
+
+ status := getStatus(task, report)
+
+ if status != p.Config.Quality {
+ logrus.WithFields(logrus.Fields{
+ "status": status,
+ }).Fatal("QualityGate status failed")
+ }
+
+ return nil
+ */
+}
+
+func getStatus(task *TaskResponse, report *SonarReport) string {
+ reportRequest := url.Values{
+ "analysisId": {task.Task.AnalysisID},
+ }
+ projectRequest, err := http.NewRequest("GET", report.ServerURL+"/api/qualitygates/project_status?"+reportRequest.Encode(), nil)
+ projectRequest.Header.Add("Authorization", "Basic "+os.Getenv("TOKEN"))
+ projectResponse, err := netClient.Do(projectRequest)
+
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Failed get status")
+ }
+ buf, _ := ioutil.ReadAll(projectResponse.Body)
+ project := ProjectStatusResponse{}
+ if err := json.Unmarshal(buf, &project); err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Failed")
+ }
+
+ return project.ProjectStatus.Status
+}
+
+func getSonarJobStatus(report *SonarReport) *TaskResponse {
+
+ taskRequest, err := http.NewRequest("GET", report.CeTaskURL, nil)
+ taskRequest.Header.Add("Authorization", "Basic "+os.Getenv("TOKEN"))
+ taskResponse, err := netClient.Do(taskRequest)
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Failed get sonar job status")
+ }
+ buf, _ := ioutil.ReadAll(taskResponse.Body)
+ task := TaskResponse{}
+ json.Unmarshal(buf, &task)
+ return &task
+}
+
+func waitForSonarJob(report *SonarReport) (*TaskResponse, error) {
+ timeout := time.After(300 * time.Second)
+ tick := time.Tick(500 * time.Millisecond)
+ for {
+ select {
+ case <-timeout:
+ return nil, errors.New("timed out")
+ case <-tick:
+ job := getSonarJobStatus(report)
+ if job.Task.Status == "SUCCESS" {
+ return job, nil
+ }
+ }
+ }
+}
+
+func staticScan(p *Plugin) (*SonarReport, error) {
+ if _, err := os.Stat(p.Config.Settings); errors.Is(err, os.ErrExist) {
+ cmd := exec.Command("sed", "-e", "s/=/=\"/", "-e", "s/$/\"/", p.Config.Settings)
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Run command sed failed")
+ return nil, err
+ }
+ // log.Printf("%s\n",output)
+
+ report := SonarReport{}
+ err = toml.Unmarshal(output, &report)
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Toml Unmarshal failed")
+ return nil, err
+ }
+
+ }
+ args := []string{
+ "-Dsonar.projectKey=" + strings.Replace(p.Config.Key, "/", ":", -1),
+ "-Dsonar.projectName=" + p.Config.Name,
+ "-Dsonar.host.url=" + p.Config.Host,
+ "-Dsonar.login=" + p.Config.Token,
+ "-Dsonar.projectVersion=" + p.Config.Version,
+ "-Dsonar.sources=" + p.Config.Sources,
+ "-Dproject.settings=" + p.Config.Settings,
+ "-Dsonar.ws.timeout=360",
+ "-Dsonar.inclusions=" + p.Config.Inclusions,
+ "-Dsonar.exclusions=" + p.Config.Exclusions,
+ "-Dsonar.profile=" + p.Config.Profile,
+ "-Dsonar.branch=" + p.Config.Branch,
+ "-Dsonar.scm.provider=git",
+ //"-Dsonar.java.binaries=" + p.Binaries,
+ }
+
+ cmd := exec.Command("sonar-scanner", args...)
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Run command sonar-scanner failed")
+ return nil, err
+ }
+ fmt.Printf("out:\n%s", output)
+ cmd = exec.Command("sed", "-e", "s/=/=\"/", "-e", "s/$/\"/", ".scannerwork/report-task.txt")
+ output, err = cmd.CombinedOutput()
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Run command sed failed")
+ return nil, err
+ }
+ // log.Printf("%s\n",output)
+
+ report := SonarReport{}
+ err = toml.Unmarshal(output, &report)
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Toml Unmarshal failed")
+ return nil, err
+ }
+
+ return &report, nil
+}
diff --git a/drone-plugin-sonar b/drone-plugin-sonar
new file mode 100755
index 0000000..28d0890
Binary files /dev/null and b/drone-plugin-sonar differ
diff --git a/drone-sonar b/drone-sonar
new file mode 100755
index 0000000..e85e38f
Binary files /dev/null and b/drone-sonar differ
diff --git a/drone-sonar-scanner b/drone-sonar-scanner
new file mode 100755
index 0000000..e85e38f
Binary files /dev/null and b/drone-sonar-scanner differ
diff --git a/go.mod b/go.mod
new file mode 100755
index 0000000..41752cb
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,12 @@
+module github.com/diegopereiraeng/drone-plugin-sonar
+
+go 1.16
+
+require (
+ github.com/drone/drone-kaniko v1.2.0
+ github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
+ github.com/pelletier/go-toml v1.9.4
+ github.com/sirupsen/logrus v1.8.1
+ github.com/stretchr/objx v0.1.1 // indirect
+ github.com/urfave/cli v1.22.5
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..21aa94f
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,41 @@
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/drone/drone-kaniko v1.2.0 h1:N8OhTryD6rJcYhKJZQJULCgORS4Qbq5HdaSAOlIBMhQ=
+github.com/drone/drone-kaniko v1.2.0/go.mod h1:mOx9kiKqi6AWizM46mj9QBs+kzD4WgKASeLNQzGDq3M=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
+github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
+github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
+github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
+github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+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/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
+github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+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=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/main.go b/main.go
index d23168f..5a0570e 100755
--- a/main.go
+++ b/main.go
@@ -1,30 +1,30 @@
package main
import (
+ "encoding/base64"
"fmt"
- "github.com/codegangsta/cli"
"os"
+
+ "github.com/urfave/cli"
)
var build = "1" // build number set at compile time
-
func main() {
app := cli.NewApp()
app.Name = "Drone-Sonar-Plugin"
- app.Usage = "Drone plugin to integrate with SonarQube."
+ app.Usage = "Drone plugin to integrate with SonarQube and check for Quality Gate."
app.Action = run
app.Version = fmt.Sprintf("1.0.%s", build)
app.Flags = []cli.Flag{
-
cli.StringFlag{
Name: "key",
Usage: "project key",
- EnvVar: "DRONE_REPO",
+ EnvVar: "PLUGIN_SONAR_KEY",
},
cli.StringFlag{
Name: "name",
Usage: "project name",
- EnvVar: "DRONE_REPO",
+ EnvVar: "PLUGIN_SONAR_NAME",
},
cli.StringFlag{
Name: "host",
@@ -36,22 +36,20 @@ func main() {
Usage: "SonarQube token",
EnvVar: "PLUGIN_SONAR_TOKEN",
},
-
- // advanced parameters
cli.StringFlag{
Name: "ver",
Usage: "Project version",
- EnvVar: "DRONE_BUILD_NUMBER",
+ EnvVar: "PLUGIN_BUILD_NUMBER",
},
cli.StringFlag{
Name: "branch",
Usage: "Project branch",
- EnvVar: "DRONE_BRANCH",
+ EnvVar: "PLUGIN_BRANCH",
},
cli.StringFlag{
Name: "timeout",
Usage: "Web request timeout",
- Value: "60",
+ Value: "300",
EnvVar: "PLUGIN_TIMEOUT",
},
cli.StringFlag{
@@ -92,33 +90,70 @@ func main() {
Usage: "using sonar-project.properties",
EnvVar: "PLUGIN_USINGPROPERTIES",
},
+ cli.StringFlag{
+ Name: "binaries",
+ Usage: "Java Binaries",
+ EnvVar: "PLUGIN_BINARIES,JAVA_BINARIES",
+ },
+ cli.StringFlag{
+ Name: "quality",
+ Usage: "Quality Gate",
+ EnvVar: "SONAR_QUALITYGATE,PLUGIN_QUALITYGATE",
+ Value: "OK",
+ },
+ cli.StringFlag{
+ Name: "quality_gate_enabled",
+ Usage: "true or false - stop pipeline if sonar quality gate conditions are not met",
+ Value: "true",
+ EnvVar: "PLUGIN_SONAR_QUALITY_ENABLED",
+ },
+ cli.StringFlag{
+ Name: "qualitygate_timeout",
+ Usage: "number in seconds for timeout",
+ Value: "300",
+ EnvVar: "PLUGIN_SONAR_QUALITYGATE_TIMEOUT",
+ },
+ cli.StringFlag{
+ Name: "artifact_file",
+ Usage: "Artifact file location that will be generated by the plugin. This file will include information of docker images that are uploaded by the plugin.",
+ Value: "artifact.json",
+ EnvVar: "PLUGIN_ARTIFACT_FILE",
+ },
+ cli.StringFlag{
+ Name: "cs_opencover_reportsPaths",
+ Usage: "Sonar CS Test Coverage parameter",
+ Value: "",
+ EnvVar: "PLUGIN_CS_OPENCOVER_REPORTPATHS",
+ },
}
-
app.Run(os.Args)
}
-
func run(c *cli.Context) {
plugin := Plugin{
Config: Config{
- Key: c.String("key"),
- Name: c.String("name"),
- Host: c.String("host"),
- Token: c.String("token"),
-
- Version: c.String("ver"),
- Branch: c.String("branch"),
- Timeout: c.String("timeout"),
- Sources: c.String("sources"),
- Inclusions: c.String("inclusions"),
- Exclusions: c.String("exclusions"),
- Level: c.String("level"),
- ShowProfiling: c.String("showProfiling"),
- BranchAnalysis: c.Bool("branchAnalysis"),
- UsingProperties: c.Bool("usingProperties"),
-
+ Key: c.String("key"),
+ Name: c.String("name"),
+ Host: c.String("host"),
+ Token: c.String("token"),
+ Version: c.String("ver"),
+ Branch: c.String("branch"),
+ Timeout: c.String("timeout"),
+ Sources: c.String("sources"),
+ Inclusions: c.String("inclusions"),
+ Exclusions: c.String("exclusions"),
+ Level: c.String("level"),
+ ShowProfiling: c.String("showProfiling"),
+ BranchAnalysis: c.Bool("branchAnalysis"),
+ UsingProperties: c.Bool("usingProperties"),
+ Binaries: c.String("binaries"),
+ Quality: c.String("quality"),
+ QualityEnabled: c.String("quality_gate_enabled"),
+ ArtifactFile: c.String("artifact_file"),
+ QualityTimeout: c.String("qualitygate_timeout"),
+ CSOpenCoverReportPaths: c.String("cs_opencover_reportsPaths"),
},
}
-
+ os.Setenv("TOKEN", base64.StdEncoding.EncodeToString([]byte(c.String("token")+":")))
if err := plugin.Exec(); err != nil {
fmt.Println(err)
os.Exit(1)
diff --git a/plugin.go b/plugin.go
index c9bf783..707b5bb 100755
--- a/plugin.go
+++ b/plugin.go
@@ -5,8 +5,27 @@ import (
"os"
"os/exec"
"strings"
+
+ "github.com/pelletier/go-toml"
+ "github.com/sirupsen/logrus"
+
+ "encoding/json"
+ "errors"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/url"
+ "time"
+
+ "encoding/xml"
)
+var netClient *http.Client
+
+var projectKey = ""
+
+var sonarDashStatic = "/dashboard?id="
+
type (
Config struct {
Key string
@@ -14,31 +33,175 @@ type (
Host string
Token string
- Version string
- Branch string
- Sources string
- Timeout string
- Inclusions string
- Exclusions string
- Level string
- ShowProfiling string
- BranchAnalysis bool
- UsingProperties bool
+ Version string
+ Branch string
+ Sources string
+ Timeout string
+ Inclusions string
+ Exclusions string
+ Level string
+ ShowProfiling string
+ BranchAnalysis bool
+ UsingProperties bool
+ Binaries string
+ Quality string
+ QualityEnabled string
+ QualityTimeout string
+ ArtifactFile string
+ CSOpenCoverReportPaths string
+ }
+ // SonarReport it is the representation of .scannerwork/report-task.txt //
+ SonarReport struct {
+ ProjectKey string `toml:"projectKey"`
+ ServerURL string `toml:"serverUrl"`
+ DashboardURL string `toml:"dashboardUrl"`
+ CeTaskID string `toml:"ceTaskId"`
+ CeTaskURL string `toml:"ceTaskUrl"`
}
Plugin struct {
Config Config
}
+ // TaskResponse Give Compute Engine task details such as type, status, duration and associated component.
+ TaskResponse struct {
+ Task struct {
+ ID string `json:"id"`
+ Type string `json:"type"`
+ ComponentID string `json:"componentId"`
+ ComponentKey string `json:"componentKey"`
+ ComponentName string `json:"componentName"`
+ AnalysisID string `json:"analysisId"`
+ Status string `json:"status"`
+ } `json:"task"`
+ }
+ // ProjectStatusResponse Get the quality gate status of a project or a Compute Engine task
+ ProjectStatusResponse struct {
+ ProjectStatus struct {
+ Status string `json:"status"`
+ } `json:"projectStatus"`
+ }
+ Project struct {
+ ProjectStatus Status `json:"projectStatus"`
+ }
+ Status struct {
+ Status string `json:"status"`
+ IgnoredConditions bool `json:"ignoredConditions"`
+ Conditions []Condition `json:"conditions"`
+ }
+
+ Condition struct {
+ Status string `json:"status"`
+ MetricKey string `json:"metricKey"`
+ Comparator string `json:"comparator"`
+ PeriodIndex int `json:"periodIndex"`
+ ErrorThreshold string `json:"errorThreshold"`
+ ActualValue string `json:"actualValue"`
+ }
+
+ Testsuites struct {
+ XMLName xml.Name `xml:"testsuites"`
+ Text string `xml:",chardata"`
+ TestSuite []Testsuite `xml:"testsuite"`
+ }
+ Testsuite struct {
+ Text string `xml:",chardata"`
+ Package string `xml:"package,attr"`
+ Time int `xml:"time,attr"`
+ Tests int `xml:"tests,attr"`
+ Errors int `xml:"errors,attr"`
+ Name string `xml:"name,attr"`
+ TestCase []Testcase `xml:"testcase"`
+ }
+
+ Testcase struct {
+ Text string `xml:",chardata"`
+ Time int `xml:"time,attr"` // Actual Value Sonar
+ Name string `xml:"name,attr"` // Metric Key
+ Classname string `xml:"classname,attr"` // The metric Rule
+ Failure *Failure `xml:"failure"` // Sonar Failure - show results
+ }
+ Failure struct {
+ Text string `xml:",chardata"`
+ Message string `xml:"message,attr"`
+ }
)
+func init() {
+ netClient = &http.Client{
+ Timeout: time.Second * 10,
+ Transport: &http.Transport{
+ Dial: (&net.Dialer{
+ Timeout: 5 * time.Second,
+ }).Dial,
+ TLSHandshakeTimeout: 5 * time.Second,
+ },
+ }
+}
+
+func TryCatch(f func()) func() error {
+ return func() (err error) {
+ defer func() {
+ if panicInfo := recover(); panicInfo != nil {
+ err = fmt.Errorf("%v", panicInfo)
+ return
+ }
+ }()
+ f() // calling the decorated function
+ return err
+ }
+}
+func ParseJunit(projectArray Project, projectName string) Testsuites {
+ errors := 0
+ total := 0
+ testCases := []Testcase{}
+
+ conditionsArray := projectArray.ProjectStatus.Conditions
+
+ for _, condition := range conditionsArray {
+ total += 1
+ if condition.Status != "OK" {
+ errors += 1
+ cond := &Testcase{
+ Name: condition.MetricKey, Classname: "Violate if " + condition.ActualValue + " is " + condition.Comparator + " " + condition.ErrorThreshold, Failure: &Failure{Message: "Violated: " + condition.ActualValue + " is " + condition.Comparator + " " + condition.ErrorThreshold},
+ }
+ testCases = append(testCases, *cond)
+ } else {
+ cond := &Testcase{Name: condition.MetricKey, Classname: "Violate if " + condition.ActualValue + " is " + condition.Comparator + " " + condition.ErrorThreshold, Time: 0}
+ testCases = append(testCases, *cond)
+ }
+ }
+ dashboardLink := os.Getenv("PLUGIN_SONAR_HOST") + sonarDashStatic + os.Getenv("PLUGIN_SONAR_NAME")
+ SonarJunitReport := &Testsuites{
+ TestSuite: []Testsuite{
+ Testsuite{
+ Time: 13, Package: projectName, Errors: errors, Tests: total, Name: dashboardLink, TestCase: testCases,
+ },
+ },
+ }
+
+ out, _ := xml.MarshalIndent(SonarJunitReport, " ", " ")
+ fmt.Println(string(out))
+ fmt.Printf("\n")
+ out, _ = xml.MarshalIndent(testCases, " ", " ")
+ fmt.Println(string(out))
+ fmt.Printf("\n")
+
+ return *SonarJunitReport
+}
+
+func GetProjectKey(key string) string {
+ projectKey = strings.Replace(key, "/", ":", -1)
+ return projectKey
+}
func (p Plugin) Exec() error {
args := []string{
"-Dsonar.host.url=" + p.Config.Host,
"-Dsonar.login=" + p.Config.Token,
}
+ projectFinalKey := p.Config.Key
if !p.Config.UsingProperties {
argsParameter := []string{
- "-Dsonar.projectKey=" + strings.Replace(p.Config.Key, "/", ":", -1),
+ "-Dsonar.projectKey=" + projectFinalKey,
"-Dsonar.projectName=" + p.Config.Name,
"-Dsonar.projectVersion=" + p.Config.Version,
"-Dsonar.sources=" + p.Config.Sources,
@@ -48,17 +211,25 @@ func (p Plugin) Exec() error {
"-Dsonar.log.level=" + p.Config.Level,
"-Dsonar.showProfiling=" + p.Config.ShowProfiling,
"-Dsonar.scm.provider=git",
+ "-Dsonar.java.binaries=" + p.Config.Binaries,
}
args = append(args, argsParameter...)
}
-
-
if p.Config.BranchAnalysis {
- args = append(args, "-Dsonar.branch.name=" + p.Config.Branch)
+ args = append(args, "-Dsonar.branch.name="+p.Config.Branch)
+ }
+ if p.Config.QualityEnabled == "true" {
+ args = append(args, "-Dsonar.qualitygate.wait="+p.Config.QualityEnabled)
+ args = append(args, "-Dsonar.qualitygate.timeout="+p.Config.QualityTimeout)
+ }
+ if len(p.Config.CSOpenCoverReportPaths) >= 1 {
+ args = append(args, "-Dsonar.cs.opencover.reportsPaths="+p.Config.CSOpenCoverReportPaths)
}
+ os.Setenv("SONAR_USER_HOME", ".sonar")
+ fmt.Printf("sonar-scanner")
+ fmt.Printf("%v", args)
cmd := exec.Command("sonar-scanner", args...)
- // fmt.Printf("==> Executing: %s\n", strings.Join(cmd.Args, " "))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("==> Code Analysis Result:\n")
@@ -67,5 +238,176 @@ func (p Plugin) Exec() error {
return err
}
+ cmd = exec.Command("cat", ".scannerwork/report-task.txt")
+
+ cmd.Stdout = os.Stdout
+
+ cmd.Stderr = os.Stderr
+ fmt.Printf("==> Report Result:\n")
+ err = cmd.Run()
+
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Run command cat reportname failed")
+ return err
+ }
+
+ report, err := staticScan(&p)
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Unable to scan")
+ }
+ logrus.WithFields(logrus.Fields{
+ "job url": report.CeTaskURL,
+ }).Info("Job url")
+
+ task, err := waitForSonarJob(report)
+
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Unable to get Job state")
+ return err
+ }
+
+ status := getStatus(task, report)
+
+ fmt.Printf("\n")
+ fmt.Printf("==> SONAR PROJECT DASHBOARD <==\n")
+ fmt.Printf(p.Config.Host)
+ fmt.Printf(sonarDashStatic)
+ fmt.Printf(p.Config.Name)
+ fmt.Printf("\n==> Harness CIE SonarQube Plugin with Quality Gateway <==\n\n")
+ //"Docker", p.Config.ArtifactFile, (p.Config.Host + sonarDashStatic + p.Config.Name), "Sonar", "Harness Sonar Plugin", []string{"Diego", "latest"})
+
+ if status != p.Config.Quality && p.Config.QualityEnabled == "true" {
+ fmt.Printf("\n==> QUALITY ENABLED ENALED - set quality_gate_enabled as false to disable qg\n")
+ logrus.WithFields(logrus.Fields{
+ "status": status,
+ }).Fatal("QualityGate status failed")
+ }
+ if status != p.Config.Quality && p.Config.QualityEnabled == "false" {
+ fmt.Printf("\n==> QUALITY GATEWAY DISABLED\n")
+ fmt.Printf("\n==> FAILED <==\n")
+ logrus.WithFields(logrus.Fields{
+ "status": status,
+ }).Info("Quality Gate Status FAILED")
+ }
+ if status == p.Config.Quality {
+ fmt.Printf("\n==> QUALITY GATEWAY ENALED \n")
+ fmt.Printf("\n==> PASSED <==\n")
+ logrus.WithFields(logrus.Fields{
+ "status": status,
+ }).Info("Quality Gate Status Success")
+ }
+
return nil
}
+
+func staticScan(p *Plugin) (*SonarReport, error) {
+
+ cmd := exec.Command("sed", "-e", "s/=/=\"/", "-e", "s/$/\"/", ".scannerwork/report-task.txt")
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Run command sed failed")
+ return nil, err
+ }
+ report := SonarReport{}
+ err = toml.Unmarshal(output, &report)
+
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Toml Unmarshal failed")
+ return nil, err
+ }
+
+ return &report, nil
+}
+
+func getStatus(task *TaskResponse, report *SonarReport) string {
+ reportRequest := url.Values{
+ "analysisId": {task.Task.AnalysisID},
+ }
+ projectRequest, err := http.NewRequest("GET", report.ServerURL+"/api/qualitygates/project_status?"+reportRequest.Encode(), nil)
+ projectRequest.Header.Add("Authorization", "Basic "+os.Getenv("TOKEN"))
+ projectResponse, err := netClient.Do(projectRequest)
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Failed get status")
+ }
+ buf, _ := ioutil.ReadAll(projectResponse.Body)
+ project := ProjectStatusResponse{}
+ if err := json.Unmarshal(buf, &project); err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Failed")
+ }
+ fmt.Printf("==> Report Result:\n")
+ fmt.Printf(string(buf))
+
+ // JUNUT
+ junitReport := ""
+ junitReport = string(buf) // returns a string of what was written to it
+ fmt.Printf("\n---------------------> JUNIT Exporter <---------------------\n")
+ bytesReport := []byte(junitReport)
+ var projectReport Project
+ err = json.Unmarshal(bytesReport, &projectReport)
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("%+v", projectReport)
+ fmt.Printf("\n")
+ result := ParseJunit(projectReport, "BankingApp")
+ file, _ := xml.MarshalIndent(result, "", " ")
+ _ = ioutil.WriteFile("sonarResults.xml", file, 0644)
+
+ fmt.Printf("\n")
+ fmt.Printf("\n======> JUNIT Exporter <======\n")
+
+ //JUNIT
+ fmt.Printf("\n======> Harness Drone/CIE SonarQube Plugin <======\n\n====> Results:")
+
+ return project.ProjectStatus.Status
+}
+
+func getSonarJobStatus(report *SonarReport) *TaskResponse {
+
+ taskRequest, err := http.NewRequest("GET", report.CeTaskURL, nil)
+ taskRequest.Header.Add("Authorization", "Basic "+os.Getenv("TOKEN"))
+ taskResponse, err := netClient.Do(taskRequest)
+ if err != nil {
+ logrus.WithFields(logrus.Fields{
+ "error": err,
+ }).Fatal("Failed get sonar job status")
+ }
+ buf, _ := ioutil.ReadAll(taskResponse.Body)
+ task := TaskResponse{}
+ json.Unmarshal(buf, &task)
+ return &task
+}
+
+func waitForSonarJob(report *SonarReport) (*TaskResponse, error) {
+ timeout := time.After(300 * time.Second)
+ tick := time.Tick(500 * time.Millisecond)
+ for {
+ select {
+ case <-timeout:
+ return nil, errors.New("timed out")
+ case <-tick:
+ job := getSonarJobStatus(report)
+ if job.Task.Status == "SUCCESS" {
+ return job, nil
+ }
+ if job.Task.Status == "ERROR" {
+ return nil, errors.New("ERROR")
+ }
+ }
+ }
+}
diff --git a/sonar-scanner-plugin b/sonar-scanner-plugin
new file mode 100755
index 0000000..e85e38f
Binary files /dev/null and b/sonar-scanner-plugin differ
diff --git a/vendor/github.com/codegangsta/cli/LICENSE b/vendor/github.com/codegangsta/cli/LICENSE
deleted file mode 100755
index 5515ccf..0000000
--- a/vendor/github.com/codegangsta/cli/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-Copyright (C) 2013 Jeremy Saenz
-All Rights Reserved.
-
-MIT LICENSE
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/codegangsta/cli/README.md b/vendor/github.com/codegangsta/cli/README.md
deleted file mode 100755
index d9371cf..0000000
--- a/vendor/github.com/codegangsta/cli/README.md
+++ /dev/null
@@ -1,434 +0,0 @@
-[](http://gocover.io/github.com/codegangsta/cli)
-[](https://travis-ci.org/codegangsta/cli)
-[](https://godoc.org/github.com/codegangsta/cli)
-[](https://codebeat.co/projects/github-com-codegangsta-cli)
-
-# cli.go
-
-`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
-
-## Overview
-
-Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
-
-**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive!
-
-## Installation
-
-Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html).
-
-To install `cli.go`, simply run:
-```
-$ go get github.com/codegangsta/cli
-```
-
-Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
-```
-export PATH=$PATH:$GOPATH/bin
-```
-
-## Getting Started
-
-One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`.
-
-``` go
-package main
-
-import (
- "os"
- "github.com/codegangsta/cli"
-)
-
-func main() {
- cli.NewApp().Run(os.Args)
-}
-```
-
-This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation:
-
-``` go
-package main
-
-import (
- "os"
- "github.com/codegangsta/cli"
-)
-
-func main() {
- app := cli.NewApp()
- app.Name = "boom"
- app.Usage = "make an explosive entrance"
- app.Action = func(c *cli.Context) {
- println("boom! I say!")
- }
-
- app.Run(os.Args)
-}
-```
-
-Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below.
-
-## Example
-
-Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness!
-
-Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
-
-``` go
-package main
-
-import (
- "os"
- "github.com/codegangsta/cli"
-)
-
-func main() {
- app := cli.NewApp()
- app.Name = "greet"
- app.Usage = "fight the loneliness!"
- app.Action = func(c *cli.Context) {
- println("Hello friend!")
- }
-
- app.Run(os.Args)
-}
-```
-
-Install our command to the `$GOPATH/bin` directory:
-
-```
-$ go install
-```
-
-Finally run our new command:
-
-```
-$ greet
-Hello friend!
-```
-
-`cli.go` also generates neat help text:
-
-```
-$ greet help
-NAME:
- greet - fight the loneliness!
-
-USAGE:
- greet [global options] command [command options] [arguments...]
-
-VERSION:
- 0.0.0
-
-COMMANDS:
- help, h Shows a list of commands or help for one command
-
-GLOBAL OPTIONS
- --version Shows version information
-```
-
-### Arguments
-
-You can lookup arguments by calling the `Args` function on `cli.Context`.
-
-``` go
-...
-app.Action = func(c *cli.Context) {
- println("Hello", c.Args()[0])
-}
-...
-```
-
-### Flags
-
-Setting and querying flags is simple.
-
-``` go
-...
-app.Flags = []cli.Flag {
- cli.StringFlag{
- Name: "lang",
- Value: "english",
- Usage: "language for the greeting",
- },
-}
-app.Action = func(c *cli.Context) {
- name := "someone"
- if c.NArg() > 0 {
- name = c.Args()[0]
- }
- if c.String("lang") == "spanish" {
- println("Hola", name)
- } else {
- println("Hello", name)
- }
-}
-...
-```
-
-You can also set a destination variable for a flag, to which the content will be scanned.
-
-``` go
-...
-var language string
-app.Flags = []cli.Flag {
- cli.StringFlag{
- Name: "lang",
- Value: "english",
- Usage: "language for the greeting",
- Destination: &language,
- },
-}
-app.Action = func(c *cli.Context) {
- name := "someone"
- if c.NArg() > 0 {
- name = c.Args()[0]
- }
- if language == "spanish" {
- println("Hola", name)
- } else {
- println("Hello", name)
- }
-}
-...
-```
-
-See full list of flags at http://godoc.org/github.com/codegangsta/cli
-
-#### Alternate Names
-
-You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
-
-``` go
-app.Flags = []cli.Flag {
- cli.StringFlag{
- Name: "lang, l",
- Value: "english",
- Usage: "language for the greeting",
- },
-}
-```
-
-That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error.
-
-#### Values from the Environment
-
-You can also have the default value set from the environment via `EnvVar`. e.g.
-
-``` go
-app.Flags = []cli.Flag {
- cli.StringFlag{
- Name: "lang, l",
- Value: "english",
- Usage: "language for the greeting",
- EnvVar: "APP_LANG",
- },
-}
-```
-
-The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
-
-``` go
-app.Flags = []cli.Flag {
- cli.StringFlag{
- Name: "lang, l",
- Value: "english",
- Usage: "language for the greeting",
- EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
- },
-}
-```
-
-#### Values from alternate input sources (YAML and others)
-
-There is a separate package altsrc that adds support for getting flag values from other input sources like YAML.
-
-In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below:
-
-``` go
- altsrc.NewIntFlag(cli.IntFlag{Name: "test"})
-```
-
-Initialization must also occur for these flags. Below is an example initializing getting data from a yaml file below.
-
-``` go
- command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
-```
-
-The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context.
-It will then use that file name to initialize the yaml input source for any flags that are defined on that command.
-As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work.
-
-Currently only YAML files are supported but developers can add support for other input sources by implementing the
-altsrc.InputSourceContext for their given sources.
-
-Here is a more complete sample of a command using YAML support:
-
-``` go
- command := &cli.Command{
- Name: "test-cmd",
- Aliases: []string{"tc"},
- Usage: "this is for testing",
- Description: "testing",
- Action: func(c *cli.Context) {
- // Action to run
- },
- Flags: []cli.Flag{
- NewIntFlag(cli.IntFlag{Name: "test"}),
- cli.StringFlag{Name: "load"}},
- }
- command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
- err := command.Run(c)
-```
-
-### Subcommands
-
-Subcommands can be defined for a more git-like command line app.
-
-```go
-...
-app.Commands = []cli.Command{
- {
- Name: "add",
- Aliases: []string{"a"},
- Usage: "add a task to the list",
- Action: func(c *cli.Context) {
- println("added task: ", c.Args().First())
- },
- },
- {
- Name: "complete",
- Aliases: []string{"c"},
- Usage: "complete a task on the list",
- Action: func(c *cli.Context) {
- println("completed task: ", c.Args().First())
- },
- },
- {
- Name: "template",
- Aliases: []string{"r"},
- Usage: "options for task templates",
- Subcommands: []cli.Command{
- {
- Name: "add",
- Usage: "add a new template",
- Action: func(c *cli.Context) {
- println("new task template: ", c.Args().First())
- },
- },
- {
- Name: "remove",
- Usage: "remove an existing template",
- Action: func(c *cli.Context) {
- println("removed task template: ", c.Args().First())
- },
- },
- },
- },
-}
-...
-```
-
-### Subcommands categories
-
-For additional organization in apps that have many subcommands, you can
-associate a category for each command to group them together in the help
-output.
-
-E.g.
-
-```go
-...
- app.Commands = []cli.Command{
- {
- Name: "noop",
- },
- {
- Name: "add",
- Category: "template",
- },
- {
- Name: "remove",
- Category: "template",
- },
- }
-...
-```
-
-Will include:
-
-```
-...
-COMMANDS:
- noop
-
- Template actions:
- add
- remove
-...
-```
-
-### Bash Completion
-
-You can enable completion commands by setting the `EnableBashCompletion`
-flag on the `App` object. By default, this setting will only auto-complete to
-show an app's subcommands, but you can write your own completion methods for
-the App or its subcommands.
-
-```go
-...
-var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
-app := cli.NewApp()
-app.EnableBashCompletion = true
-app.Commands = []cli.Command{
- {
- Name: "complete",
- Aliases: []string{"c"},
- Usage: "complete a task on the list",
- Action: func(c *cli.Context) {
- println("completed task: ", c.Args().First())
- },
- BashComplete: func(c *cli.Context) {
- // This will complete if no args are passed
- if c.NArg() > 0 {
- return
- }
- for _, t := range tasks {
- fmt.Println(t)
- }
- },
- }
-}
-...
-```
-
-#### To Enable
-
-Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
-setting the `PROG` variable to the name of your program:
-
-`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
-
-#### To Distribute
-
-Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename
-it to the name of the program you wish to add autocomplete support for (or
-automatically install it there if you are distributing a package). Don't forget
-to source the file to make it active in the current shell.
-
-```
-sudo cp src/bash_autocomplete /etc/bash_completion.d/")
+ preCloseTag = []byte("")
+ codeTag = []byte("")
+ codeCloseTag = []byte("")
+ pTag = []byte("") + pCloseTag = []byte("
") + blockquoteTag = []byte("") + blockquoteCloseTag = []byte("") + hrTag = []byte("