From 79c7fc972c60f57407df772d013ae892680039d8 Mon Sep 17 00:00:00 2001 From: Kota Kanbe Date: Fri, 5 Nov 2021 10:01:18 +0900 Subject: [PATCH] reverse deps recursively --- models/packages.go | 28 +++++++ models/packages_test.go | 163 ++++++++++++++++++++++++++++++++++++++++ scanner/redhatbase.go | 3 +- 3 files changed, 193 insertions(+), 1 deletion(-) diff --git a/models/packages.go b/models/packages.go index 7dbd15b8..8135700f 100644 --- a/models/packages.go +++ b/models/packages.go @@ -72,6 +72,31 @@ func (ps Packages) FindByFQPN(nameVerRel string) (*Package, error) { return nil, xerrors.Errorf("Failed to find the package: %s", nameVerRel) } +// ResolveReverseDepsRecursively resolve and set reverse dependencies for each packages recursively +func (ps Packages) ResolveReverseDepsRecursively() { + for name, pkg := range ps { + depsmap := ps.resolveReverseDeps(pkg, map[string]struct{}{}) + delete(depsmap, pkg.Name) + for depname := range depsmap { + pkg.ReverseDependenciesRecursively = append(pkg.ReverseDependenciesRecursively, depname) + } + ps[name] = pkg + } +} + +func (ps Packages) resolveReverseDeps(pkg Package, acc map[string]struct{}) map[string]struct{} { + acc[pkg.Name] = struct{}{} + for _, name := range pkg.ReverseDependencies { + if _, ok := acc[name]; ok { + continue + } + if p, ok := ps[name]; ok { + acc = ps.resolveReverseDeps(p, acc) + } + } + return acc +} + // Package has installed binary packages. type Package struct { Name string `json:"name"` @@ -91,6 +116,9 @@ type Package struct { // ReverseDependencies are packages which depend on this package ReverseDependencies []string `json:",omitempty"` + // ReverseDependencies are packages which depend on this package + ReverseDependenciesRecursively []string `json:",omitempty"` + // DependenciesForUpdate are packages that needs to be updated together DependenciesForUpdate []string `json:",omitempty"` } diff --git a/models/packages_test.go b/models/packages_test.go index ee07ee89..9aa54058 100644 --- a/models/packages_test.go +++ b/models/packages_test.go @@ -428,3 +428,166 @@ func Test_NewPortStat(t *testing.T) { }) } } + +func TestPackages_resolveReverseDeps(t *testing.T) { + type args struct { + pkg Package + acc map[string]struct{} + } + tests := []struct { + name string + ps Packages + args args + want map[string]struct{} + }{ + { + name: "", + ps: map[string]Package{ + "pkgA": { + Name: "pkgA", + ReverseDependencies: []string{"pkgB"}, + }, + "pkgB": { + Name: "pkgB", + ReverseDependencies: []string{"pkgC"}, + }, + "pkgC": { + Name: "pkgC", + ReverseDependencies: []string{""}, + }, + }, + args: args{ + pkg: Package{ + Name: "pkgA", + ReverseDependencies: []string{"pkgB"}, + }, + acc: map[string]struct{}{}, + }, + want: map[string]struct{}{ + "pkgA": {}, + "pkgB": {}, + "pkgC": {}, + }, + }, + { + name: "", + ps: map[string]Package{ + "pkgA": { + Name: "pkgA", + ReverseDependencies: []string{"pkgB"}, + }, + "pkgB": { + Name: "pkgB", + ReverseDependencies: []string{"pkgA", "pkgC"}, + }, + "pkgC": { + Name: "pkgC", + ReverseDependencies: []string{"pkgB"}, + }, + }, + args: args{ + pkg: Package{ + Name: "pkgA", + ReverseDependencies: []string{"pkgB"}, + }, + acc: map[string]struct{}{}, + }, + want: map[string]struct{}{ + "pkgA": {}, + "pkgB": {}, + "pkgC": {}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.ps.resolveReverseDeps(tt.args.pkg, tt.args.acc); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Packages.resolveReverseDeps() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPackages_resolveReverseDepsRecursively(t *testing.T) { + tests := []struct { + name string + ps Packages + want Packages + }{ + { + name: "", + ps: map[string]Package{ + "pkgA": { + Name: "pkgA", + ReverseDependencies: []string{"pkgB"}, + }, + "pkgB": { + Name: "pkgB", + ReverseDependencies: []string{"pkgC"}, + }, + "pkgC": { + Name: "pkgC", + ReverseDependencies: []string{""}, + }, + }, + want: map[string]Package{ + "pkgA": { + Name: "pkgA", + ReverseDependencies: []string{"pkgB"}, + ReverseDependenciesRecursively: []string{"pkgB", "pkgC"}, + }, + "pkgB": { + Name: "pkgB", + ReverseDependencies: []string{"pkgC"}, + ReverseDependenciesRecursively: []string{"pkgC"}, + }, + "pkgC": { + Name: "pkgC", + ReverseDependencies: []string{""}, + }, + }, + }, + { + name: "", + ps: map[string]Package{ + "pkgA": { + Name: "pkgA", + ReverseDependencies: []string{"pkgB"}, + }, + "pkgB": { + Name: "pkgB", + ReverseDependencies: []string{"pkgA", "pkgC"}, + }, + "pkgC": { + Name: "pkgC", + ReverseDependencies: []string{"pkgB"}, + }, + }, + want: map[string]Package{ + "pkgA": { + Name: "pkgA", + ReverseDependencies: []string{"pkgB"}, + ReverseDependenciesRecursively: []string{"pkgB", "pkgC"}, + }, + "pkgB": { + Name: "pkgB", + ReverseDependencies: []string{"pkgA", "pkgC"}, + ReverseDependenciesRecursively: []string{"pkgA", "pkgC"}, + }, + "pkgC": { + Name: "pkgC", + ReverseDependencies: []string{"pkgB"}, + ReverseDependenciesRecursively: []string{"pkgA", "pkgB"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.ps.ResolveReverseDepsRecursively() + if !reflect.DeepEqual(tt.ps, tt.want) { + t.Errorf("Packages.resolveReverseDepsRecursively() = \n%+v, want \n%+v", tt.ps, tt.want) + } + }) + } +} diff --git a/scanner/redhatbase.go b/scanner/redhatbase.go index 5e07b9a5..40d23fd1 100644 --- a/scanner/redhatbase.go +++ b/scanner/redhatbase.go @@ -292,6 +292,7 @@ func (o *redhatBase) scanPackages() (err error) { resolver := yumDependentResolver{redhat: o} resolver.detectDependenciesForUpdate() resolver.detectReverseDependencies() + o.Packages.ResolveReverseDepsRecursively() } return nil @@ -812,7 +813,7 @@ func (o *yumDependentResolver) detectReverseDependencies() { } func (o *yumDependentResolver) repoqueryWhatRequires(pkgName string) (depsPkgNames []string, err error) { - cmd := `LANGUAGE=en_US.UTF-8 repoquery --whatrequires --resolve --pkgnarrow=installed --qf "%{name}" ` + pkgName + cmd := `LANGUAGE=en_US.UTF-8 repoquery --cache --whatrequires --resolve --pkgnarrow=installed --qf "%{name}" ` + pkgName r := o.redhat.exec(cmd, true) if !r.isSuccess() { return nil, xerrors.Errorf("Failed to SSH: %s", r)