Files
aquasecurity-trivy/pkg/github/github.go
Teppei Fukuda 74717b888e feat: support client/server mode (#295)
* chore(app): change dir

* feat(rpc): add a proto file and auto-generated files

* chore(dep): add dependencies

* fix(app): fix import path

* fix(integration): fix import path

* fix(protoc): use enum for severity

* chore(Makefile): add fmt andd protoc

* chore(clang): add .clang-format

* refactor: split functions for client/server (#296)

* refactor(db): split db.Download

* refactor(standalone): create a different package

* refactor(vulnerability): split FillAndFilter

* fix(protoc): use enum for severity

* chore(Makefile): add fmt andd protoc

* chore(clang): add .clang-format

* fix(db): remove an unused variable

* fix(db): expose the github client as an argument of constructor

* refactor(vulnerability): add the detail message

* feat(rpc): add rpc client (#302)

* fix(protoc): use enum for severity

* chore(Makefile): add fmt andd protoc

* chore(clang): add .clang-format

* feat(rpc): convert types

* feat(rpc): add rpc client

* token: Refactor to handle bad headers being set

Signed-off-by: Simarpreet Singh <simar@linux.com>

* feat(rpc): add rpc server (#303)

* feat(rpc): add rpc server

* feat(utils): add CopyFile

* feat(server/config): add config struct

* feat(detector): add detector

* feat(scanner): delegate procedures to detector

* fix(scanner): fix the interface

* test(mock): add mocks

* test(rpc/server): add tests

* test(rpc/ospkg/server): add tests

* tets(os/detector): add tests

* refactor(library): move directories

* chore(dependency): add google/wire

* refactor(library): introduce google/wire

* refactor(ospkg/detector): move directory

* feat(rpc): add eosl

* refactor(ospkg): introduce google/wire

* refactor(wire): bind an interface

* refactor(client): use wire.Struct

* chore(Makefile): fix wire

* test(server): add AssertExpectations

* test(server): add AssertExpectations

* refactor(server): remove debug log

* refactor(error): add more context messages

* test(server): fix error message

* refactor(test): create a constructor of mock

* refactor(config): remove an unused variable

* test(config): add an assertion to test the config struct

* feat(client/server): add sub commands (#304)

* feat(rpc): add rpc server

* feat(utils): add CopyFile

* feat(server/config): add config struct

* feat(detector): add detector

* feat(scanner): delegate procedures to detector

* fix(scanner): fix the interface

* feat(client/server): add sub commands

* merge(server3)

* test(scan): remove an unused mock

* refactor(client): generate the constructor by wire

* fix(cli): change the default port

* fix(server): use auto-generated constructor

* feat(ospkg): return eosl

* test(integration): add integration tests for client/server (#306)

* fix(server): remove unnecessary options

* test(integration): add integration tests for client/server

* fix(server): wrap an error

* fix(server): change the update interval

* fix(server): display the error detail

* test(config): add an assertion to test the config struct

* fix(client): returns an error when failing to initizlie a logger

* test(ospkg/server): add eosl

* Squashed commit of the following:

* test(server): refactor and add tests (#307)

* test(github): create a mock

* test(db): create a mock

* test(server): add tests for DB hot update

* chore(db): add a log message

* refactor(db): introduce google/wire

* refactor(rpc): move directory

* refactor(injector): fix import name

* refactor(import): remove new lines

* fix(server): display the error detail

* fix(server): change the update interval

* fix(server): wrap an error

* test(integration): add integration tests for client/server

* fix(server): remove unnecessary options

* refactor(server): return an error when failing to initialize a logger

* refactor(server): remove unused error

* fix(client/server): fix default port

* chore(README): add client/server

* chore(README): update
2019-12-13 15:00:11 +02:00

128 lines
3.2 KiB
Go

package github
import (
"context"
"fmt"
"io"
"net/http"
"os"
"sort"
"strings"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/google/go-github/v28/github"
"golang.org/x/oauth2"
"golang.org/x/xerrors"
)
const (
owner = "aquasecurity"
repo = "trivy-db"
)
type RepositoryInterface interface {
ListReleases(ctx context.Context, opt *github.ListOptions) ([]*github.RepositoryRelease, *github.Response, error)
DownloadAsset(ctx context.Context, id int64) (io.ReadCloser, string, error)
}
type Repository struct {
repository *github.RepositoriesService
git *github.GitService
owner string
repoName string
}
func (r Repository) ListReleases(ctx context.Context, opt *github.ListOptions) ([]*github.RepositoryRelease, *github.Response, error) {
return r.repository.ListReleases(ctx, r.owner, r.repoName, opt)
}
func (r Repository) DownloadAsset(ctx context.Context, id int64) (io.ReadCloser, string, error) {
return r.repository.DownloadReleaseAsset(ctx, r.owner, r.repoName, id)
}
type Operation interface {
DownloadDB(ctx context.Context, fileName string) (io.ReadCloser, error)
}
type Client struct {
Repository RepositoryInterface
}
func NewClient() Client {
var client *http.Client
githubToken := os.Getenv("GITHUB_TOKEN")
if githubToken != "" {
log.Logger.Info("Use your github token")
ctx := context.Background()
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: githubToken})
client = oauth2.NewClient(ctx, ts)
}
gc := github.NewClient(client)
repo := Repository{
repository: gc.Repositories,
git: gc.Git,
owner: owner,
repoName: repo,
}
return Client{
Repository: repo,
}
}
func (c Client) DownloadDB(ctx context.Context, fileName string) (io.ReadCloser, error) {
options := github.ListOptions{}
releases, _, err := c.Repository.ListReleases(ctx, &options)
if err != nil {
return nil, xerrors.Errorf("failed to list releases: %w", err)
}
sort.Slice(releases, func(i, j int) bool {
return releases[i].GetPublishedAt().After(releases[j].GetPublishedAt().Time)
})
prefix := fmt.Sprintf("v%d", db.SchemaVersion)
for _, release := range releases {
log.Logger.Debugf("release name: %s", release.GetName())
if !strings.HasPrefix(release.GetName(), prefix) {
continue
}
for _, asset := range release.Assets {
rc, err := c.downloadAsset(ctx, asset, fileName)
if err != nil {
log.Logger.Debug(err)
continue
}
return rc, nil
}
}
return nil, xerrors.New("DB file not found")
}
func (c Client) downloadAsset(ctx context.Context, asset github.ReleaseAsset, fileName string) (io.ReadCloser, error) {
log.Logger.Debugf("asset name: %s", asset.GetName())
if asset.GetName() != fileName {
return nil, xerrors.New("file name doesn't match")
}
rc, url, err := c.Repository.DownloadAsset(ctx, asset.GetID())
if err != nil {
return nil, xerrors.Errorf("unable to download the asset: %w", err)
}
if rc != nil {
return rc, nil
}
log.Logger.Debugf("asset URL: %s", url)
resp, err := http.Get(url)
if err != nil || resp.StatusCode != http.StatusOK {
return nil, xerrors.Errorf("unable to download the asset via URL: %w", err)
}
return resp.Body, nil
}