Skip to content

Commit 1e6c674

Browse files
authored
Multiple Feature Release (#10)
* Refactor workq to support callbacks * Use workq callbacks in scan * Add callbacks in scan for progress reporting * Add a progress tracker based UI * Add minimal markdown report * Add container image releaser action Fix typo in dockerfile * Release container on tags
1 parent 654bd5c commit 1e6c674

13 files changed

Lines changed: 419 additions & 64 deletions

File tree

.github/workflows/container.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Container Image Releaser
2+
3+
on:
4+
push:
5+
tags:
6+
- "*"
7+
8+
env:
9+
REGISTRY: ghcr.io
10+
IMAGE_NAME: ${{ github.repository }}
11+
12+
jobs:
13+
build:
14+
if: "!contains(github.event.commits[0].message, '[noci]')"
15+
runs-on: ubuntu-latest
16+
permissions:
17+
contents: read
18+
packages: write
19+
id-token: write
20+
21+
steps:
22+
- uses: actions/checkout@v3
23+
with:
24+
submodules: true
25+
fetch-depth: 0
26+
27+
- name: Registry Login
28+
uses: docker/login-action@v1
29+
with:
30+
registry: ${{ env.REGISTRY }}
31+
username: ${{ github.actor }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
33+
34+
- name: Setup QEMU
35+
uses: docker/setup-qemu-action@v2
36+
37+
- name: Setup Docker Buildx
38+
uses: docker/setup-buildx-action@v2
39+
40+
- name: Build and Push Container Image
41+
run: |
42+
docker buildx build --push --platform linux/amd64 \
43+
-t $REGISTRY/$IMAGE_NAME:latest \
44+
.
45+
46+

Dockerfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
FROM --platform=$BUILDPLATFORM golang:1.19-buster AS build
2+
3+
WORKDIR /build
4+
5+
COPY go.mod go.sum ./
6+
7+
RUN go mod download
8+
9+
COPY . .
10+
11+
ENV CGO_ENABLED=0
12+
13+
RUN go build -o vet
14+
15+
FROM gcr.io/distroless/base-debian11
16+
17+
ARG TARGETPLATFORM
18+
19+
LABEL org.opencontainers.image.source=https://github.com/safedep/vet
20+
LABEL org.opencontainers.image.description="Open source software supply chain security tool"
21+
LABEL org.opencontainers.image.licenses=Apache-2.0
22+
23+
COPY --from=build /build/vet /usr/local/bin/vet
24+
25+
USER nonroot:nonroot
26+
27+
ENTRYPOINT ["vet"]

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,22 @@ Look at `api/insights-v1.yml`. It contains the contract expected for Insights
100100
API. You can perhaps consider rolling out your own to avoid dependency with our
101101
backend.
102102

103+
### Something is wrong! How do I debug this thing?
104+
105+
Run without the eye candy UI and enable log to file or to `stdout`.
106+
107+
Log to `stdout`:
108+
109+
```bash
110+
vet scan -D /path/to/repo -s -l- -v
111+
```
112+
113+
Log to file:
114+
115+
```bash
116+
vet scan -D /path/to/repo -l /tmp/vet.log -v
117+
```
118+
103119
## References
104120

105121
* https://github.com/google/osv-scanner

internal/ui/ui.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package ui
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/jedib0t/go-pretty/v6/progress"
8+
)
9+
10+
var progressWriter progress.Writer
11+
var spinnerChan chan bool
12+
13+
func StartSpinner(msg string) {
14+
style := `⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`
15+
frames := []rune(style)
16+
length := len(frames)
17+
18+
spinnerChan = make(chan bool)
19+
20+
ticker := time.NewTicker(100 * time.Millisecond)
21+
go func() {
22+
pos := 0
23+
24+
for {
25+
select {
26+
case <-spinnerChan:
27+
ticker.Stop()
28+
return
29+
case <-ticker.C:
30+
fmt.Printf("\r%s ... %s", msg, string(frames[pos%length]))
31+
pos += 1
32+
}
33+
}
34+
}()
35+
}
36+
37+
func StopSpinner() {
38+
spinnerChan <- true
39+
40+
fmt.Printf("\r")
41+
fmt.Println()
42+
}
43+
44+
func StartProgressWriter() {
45+
pw := progress.NewWriter()
46+
47+
pw.SetTrackerLength(25)
48+
pw.SetMessageWidth(20)
49+
pw.SetSortBy(progress.SortByPercentDsc)
50+
pw.SetStyle(progress.StyleDefault)
51+
pw.SetTrackerPosition(progress.PositionRight)
52+
pw.SetUpdateFrequency(time.Millisecond * 100)
53+
pw.Style().Colors = progress.StyleColorsExample
54+
pw.Style().Options.PercentFormat = "%4.1f%%"
55+
pw.Style().Visibility.Pinned = true
56+
pw.Style().Visibility.ETA = true
57+
pw.Style().Visibility.Value = true
58+
59+
progressWriter = pw
60+
go progressWriter.Render()
61+
}
62+
63+
func StopProgressWriter() {
64+
if progressWriter != nil {
65+
progressWriter.Stop()
66+
time.Sleep(1 * time.Second)
67+
}
68+
}
69+
70+
func TrackProgress(message string, total int) any {
71+
tracker := progress.Tracker{Message: message, Total: int64(total),
72+
Units: progress.UnitsDefault}
73+
74+
if progressWriter != nil {
75+
progressWriter.AppendTracker(&tracker)
76+
}
77+
78+
return &tracker
79+
}
80+
81+
func MarkTrackerAsDone(i any) {
82+
if tracker, ok := i.(*progress.Tracker); ok {
83+
tracker.MarkAsDone()
84+
}
85+
}
86+
87+
func IncrementTrackerTotal(i any, count int) {
88+
if tracker, ok := i.(*progress.Tracker); ok {
89+
tracker.UpdateTotal(tracker.Total + int64(count))
90+
}
91+
}
92+
93+
func IncrementProgress(i any, count int) {
94+
if tracker, ok := i.(*progress.Tracker); ok {
95+
tracker.Increment(int64(count))
96+
}
97+
}

pkg/common/utils/workq.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ type WorkQueueItem interface {
1212

1313
type WorkQueueFn[T WorkQueueItem] func(q *WorkQueue[T], item T) error
1414

15+
type WorkQueueCallbackOnItemFn[T WorkQueueItem] func(q *WorkQueue[T], item T)
16+
17+
type WorkQueueCallbacks[T WorkQueueItem] struct {
18+
OnAdd WorkQueueCallbackOnItemFn[T]
19+
OnDone WorkQueueCallbackOnItemFn[T]
20+
}
21+
1522
type WorkQueue[T WorkQueueItem] struct {
1623
done chan bool
1724
m sync.Mutex
@@ -20,6 +27,7 @@ type WorkQueue[T WorkQueueItem] struct {
2027
handler WorkQueueFn[T]
2128
status sync.Map
2229
items chan T
30+
callbacks WorkQueueCallbacks[T]
2331
}
2432

2533
func NewWorkQueue[T WorkQueueItem](bufferSize int, concurrency int,
@@ -32,6 +40,10 @@ func NewWorkQueue[T WorkQueueItem](bufferSize int, concurrency int,
3240
}
3341
}
3442

43+
func (q *WorkQueue[T]) WithCallbacks(callbacks WorkQueueCallbacks[T]) {
44+
q.callbacks = callbacks
45+
}
46+
3547
func (q *WorkQueue[T]) Start() {
3648
for i := 0; i < q.concurrency; i++ {
3749
go func() {
@@ -46,6 +58,7 @@ func (q *WorkQueue[T]) Start() {
4658
}
4759

4860
q.wg.Done()
61+
q.dispatchOnDone(item)
4962
}
5063
}
5164
}()
@@ -69,5 +82,19 @@ func (q *WorkQueue[T]) Add(item T) bool {
6982
q.wg.Add(1)
7083

7184
q.items <- item
85+
q.dispatchOnAdd(item)
86+
7287
return true
7388
}
89+
90+
func (q *WorkQueue[T]) dispatchOnAdd(item T) {
91+
if q.callbacks.OnAdd != nil {
92+
q.callbacks.OnAdd(q, item)
93+
}
94+
}
95+
96+
func (q *WorkQueue[T]) dispatchOnDone(item T) {
97+
if q.callbacks.OnDone != nil {
98+
q.callbacks.OnDone(q, item)
99+
}
100+
}

pkg/reporter/markdown.go

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package reporter
22

33
import (
4+
"fmt"
45
"os"
56
"sync"
67
"text/template"
@@ -20,26 +21,32 @@ type MarkdownReportingConfig struct {
2021
Path string
2122
}
2223

24+
type markdownTemplateInputRemediation struct {
25+
Pkg *models.Package
26+
PkgRemediationName string
27+
Score int
28+
}
29+
2330
type markdownTemplateInput struct {
24-
Manifests []*models.PackageManifest
25-
AnalyzerEvents []*analyzer.AnalyzerEvent
26-
PolicyEvents []*policy.PolicyEvent
31+
Remediations []markdownTemplateInputRemediation
32+
ManifestsCount int
33+
PackagesCount int
2734
}
2835

36+
// Markdown reporter is built on top of summary reporter to
37+
// provide extended visibility
2938
type markdownReportGenerator struct {
30-
m sync.Mutex
31-
config MarkdownReportingConfig
32-
templateInput markdownTemplateInput
39+
m sync.Mutex
40+
config MarkdownReportingConfig
41+
summaryReporter Reporter
42+
templateInput markdownTemplateInput
3343
}
3444

3545
func NewMarkdownReportGenerator(config MarkdownReportingConfig) (Reporter, error) {
46+
summaryReporter, _ := NewSummaryReporter()
3647
return &markdownReportGenerator{
37-
config: config,
38-
templateInput: markdownTemplateInput{
39-
Manifests: make([]*models.PackageManifest, 0),
40-
AnalyzerEvents: make([]*analyzer.AnalyzerEvent, 0),
41-
PolicyEvents: make([]*policy.PolicyEvent, 0),
42-
},
48+
config: config,
49+
summaryReporter: summaryReporter,
4350
}, nil
4451
}
4552

@@ -48,26 +55,34 @@ func (r *markdownReportGenerator) Name() string {
4855
}
4956

5057
func (r *markdownReportGenerator) AddManifest(manifest *models.PackageManifest) {
51-
r.m.Lock()
52-
defer r.m.Unlock()
53-
r.templateInput.Manifests = append(r.templateInput.Manifests, manifest)
58+
r.summaryReporter.AddManifest(manifest)
5459
}
5560

56-
func (r *markdownReportGenerator) AddAnalyzerEvent(event *analyzer.AnalyzerEvent) {
57-
r.m.Lock()
58-
defer r.m.Unlock()
59-
r.templateInput.AnalyzerEvents = append(r.templateInput.AnalyzerEvents, event)
60-
}
61+
func (r *markdownReportGenerator) AddAnalyzerEvent(event *analyzer.AnalyzerEvent) {}
6162

62-
func (r *markdownReportGenerator) AddPolicyEvent(event *policy.PolicyEvent) {
63-
r.m.Lock()
64-
defer r.m.Unlock()
65-
r.templateInput.PolicyEvents = append(r.templateInput.PolicyEvents, event)
66-
}
63+
func (r *markdownReportGenerator) AddPolicyEvent(event *policy.PolicyEvent) {}
6764

6865
func (r *markdownReportGenerator) Finish() error {
6966
logger.Infof("Generating consolidated markdown report: %s", r.config.Path)
7067

68+
var sr *summaryReporter
69+
var ok bool
70+
71+
if sr, ok = r.summaryReporter.(*summaryReporter); !ok {
72+
return fmt.Errorf("failed to duck type Reporter to summaryReporter")
73+
}
74+
75+
sortedList := sr.sortedRemediations()
76+
remediations := []markdownTemplateInputRemediation{}
77+
78+
for _, s := range sortedList {
79+
remediations = append(remediations, markdownTemplateInputRemediation{
80+
Pkg: s.pkg,
81+
PkgRemediationName: sr.packageNameForRemediationAdvice(s.pkg),
82+
Score: s.score,
83+
})
84+
}
85+
7186
tmpl, err := template.New("markdown").Parse(markdownTemplate)
7287
if err != nil {
7388
return err
@@ -79,5 +94,9 @@ func (r *markdownReportGenerator) Finish() error {
7994
}
8095

8196
defer file.Close()
82-
return tmpl.Execute(file, r.templateInput)
97+
return tmpl.Execute(file, markdownTemplateInput{
98+
Remediations: remediations,
99+
ManifestsCount: sr.summary.manifests,
100+
PackagesCount: sr.summary.packages,
101+
})
83102
}

0 commit comments

Comments
 (0)