// Copyright 2021 Woodpecker Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "log" "os" "time" plugLib "git.kle.li/woodpecker/plugin-lib/urfave" "git.kle.li/woodpecker/plugin-sonar/pkg/plugin" "git.kle.li/woodpecker/plugin-sonar/pkg/sonar" "github.com/urfave/cli/v2" "go.uber.org/zap" ) var appVersion = "v0.0.1" func main() { app := &cli.App{ Name: "Woodpecker-SonarQube-Plugin", Usage: "Trigger SonarQube scan from a Woodpecker-CI pipeline", Version: appVersion, Compiled: time.Now(), Authors: []*cli.Author{ { Name: "Michael Amann [@gapodo:kle.li]", Email: "gapodo@geekvoid.net", }, }, Copyright: "(c) 2022 Woodpecker Authors", Before: func(ctx *cli.Context) error { fmt.Fprintf(ctx.App.Writer, "Woodpecker-CI SonarQube plugin\n") return nil }, After: func(ctx *cli.Context) error { fmt.Fprintf(ctx.App.Writer, "Exiting Woodpecker-CI SonarQube plugin\n") return nil }, Action: run, Flags: Flags(), } app.Run(os.Args) } func Flags() []cli.Flag { flags := []cli.Flag{ &cli.StringFlag{ Name: "logLevel", Usage: "plugin log level (debug, info, warn, error, dpanic, panic, and fatal)", Value: "info", EnvVars: []string{"PLUGIN_LOG_LEVEL"}, }, &cli.StringFlag{ Name: "key", Usage: "SonarQube project key", EnvVars: []string{"PLUGIN_SONAR_KEY"}, }, &cli.StringFlag{ Name: "name", Usage: "SonarQube project name", EnvVars: []string{"PLUGIN_SONAR_NAME"}, }, &cli.StringFlag{ Name: "host-url", Usage: "SonarQube host url", EnvVars: []string{"PLUGIN_SONAR_URL"}, }, &cli.StringFlag{ Name: "token", Usage: "SonarQube analysis token", EnvVars: []string{"PLUGIN_SONAR_TOKEN"}, }, &cli.StringFlag{ Name: "ver", Usage: "Project version", EnvVars: []string{"PLUGIN_SONAR_VERSION"}, }, &cli.IntFlag{ Name: "http-timeout", Usage: "Web request timeout", Value: 300, EnvVars: []string{"PLUGIN_TIMEOUT"}, }, &cli.StringFlag{ Name: "sources", Usage: "analysis sources", Value: ".", EnvVars: []string{"PLUGIN_SOURCES"}, }, &cli.StringFlag{ Name: "inclusions", Usage: "code inclusions", EnvVars: []string{"PLUGIN_INCLUSIONS"}, }, &cli.StringFlag{ Name: "exclusions", Usage: "code exclusions", EnvVars: []string{"PLUGIN_EXCLUSIONS"}, }, &cli.StringFlag{ Name: "sonar-log-Level", Usage: "sonar-scanner log level (as per sonar-scanner)", Value: "INFO", EnvVars: []string{"PLUGIN_SONAR_LOG_LEVEL"}, }, &cli.BoolFlag{ Name: "showProfiling", Usage: "showProfiling during analysis", Value: false, EnvVars: []string{"PLUGIN_SHOWPROFILING"}, }, &cli.StringFlag{ Name: "branchAnalysis", Usage: "execute branchAnalysis (true, auto, false)", EnvVars: []string{"PLUGIN_BRANCHANALYSIS"}, Value: "false", }, &cli.StringFlag{ Name: "usingProperties", Usage: "using sonar-project.properties", EnvVars: []string{"PLUGIN_USINGPROPERTIES"}, Value: "false", }, &cli.StringFlag{ Name: "binaries", Usage: "Java Binaries", EnvVars: []string{"PLUGIN_BINARIES"}, }, &cli.StringFlag{ Name: "quality-target", Usage: "Quality Gate target", EnvVars: []string{"PLUGIN_QUALITYGATE"}, Value: "OK", }, &cli.BoolFlag{ Name: "quality-gate-enabled", Usage: "true or false - stop pipeline if sonar quality gate conditions are not met", Value: true, EnvVars: []string{"PLUGIN_SONAR_QUALITY_ENABLED"}, }, &cli.IntFlag{ Name: "quality-gate-timeout", Usage: "number in seconds for timeout", Value: 300, EnvVars: []string{"PLUGIN_SONAR_QUALITY_GATE_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", EnvVars: []string{"PLUGIN_ARTIFACT_FILE"}, }, } flags = append(flags, plugLib.BuildFlags()...) return flags } func run(ctx *cli.Context) error { slog := setupSugaredLogger(ctx.String("logLevel")) slog.Infow("SonarQube plugin for Woodpecker", "version", appVersion) slog.Infow("Parsing values from env") plugin := pluginFromContext(ctx, slog) slog.Infow("Starting scanner") plugin.Exec() return nil } func pluginFromContext(ctx *cli.Context, logger *zap.SugaredLogger) plugin.Plugin { config := sonar.Config{ Key: ctx.String("key"), Name: ctx.String("name"), HostURL: ctx.String("host-url"), Token: ctx.String("token"), Version: ctx.String("ver"), HttpTimeout: ctx.Int("http-timeout"), Sources: ctx.String("sources"), Inclusions: ctx.String("inclusions"), Exclusions: ctx.String("exclusions"), SonarLogLevel: ctx.String("sonar-log-Level"), ShowProfiling: ctx.String("showProfiling"), BranchAnalysis: ctx.String("branchAnalysis"), UsingProperties: ctx.Bool("usingProperties"), Binaries: ctx.String("binaries"), Quality: ctx.String("quality-target"), QualityEnabled: ctx.Bool("quality-gate-enabled"), QualityTimeout: ctx.Int("quality-gate-timeout"), ArtifactFile: ctx.String("artifact-file"), Logger: logger, } bdata := plugLib.BuildDataFromContext(ctx, nil) plug := plugin.Plugin{ Config: &config, BuildData: &bdata, Logger: logger, } return plug } func setupSugaredLogger(level string) *zap.SugaredLogger { fmt.Printf("loglevel %s", level) alvl, err := zap.ParseAtomicLevel(level) if err != nil { log.Fatal(fmt.Errorf("provided plugin log level failed to parse \"%s\": %w", level, err)) } conf := zap.NewProductionConfig() conf.EncoderConfig.TimeKey = "" // don't print a timestamp conf.EncoderConfig.CallerKey = "" // don't show caller conf.Level = alvl if conf.Level.Level() == zap.DebugLevel { conf.EncoderConfig.CallerKey = "caller" conf.EncoderConfig.FunctionKey = "func" } logger := zap.Must(conf.Build()) defer logger.Sync() sugar := logger.Sugar() sugar.Infow("Logger setup", "levelConf", level) sugar.Debugw("debug level enabled") return sugar }