-
Notifications
You must be signed in to change notification settings - Fork 15
/
main.go
137 lines (121 loc) · 3.23 KB
/
main.go
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
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"text/template"
"github.com/hashicorp/go-multierror"
"github.com/muesli/termenv"
"github.com/urfave/cli/v2"
"github.com/uw-labs/lichen/internal/scan"
"gopkg.in/yaml.v2"
)
const tmpl = `{{range .Modules}}
{{- .Module}}: {{range $i, $_ := .Module.Licenses}}{{if $i}}, {{end}}{{.Name}}{{end}}
{{- if .Allowed}} ({{ Color "#00ff00" .ExplainDecision}}){{else}} ({{ Color "#ff0000" .ExplainDecision}}){{end}}
{{end}}`
func main() {
a := &cli.App{
Name: "lichen",
Usage: "evaluate module dependencies from go compiled binaries",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "path to config file",
},
&cli.StringFlag{
Name: "template",
Aliases: []string{"t"},
Usage: "template for writing out each module and resolved licenses",
Value: tmpl,
},
&cli.StringFlag{
Name: "json",
Aliases: []string{"j"},
Usage: "write JSON results to the supplied file",
},
},
Action: run,
}
if err := a.Run(os.Args); err != nil {
log.Fatal(err)
}
}
func run(c *cli.Context) error {
if c.NArg() == 0 {
_ = cli.ShowAppHelp(c)
return errors.New("path to at least one binary must be supplied")
}
f := termenv.TemplateFuncs(termenv.ColorProfile())
output, err := template.New("output").Funcs(f).Parse(c.String("template"))
if err != nil {
return err
}
conf, err := parseConfig(c.String("config"))
if err != nil {
return fmt.Errorf("invalid config: %w", err)
}
paths, err := absolutePaths(c.Args().Slice())
if err != nil {
return fmt.Errorf("invalid arguments: %w", err)
}
summary, err := scan.Run(c.Context, conf, paths...)
if err != nil {
return fmt.Errorf("failed to evaluate licenses: %w", err)
}
if jsonPath := c.String("json"); jsonPath != "" {
if err := writeJSON(jsonPath, summary); err != nil {
return fmt.Errorf("failed to write json: %w", err)
}
}
if err := output.Execute(os.Stdout, summary); err != nil {
return fmt.Errorf("failed to write results: %w", err)
}
var rErr error
for _, m := range summary.Modules {
if !m.Allowed() {
rErr = multierror.Append(rErr, fmt.Errorf("%s: %s", m.Module.ModuleReference, m.ExplainDecision()))
}
}
return rErr
}
func parseConfig(path string) (scan.Config, error) {
var conf scan.Config
if path != "" {
b, err := ioutil.ReadFile(path)
if err != nil {
return scan.Config{}, fmt.Errorf("failed to read file %q: %w", path, err)
}
if err := yaml.Unmarshal(b, &conf); err != nil {
return scan.Config{}, fmt.Errorf("failed to parse yaml: %w", err)
}
}
return conf, nil
}
func absolutePaths(paths []string) ([]string, error) {
mapped := make([]string, len(paths))
for i, path := range paths {
abs, err := filepath.Abs(path)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path: %w", err)
}
mapped[i] = abs
}
return mapped, nil
}
func writeJSON(path string, summary scan.Summary) error {
f, err := os.Create(path)
if err != nil {
return fmt.Errorf("failed to create file for json output: %w", err)
}
defer f.Close()
if err := json.NewEncoder(f).Encode(summary); err != nil {
return fmt.Errorf("json encode failed: %w", err)
}
return nil
}