-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathgenerate.go
210 lines (166 loc) · 5.87 KB
/
generate.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
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
/*
Copyright 2023 The OpenVEX Authors
SPDX-License-Identifier: Apache-2.0
*/
package cmd
import (
"errors"
"fmt"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/openvex/go-vex/pkg/vex"
"github.com/openvex/vexctl/pkg/ctl"
)
type generateOptions struct {
vexDocOptions
outFileOption
Product string
TemplatesPath string
Init bool
}
// Validates the options in context with arguments
func (o *generateOptions) Validate() error {
var err, errInit error
if o.Product == "" && !o.Init {
err = errors.New("a required product id is needed to generate a valid VEX statement")
}
if o.Init && o.Product != "" {
errInit = errors.New("when specifying --init, no product can be set")
}
return errors.Join(
err, errInit,
o.outFileOption.Validate(),
o.vexDocOptions.Validate(),
)
}
func (o *generateOptions) AddFlags(cmd *cobra.Command) {
o.vexDocOptions.AddFlags(cmd)
o.outFileOption.AddFlags(cmd)
cmd.PersistentFlags().StringVarP(
&o.Product,
productLongFlag,
"p",
"",
"main identifier of the product, a package URL or another IRI",
)
cmd.PersistentFlags().StringVarP(
&o.TemplatesPath,
"templates",
"t",
ctl.DefaultTemplatesPath,
"path to templates directory",
)
cmd.PersistentFlags().BoolVar(
&o.Init,
"init",
false,
"initialize a new templates directory in the path specified with -t",
)
}
func addGenerate(parentCmd *cobra.Command) {
opts := generateOptions{}
generateCmd := &cobra.Command{
Short: fmt.Sprintf("%s generate: generates VEX data", appname),
Long: fmt.Sprintf(`%s generate: generates VEX data from golden templates
The generate subcommand reads a set of golden template files and
creates a new document for a new artifact based on the golden samples.
To start, create your golden templates directory. You can initialize a new
templates directory using the --init flag:
vexctl generate --init --templates=".openvex/templates"
That invocation will create a new directory and add a new empty openvex document.
You can add more statements to it with "vexctl add" (see vexctl add --help).
The golden templates are normal OpenVEX documents. Their only difference is that
statements have generic identifiers that will be included in the generated data
when matched by a more specific data.
For example, to create a golden template for an OCI image, add a product with
an unversioned purl like this:
"statements": [
{
"vulnerability": { "name": "CVE-1234-5678" },
"products": [
{ "@id": "pkg:oci/test" }
],
"status": "fixed",
"timestamp": "2023-12-05T05:04:34.77929922Z"
}
],
You can add that statement using the following invocation:
vexctl add --in-place main.openvex.json "pkg:oci/test" "CVE-1234-5678" fixed
The added statement will cause vexctl to generate a VEX document with data for
CVE-1234-5678 for every version of the test image. In other words, when generating
VEX data, products identified by these purls will get a statement with a status of
fixed:
# Versioned purl (image with digest)
pkg:oci/test@sha256%%3Af87abf1735e79b70407288f665316644d414dbf7bdf38c2f1c8e3a541d304d84
# Image with tag
pkg:oci/test?tag=latest
# Image with tag and repository
pkg:oci/test?tag=latest&repository_url=ghcr.io%%2Fopenvex
Examples:
# Generate a document with all data for the an image with the reference
# ghcr.io/openvex/test@sha256:f87abf1735e79b70407288f665316644d414dbf7bdf38c2f1c8e3a541d304d84
%s generate --templates=".openvex/templates/" \
--product="pkg:oci/test@sha256%%3Af87abf1735e79b70407288f665316644d414dbf7bdf38c2f1c8e3a541d304d84&repository_url=ghcr.io%%2Fopenvex"
With that invocation, %s will read all template data from a directory
called .openvex/templates, filter out the relevant statements and generate a
VEX document for the specified product (the test image).
Note, that in this iteration, %s can only match products, subcomponents
are not considered when filtering out statements.
You can customize the metadata of the document via the command line flags.
%s will honor the SOURCE_DATE_EPOCH environment variable and use that date for
the document (it can be formatted in UNIX time or RFC3339).
If you don't specify an ID for the document, one will be generated
using its canonicalization hash.
`, appname, appname, appname, appname, appname),
Use: "generate [flags] [product_id]",
Example: fmt.Sprintf("%s generate \"pkg:apk/wolfi/git", appname),
SilenceUsage: false,
SilenceErrors: true,
PersistentPreRunE: initLogging,
RunE: func(_ *cobra.Command, args []string) error {
if len(args) > 0 {
if opts.Product != "" && opts.Product != args[0] {
return errors.New("product can only be specified once")
}
opts.Product = args[0]
}
if err := opts.Validate(); err != nil {
return err
}
// Options are relatively simple for now
genopts := ctl.GenerateOpts{
TemplatesPath: opts.TemplatesPath,
}
vexctl := ctl.New()
// If initializing, do that and exit
if opts.Init {
if err := vexctl.InitTemplatesDirectory(&genopts); err != nil {
return fmt.Errorf("initializing templates directory: %w", err)
}
logrus.Infof("Initialized new templates directory in %s", genopts.TemplatesPath)
return nil
}
newDoc, err := vexctl.Generate(&genopts, []*vex.Product{
{Component: vex.Component{ID: opts.Product}},
})
if err != nil {
return fmt.Errorf("generating VEX data: %w", err)
}
if newDoc == nil {
logrus.Warnf("No VEX data found for %s", opts.Product)
return nil
}
newDoc.Metadata.Author = opts.Author
newDoc.Metadata.AuthorRole = opts.AuthorRole
if opts.DocumentID != "" {
newDoc.Metadata.ID = opts.DocumentID
}
if err := writeDocument(newDoc, opts.outFilePath); err != nil {
return fmt.Errorf("writing openvex document: %w", err)
}
return nil
},
}
opts.AddFlags(generateCmd)
parentCmd.AddCommand(generateCmd)
}