mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 07:40:48 -08:00
feat(server): add version endpoint (#4869)
* feat(server): add version endpoint * fix panic and test * move version.go * move version variable * add docs about endpoints * move testdata * refactor * update build command * refactor
This commit is contained in:
@@ -13,10 +13,6 @@ import (
|
||||
_ "modernc.org/sqlite" // sqlite driver for RPM DB and Java DB
|
||||
)
|
||||
|
||||
var (
|
||||
version = "dev"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -35,7 +31,7 @@ func run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
app := commands.NewApp(version)
|
||||
app := commands.NewApp()
|
||||
if err := app.Execute(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -288,6 +288,48 @@ $ trivy server --listen localhost:8080 --token dummy
|
||||
$ trivy image --server http://localhost:8080 --token dummy alpine:3.10
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Health
|
||||
Checks whether the Trivy server is running. Authentication is not required.
|
||||
|
||||
Example request:
|
||||
```bash
|
||||
curl -s 0.0.0.0:8080/healthz
|
||||
ok
|
||||
```
|
||||
|
||||
Returns the `200 OK` status if the request was successful.
|
||||
### Version
|
||||
|
||||
Returns the version of the Trivy and all components (db, policy). Authentication is not required.
|
||||
|
||||
Example request:
|
||||
```bash
|
||||
curl -s 0.0.0.0:8080/version | jq
|
||||
{
|
||||
"Version": "dev",
|
||||
"VulnerabilityDB": {
|
||||
"Version": 2,
|
||||
"NextUpdate": "2023-07-25T14:15:29.876639806Z",
|
||||
"UpdatedAt": "2023-07-25T08:15:29.876640206Z",
|
||||
"DownloadedAt": "2023-07-25T09:36:25.599004Z"
|
||||
},
|
||||
"JavaDB": {
|
||||
"Version": 1,
|
||||
"NextUpdate": "2023-07-28T01:03:52.169192565Z",
|
||||
"UpdatedAt": "2023-07-25T01:03:52.169192765Z",
|
||||
"DownloadedAt": "2023-07-25T09:37:48.906152Z"
|
||||
},
|
||||
"PolicyBundle": {
|
||||
"Digest": "sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43",
|
||||
"DownloadedAt": "2023-07-23T11:40:33.122462Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Returns the `200 OK` status if the request was successful.
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
@@ -6,7 +6,7 @@ builds:
|
||||
ldflags:
|
||||
- -s -w
|
||||
- "-extldflags '-static'"
|
||||
- -X main.version={{.Version}}
|
||||
- -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
|
||||
@@ -6,7 +6,7 @@ builds:
|
||||
ldflags:
|
||||
- -s -w
|
||||
- "-extldflags '-static'"
|
||||
- -X main.version={{.Version}}
|
||||
- -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
@@ -26,7 +26,7 @@ builds:
|
||||
ldflags:
|
||||
- -s -w
|
||||
- "-extldflags '-static'"
|
||||
- -X main.version={{.Version}}
|
||||
- -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
@@ -41,7 +41,7 @@ builds:
|
||||
ldflags:
|
||||
- -s -w
|
||||
- "-extldflags '-static'"
|
||||
- -X main.version={{.Version}}
|
||||
- -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
@@ -57,7 +57,7 @@ builds:
|
||||
ldflags:
|
||||
- -s -w
|
||||
- "-extldflags '-static'"
|
||||
- -X main.version={{.Version}}
|
||||
- -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
|
||||
@@ -184,7 +184,7 @@ func readSpdxJson(t *testing.T, filePath string) *spdx.Document {
|
||||
|
||||
func execute(osArgs []string) error {
|
||||
// Setup CLI App
|
||||
app := commands.NewApp("dev")
|
||||
app := commands.NewApp()
|
||||
app.SetOut(io.Discard)
|
||||
|
||||
// Run Trivy
|
||||
|
||||
@@ -12,17 +12,13 @@ import (
|
||||
|
||||
// Generate CLI references
|
||||
func main() {
|
||||
ver, err := version()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Set a dummy path for the documents
|
||||
flag.CacheDirFlag.Default = "/path/to/cache"
|
||||
flag.ModuleDirFlag.Default = "$HOME/.trivy/modules"
|
||||
|
||||
cmd := commands.NewApp(ver)
|
||||
cmd := commands.NewApp()
|
||||
cmd.DisableAutoGenTag = true
|
||||
if err = doc.GenMarkdownTree(cmd, "./docs/docs/references/configuration/cli"); err != nil {
|
||||
if err := doc.GenMarkdownTree(cmd, "./docs/docs/references/configuration/cli"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func buildLdflags() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("-s -w -X=main.version=%s", ver), nil
|
||||
return fmt.Sprintf("-s -w -X=github.com/aquasecurity/trivy/pkg/version.ver=%s", ver), nil
|
||||
}
|
||||
|
||||
type Tool mg.Namespace
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -15,8 +14,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
awsScanner "github.com/aquasecurity/defsec/pkg/scanners/cloud/aws"
|
||||
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
||||
javadb "github.com/aquasecurity/trivy-java-db/pkg/db"
|
||||
|
||||
awscommands "github.com/aquasecurity/trivy/pkg/cloud/aws/commands"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/artifact"
|
||||
"github.com/aquasecurity/trivy/pkg/commands/convert"
|
||||
@@ -27,19 +25,11 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/module"
|
||||
"github.com/aquasecurity/trivy/pkg/plugin"
|
||||
"github.com/aquasecurity/trivy/pkg/policy"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/version"
|
||||
xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
|
||||
)
|
||||
|
||||
// VersionInfo holds the trivy DB version Info
|
||||
type VersionInfo struct {
|
||||
Version string `json:",omitempty"`
|
||||
VulnerabilityDB *metadata.Metadata `json:",omitempty"`
|
||||
JavaDB *metadata.Metadata `json:",omitempty"`
|
||||
PolicyBundle *policy.Metadata `json:",omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
usageTemplate = `Usage:{{if .Runnable}}
|
||||
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
|
||||
@@ -72,9 +62,9 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e
|
||||
)
|
||||
|
||||
// NewApp is the factory method to return Trivy CLI
|
||||
func NewApp(version string) *cobra.Command {
|
||||
func NewApp() *cobra.Command {
|
||||
globalFlags := flag.NewGlobalFlagGroup()
|
||||
rootCmd := NewRootCommand(version, globalFlags)
|
||||
rootCmd := NewRootCommand(globalFlags)
|
||||
rootCmd.AddGroup(
|
||||
&cobra.Group{
|
||||
ID: groupScanning,
|
||||
@@ -161,7 +151,7 @@ func initConfig(configFile string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRootCommand(version string, globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
func NewRootCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
var versionFormat string
|
||||
cmd := &cobra.Command{
|
||||
Use: "trivy [global flags] command [flags] target",
|
||||
@@ -181,7 +171,7 @@ func NewRootCommand(version string, globalFlags *flag.GlobalFlagGroup) *cobra.Co
|
||||
Args: cobra.NoArgs,
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Set the Trivy version here so that we can override version printer.
|
||||
cmd.Version = version
|
||||
cmd.Version = version.AppVersion()
|
||||
|
||||
// viper.BindPFlag cannot be called in init().
|
||||
// cf. https://github.com/spf13/cobra/issues/875
|
||||
@@ -213,7 +203,7 @@ func NewRootCommand(version string, globalFlags *flag.GlobalFlagGroup) *cobra.Co
|
||||
globalOptions := globalFlags.ToOptions()
|
||||
if globalOptions.ShowVersion {
|
||||
// Customize version output
|
||||
return showVersion(globalOptions.CacheDir, versionFormat, version, cmd.OutOrStdout())
|
||||
return showVersion(globalOptions.CacheDir, versionFormat, cmd.OutOrStdout())
|
||||
} else {
|
||||
return cmd.Help()
|
||||
}
|
||||
@@ -298,7 +288,7 @@ func NewImageCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
return validateArgs(cmd, args)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options, err := imageFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
options, err := imageFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -357,7 +347,7 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := fsFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := fsFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
options, err := fsFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -416,7 +406,7 @@ func NewRootfsCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := rootfsFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := rootfsFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
options, err := rootfsFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -469,7 +459,7 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := repoFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := repoFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
options, err := repoFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -509,7 +499,7 @@ func NewConvertCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := convertFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
opts, err := convertFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
opts, err := convertFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -566,7 +556,7 @@ func NewClientCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := clientFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := clientFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
options, err := clientFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -607,7 +597,7 @@ func NewServerCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := serverFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := serverFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
options, err := serverFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -669,7 +659,7 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := configFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := configFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
options, err := configFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -742,7 +732,7 @@ func NewPluginCommand() *cobra.Command {
|
||||
if err != nil {
|
||||
return xerrors.Errorf("plugin list display error: %w", err)
|
||||
}
|
||||
if _, err = fmt.Fprintf(os.Stdout, info); err != nil {
|
||||
if _, err := fmt.Fprint(os.Stdout, info); err != nil {
|
||||
return xerrors.Errorf("print error: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -759,7 +749,7 @@ func NewPluginCommand() *cobra.Command {
|
||||
if err != nil {
|
||||
return xerrors.Errorf("plugin information display error: %w", err)
|
||||
}
|
||||
if _, err = fmt.Fprintf(os.Stdout, info); err != nil {
|
||||
if _, err := fmt.Fprint(os.Stdout, info); err != nil {
|
||||
return xerrors.Errorf("print error: %w", err)
|
||||
}
|
||||
return nil
|
||||
@@ -827,7 +817,7 @@ func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
}
|
||||
|
||||
repo := args[0]
|
||||
opts, err := moduleFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
opts, err := moduleFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -851,7 +841,7 @@ func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
}
|
||||
|
||||
repo := args[0]
|
||||
opts, err := moduleFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
opts, err := moduleFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -940,7 +930,7 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := k8sFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
opts, err := k8sFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
opts, err := k8sFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -1007,7 +997,7 @@ The following services are supported:
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts, err := awsFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
opts, err := awsFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -1071,7 +1061,7 @@ func NewVMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := vmFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := vmFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
options, err := vmFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -1130,7 +1120,7 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
if err := sbomFlags.Bind(cmd); err != nil {
|
||||
return xerrors.Errorf("flag bind error: %w", err)
|
||||
}
|
||||
options, err := sbomFlags.ToOptions(cmd.Version, args, globalFlags)
|
||||
options, err := sbomFlags.ToOptions(args, globalFlags)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("flag error: %w", err)
|
||||
}
|
||||
@@ -1159,7 +1149,7 @@ func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options := globalFlags.ToOptions()
|
||||
return showVersion(options.CacheDir, versionFormat, cmd.Version, cmd.OutOrStdout())
|
||||
return showVersion(options.CacheDir, versionFormat, cmd.OutOrStdout())
|
||||
},
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
@@ -1172,85 +1162,15 @@ func NewVersionCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func showVersion(cacheDir, outputFormat, version string, w io.Writer) error {
|
||||
var dbMeta *metadata.Metadata
|
||||
var javadbMeta *metadata.Metadata
|
||||
|
||||
mc := metadata.NewClient(cacheDir)
|
||||
meta, err := mc.Get()
|
||||
if err != nil {
|
||||
log.Logger.Debugw("Failed to get DB metadata", "error", err)
|
||||
}
|
||||
if !meta.UpdatedAt.IsZero() && !meta.NextUpdate.IsZero() && meta.Version != 0 {
|
||||
dbMeta = &metadata.Metadata{
|
||||
Version: meta.Version,
|
||||
NextUpdate: meta.NextUpdate.UTC(),
|
||||
UpdatedAt: meta.UpdatedAt.UTC(),
|
||||
DownloadedAt: meta.DownloadedAt.UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
mcJava := javadb.NewMetadata(filepath.Join(cacheDir, "java-db"))
|
||||
metaJava, err := mcJava.Get()
|
||||
if err != nil {
|
||||
log.Logger.Debugw("Failed to get Java DB metadata", "error", err)
|
||||
}
|
||||
if !metaJava.UpdatedAt.IsZero() && !metaJava.NextUpdate.IsZero() && metaJava.Version != 0 {
|
||||
javadbMeta = &metadata.Metadata{
|
||||
Version: metaJava.Version,
|
||||
NextUpdate: metaJava.NextUpdate.UTC(),
|
||||
UpdatedAt: metaJava.UpdatedAt.UTC(),
|
||||
DownloadedAt: metaJava.DownloadedAt.UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
var pbMeta *policy.Metadata
|
||||
pc, err := policy.NewClient(cacheDir, false, "")
|
||||
if pc != nil && err == nil {
|
||||
pbMeta, err = pc.GetMetadata()
|
||||
if err != nil {
|
||||
log.Logger.Debugw("Failed to get policy metadata", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func showVersion(cacheDir, outputFormat string, w io.Writer) error {
|
||||
versionInfo := version.NewVersionInfo(cacheDir)
|
||||
switch outputFormat {
|
||||
case "json":
|
||||
err = json.NewEncoder(w).Encode(VersionInfo{
|
||||
Version: version,
|
||||
VulnerabilityDB: dbMeta,
|
||||
JavaDB: javadbMeta,
|
||||
PolicyBundle: pbMeta,
|
||||
})
|
||||
if err != nil {
|
||||
if err := json.NewEncoder(w).Encode(versionInfo); err != nil {
|
||||
return xerrors.Errorf("json encode error: %w", err)
|
||||
}
|
||||
default:
|
||||
output := fmt.Sprintf("Version: %s\n", version)
|
||||
if dbMeta != nil {
|
||||
output += fmt.Sprintf(`Vulnerability DB:
|
||||
Version: %d
|
||||
UpdatedAt: %s
|
||||
NextUpdate: %s
|
||||
DownloadedAt: %s
|
||||
`, dbMeta.Version, dbMeta.UpdatedAt.UTC(), dbMeta.NextUpdate.UTC(), dbMeta.DownloadedAt.UTC())
|
||||
}
|
||||
|
||||
if javadbMeta != nil {
|
||||
output += fmt.Sprintf(`Java DB:
|
||||
Version: %d
|
||||
UpdatedAt: %s
|
||||
NextUpdate: %s
|
||||
DownloadedAt: %s
|
||||
`, javadbMeta.Version, javadbMeta.UpdatedAt.UTC(), javadbMeta.NextUpdate.UTC(), javadbMeta.DownloadedAt.UTC())
|
||||
}
|
||||
|
||||
if pbMeta != nil {
|
||||
output += fmt.Sprintf(`Policy Bundle:
|
||||
Digest: %s
|
||||
DownloadedAt: %s
|
||||
`, pbMeta.Digest, pbMeta.DownloadedAt.UTC())
|
||||
}
|
||||
fmt.Fprintf(w, output)
|
||||
fmt.Fprint(w, versionInfo.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ func Test_showVersion(t *testing.T) {
|
||||
name: "happy path, table output",
|
||||
args: args{
|
||||
outputFormat: "table",
|
||||
version: "v1.2.3",
|
||||
version: "dev",
|
||||
cacheDir: "testdata",
|
||||
},
|
||||
want: `Version: v1.2.3
|
||||
want: `Version: dev
|
||||
Vulnerability DB:
|
||||
Version: 2
|
||||
UpdatedAt: 2022-03-02 06:07:07.99504083 +0000 UTC
|
||||
@@ -52,17 +52,17 @@ Policy Bundle:
|
||||
name: "sad path, bogus cache dir",
|
||||
args: args{
|
||||
outputFormat: "json",
|
||||
version: "1.2.3",
|
||||
version: "dev",
|
||||
cacheDir: "/foo/bar/bogus",
|
||||
},
|
||||
want: `{"Version":"1.2.3"}
|
||||
want: `{"Version":"dev"}
|
||||
`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := new(bytes.Buffer)
|
||||
showVersion(tt.args.cacheDir, tt.args.outputFormat, tt.args.version, got)
|
||||
showVersion(tt.args.cacheDir, tt.args.outputFormat, got)
|
||||
assert.Equal(t, tt.want, got.String(), tt.name)
|
||||
})
|
||||
}
|
||||
@@ -70,7 +70,7 @@ Policy Bundle:
|
||||
|
||||
// Check flag and command for print version
|
||||
func TestPrintVersion(t *testing.T) {
|
||||
tableOutput := `Version: test
|
||||
tableOutput := `Version: dev
|
||||
Vulnerability DB:
|
||||
Version: 2
|
||||
UpdatedAt: 2022-03-02 06:07:07.99504083 +0000 UTC
|
||||
@@ -85,7 +85,7 @@ Policy Bundle:
|
||||
Digest: sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd
|
||||
DownloadedAt: 2023-03-02 01:06:08.191725 +0000 UTC
|
||||
`
|
||||
jsonOutput := `{"Version":"test","VulnerabilityDB":{"Version":2,"NextUpdate":"2022-03-02T12:07:07.99504023Z","UpdatedAt":"2022-03-02T06:07:07.99504083Z","DownloadedAt":"2022-03-02T10:03:38.383312Z"},"JavaDB":{"Version":1,"NextUpdate":"2023-03-17T00:47:02.774253254Z","UpdatedAt":"2023-03-14T00:47:02.774253754Z","DownloadedAt":"2023-03-14T03:04:55.058541039Z"},"PolicyBundle":{"Digest":"sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd","DownloadedAt":"2023-03-01T17:06:08.191725-08:00"}}
|
||||
jsonOutput := `{"Version":"dev","VulnerabilityDB":{"Version":2,"NextUpdate":"2022-03-02T12:07:07.99504023Z","UpdatedAt":"2022-03-02T06:07:07.99504083Z","DownloadedAt":"2022-03-02T10:03:38.383312Z"},"JavaDB":{"Version":1,"NextUpdate":"2023-03-17T00:47:02.774253254Z","UpdatedAt":"2023-03-14T00:47:02.774253754Z","DownloadedAt":"2023-03-14T03:04:55.058541039Z"},"PolicyBundle":{"Digest":"sha256:19a017cdc798631ad42f6f4dce823d77b2989128f0e1a7f9bc83ae3c59024edd","DownloadedAt":"2023-03-02T01:06:08.191725Z"}}
|
||||
`
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -157,7 +157,7 @@ Policy Bundle:
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := new(bytes.Buffer)
|
||||
app := NewApp("test")
|
||||
app := NewApp()
|
||||
app.SetOut(got)
|
||||
app.SetArgs(test.arguments)
|
||||
|
||||
@@ -257,7 +257,7 @@ func TestFlags(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
globalFlags := flag.NewGlobalFlagGroup()
|
||||
rootCmd := NewRootCommand("dev", globalFlags)
|
||||
rootCmd := NewRootCommand(globalFlags)
|
||||
rootCmd.SetErr(io.Discard)
|
||||
rootCmd.SetOut(io.Discard)
|
||||
|
||||
@@ -270,7 +270,7 @@ func TestFlags(t *testing.T) {
|
||||
// Bind
|
||||
require.NoError(t, flags.Bind(cmd))
|
||||
|
||||
options, err := flags.ToOptions("dev", args, globalFlags)
|
||||
options, err := flags.ToOptions(args, globalFlags)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.want.format, options.Format)
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/result"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/version"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
|
||||
)
|
||||
@@ -439,10 +440,10 @@ func (f *Flags) Bind(cmd *cobra.Command) error {
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalFlagGroup) (Options, error) {
|
||||
func (f *Flags) ToOptions(args []string, globalFlags *GlobalFlagGroup) (Options, error) {
|
||||
var err error
|
||||
opts := Options{
|
||||
AppVersion: appVersion,
|
||||
AppVersion: version.AppVersion(),
|
||||
GlobalOptions: globalFlags.ToOptions(),
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,13 @@ type Metadata struct {
|
||||
DownloadedAt time.Time
|
||||
}
|
||||
|
||||
func (m Metadata) String() string {
|
||||
return fmt.Sprintf(`Policy Bundle:
|
||||
Digest: %s
|
||||
DownloadedAt: %s
|
||||
`, m.Digest, m.DownloadedAt.UTC())
|
||||
}
|
||||
|
||||
// NewClient is the factory method for policy client
|
||||
func NewClient(cacheDir string, quiet bool, policyBundleRepo string, opts ...Option) (*Client, error) {
|
||||
o := &options{
|
||||
|
||||
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/utils/fsutils"
|
||||
"github.com/aquasecurity/trivy/pkg/version"
|
||||
rpcCache "github.com/aquasecurity/trivy/rpc/cache"
|
||||
rpcScanner "github.com/aquasecurity/trivy/rpc/scanner"
|
||||
)
|
||||
@@ -66,13 +68,14 @@ func (s Server) ListenAndServe(serverCache cache.Cache, skipDBUpdate bool) error
|
||||
}
|
||||
}()
|
||||
|
||||
mux := newServeMux(serverCache, dbUpdateWg, requestWg, s.token, s.tokenHeader)
|
||||
mux := newServeMux(serverCache, dbUpdateWg, requestWg, s.token, s.tokenHeader, s.cacheDir)
|
||||
log.Logger.Infof("Listening %s...", s.addr)
|
||||
|
||||
return http.ListenAndServe(s.addr, mux)
|
||||
}
|
||||
|
||||
func newServeMux(serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup, token, tokenHeader string) *http.ServeMux {
|
||||
func newServeMux(serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup,
|
||||
token, tokenHeader, cacheDir string) *http.ServeMux {
|
||||
withWaitGroup := func(base http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Stop processing requests during DB update
|
||||
@@ -103,6 +106,14 @@ func newServeMux(serverCache cache.Cache, dbUpdateWg, requestWg *sync.WaitGroup,
|
||||
}
|
||||
})
|
||||
|
||||
mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
if err := json.NewEncoder(w).Encode(version.NewVersionInfo(cacheDir)); err != nil {
|
||||
log.Logger.Errorf("get version error: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -20,7 +21,9 @@ import (
|
||||
dbFile "github.com/aquasecurity/trivy/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/cache"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/policy"
|
||||
"github.com/aquasecurity/trivy/pkg/utils/fsutils"
|
||||
"github.com/aquasecurity/trivy/pkg/version"
|
||||
rpcCache "github.com/aquasecurity/trivy/rpc/cache"
|
||||
)
|
||||
|
||||
@@ -251,7 +254,7 @@ func Test_newServeMux(t *testing.T) {
|
||||
defer func() { _ = c.Close() }()
|
||||
|
||||
ts := httptest.NewServer(newServeMux(
|
||||
c, dbUpdateWg, requestWg, tt.args.token, tt.args.tokenHeader),
|
||||
c, dbUpdateWg, requestWg, tt.args.token, tt.args.tokenHeader, ""),
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
@@ -274,3 +277,39 @@ func Test_newServeMux(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_VersionEndpoint(t *testing.T) {
|
||||
dbUpdateWg, requestWg := &sync.WaitGroup{}, &sync.WaitGroup{}
|
||||
c, err := cache.NewFSCache(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = c.Close() }()
|
||||
|
||||
ts := httptest.NewServer(newServeMux(
|
||||
c, dbUpdateWg, requestWg, "", "", "testdata/testcache"),
|
||||
)
|
||||
defer ts.Close()
|
||||
|
||||
resp, err := http.Get(ts.URL + "/version")
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var versionInfo version.VersionInfo
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&versionInfo))
|
||||
|
||||
expected := version.VersionInfo{
|
||||
Version: "dev",
|
||||
VulnerabilityDB: &metadata.Metadata{
|
||||
Version: 2,
|
||||
NextUpdate: time.Date(2023, 7, 20, 18, 11, 37, 696263532, time.UTC),
|
||||
UpdatedAt: time.Date(2023, 7, 20, 12, 11, 37, 696263932, time.UTC),
|
||||
DownloadedAt: time.Date(2023, 7, 25, 7, 1, 41, 239158000, time.UTC),
|
||||
},
|
||||
PolicyBundle: &policy.Metadata{
|
||||
Digest: "sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43",
|
||||
DownloadedAt: time.Date(2023, 7, 23, 16, 40, 33, 122462000, time.UTC),
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, versionInfo)
|
||||
}
|
||||
|
||||
1
pkg/rpc/server/testdata/testcache/db/metadata.json
vendored
Normal file
1
pkg/rpc/server/testdata/testcache/db/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"Version":2,"NextUpdate":"2023-07-20T18:11:37.696263532Z","UpdatedAt":"2023-07-20T12:11:37.696263932Z","DownloadedAt":"2023-07-25T07:01:41.239158Z"}
|
||||
1
pkg/rpc/server/testdata/testcache/policy/metadata.json
vendored
Normal file
1
pkg/rpc/server/testdata/testcache/policy/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"Digest":"sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43","DownloadedAt":"2023-07-23T17:40:33.122462+01:00"}
|
||||
1
pkg/version/testdata/testcache/db/metadata.json
vendored
Normal file
1
pkg/version/testdata/testcache/db/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"Version":2,"NextUpdate":"2023-07-20T18:11:37.696263532Z","UpdatedAt":"2023-07-20T12:11:37.696263932Z","DownloadedAt":"2023-07-25T07:01:41.239158Z"}
|
||||
1
pkg/version/testdata/testcache/java-db/metadata.json
vendored
Normal file
1
pkg/version/testdata/testcache/java-db/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"Version":1,"NextUpdate":"2023-07-28T01:03:52.169192565Z","UpdatedAt":"2023-07-25T01:03:52.169192765Z","DownloadedAt":"2023-07-25T09:37:48.906152Z"}
|
||||
1
pkg/version/testdata/testcache/policy/metadata.json
vendored
Normal file
1
pkg/version/testdata/testcache/policy/metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"Digest":"sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43","DownloadedAt":"2023-07-23T17:40:33.122462+01:00"}
|
||||
107
pkg/version/version.go
Normal file
107
pkg/version/version.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
||||
javadb "github.com/aquasecurity/trivy-java-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/policy"
|
||||
)
|
||||
|
||||
var (
|
||||
ver = "dev"
|
||||
)
|
||||
|
||||
func AppVersion() string {
|
||||
return ver
|
||||
}
|
||||
|
||||
type VersionInfo struct {
|
||||
Version string `json:",omitempty"`
|
||||
VulnerabilityDB *metadata.Metadata `json:",omitempty"`
|
||||
JavaDB *metadata.Metadata `json:",omitempty"`
|
||||
PolicyBundle *policy.Metadata `json:",omitempty"`
|
||||
}
|
||||
|
||||
func formatDBMetadata(title string, meta metadata.Metadata) string {
|
||||
return fmt.Sprintf(`%s:
|
||||
Version: %d
|
||||
UpdatedAt: %s
|
||||
NextUpdate: %s
|
||||
DownloadedAt: %s
|
||||
`, title, meta.Version, meta.UpdatedAt.UTC(), meta.NextUpdate.UTC(), meta.DownloadedAt.UTC())
|
||||
}
|
||||
|
||||
func (v *VersionInfo) String() string {
|
||||
output := fmt.Sprintf("Version: %s\n", v.Version)
|
||||
if v.VulnerabilityDB != nil {
|
||||
output += formatDBMetadata("Vulnerability DB", *v.VulnerabilityDB)
|
||||
}
|
||||
if v.JavaDB != nil {
|
||||
output += formatDBMetadata("Java DB", *v.JavaDB)
|
||||
}
|
||||
if v.PolicyBundle != nil {
|
||||
output += v.PolicyBundle.String()
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func NewVersionInfo(cacheDir string) VersionInfo {
|
||||
var dbMeta *metadata.Metadata
|
||||
var javadbMeta *metadata.Metadata
|
||||
|
||||
mc := metadata.NewClient(cacheDir)
|
||||
meta, err := mc.Get()
|
||||
if err != nil {
|
||||
log.Logger.Debugw("Failed to get DB metadata", "error", err)
|
||||
}
|
||||
if !meta.UpdatedAt.IsZero() && !meta.NextUpdate.IsZero() && meta.Version != 0 {
|
||||
dbMeta = &metadata.Metadata{
|
||||
Version: meta.Version,
|
||||
NextUpdate: meta.NextUpdate.UTC(),
|
||||
UpdatedAt: meta.UpdatedAt.UTC(),
|
||||
DownloadedAt: meta.DownloadedAt.UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
mcJava := javadb.NewMetadata(filepath.Join(cacheDir, "java-db"))
|
||||
metaJava, err := mcJava.Get()
|
||||
if err != nil {
|
||||
log.Logger.Debugw("Failed to get Java DB metadata", "error", err)
|
||||
}
|
||||
if !metaJava.UpdatedAt.IsZero() && !metaJava.NextUpdate.IsZero() && metaJava.Version != 0 {
|
||||
javadbMeta = &metadata.Metadata{
|
||||
Version: metaJava.Version,
|
||||
NextUpdate: metaJava.NextUpdate.UTC(),
|
||||
UpdatedAt: metaJava.UpdatedAt.UTC(),
|
||||
DownloadedAt: metaJava.DownloadedAt.UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
var pbMeta *policy.Metadata
|
||||
pc, err := policy.NewClient(cacheDir, false, "")
|
||||
if err != nil {
|
||||
log.Logger.Debugw("Failed to instantiate policy client", "error", err)
|
||||
}
|
||||
if pc != nil && err == nil {
|
||||
pbMetaRaw, err := pc.GetMetadata()
|
||||
|
||||
if err != nil {
|
||||
log.Logger.Debugw("Failed to get policy metadata", "error", err)
|
||||
} else {
|
||||
pbMeta = &policy.Metadata{
|
||||
Digest: pbMetaRaw.Digest,
|
||||
DownloadedAt: pbMetaRaw.DownloadedAt.UTC(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VersionInfo{
|
||||
Version: ver,
|
||||
VulnerabilityDB: dbMeta,
|
||||
JavaDB: javadbMeta,
|
||||
PolicyBundle: pbMeta,
|
||||
}
|
||||
}
|
||||
54
pkg/version/version_test.go
Normal file
54
pkg/version/version_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
||||
"github.com/aquasecurity/trivy/pkg/policy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_BuildVersionInfo(t *testing.T) {
|
||||
|
||||
expected := VersionInfo{
|
||||
Version: "dev",
|
||||
VulnerabilityDB: &metadata.Metadata{
|
||||
Version: 2,
|
||||
NextUpdate: time.Date(2023, 7, 20, 18, 11, 37, 696263532, time.UTC),
|
||||
UpdatedAt: time.Date(2023, 7, 20, 12, 11, 37, 696263932, time.UTC),
|
||||
DownloadedAt: time.Date(2023, 7, 25, 7, 1, 41, 239158000, time.UTC),
|
||||
},
|
||||
JavaDB: &metadata.Metadata{
|
||||
Version: 1,
|
||||
NextUpdate: time.Date(2023, 7, 28, 1, 3, 52, 169192565, time.UTC),
|
||||
UpdatedAt: time.Date(2023, 7, 25, 1, 3, 52, 169192765, time.UTC),
|
||||
DownloadedAt: time.Date(2023, 7, 25, 9, 37, 48, 906152000, time.UTC),
|
||||
},
|
||||
PolicyBundle: &policy.Metadata{
|
||||
Digest: "sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43",
|
||||
DownloadedAt: time.Date(2023, 7, 23, 16, 40, 33, 122462000, time.UTC),
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, NewVersionInfo("testdata/testcache"))
|
||||
}
|
||||
|
||||
func Test_VersionInfoString(t *testing.T) {
|
||||
expected := `Version: dev
|
||||
Vulnerability DB:
|
||||
Version: 2
|
||||
UpdatedAt: 2023-07-20 12:11:37.696263932 +0000 UTC
|
||||
NextUpdate: 2023-07-20 18:11:37.696263532 +0000 UTC
|
||||
DownloadedAt: 2023-07-25 07:01:41.239158 +0000 UTC
|
||||
Java DB:
|
||||
Version: 1
|
||||
UpdatedAt: 2023-07-25 01:03:52.169192765 +0000 UTC
|
||||
NextUpdate: 2023-07-28 01:03:52.169192565 +0000 UTC
|
||||
DownloadedAt: 2023-07-25 09:37:48.906152 +0000 UTC
|
||||
Policy Bundle:
|
||||
Digest: sha256:829832357626da2677955e3b427191212978ba20012b6eaa03229ca28569ae43
|
||||
DownloadedAt: 2023-07-23 16:40:33.122462 +0000 UTC
|
||||
`
|
||||
versionInfo := NewVersionInfo("testdata/testcache")
|
||||
assert.Equal(t, expected, versionInfo.String())
|
||||
}
|
||||
Reference in New Issue
Block a user