From de2cc16de49d84718f204df5dd14a078308e73ab Mon Sep 17 00:00:00 2001 From: Dentrax Date: Mon, 8 Jul 2024 22:48:11 +0300 Subject: [PATCH 1/5] advisory: stream command Signed-off-by: Dentrax --- pkg/cli/advisory.go | 1 + pkg/cli/advisory_copy.go | 73 +++++++++++++---------- pkg/cli/advisory_stream.go | 117 +++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 32 deletions(-) create mode 100644 pkg/cli/advisory_stream.go diff --git a/pkg/cli/advisory.go b/pkg/cli/advisory.go index 27dc6be0f..4d187bed9 100644 --- a/pkg/cli/advisory.go +++ b/pkg/cli/advisory.go @@ -48,6 +48,7 @@ func cmdAdvisory() *cobra.Command { cmdAdvisorySecDB(), cmdAdvisoryUpdate(), cmdAdvisoryValidate(), + cmdAdvisoryStream(), ) return cmd diff --git a/pkg/cli/advisory_copy.go b/pkg/cli/advisory_copy.go index c6e8e3b77..dbc3e5e67 100644 --- a/pkg/cli/advisory_copy.go +++ b/pkg/cli/advisory_copy.go @@ -49,39 +49,9 @@ of the event to now. The command will not copy events of type "detection", "fixe out.Advisories = nil for _, adv := range hdoc.Advisories { - evts := make([]v2.Event, 0, len(adv.Events)) - - for _, evt := range adv.Events { - switch evt.Type { - case v2.EventTypeDetection, v2.EventTypeFixed, v2.EventTypeAnalysisNotPlanned, v2.EventTypeFixNotPlanned: - // Don't carry these over. - continue - - case v2.EventTypePendingUpstreamFix, v2.EventTypeFalsePositiveDetermination, v2.EventTypeTruePositiveDetermination: - // Carry these over as-is. - evts = append(evts, evt) - - default: - // A new type was added and we don't know how to handle it. Default to not carrying it over. - } - } - - if len(evts) == 0 { - // No events to carry over. - continue + if carried, ok := carryAdvisory(adv); ok { + out.Advisories = append(out.Advisories, carried) } - - // Sort events by timestamp and only take the latest event. - sort.Slice(evts, func(i, j int) bool { - return evts[i].Timestamp.Before(evts[j].Timestamp) - }) - evts = []v2.Event{evts[len(evts)-1]} - - // Update the timestamp to now. - evts[0].Timestamp = v2.Now() - - adv.Events = evts - out.Advisories = append(out.Advisories, adv) } return advisoryCfgs.Create(ctx, want+".advisories.yaml", out) @@ -91,3 +61,42 @@ of the event to now. The command will not copy events of type "detection", "fixe return cmd } + +// carryAdvisory decides whether to carry over an advisory and its events. +// Returns true with the updated advisory if it should be carried over. Otherwise, returns false +// and the current advisory. +func carryAdvisory(advisory v2.Advisory) (v2.Advisory, bool) { + evts := make([]v2.Event, 0, len(advisory.Events)) + + for _, evt := range advisory.Events { + switch evt.Type { + case v2.EventTypeDetection, v2.EventTypeFixed, v2.EventTypeAnalysisNotPlanned, v2.EventTypeFixNotPlanned: + // Don't carry these over. + continue + + case v2.EventTypePendingUpstreamFix, v2.EventTypeFalsePositiveDetermination, v2.EventTypeTruePositiveDetermination: + // Carry these over as-is. + evts = append(evts, evt) + + default: + // A new type was added and we don't know how to handle it. Default to not carrying it over. + } + } + + if len(evts) == 0 { + // No events to carry over. + return advisory, false + } + + // Sort events by timestamp and only take the latest event. + sort.Slice(evts, func(i, j int) bool { + return evts[i].Timestamp.Before(evts[j].Timestamp) + }) + evts = []v2.Event{evts[len(evts)-1]} + + // Update the timestamp to now. + evts[0].Timestamp = v2.Now() + + advisory.Events = evts + return advisory, true +} diff --git a/pkg/cli/advisory_stream.go b/pkg/cli/advisory_stream.go new file mode 100644 index 000000000..58218e797 --- /dev/null +++ b/pkg/cli/advisory_stream.go @@ -0,0 +1,117 @@ +package cli + +import ( + "fmt" + "os" + "regexp" + "strings" + + "github.com/spf13/cobra" + v2 "github.com/wolfi-dev/wolfictl/pkg/configs/advisory/v2" + rwos "github.com/wolfi-dev/wolfictl/pkg/configs/rwfs/os" +) + +func cmdAdvisoryStream() *cobra.Command { + var dir string + cmd := &cobra.Command{ + Use: "stream ", + Aliases: []string{"stream"}, + Short: "Start version streaming for a package by moving its advisories into a new package.", + Long: `Start version streaming for a package by moving its advisories into a new package. + +This command will move most advisories for the given package into a new package. And rename the +package to the new package name. (i.e., from foo.advisories.yaml to foo-X.Y.advisories.yaml) If the +target file already exists, the command will try to merge the advisories. To ensure the advisories +are up-to-date, the command will start a scan for the new package. + +The command will move the latest event for each advisory, and will update the timestamp +of the event to now. The command will not copy events of type "detection", "fixed", +"analysis_not_planned", or "fix_not_planned". +`, + SilenceErrors: true, + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + old, new := args[0], args[1] + + old = strings.TrimSuffix(old, ".advisories.yaml") + new = strings.TrimSuffix(new, ".advisories.yaml") + + if err := checkPackageHasVersionStreamSuffix(new); err != nil { + return err + } + + advisoryFsys := rwos.DirFS(dir) + advisoryCfgs, err := v2.NewIndex(ctx, advisoryFsys) + if err != nil { + return err + } + + oldEntry, err := advisoryCfgs.Select().WhereName(old).First() + if err != nil { + return fmt.Errorf("unable to find advisory for package %q: %w", old, err) + } + oldDoc := oldEntry.Configuration() + + shouldMergeExistings := false + newEntry, err := advisoryCfgs.Select().WhereName(new).First() + if err == nil && len(newEntry.Configuration().Advisories) > 0 { + shouldMergeExistings = true + } + + out := *oldDoc + out.Package.Name = new + out.Advisories = nil + + for _, adv := range oldDoc.Advisories { + if carried, ok := carryAdvisory(adv); ok { + out.Advisories = append(out.Advisories, carried) + } + } + + path := new + ".advisories.yaml" + + if shouldMergeExistings { + newDoc := newEntry.Configuration() + out.Advisories = mergeExistingAdvisories(out.Advisories, newDoc.Advisories) + + // Remove the existing file to re-create it. + if err := os.Remove(path); err != nil { + return fmt.Errorf("unable to remove existing file %q: %w", path, err) + } + } + + return advisoryCfgs.Create(ctx, path, out) + }, + } + cmd.PersistentFlags().StringVarP(&dir, "dir", "d", ".", "directory containing the advisories to copy") + + return cmd +} + +// checkPackageHasVersionStreamSuffix ensures the package name has the "-X" or "-X.Y" suffix. +// X and Y are positive integers. +func checkPackageHasVersionStreamSuffix(pkg string) error { + re := regexp.MustCompile(`-\d+(\.\d+)?$`) + if re.MatchString(pkg) { + return nil + } + return fmt.Errorf("new package name %q does not have the version stream suffix", pkg) +} + +// mergeExistingAdvisories merges the current advisories with the existing advisories. +func mergeExistingAdvisories(current, existing v2.Advisories) v2.Advisories { + res := make(v2.Advisories, 0, len(current)+len(existing)) + + // Add current advisories to the result and mark their IDs as seen + res = append(res, current...) + + // Add existing advisories to the result if they are not already present + for _, adv := range existing { + if _, found := res.Get(adv.ID); !found { + res = append(res, adv) + } + } + + return res +} From 00fb6d975765f65b26c9e5e9d12a06a4ff211105 Mon Sep 17 00:00:00 2001 From: Dentrax Date: Mon, 8 Jul 2024 22:56:38 +0300 Subject: [PATCH 2/5] remove old file Signed-off-by: Dentrax --- pkg/cli/advisory_stream.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/cli/advisory_stream.go b/pkg/cli/advisory_stream.go index 58218e797..897d269db 100644 --- a/pkg/cli/advisory_stream.go +++ b/pkg/cli/advisory_stream.go @@ -81,6 +81,11 @@ of the event to now. The command will not copy events of type "detection", "fixe } } + // Remove the old non-version-streamed file. + if err := os.Remove(old + ".advisories.yaml"); err != nil { + return fmt.Errorf("unable to remove old file %q: %w", old+".advisories.yaml", err) + } + return advisoryCfgs.Create(ctx, path, out) }, } From 2b2774af2ad63f7fdb7ebbca11cc33b1e78f3680 Mon Sep 17 00:00:00 2001 From: Dentrax Date: Tue, 9 Jul 2024 17:57:18 +0300 Subject: [PATCH 3/5] add remove command to index Signed-off-by: Dentrax --- pkg/cli/advisory_stream.go | 44 ++++++++++++++++------------ pkg/configs/index.go | 22 ++++++++++++++ pkg/configs/index_test.go | 36 +++++++++++++++++++++++ pkg/configs/rwfs/fs.go | 1 + pkg/configs/rwfs/os/memfs/memfs.go | 8 +++++ pkg/configs/rwfs/os/os.go | 5 ++++ pkg/configs/rwfs/os/tester/tester.go | 9 ++++++ 7 files changed, 107 insertions(+), 18 deletions(-) diff --git a/pkg/cli/advisory_stream.go b/pkg/cli/advisory_stream.go index 897d269db..fd7e14ad2 100644 --- a/pkg/cli/advisory_stream.go +++ b/pkg/cli/advisory_stream.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "os" "regexp" "strings" @@ -32,12 +31,12 @@ of the event to now. The command will not copy events of type "detection", "fixe Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - old, new := args[0], args[1] + have, want := args[0], args[1] - old = strings.TrimSuffix(old, ".advisories.yaml") - new = strings.TrimSuffix(new, ".advisories.yaml") + have = strings.TrimSuffix(have, ".advisories.yaml") + want = strings.TrimSuffix(want, ".advisories.yaml") - if err := checkPackageHasVersionStreamSuffix(new); err != nil { + if err := checkPackageHasVersionStreamSuffix(want); err != nil { return err } @@ -47,20 +46,20 @@ of the event to now. The command will not copy events of type "detection", "fixe return err } - oldEntry, err := advisoryCfgs.Select().WhereName(old).First() + oldEntry, err := advisoryCfgs.Select().WhereName(have).First() if err != nil { - return fmt.Errorf("unable to find advisory for package %q: %w", old, err) + return fmt.Errorf("unable to find advisory for package %q: %w", have, err) } oldDoc := oldEntry.Configuration() shouldMergeExistings := false - newEntry, err := advisoryCfgs.Select().WhereName(new).First() + newEntry, err := advisoryCfgs.Select().WhereName(want).First() if err == nil && len(newEntry.Configuration().Advisories) > 0 { shouldMergeExistings = true } out := *oldDoc - out.Package.Name = new + out.Package.Name = want out.Advisories = nil for _, adv := range oldDoc.Advisories { @@ -69,24 +68,33 @@ of the event to now. The command will not copy events of type "detection", "fixe } } - path := new + ".advisories.yaml" + havePath := have + ".advisories.yaml" + wantPath := want + ".advisories.yaml" + // If automation already created the new advisory file before manual version streaming, + // respect the existing file and merge the advisories to it. if shouldMergeExistings { newDoc := newEntry.Configuration() - out.Advisories = mergeExistingAdvisories(out.Advisories, newDoc.Advisories) - // Remove the existing file to re-create it. - if err := os.Remove(path); err != nil { - return fmt.Errorf("unable to remove existing file %q: %w", path, err) + updater := v2.NewAdvisoriesSectionUpdater(func(_ v2.Document) (v2.Advisories, error) { + return mergeExistingAdvisories(out.Advisories, newDoc.Advisories), nil + }) + + if err := newEntry.Update(ctx, updater); err != nil { + return fmt.Errorf("unable to update %q: %w", wantPath, err) + } + + // Remove the existing file to re-create it since it's already existed. + if err := advisoryCfgs.Remove(wantPath); err != nil { + return fmt.Errorf("unable to remove old file %q: %w", wantPath, err) } } - // Remove the old non-version-streamed file. - if err := os.Remove(old + ".advisories.yaml"); err != nil { - return fmt.Errorf("unable to remove old file %q: %w", old+".advisories.yaml", err) + if err := advisoryCfgs.Remove(havePath); err != nil { + return fmt.Errorf("unable to remove old file %q: %w", havePath, err) } - return advisoryCfgs.Create(ctx, path, out) + return advisoryCfgs.Create(ctx, wantPath, out) }, } cmd.PersistentFlags().StringVarP(&dir, "dir", "d", ".", "directory containing the advisories to copy") diff --git a/pkg/configs/index.go b/pkg/configs/index.go index e6abc33f1..803d6ee82 100644 --- a/pkg/configs/index.go +++ b/pkg/configs/index.go @@ -157,6 +157,28 @@ func (i *Index[T]) Create(ctx context.Context, filepath string, cfg T) error { return nil } +// Delete deletes the configuration file at the given path. The configuration is +// also removed from the Index. +func (i *Index[T]) Remove(filepath string) error { + idx, ok := i.byPath[filepath] + if !ok { + return fmt.Errorf("unable to remove configuration: no configuration found at %q", filepath) + } + + if err := i.fsys.Remove(filepath); err != nil { + return err + } + + delete(i.byPath, filepath) + delete(i.byName, i.cfgs[idx].Name()) + + i.paths = append(i.paths[:idx], i.paths[idx+1:]...) + i.yamlRoots = append(i.yamlRoots[:idx], i.yamlRoots[idx+1:]...) + i.cfgs = append(i.cfgs[:idx], i.cfgs[idx+1:]...) + + return nil +} + // Path returns the path to the configuration file for the given name. func (i *Index[T]) Path(name string) string { idx, ok := i.byName[name] diff --git a/pkg/configs/index_test.go b/pkg/configs/index_test.go index 43f085dc8..995466c77 100644 --- a/pkg/configs/index_test.go +++ b/pkg/configs/index_test.go @@ -32,3 +32,39 @@ func TestNewIndex(t *testing.T) { assert.NotContains(t, index.paths, ".not-a-config.yaml") }) } + +func TestRemove(t *testing.T) { + ctx := context.Background() + fsys := rwos.DirFS("testdata/index-1") + + index, err := NewIndex[config.Configuration](ctx, fsys, func(ctx context.Context, path string) (*config.Configuration, error) { + return config.ParseConfiguration(ctx, path, config.WithFS(fsys)) + }) + require.NoError(t, err) + + name := "config-new" + filename := name + ".advisories.yaml" + + err = index.Create(ctx, filename, config.Configuration{ + Package: config.Package{ + Name: name, + Version: "1.0.0", + }, + }) + require.NoError(t, err) + + _, err = index.Select().WhereName(name).First() + require.NoError(t, err) + + t.Run("removes a config", func(t *testing.T) { + err := index.Remove(filename) + require.NoError(t, err) + + assert.NotContains(t, index.paths, filename) + }) + + t.Run("ensure the config is removed", func(t *testing.T) { + _, err := index.Select().WhereName(name).First() + require.Error(t, err) + }) +} diff --git a/pkg/configs/rwfs/fs.go b/pkg/configs/rwfs/fs.go index d01c8d7e1..77bc242b4 100644 --- a/pkg/configs/rwfs/fs.go +++ b/pkg/configs/rwfs/fs.go @@ -10,6 +10,7 @@ type FS interface { OpenAsWritable(name string) (File, error) Truncate(name string, size int64) error Create(name string) (File, error) + Remove(name string) error } type File interface { diff --git a/pkg/configs/rwfs/os/memfs/memfs.go b/pkg/configs/rwfs/os/memfs/memfs.go index 5ec6f632c..e984f0279 100644 --- a/pkg/configs/rwfs/os/memfs/memfs.go +++ b/pkg/configs/rwfs/os/memfs/memfs.go @@ -124,6 +124,14 @@ func (m *memWriteFS) Truncate(name string, size int64) error { return nil } +func (m *memWriteFS) Remove(name string) error { + m.mu.Lock() + defer m.mu.Unlock() + + delete(m.data, name) + return nil +} + func (m *memWriteFS) Create(name string) (rwfs.File, error) { m.mu.Lock() defer m.mu.Unlock() diff --git a/pkg/configs/rwfs/os/os.go b/pkg/configs/rwfs/os/os.go index 5f5194f8e..9af7c8a84 100644 --- a/pkg/configs/rwfs/os/os.go +++ b/pkg/configs/rwfs/os/os.go @@ -36,6 +36,11 @@ func (fsys FS) Truncate(name string, size int64) error { return os.Truncate(p, size) } +func (fsys FS) Remove(name string) error { + p := fsys.fullPath(name) + return os.Remove(p) +} + func (fsys FS) fullPath(name string) string { return filepath.Join(fsys.rootDir, name) } diff --git a/pkg/configs/rwfs/os/tester/tester.go b/pkg/configs/rwfs/os/tester/tester.go index 79a589b25..1c733406a 100644 --- a/pkg/configs/rwfs/os/tester/tester.go +++ b/pkg/configs/rwfs/os/tester/tester.go @@ -131,6 +131,15 @@ func (fsys *FS) Truncate(string, int64) error { return nil } +func (fsys *FS) Remove(name string) error { + if _, ok := fsys.fixtures[name]; ok { + delete(fsys.fixtures, name) + return nil + } + + return os.ErrNotExist +} + func (fsys *FS) Diff(name string) string { if tf, ok := fsys.fixtures[name]; ok { want := tf.expectedRead From 3eeddb46825600d1eba7a4275e1fca802ad55d70 Mon Sep 17 00:00:00 2001 From: Dentrax Date: Tue, 9 Jul 2024 17:59:24 +0300 Subject: [PATCH 4/5] update docs for stream Signed-off-by: Dentrax --- docs/cmd/wolfictl_advisory_stream.md | 44 +++++++++++++++++++ docs/man/man1/wolfictl-advisory-stream.1 | 54 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 docs/cmd/wolfictl_advisory_stream.md create mode 100644 docs/man/man1/wolfictl-advisory-stream.1 diff --git a/docs/cmd/wolfictl_advisory_stream.md b/docs/cmd/wolfictl_advisory_stream.md new file mode 100644 index 000000000..f5c55c677 --- /dev/null +++ b/docs/cmd/wolfictl_advisory_stream.md @@ -0,0 +1,44 @@ +## wolfictl advisory stream + +Start version streaming for a package by moving its advisories into a new package. + +***Aliases**: stream* + +### Usage + +``` +wolfictl advisory stream +``` + +### Synopsis + +Start version streaming for a package by moving its advisories into a new package. + +This command will move most advisories for the given package into a new package. And rename the +package to the new package name. (i.e., from foo.advisories.yaml to foo-X.Y.advisories.yaml) If the +target file already exists, the command will try to merge the advisories. To ensure the advisories +are up-to-date, the command will start a scan for the new package. + +The command will move the latest event for each advisory, and will update the timestamp +of the event to now. The command will not copy events of type "detection", "fixed", +"analysis_not_planned", or "fix_not_planned". + + +### Options + +``` + -d, --dir string directory containing the advisories to copy (default ".") + -h, --help help for stream +``` + +### Options inherited from parent commands + +``` + --log-level string log level (e.g. debug, info, warn, error) (default "info") + --log-policy strings log policy (e.g. builtin:stderr, /tmp/log/foo) (default [builtin:stderr]) +``` + +### SEE ALSO + +* [wolfictl advisory](wolfictl_advisory.md) - Commands for consuming and maintaining security advisory data + diff --git a/docs/man/man1/wolfictl-advisory-stream.1 b/docs/man/man1/wolfictl-advisory-stream.1 new file mode 100644 index 000000000..c5f665764 --- /dev/null +++ b/docs/man/man1/wolfictl-advisory-stream.1 @@ -0,0 +1,54 @@ +.TH "WOLFICTL\-ADVISORY\-STREAM" "1" "" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +wolfictl\-advisory\-stream \- Start version streaming for a package by moving its advisories into a new package. + + +.SH SYNOPSIS +.PP +\fBwolfictl advisory stream \fP + + +.SH DESCRIPTION +.PP +Start version streaming for a package by moving its advisories into a new package. + +.PP +This command will move most advisories for the given package into a new package. And rename the +package to the new package name. (i.e., from foo.advisories.yaml to foo\-X.Y.advisories.yaml) If the +target file already exists, the command will try to merge the advisories. To ensure the advisories +are up\-to\-date, the command will start a scan for the new package. + +.PP +The command will move the latest event for each advisory, and will update the timestamp +of the event to now. The command will not copy events of type "detection", "fixed", +"analysis\_not\_planned", or "fix\_not\_planned". + + +.SH OPTIONS +.PP +\fB\-d\fP, \fB\-\-dir\fP="." + directory containing the advisories to copy + +.PP +\fB\-h\fP, \fB\-\-help\fP[=false] + help for stream + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-log\-level\fP="info" + log level (e.g. debug, info, warn, error) + +.PP +\fB\-\-log\-policy\fP=[builtin:stderr] + log policy (e.g. builtin:stderr, /tmp/log/foo) + + +.SH SEE ALSO +.PP +\fBwolfictl\-advisory(1)\fP From a844bf3fe1cc9e33b7fa8bc65e3e200b02373b4b Mon Sep 17 00:00:00 2001 From: Dentrax Date: Wed, 10 Jul 2024 16:06:28 +0300 Subject: [PATCH 5/5] rename command to move Signed-off-by: Dentrax --- ...ry_stream.md => wolfictl_advisory_move.md} | 15 +++++---- ...sory-stream.1 => wolfictl-advisory-move.1} | 14 ++++++--- pkg/cli/advisory.go | 2 +- .../{advisory_stream.go => advisory_move.go} | 31 ++++++------------- 4 files changed, 28 insertions(+), 34 deletions(-) rename docs/cmd/{wolfictl_advisory_stream.md => wolfictl_advisory_move.md} (71%) rename docs/man/man1/{wolfictl-advisory-stream.1 => wolfictl-advisory-move.1} (68%) rename pkg/cli/{advisory_stream.go => advisory_move.go} (78%) diff --git a/docs/cmd/wolfictl_advisory_stream.md b/docs/cmd/wolfictl_advisory_move.md similarity index 71% rename from docs/cmd/wolfictl_advisory_stream.md rename to docs/cmd/wolfictl_advisory_move.md index f5c55c677..d57c3e87e 100644 --- a/docs/cmd/wolfictl_advisory_stream.md +++ b/docs/cmd/wolfictl_advisory_move.md @@ -1,24 +1,27 @@ -## wolfictl advisory stream +## wolfictl advisory move -Start version streaming for a package by moving its advisories into a new package. +Move a package's advisories into a new package. -***Aliases**: stream* +***Aliases**: mv* ### Usage ``` -wolfictl advisory stream +wolfictl advisory move ``` ### Synopsis -Start version streaming for a package by moving its advisories into a new package. +Move a package's advisories into a new package. This command will move most advisories for the given package into a new package. And rename the package to the new package name. (i.e., from foo.advisories.yaml to foo-X.Y.advisories.yaml) If the target file already exists, the command will try to merge the advisories. To ensure the advisories are up-to-date, the command will start a scan for the new package. +This command is also useful to start version streaming for an existing package that has not been +version streamed before. Especially that requires manual intervention to move the advisories. + The command will move the latest event for each advisory, and will update the timestamp of the event to now. The command will not copy events of type "detection", "fixed", "analysis_not_planned", or "fix_not_planned". @@ -28,7 +31,7 @@ of the event to now. The command will not copy events of type "detection", "fixe ``` -d, --dir string directory containing the advisories to copy (default ".") - -h, --help help for stream + -h, --help help for move ``` ### Options inherited from parent commands diff --git a/docs/man/man1/wolfictl-advisory-stream.1 b/docs/man/man1/wolfictl-advisory-move.1 similarity index 68% rename from docs/man/man1/wolfictl-advisory-stream.1 rename to docs/man/man1/wolfictl-advisory-move.1 index c5f665764..47f484227 100644 --- a/docs/man/man1/wolfictl-advisory-stream.1 +++ b/docs/man/man1/wolfictl-advisory-move.1 @@ -1,21 +1,21 @@ -.TH "WOLFICTL\-ADVISORY\-STREAM" "1" "" "Auto generated by spf13/cobra" "" +.TH "WOLFICTL\-ADVISORY\-MOVE" "1" "" "Auto generated by spf13/cobra" "" .nh .ad l .SH NAME .PP -wolfictl\-advisory\-stream \- Start version streaming for a package by moving its advisories into a new package. +wolfictl\-advisory\-move \- Move a package's advisories into a new package. .SH SYNOPSIS .PP -\fBwolfictl advisory stream \fP +\fBwolfictl advisory move \fP .SH DESCRIPTION .PP -Start version streaming for a package by moving its advisories into a new package. +Move a package's advisories into a new package. .PP This command will move most advisories for the given package into a new package. And rename the @@ -23,6 +23,10 @@ package to the new package name. (i.e., from foo.advisories.yaml to foo\-X.Y.adv target file already exists, the command will try to merge the advisories. To ensure the advisories are up\-to\-date, the command will start a scan for the new package. +.PP +This command is also useful to start version streaming for an existing package that has not been +version streamed before. Especially that requires manual intervention to move the advisories. + .PP The command will move the latest event for each advisory, and will update the timestamp of the event to now. The command will not copy events of type "detection", "fixed", @@ -36,7 +40,7 @@ of the event to now. The command will not copy events of type "detection", "fixe .PP \fB\-h\fP, \fB\-\-help\fP[=false] - help for stream + help for move .SH OPTIONS INHERITED FROM PARENT COMMANDS diff --git a/pkg/cli/advisory.go b/pkg/cli/advisory.go index 4d187bed9..d55eb04b0 100644 --- a/pkg/cli/advisory.go +++ b/pkg/cli/advisory.go @@ -48,7 +48,7 @@ func cmdAdvisory() *cobra.Command { cmdAdvisorySecDB(), cmdAdvisoryUpdate(), cmdAdvisoryValidate(), - cmdAdvisoryStream(), + cmdAdvisoryMove(), ) return cmd diff --git a/pkg/cli/advisory_stream.go b/pkg/cli/advisory_move.go similarity index 78% rename from pkg/cli/advisory_stream.go rename to pkg/cli/advisory_move.go index fd7e14ad2..fe004c819 100644 --- a/pkg/cli/advisory_stream.go +++ b/pkg/cli/advisory_move.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "regexp" "strings" "github.com/spf13/cobra" @@ -10,19 +9,22 @@ import ( rwos "github.com/wolfi-dev/wolfictl/pkg/configs/rwfs/os" ) -func cmdAdvisoryStream() *cobra.Command { +func cmdAdvisoryMove() *cobra.Command { var dir string cmd := &cobra.Command{ - Use: "stream ", - Aliases: []string{"stream"}, - Short: "Start version streaming for a package by moving its advisories into a new package.", - Long: `Start version streaming for a package by moving its advisories into a new package. + Use: "move ", + Aliases: []string{"mv"}, + Short: "Move a package's advisories into a new package.", + Long: `Move a package's advisories into a new package. This command will move most advisories for the given package into a new package. And rename the package to the new package name. (i.e., from foo.advisories.yaml to foo-X.Y.advisories.yaml) If the target file already exists, the command will try to merge the advisories. To ensure the advisories are up-to-date, the command will start a scan for the new package. +This command is also useful to start version streaming for an existing package that has not been +version streamed before. Especially that requires manual intervention to move the advisories. + The command will move the latest event for each advisory, and will update the timestamp of the event to now. The command will not copy events of type "detection", "fixed", "analysis_not_planned", or "fix_not_planned". @@ -36,10 +38,6 @@ of the event to now. The command will not copy events of type "detection", "fixe have = strings.TrimSuffix(have, ".advisories.yaml") want = strings.TrimSuffix(want, ".advisories.yaml") - if err := checkPackageHasVersionStreamSuffix(want); err != nil { - return err - } - advisoryFsys := rwos.DirFS(dir) advisoryCfgs, err := v2.NewIndex(ctx, advisoryFsys) if err != nil { @@ -71,8 +69,7 @@ of the event to now. The command will not copy events of type "detection", "fixe havePath := have + ".advisories.yaml" wantPath := want + ".advisories.yaml" - // If automation already created the new advisory file before manual version streaming, - // respect the existing file and merge the advisories to it. + // If the new file already exists, merge the old advisories to it and re-create. if shouldMergeExistings { newDoc := newEntry.Configuration() @@ -102,16 +99,6 @@ of the event to now. The command will not copy events of type "detection", "fixe return cmd } -// checkPackageHasVersionStreamSuffix ensures the package name has the "-X" or "-X.Y" suffix. -// X and Y are positive integers. -func checkPackageHasVersionStreamSuffix(pkg string) error { - re := regexp.MustCompile(`-\d+(\.\d+)?$`) - if re.MatchString(pkg) { - return nil - } - return fmt.Errorf("new package name %q does not have the version stream suffix", pkg) -} - // mergeExistingAdvisories merges the current advisories with the existing advisories. func mergeExistingAdvisories(current, existing v2.Advisories) v2.Advisories { res := make(v2.Advisories, 0, len(current)+len(existing))