-
Notifications
You must be signed in to change notification settings - Fork 23
Expand file tree
/
Copy pathreport.go
More file actions
297 lines (261 loc) · 9.98 KB
/
report.go
File metadata and controls
297 lines (261 loc) · 9.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
package report
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/MakeNowJust/heredoc"
"github.com/deepsourcelabs/cli/internal/cli/args"
"github.com/deepsourcelabs/cli/internal/cli/style"
"github.com/deepsourcelabs/cli/internal/container"
"github.com/deepsourcelabs/cli/internal/interfaces"
reportsvc "github.com/deepsourcelabs/cli/internal/services/report"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
type ReportOptions struct {
Analyzer string
AnalyzerType string
CommitOID string
Key string
Value string
ValueFile string
SkipCertificateVerification bool
DSN string
UseOIDC bool
OIDCRequestToken string // id token to manually get an OIDC token
OIDCRequestUrl string // url to manually get an OIDC token
DeepSourceHostEndpoint string // DeepSource host endpoint where the app is running. Defaults to the cloud endpoint https://app.deepsource.com
OIDCProvider string // OIDC provider to use for authentication
Output string // Output format: pretty, json
}
// NewCmdReport returns the command to report artifacts to DeepSource
func NewCmdReport() *cobra.Command {
return NewCmdReportWithDeps(nil)
}
// NewCmdReportWithDeps builds the report command with injected dependencies.
// When deps is nil, it will be created at execution time to respect flags/env.
func NewCmdReportWithDeps(deps *container.Container) *cobra.Command {
opts := ReportOptions{}
doc := heredoc.Docf(`
Report artifacts to DeepSource.
Use %[1]s to specify the analyzer shortcode and %[2]s to identify the language:
%[3]s
Provide the artifact value inline or from a file:
%[4]s
%[5]s
`,
style.Yellow("--analyzer"),
style.Yellow("--key"),
style.Cyan("deepsource report --analyzer test-coverage --key python --value-file coverage.xml"),
style.Cyan("deepsource report --analyzer test-coverage --key go --value '<coverage_data>'"),
style.Cyan("deepsource report --analyzer test-coverage --key go --value-file coverage.out"),
)
cmd := &cobra.Command{
Use: "report",
Short: "Report artifacts to DeepSource",
Long: doc,
Args: args.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
if deps == nil {
deps = container.New()
}
// Resolve host: explicit flag > config > default
if !cmd.Flags().Changed("host") && !cmd.Flags().Changed("deepsource-host-endpoint") {
if cfg, err := deps.Config.Load(); err == nil && cfg.Host != "" {
opts.DeepSourceHostEndpoint = hostToEndpoint(cfg.Host)
}
}
if opts.DeepSourceHostEndpoint == "" {
opts.DeepSourceHostEndpoint = "https://app.deepsource.com"
}
// Resolve skip-tls-verify: local --skip-verify | global --skip-tls-verify | config
if !opts.SkipCertificateVerification {
if f := cmd.Root().PersistentFlags().Lookup("skip-tls-verify"); f != nil && f.Changed {
opts.SkipCertificateVerification = true
}
}
if !opts.SkipCertificateVerification {
if cfg, err := deps.Config.Load(); err == nil {
opts.SkipCertificateVerification = cfg.SkipTLSVerify
}
}
svc := reportsvc.NewService(reportsvc.ServiceDeps{
GitClient: deps.GitClient,
HTTPClient: deps.HTTPClient,
FileSystem: deps.FileSystem,
Environment: deps.Environment,
Sentry: deps.Sentry,
Output: deps.Output,
Workdir: os.Getwd,
})
return opts.Run(cmd.Context(), svc, deps.Output)
},
}
cmd.Flags().StringVar(&opts.Analyzer, "analyzer", "", "name of the analyzer to report the artifact to (example: test-coverage)")
cmd.Flags().StringVar(&opts.AnalyzerType, "analyzer-type", "", "type of the analyzer (example: community)")
cmd.Flags().StringVar(&opts.CommitOID, "commit", "", "commit SHA to report against (skips git detection)")
cmd.Flags().StringVar(&opts.Key, "key", "", "shortcode of the language (example: go)")
cmd.Flags().StringVar(&opts.Value, "value", "", "value of the artifact")
cmd.Flags().StringVar(&opts.ValueFile, "value-file", "", "path to the artifact value file")
cmd.Flags().BoolVar(&opts.UseOIDC, "use-oidc", false, "use OIDC to authenticate with DeepSource")
cmd.Flags().StringVar(&opts.OIDCRequestToken, "oidc-request-token", "", "request ID token to fetch an OIDC token from OIDC provider")
cmd.Flags().StringVar(&opts.OIDCRequestUrl, "oidc-request-url", "", "OIDC provider's request URL to fetch an OIDC token")
cmd.Flags().StringVar(&opts.DeepSourceHostEndpoint, "host", "", "DeepSource host endpoint")
cmd.Flags().StringVar(&opts.DeepSourceHostEndpoint, "deepsource-host-endpoint", "", "DeepSource host endpoint")
_ = cmd.Flags().MarkDeprecated("deepsource-host-endpoint", "use --host instead")
cmd.Flags().StringVar(&opts.OIDCProvider, "oidc-provider", "", "OIDC provider to use for authentication. Supported providers: github-actions")
cmd.Flags().StringVar(&opts.Output, "output", "pretty", "Output format: pretty, json")
cmd.Flags().BoolVar(&opts.SkipCertificateVerification, "skip-verify", false, "skip SSL certificate verification while sending the test coverage data")
_ = cmd.Flags().MarkDeprecated("skip-verify", "use the global --skip-tls-verify flag instead")
_ = cmd.RegisterFlagCompletionFunc("analyzer", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return []string{
"test-coverage\tReport test coverage data",
}, cobra.ShellCompDirectiveNoFileComp
})
_ = cmd.RegisterFlagCompletionFunc("key", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return []string{
"python", "go", "javascript", "ruby", "java", "scala", "php", "csharp", "cxx", "rust", "swift", "kotlin",
}, cobra.ShellCompDirectiveNoFileComp
})
_ = cmd.RegisterFlagCompletionFunc("output", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return []string{
"pretty\tPretty-printed output",
"json\tJSON output",
}, cobra.ShellCompDirectiveNoFileComp
})
_ = cmd.RegisterFlagCompletionFunc("analyzer-type", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return []string{
"community\tCommunity analyzer",
}, cobra.ShellCompDirectiveNoFileComp
})
_ = cmd.RegisterFlagCompletionFunc("oidc-provider", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return []string{
"github-actions\tGitHub Actions OIDC",
}, cobra.ShellCompDirectiveNoFileComp
})
setReportUsageFunc(cmd)
return cmd
}
func (opts *ReportOptions) Run(ctx context.Context, svc *reportsvc.Service, output interfaces.OutputWriter) error {
result, err := svc.Report(ctx, reportsvc.Options{
Analyzer: opts.Analyzer,
AnalyzerType: opts.AnalyzerType,
CommitOID: opts.CommitOID,
Key: opts.Key,
Value: opts.Value,
ValueFile: opts.ValueFile,
SkipCertificateVerification: opts.SkipCertificateVerification,
DSN: opts.DSN,
UseOIDC: opts.UseOIDC,
OIDCRequestToken: opts.OIDCRequestToken,
OIDCRequestUrl: opts.OIDCRequestUrl,
DeepSourceHostEndpoint: opts.DeepSourceHostEndpoint,
OIDCProvider: opts.OIDCProvider,
})
if err != nil {
return err
}
if err := printReportResult(output, opts.Output, result); err != nil {
return err
}
return nil
}
func setReportUsageFunc(cmd *cobra.Command) {
cmd.SetUsageFunc(func(c *cobra.Command) error {
groups := []struct {
title string
flags []string
}{
{"Artifact", []string{"analyzer", "analyzer-type", "commit", "key", "value", "value-file"}},
{"Authentication", []string{"use-oidc", "oidc-provider", "oidc-request-token", "oidc-request-url", "host"}},
{"Output", []string{"output"}},
{"General", []string{"skip-verify", "help"}},
}
w := c.OutOrStderr()
fmt.Fprintf(w, "Usage:\n %s\n", c.UseLine())
for _, g := range groups {
fmt.Fprintf(w, "\n%s:\n", g.title)
for _, name := range g.flags {
f := c.Flags().Lookup(name)
if f == nil {
continue
}
fmt.Fprintf(w, " %s\n", flagUsageLine(f))
}
}
fmt.Fprintln(w)
return nil
})
}
func flagUsageLine(f *pflag.Flag) string {
var line string
if f.Shorthand != "" {
line = fmt.Sprintf("-%s, --%s", f.Shorthand, f.Name)
} else {
line = fmt.Sprintf(" --%s", f.Name)
}
vartype := f.Value.Type()
switch vartype {
case "bool":
// no type suffix for booleans
case "stringSlice":
line += " strings"
default:
line += " " + vartype
}
const pad = 28
if len(line) < pad {
line += strings.Repeat(" ", pad-len(line))
} else {
line += " "
}
line += f.Usage
if f.DefValue != "" && f.DefValue != "false" && f.DefValue != "[]" && f.DefValue != "0" {
line += fmt.Sprintf(" (default %s)", f.DefValue)
}
return line
}
// hostToEndpoint converts a bare config hostname to a full endpoint URL.
func hostToEndpoint(host string) string {
switch host {
case "deepsource.com", "deepsource.io":
return "https://app.deepsource.com"
case "deepsource.one":
return "https://app.deepsource.one"
default:
return "https://" + host
}
}
func printReportResult(output interfaces.OutputWriter, format string, result *reportsvc.Result) error {
write := func(format string, args ...interface{}) {
if output == nil {
fmt.Printf(format, args...)
return
}
output.Printf(format, args...)
}
switch format {
case "", "pretty":
write("Artifact published successfully\n")
write("Analyzer %s\n", result.Analyzer)
write("Key %s\n", result.Key)
if result.Message != "" {
write("Message %s\n", result.Message)
}
if result.Warning != "" {
write("%s", result.Warning)
}
return nil
case "json":
payload, err := json.MarshalIndent(result, "", " ")
if err != nil {
return fmt.Errorf("Failed to format JSON output: %w", err)
}
write("%s\n", payload)
return nil
default:
return fmt.Errorf("Unsupported output format: %s", format)
}
}