diff --git a/cmd/guacone/cmd/analyze.go b/cmd/guacone/cmd/analyze.go index 424c2db0ec1..6262da6fdeb 100644 --- a/cmd/guacone/cmd/analyze.go +++ b/cmd/guacone/cmd/analyze.go @@ -21,6 +21,7 @@ import ( "io" "net/http" "os" + "strings" "github.com/Khan/genqlient/graphql" "github.com/dominikbraun/graph" @@ -55,13 +56,13 @@ var analyzeCmd = &cobra.Command{ $ guacone collect files guac-data-main/docs/spdx/spdx_vuln.json Difference - $ guacone analyze --diff --uri --sboms=https://anchore.com/syft/image/ghcr.io/guacsec/vul-image-latest-6fd9de7b-9bec-4ae7-99d9-4b5e5ef6b869,https://anchore.com/syft/image/k8s.gcr.io/kube-apiserver-v1.24.4-b15339bc-a146-476e-a789-6a65e4e22e54 + $ guacone analyze diff --uri --sboms=https://anchore.com/syft/image/ghcr.io/guacsec/vul-image-latest-6fd9de7b-9bec-4ae7-99d9-4b5e5ef6b869,https://anchore.com/syft/image/k8s.gcr.io/kube-apiserver-v1.24.4-b15339bc-a146-476e-a789-6a65e4e22e54 Union - $ guacone analyze --union --uri --sboms=https://anchore.com/syft/image/ghcr.io/guacsec/vul-image-latest-6fd9de7b-9bec-4ae7-99d9-4b5e5ef6b869,https://anchore.com/syft/image/k8s.gcr.io/kube-apiserver-v1.24.4-b15339bc-a146-476e-a789-6a65e4e22e54 + $ guacone analyze union --uri --sboms=https://anchore.com/syft/image/ghcr.io/guacsec/vul-image-latest-6fd9de7b-9bec-4ae7-99d9-4b5e5ef6b869,https://anchore.com/syft/image/k8s.gcr.io/kube-apiserver-v1.24.4-b15339bc-a146-476e-a789-6a65e4e22e54 Intersection - $ guacone analyze --intersect --uri --sboms=https://anchore.com/syft/image/ghcr.io/guacsec/vul-image-latest-6fd9de7b-9bec-4ae7-99d9-4b5e5ef6b869,https://anchore.com/syft/image/k8s.gcr.io/kube-apiserver-v1.24.4-b15339bc-a146-476e-a789-6a65e4e22e54 + $ guacone analyze intersect --uri --sboms=https://anchore.com/syft/image/ghcr.io/guacsec/vul-image-latest-6fd9de7b-9bec-4ae7-99d9-4b5e5ef6b869,https://anchore.com/syft/image/k8s.gcr.io/kube-apiserver-v1.24.4-b15339bc-a146-476e-a789-6a65e4e22e54 `, Run: func(cmd *cobra.Command, args []string) { @@ -81,8 +82,8 @@ var analyzeCmd = &cobra.Command{ gqlclient := graphql.NewClient(viper.GetString("gql-addr"), &httpClient) //get necessary flags - all, _ := cmd.Flags().GetBool("all") - maxprint, _ := cmd.Flags().GetInt("maxprint") + // all, _ := cmd.Flags().GetBool("all") + // maxprint, _ := cmd.Flags().GetInt("maxprint") slsas, errSlsa := cmd.Flags().GetStringSlice("slsa") sboms, errSbom := cmd.Flags().GetStringSlice("sboms") @@ -95,98 +96,238 @@ var analyzeCmd = &cobra.Command{ inclOccur, _ := cmd.Flags().GetBool("incl-occur") namespaces, _ := cmd.Flags().GetBool("namespaces") id, _ := cmd.Flags().GetBool("id") - test, _ := cmd.Flags().GetString("test") + test, _ := cmd.Flags().GetString("test") var graphs []graph.Graph[string, *analyzer.Node] var err error - if test == ""{ + if test == "" { if err = verifyAnalyzeFlags(slsas, sboms, errSlsa, errSbom, uri, purl, id); err != nil { fmt.Fprintf(os.Stderr, "Error: %s", err) _ = cmd.Help() os.Exit(1) } - + //create graphs graphs, err = hasSBOMToGraph(ctx, gqlclient, sboms, AnalyzeOpts{ Metadata: metadata, InclSoft: inclSoft, InclDeps: inclDeps, InclOccur: inclOccur, Namespaces: namespaces, URI: uri, PURL: purl, ID: id}) - + if err != nil { logger.Fatalf("Unable to generate graphs: %v", err) } - }else { + } else { graphs, err = readTwoSBOM(test) if err != nil { logger.Fatalf("Unable to generate graphs: %v", err) } } - - if args[0] == "diff" { - analysisList, err := analyzer.HighlightAnalysis(graphs[0], graphs[1], 0) + analysisOne, analysisTwo, err := analyzer.HighlightAnalysis(graphs[0], graphs[1], 0) + if err != nil { + logger.Fatalf("unable to generate diff analysis: %v", err) + } + + diffs, err := analyzer.CompareAllPaths(analysisOne, analysisTwo) if err != nil { - logger.Fatalf("Unable to generate diff analysis: %v", err) + logger.Fatalf("unable to generate diff analysis: %v", err) } - if printHighlightedAnalysis(analysisList, all, maxprint, 0) != nil { - logger.Fatalf("Unable to generate diff analysis output: %v", err) + if err = printDiffedPathTable(diffs); err != nil { + logger.Fatalf("unable to print diff analysis: %v", err) } + } else if args[0] == "intersect" { - analysisList, err := analyzer.HighlightAnalysis(graphs[0], graphs[1], 1) + analysisOne, analysisTwo, err := analyzer.HighlightAnalysis(graphs[0], graphs[1], 1) if err != nil { logger.Fatalf("Unable to generate intersect analysis: %v", err) } - if printHighlightedAnalysis(analysisList, all, maxprint, 1) != nil { - logger.Fatalf("Unable to generate diff analysis output: %v", err) + if err = printPathTable("Common Paths", analysisOne, analysisTwo); err != nil { + logger.Fatalf("unable to print intersect analysis: %v", err) } } else if args[0] == "union" { - analysisList, err := analyzer.HighlightAnalysis(graphs[0], graphs[1], 2) + analysisOne, analysisTwo, err := analyzer.HighlightAnalysis(graphs[0], graphs[1], 2) if err != nil { - logger.Fatalf("Unable to generate union analysis: %v", err) + logger.Fatalf("unable to generate union analysis: %v", err) } - if printHighlightedAnalysis(analysisList, all, maxprint, 2) != nil { - logger.Fatalf("Unable to generate diff analysis output: %v", err) + if err = printPathTable("All Paths", analysisOne, analysisTwo); err != nil { + logger.Fatalf("unable to print union analysis: %v", err) } } }, } +func printDiffedPathTable(diffs []analyzer.DiffedPath) error { -func printHighlightedAnalysis(diffList [][]*analyzer.Node, all bool, maxprint, action int) error { + table := tablewriter.NewWriter(os.Stdout) + table.SetAutoWrapText(false) - //use action here to do different things + table.SetBorders(tablewriter.Border{Left: true, Bottom: true}) - table := tablewriter.NewWriter(os.Stdout) + table.SetNoWhiteSpace(true) - switch action { - case 0: - table.SetHeader([]string{"Missing Paths"}) - case 1: - table.SetHeader([]string{"Common Paths"}) - case 2: - table.SetHeader([]string{"All Paths"}) - } - for i := 0; i < len(diffList); i++ { + table.SetColumnSeparator("\t\t") + table.SetAutoMergeCells(false) + + table.SetHeader([]string{"Path Differences"}) + + for _, diff := range diffs { + var row []string + + for i, nodeOne := range diff.PathOne { - if !all && i+1 == maxprint { - break + if len(row) != 0 { + row = append(row, "--->") + table.SetColMinWidth(i+1, 90) + } else { + table.SetColMinWidth(i, 90) + } + + if nodeOne.Attributes["nodeType"] == "Package" { + s, err := analyzer.GetNodeString(1, nodeOne.Attributes["data"]) + if err != nil { + return fmt.Errorf("unable to print diffs: %v", err) + } + row = append(row, s) + + } else if nodeOne.Attributes["nodeType"] == "DependencyPackage" { + s, err := analyzer.GetNodeString(2, nodeOne.Attributes["data"]) + if err != nil { + return fmt.Errorf("unable to print diffs: %v", err) + } + row = append(row, s) + } } - var appendList []string - for _, val := range diffList[i] { - appendList = append(appendList, val.ID) + table.Append(row) + row = []string{} + + for i, nodeOne := range diff.PathTwo { + + if len(row) != 0 { + row = append(row, "--->") + table.SetColMinWidth(i+1, 50) + } else { + table.SetColMinWidth(i, 50) + } + + if nodeOne.Attributes["nodeType"] == "Package" { + s, err := analyzer.GetNodeString(1, nodeOne.Attributes["data"]) + if err != nil { + return fmt.Errorf("unable to print diffs: %v", err) + } + row = append(row, s) + } else if nodeOne.Attributes["nodeType"] == "DependencyPackage" { + s, err := analyzer.GetNodeString(2, nodeOne.Attributes["data"]) + if err != nil { + return fmt.Errorf("unable to print diffs: %v", err) + } + row = append(row, s) + } } + table.Append(row) + + table.Append([]string{"================================="}) + + row = []string{} + for i, diff := range diff.Diffs { + + if len(row) != 0 { + row = append(row, " ") + table.SetColMinWidth(i+1, 50) + }else{ + table.SetColMinWidth(i, 50) + } + + row = append(row, strings.Join(diff, "\n")) + } + table.Append(row) - table.Append(appendList) + + table.Append([]string{"================================="}) } + table.SetAlignment(tablewriter.ALIGN_LEFT) table.Render() - if !all && len(diffList) > maxprint { - fmt.Println("Run with --all to see full list") + return nil + +} + +func printPathTable(header string, analysisOne, analysisTwo [][]*analyzer.Node) error { + table := tablewriter.NewWriter(os.Stdout) + table.SetAutoWrapText(false) + + table.SetBorders(tablewriter.Border{Left: true, Bottom: true}) + + table.SetNoWhiteSpace(true) + + table.SetColumnSeparator("\t\t") + table.SetAutoMergeCells(false) + + table.SetHeader([]string{header}) + + for _, pathOne := range analysisOne { + var row []string + for i, nodeOne := range pathOne { + + if len(row) != 0 { + row = append(row, "--->") + table.SetColMinWidth(i+1, 50) + } else { + table.SetColMinWidth(i, 50) + } + + if nodeOne.Attributes["nodeType"] == "Package" { + s, err := analyzer.GetNodeString(1, nodeOne.Attributes["data"]) + if err != nil { + return fmt.Errorf("unable to print diffs: %v", err) + } + + row = append(row, s) + + } else if nodeOne.Attributes["nodeType"] == "DependencyPackage" { + s, err := analyzer.GetNodeString(2, nodeOne.Attributes["data"]) + if err != nil { + return fmt.Errorf("unable to print diffs: %v", err) + } + row = append(row, s) + } + } + table.Append(row) + } + + for _, pathTwo := range analysisTwo { + var row []string + for i, nodeOne := range pathTwo { + if len(row) != 0 { + row = append(row, "--->") + table.SetColMinWidth(i+1, 50) + } else { + table.SetColMinWidth(i, 50) + } + + if nodeOne.Attributes["nodeType"] == "Package" { + s, err := analyzer.GetNodeString(1, nodeOne.Attributes["data"]) + if err != nil { + return fmt.Errorf("unable to print diffs: %v", err) + } + row = append(row, s) + + } else if nodeOne.Attributes["nodeType"] == "DependencyPackage" { + s, err := analyzer.GetNodeString(2, nodeOne.Attributes["data"]) + if err != nil { + return fmt.Errorf("unable to print diffs: %v", err) + } + row = append(row, s) + } + } + table.Append(row) + } + + table.Render() return nil } @@ -224,8 +365,8 @@ func hasSBOMToGraph(ctx context.Context, gqlclient graphql.Client, sboms []strin if err != nil { return []graph.Graph[string, *analyzer.Node]{}, fmt.Errorf("(id)failed to lookup sbom: %v %v", sboms[1], err) } - } + if hasSBOMResponseOne == nil || hasSBOMResponseTwo == nil { return []graph.Graph[string, *analyzer.Node]{}, fmt.Errorf("failed to lookup sboms: nil") } @@ -288,25 +429,22 @@ func verifyAnalyzeFlags(slsas, sboms []string, errSlsa, errSbom error, uri, purl return nil } - - -func readTwoSBOM(filename string)([]graph.Graph[string, *analyzer.Node], error){ +func readTwoSBOM(filename string) ([]graph.Graph[string, *analyzer.Node], error) { file, err := os.Open(filename) if err != nil { - return []graph.Graph[string, *analyzer.Node]{}, fmt.Errorf("Error opening rearranged test file") - + return []graph.Graph[string, *analyzer.Node]{}, fmt.Errorf("error opening test file") } defer file.Close() data, err := io.ReadAll(file) if err != nil { - return []graph.Graph[string, *analyzer.Node]{}, fmt.Errorf("Error reading test file") + return []graph.Graph[string, *analyzer.Node]{}, fmt.Errorf("error reading test file") } var sboms []model.HasSBOMsHasSBOM err = json.Unmarshal(data, &sboms) if err != nil { - return []graph.Graph[string, *analyzer.Node]{}, fmt.Errorf("Error unmarshaling JSON") + return []graph.Graph[string, *analyzer.Node]{}, fmt.Errorf("error unmarshaling JSON") } graphOne, errOne := analyzer.MakeGraph(sboms[0], false, false, false, false, false) @@ -314,7 +452,7 @@ func readTwoSBOM(filename string)([]graph.Graph[string, *analyzer.Node], error){ graphTwo, errTwo := analyzer.MakeGraph(sboms[1], false, false, false, false, false) if errOne != nil || errTwo != nil { - return []graph.Graph[string, *analyzer.Node]{}, fmt.Errorf("Error making graph %v %v", errOne.Error(), errTwo.Error()) + return []graph.Graph[string, *analyzer.Node]{}, fmt.Errorf("error making graph %v %v", errOne.Error(), errTwo.Error()) } return []graph.Graph[string, *analyzer.Node]{graphOne, graphTwo}, nil @@ -337,9 +475,6 @@ func init() { analyzeCmd.PersistentFlags().Bool("all", false, " lists all") analyzeCmd.PersistentFlags().Int("maxprint", 20, "max number of items to print") analyzeCmd.PersistentFlags().String("test", "", "test file with sbom") - rootCmd.AddCommand(analyzeCmd) } - - diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index ac2fd645497..2c37d985c37 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -20,6 +20,7 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "math" "sort" "strings" @@ -46,9 +47,15 @@ type HighlightedUnion struct { type Node struct { ID string + Message string Attributes map[string]interface{} - color string - nodeType string + Color string +} + +type DiffedPath struct { + PathOne []*Node + PathTwo []*Node + Diffs [][]string } type packageNameSpaces []model.AllPkgTreeNamespacesPackageNamespace @@ -218,27 +225,24 @@ func FindPathsFromHasSBOMNode(g graph.Graph[string, *Node]) ([][]string, error) return paths, nil } -func HighlightAnalysis(gOne, gTwo graph.Graph[string, *Node], action int) ([][]*Node, error) { +func HighlightAnalysis(gOne, gTwo graph.Graph[string, *Node], action int) ([][]*Node, [][]*Node, error) { pathsOne, errOne := FindPathsFromHasSBOMNode(gOne) pathsTwo, errTwo := FindPathsFromHasSBOMNode(gTwo) + var analysisOne, analysisTwo [][]*Node if errOne != nil || errTwo != nil { - return [][]*Node{}, fmt.Errorf("error getting graph paths errOne-%v, errTwo-%v", errOne.Error(), errTwo.Error()) + return analysisOne, analysisTwo, fmt.Errorf("error getting graph paths errOne-%v, errTwo-%v", errOne.Error(), errTwo.Error()) } - - pathsOneStrings := concatenateLists(pathsOne) pathsTwoStrings := concatenateLists(pathsTwo) pathsOneMap := make(map[string][]*Node) pathsTwoMap := make(map[string][]*Node) - var analysis [][]*Node - for i := range pathsOne { nodes, err := nodeIDListToNodeList(gOne, pathsOne[i]) if err != nil { - return analysis, err + return analysisOne, analysisTwo, err } pathsOneMap[pathsOneStrings[i]] = nodes } @@ -246,9 +250,10 @@ func HighlightAnalysis(gOne, gTwo graph.Graph[string, *Node], action int) ([][]* for i := range pathsTwo { nodes, err := nodeIDListToNodeList(gTwo, pathsTwo[i]) if err != nil { - return analysis, err + return analysisOne, analysisTwo, err } pathsTwoMap[pathsTwoStrings[i]] = nodes + } switch action { @@ -257,42 +262,47 @@ func HighlightAnalysis(gOne, gTwo graph.Graph[string, *Node], action int) ([][]* for key, val := range pathsOneMap { _, ok := pathsTwoMap[key] if !ok { - //common - analysis = append(analysis, val) + //missing + analysisOne = append(analysisOne, val) } } for key, val := range pathsTwoMap { _, ok := pathsOneMap[key] if !ok { - //common - analysis = append(analysis, val) + //missing + analysisTwo = append(analysisTwo, val) } } + + //do the compare here case 1: // 1 is intersect for key := range pathsOneMap { val, ok := pathsTwoMap[key] if ok { //common - analysis = append(analysis, val) + analysisOne = append(analysisOne, val) } } + //do the compare here case 2: //2 is union for _, val := range pathsOneMap { - analysis = append(analysis, val) + analysisOne = append(analysisOne, val) } for key, val := range pathsTwoMap { _, ok := pathsOneMap[key] if !ok { //common - analysis = append(analysis, val) + analysisTwo = append(analysisTwo, val) } } + //do the compare here } - return analysis, nil + + return analysisOne, analysisTwo, nil } func MakeGraph(hasSBOM model.HasSBOMsHasSBOM, metadata, inclSoft, inclDeps, inclOccur, namespaces bool) (graph.Graph[string, *Node], error) { @@ -316,6 +326,7 @@ func MakeGraph(hasSBOM model.HasSBOMsHasSBOM, metadata, inclSoft, inclDeps, incl if inclDeps || compareAll { //add included dependencies + //TODO: sort dependencies as well here for _, dependency := range hasSBOM.IncludedDependencies { //package node //sort namespaces @@ -389,7 +400,6 @@ func MakeGraph(hasSBOM model.HasSBOMsHasSBOM, metadata, inclSoft, inclDeps, incl } AddGraphEdge(g, hashValPackage, hashValDependencyPackage, "black") - } } return g, nil @@ -407,7 +417,7 @@ func AddGraphNode(g graph.Graph[string, *Node], _ID, color string) { newNode := &Node{ ID: _ID, - color: color, + Color: color, Attributes: make(map[string]interface{}), } @@ -505,6 +515,7 @@ func concatenateLists(list [][]string) []string { } return concatenated } + func nodeIDListToNodeList(g graph.Graph[string, *Node], list []string) ([]*Node, error) { var nodeList []*Node @@ -517,3 +528,477 @@ func nodeIDListToNodeList(g graph.Graph[string, *Node], list []string) ([]*Node, } return nodeList, nil } + +func compareNodes(nodeOne, nodeTwo Node, nodeType string) ([]string, error) { + var diffs []string + var namespaceBig, namespaceSmall []model.AllPkgTreeNamespacesPackageNamespace + + var namesBig, namesSmall []model.AllPkgTreeNamespacesPackageNamespaceNamesPackageName + var versionBig, versionSmall []model.AllPkgTreeNamespacesPackageNamespaceNamesPackageNameVersionsPackageVersion + var qualifierBig, qualifierSmall []model.AllPkgTreeNamespacesPackageNamespaceNamesPackageNameVersionsPackageVersionQualifiersPackageQualifier + dataOne, ok := nodeOne.Attributes["data"] + + if !ok { + return []string{}, fmt.Errorf("could not get data attributes") + } + dataTwo, ok := nodeTwo.Attributes["data"] + + if !ok { + return []string{}, fmt.Errorf("could not get data attributes") + } + + switch nodeType { + + case "Package": + + nOne, ok := dataOne.(model.AllIsDependencyTreePackage) + if !ok { + return []string{}, fmt.Errorf("could not cast node to tree pkg") + } + + nTwo, ok := dataTwo.(model.AllIsDependencyTreePackage) + if !ok { + return []string{}, fmt.Errorf("could not cast node to tree pkg") + } + + if nodeOne.ID == nodeTwo.ID { + return []string{}, nil + } + + if nOne.Type != nTwo.Type { + diffs = append(diffs, "Type: "+nOne.Type+" != "+nTwo.Type) + + } + sort.Sort(packageNameSpaces(nOne.Namespaces)) + sort.Sort(packageNameSpaces(nTwo.Namespaces)) + + if len(nTwo.Namespaces) > len(nOne.Namespaces) { + namespaceBig = nTwo.Namespaces + namespaceSmall = nOne.Namespaces + } else if len(nTwo.Namespaces) < len(nOne.Namespaces) { + namespaceBig = nOne.Namespaces + namespaceSmall = nTwo.Namespaces + } else { + namespaceBig = nTwo.Namespaces + namespaceSmall = nOne.Namespaces + } + + // Compare namespaces + for i, namespace1 := range namespaceBig { + if i >= len(namespaceSmall) { + diffs = append(diffs, fmt.Sprintf("Namespace %s not present", namespace1.Namespace)) + continue + } + namespace2 := namespaceSmall[i] + + sort.Sort(packageNameSpacesNames(namespace1.Names)) + sort.Sort(packageNameSpacesNames(namespace2.Names)) + + // Compare namespace fields + if namespace1.Namespace != namespace2.Namespace { + diffs = append(diffs, fmt.Sprintf("Namespace %s != %s", namespace1.Namespace, namespace2.Namespace)) + } + + if len(namespace1.Names) > len(namespace2.Names) { + namesBig = namespace1.Names + namesSmall = namespace2.Names + } else if len(namespace1.Names) < len(namespace2.Names) { + namesBig = namespace2.Names + namesSmall = namespace1.Names + } else { + namesBig = namespace1.Names + namesSmall = namespace2.Names + } + + // Compare names + for j, name1 := range namesBig { + + if j >= len(namesSmall) { + diffs = append(diffs, fmt.Sprintf("Name %s not present in namespace %s", name1.Name, namespace1.Namespace)) + continue + } + name2 := namesSmall[j] + + sort.Sort(packageNameSpacesNamesVersions(name1.Versions)) + sort.Sort(packageNameSpacesNamesVersions(name2.Versions)) + + // Compare name fields + if name1.Name != name2.Name { + diffs = append(diffs, fmt.Sprintf("Name %s != %s in Namespace %s", name1.Name, name2.Name, namespace1.Namespace)) + + } + + if len(name1.Versions) > len(name2.Versions) { + versionBig = name1.Versions + versionSmall = name2.Versions + } else if len(name1.Versions) < len(name2.Versions) { + versionBig = name2.Versions + versionSmall = name1.Versions + } else { + versionBig = name1.Versions + versionSmall = name2.Versions + } + + // Compare versions + for k, version1 := range versionBig { + if k >= len(versionSmall) { + diffs = append(diffs, fmt.Sprintf("Version %s not present for name %s in namespace %s,", version1.Version, name1.Name, namespace1.Namespace)) + continue + + } + + version2 := versionSmall[k] + sort.Sort(packageNameSpacesNamesVersionsQualifiers(version1.Qualifiers)) + sort.Sort(packageNameSpacesNamesVersionsQualifiers(version2.Qualifiers)) + + if version1.Version != version2.Version { + diffs = append(diffs, fmt.Sprintf("Version %s != %s for name %s in namespace %s", version1.Version, version2.Version, name1.Name, namespace1.Namespace)) + } + + if version1.Subpath != version2.Subpath { + diffs = append(diffs, fmt.Sprintf("Subpath %s != %s for version %s for name %s in namespace %s", version1.Subpath, version2.Subpath, version1.Version, name1.Name, namespace1.Namespace)) + } + + if len(version1.Qualifiers) > len(version2.Qualifiers) { + qualifierBig = version1.Qualifiers + qualifierSmall = version2.Qualifiers + } else if len(version1.Qualifiers) < len(version2.Qualifiers) { + qualifierBig = version2.Qualifiers + qualifierSmall = version1.Qualifiers + } else { + qualifierBig = version1.Qualifiers + qualifierSmall = version2.Qualifiers + } + + for l, qualifier1 := range qualifierBig { + if l >= len(qualifierSmall) { + diffs = append(diffs, fmt.Sprintf("Qualifier %s:%s not present for version %s in name %s in namespace %s,", qualifier1.Key, qualifier1.Value, version1.Version, name1.Name, namespace1.Namespace)) + continue + } + + qualifier2 := qualifierSmall[l] + if qualifier2.Key != qualifier1.Key || qualifier1.Value != qualifier2.Value { + + diffs = append(diffs, fmt.Sprintf("Qualifier unequal for version %s in name %s in namespace %s: %s:%s | %s:%s", version1.Version, name1.Name, namespace1.Namespace, qualifier1.Key, qualifier1.Value, qualifier2.Key, qualifier2.Value)) + + } + } + } + } + } + case "DependencyPackage": + nOne, ok := dataOne.(model.AllIsDependencyTreeDependencyPackage) + if !ok { + return []string{}, fmt.Errorf("could not case node to tree dePkg") + } + + nTwo, ok := dataTwo.(model.AllIsDependencyTreeDependencyPackage) + if !ok { + return []string{}, fmt.Errorf("could not case node to tree depPkg") + } + + if nodeOne.ID == nodeTwo.ID { + + return []string{}, nil + } + + if nOne.Type != nTwo.Type { + diffs = append(diffs, "Type: "+nOne.Type+" != "+nTwo.Type) + } + sort.Sort(packageNameSpaces(nOne.Namespaces)) + sort.Sort(packageNameSpaces(nTwo.Namespaces)) + + if len(nTwo.Namespaces) > len(nOne.Namespaces) { + namespaceBig = nTwo.Namespaces + namespaceSmall = nOne.Namespaces + } else if len(nTwo.Namespaces) < len(nOne.Namespaces) { + namespaceBig = nOne.Namespaces + namespaceSmall = nTwo.Namespaces + } else { + namespaceBig = nTwo.Namespaces + namespaceSmall = nOne.Namespaces + } + + // Compare namespaces + for i, namespace1 := range namespaceBig { + if i >= len(namespaceSmall) { + diffs = append(diffs, fmt.Sprintf("Namespace %s not present", namespace1.Namespace)) + continue + } + namespace2 := namespaceSmall[i] + + sort.Sort(packageNameSpacesNames(namespace1.Names)) + sort.Sort(packageNameSpacesNames(namespace2.Names)) + + // Compare namespace fields + if namespace1.Namespace != namespace2.Namespace { + diffs = append(diffs, fmt.Sprintf("Namespace %s != %s", namespace1.Namespace, namespace2.Namespace)) + + } + + if len(namespace1.Names) > len(namespace2.Names) { + namesBig = namespace1.Names + namesSmall = namespace2.Names + } else if len(namespace1.Names) < len(namespace2.Names) { + namesBig = namespace2.Names + namesSmall = namespace1.Names + } else { + namesBig = namespace1.Names + namesSmall = namespace2.Names + } + + // Compare names + for j, name1 := range namesBig { + + if j >= len(namesSmall) { + diffs = append(diffs, fmt.Sprintf("Name %s not present in namespace %s", name1.Name, namespace1.Namespace)) + continue + } + name2 := namesSmall[j] + + sort.Sort(packageNameSpacesNamesVersions(name1.Versions)) + sort.Sort(packageNameSpacesNamesVersions(name2.Versions)) + + // Compare name fields + if name1.Name != name2.Name { + diffs = append(diffs, fmt.Sprintf("Name %s != %s in Namespace %s", name1.Name, name2.Name, namespace1.Namespace)) + + } + + if len(name1.Versions) > len(name2.Versions) { + versionBig = name1.Versions + versionSmall = name2.Versions + } else if len(name1.Versions) < len(name2.Versions) { + versionBig = name2.Versions + versionSmall = name1.Versions + } else { + versionBig = name1.Versions + versionSmall = name2.Versions + } + + // Compare versions + for k, version1 := range versionBig { + if k >= len(versionSmall) { + diffs = append(diffs, fmt.Sprintf("Version %s not present for name %s in namespace %s,", version1.Version, name1.Name, namespace1.Namespace)) + continue + } + + version2 := versionSmall[k] + sort.Sort(packageNameSpacesNamesVersionsQualifiers(version1.Qualifiers)) + sort.Sort(packageNameSpacesNamesVersionsQualifiers(version2.Qualifiers)) + + if version1.Version != version2.Version { + diffs = append(diffs, fmt.Sprintf("Version %s != %s for name %s in namespace %s", version1.Version, version2.Version, name1.Name, namespace1.Namespace)) + + } + + if version1.Subpath != version2.Subpath { + diffs = append(diffs, fmt.Sprintf("Subpath %s != %s for version %s for name %s in namespace %s,", version1.Subpath, version2.Subpath, version1.Version, name1.Name, namespace1.Namespace)) + + } + + if len(version1.Qualifiers) > len(version2.Qualifiers) { + qualifierBig = version1.Qualifiers + qualifierSmall = version2.Qualifiers + } else if len(version1.Qualifiers) < len(version2.Qualifiers) { + qualifierBig = version2.Qualifiers + qualifierSmall = version1.Qualifiers + } else { + qualifierBig = version1.Qualifiers + qualifierSmall = version2.Qualifiers + } + + for l, qualifier1 := range qualifierBig { + if l >= len(qualifierSmall) { + diffs = append(diffs, fmt.Sprintf("Qualifier %s:%s not present for version %s in name %s in namespace %s,", qualifier1.Key, qualifier1.Value, version1.Version, name1.Name, namespace1.Namespace)) + continue + } + qualifier2 := qualifierSmall[l] + if qualifier2.Key != qualifier1.Key || qualifier1.Value != qualifier2.Value { + + diffs = append(diffs, fmt.Sprintf("Qualifier unequal for version %s in name %s in namespace %s: %s:%s | %s:%s", version1.Version, name1.Name, namespace1.Namespace, qualifier1.Key, qualifier1.Value, qualifier2.Key, qualifier2.Value)) + } + } + } + } + } + + } + return diffs, nil + +} + +func CompareTwoPaths(analysisListOne, analysisListTwo []*Node) ([][]string, int, error) { + + var longerPath, shorterPath []*Node + var pathDiff [][]string + var diffCount int + + if len(analysisListOne) > len(analysisListTwo) { + longerPath = analysisListOne + shorterPath = analysisListTwo + } else if len(analysisListOne) < len(analysisListTwo) { + longerPath = analysisListTwo + shorterPath = analysisListOne + } else { + longerPath = analysisListOne + shorterPath = analysisListTwo + } + + for i, node := range longerPath { + nodeType, ok := node.Attributes["nodeType"].(string) + if !ok { + return pathDiff, 0, fmt.Errorf("cannot case nodeType to string") + } + if i >= len(shorterPath) { + dumnode := &Node{Attributes: make(map[string]interface{})} + if nodeType == "Package" { + dumnode.Attributes["data"] = model.AllIsDependencyTreePackage{} + } else if nodeType == "DependencyPackage" { + dumnode.Attributes["data"] = model.AllIsDependencyTreeDependencyPackage{} + } + + diff, err := compareNodes(*node, *dumnode, nodeType) + if err != nil { + return pathDiff, 0, fmt.Errorf(err.Error()) + } + + pathDiff = append(pathDiff, diff) + diffCount += len(diff) + + } else { + diff, err := compareNodes(*node, *shorterPath[i], nodeType) + if err != nil { + return pathDiff, 0, fmt.Errorf(err.Error()) + } + pathDiff = append(pathDiff, diff) + diffCount += len(diff) + } + } + + return pathDiff, diffCount, nil + +} + +func CompareAllPaths(listOne, listTwo [][]*Node) ([]DiffedPath, error) { + if len(listTwo) != len(listOne) { + //do something? + } + + // var small, big [][]*Node + // if len(listOne) > len(listTwo) { + // small= listTwo + // big = listOne + // } else if len(listTwo) > len(listOne) { + // small= listOne + // big = listTwo + // } else { + // small= listTwo + // big = listOne + // } + + var results []DiffedPath + for _, pathOne := range listOne { + + var diff DiffedPath + diff.PathOne = pathOne + min := math.MaxInt64 + for _, pathTwo := range listTwo { + diffs, diffNum, err := CompareTwoPaths(pathOne, pathTwo) + if err != nil { + return results, fmt.Errorf(err.Error()) + } + if diffNum < min { + diff.PathTwo = pathTwo + min = diffNum + diff.Diffs = diffs + } + } + results = append(results, diff) + } + return results, nil +} + +func GetNodeString(option int, node interface{}) (string, error) { + switch option { + + case 1: + pkg, ok := node.(model.AllIsDependencyTreePackage) + if !ok { + return "", fmt.Errorf("could not case node to tree Pkg") + } + + sort.Sort(packageNameSpaces(pkg.Namespaces)) + message := "Type:" + pkg.Type + "\n" + for _, namespace := range pkg.Namespaces { + message += "Namespace: " + namespace.Namespace + "\n" + + for _, name := range namespace.Names { + message += "\t" + message += "Name: " + name.Name + message += "\n" + + for _, version := range name.Versions { + message += "\t\t" + message += "Version: " + version.Version + "\n" + message += "\t\t" + message += "Subpath: " + version.Subpath + "\n" + message += "\t\tQualifiers: {\n" + + for _, outlier := range version.Qualifiers { + message += "\t\t\t" + message += outlier.Key + ": " + outlier.Value + "\n" + } + message += "\t\t}\n" + } + } + message += "\n" + } + return message, nil + case 2: + depPkg, ok := node.(model.AllIsDependencyTreeDependencyPackage) + if !ok { + return "", fmt.Errorf("could not case node to tree depPkg") + } + + message := "Type:" + CheckEmpty(depPkg.Type) + "\n" + for _, namespace := range depPkg.Namespaces { + message += "Namespace: " + CheckEmpty(namespace.Namespace) + "\n" + + for _, name := range namespace.Names { + message += "\t" + message += "Name: " + CheckEmpty(name.Name) + message += "\n" + + for _, version := range name.Versions { + message += "\t\t" + message += "Version: " + CheckEmpty(version.Version) + "\n" + message += "\t\t" + message += "Subpath: " + CheckEmpty(version.Subpath) + "\n" + message += "\t\tQualifiers: {\n" + + for _, outlier := range version.Qualifiers { + message += "\t\t\t" + message += CheckEmpty(outlier.Key) + ": " + CheckEmpty(outlier.Value) + "\n" + } + message += "\t\t}\n" + } + } + message += "\n" + } + return message, nil + + } + + return "", nil +} + +func CheckEmpty(value string) string { + if len(value) > 20 { + return value[:20] + "..." + } + if value == "" { + return "\"\"" + } + return value +} diff --git a/pkg/analyzer/analyzer_test.go b/pkg/analyzer/analyzer_test.go index 5e643f1a6c8..4d30b2f330d 100644 --- a/pkg/analyzer/analyzer_test.go +++ b/pkg/analyzer/analyzer_test.go @@ -67,7 +67,7 @@ func TestHighlightAnalysis(t *testing.T) { t.Errorf("Error checking edge equivalence %v", err.Error()) } - _, err = analyzer.HighlightAnalysis(graphs[0], graphs[1], 0) + _, _,err = analyzer.HighlightAnalysis(graphs[0], graphs[1], 0) if err == nil { t.Errorf("Error highlighting diff %v", err.Error()) }