Skip to content

Commit

Permalink
Move Java Dep Tree content from Core (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
attiasas authored Mar 21, 2024
1 parent e6de042 commit 6f6d5d3
Show file tree
Hide file tree
Showing 13 changed files with 1,381 additions and 1 deletion.
29 changes: 29 additions & 0 deletions .github/workflows/embedded-jar-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This test verifies that gradle-dep-tree.jar and maven-dep-tree.jar are kept up-to-date with the version specified in buildscripts/download-jars.js.
# It accomplishes this by downloading the JARs and executing a "git diff" command.
# In case there are any differences detected, the test will result in failure.
name: Embedded Jars Tests
on:
push:
branches:
- '**'
tags-ignore:
- '**'
pull_request:
jobs:
test:
runs-on: ubuntu-latest
env:
GOPROXY: direct
steps:
- uses: actions/checkout@v4

- name: Download JARs
run: buildscripts/download-jars.sh

- name: Check Diff
run: git diff --exit-code

- name: Log if Failure
run: echo "::warning::Please run ./buildscripts/download-jars to use compatible Maven and Gradle dependency tree JARs."
if: ${{ failure() }}

15 changes: 15 additions & 0 deletions buildscripts/download-jars.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh

# Please use this script to download the JAR files for maven-dep-tree and gradle-dep-tree into the directory utils/java/.
# These JARs allow us to build Maven and Gradle dependency trees efficiently and without compilation.
# Learn more about them here:
# https://github.com/jfrog/gradle-dep-tree
# https://github.com/jfrog/maven-dep-tree

# Once you have updated the versions mentioned below, please execute this script from the root directory of the jfrog-cli-core to ensure the JAR files are updated.
GRADLE_DEP_TREE_VERSION="3.0.2"
# Changing this version also requires a change in mavenDepTreeVersion within utils/java/mvn.go.
MAVEN_DEP_TREE_VERSION="1.1.0"

curl -fL https://releases.jfrog.io/artifactory/oss-release-local/com/jfrog/gradle-dep-tree/${GRADLE_DEP_TREE_VERSION}/gradle-dep-tree-${GRADLE_DEP_TREE_VERSION}.jar -o commands/audit/sca/java/resources/gradle-dep-tree.jar
curl -fL https://releases.jfrog.io/artifactory/oss-release-local/com/jfrog/maven-dep-tree/${MAVEN_DEP_TREE_VERSION}/maven-dep-tree-${MAVEN_DEP_TREE_VERSION}.jar -o commands/audit/sca/java/resources/maven-dep-tree.jar
124 changes: 124 additions & 0 deletions commands/audit/sca/java/deptreemanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package java

import (
"encoding/json"
"os"
"strings"

"github.com/jfrog/jfrog-cli-core/v2/utils/config"
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
"github.com/jfrog/jfrog-cli-core/v2/utils/xray"
"github.com/jfrog/jfrog-client-go/utils/errorutils"
xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils"
)

const (
GavPackageTypeIdentifier = "gav://"
)

func BuildDependencyTree(depTreeParams DepTreeParams, tech coreutils.Technology) ([]*xrayUtils.GraphNode, map[string][]string, error) {
if tech == coreutils.Maven {
return buildMavenDependencyTree(&depTreeParams)
}
return buildGradleDependencyTree(&depTreeParams)
}

type DepTreeParams struct {
UseWrapper bool
Server *config.ServerDetails
DepsRepo string
IsMavenDepTreeInstalled bool
IsCurationCmd bool
CurationCacheFolder string
}

type DepTreeManager struct {
server *config.ServerDetails
depsRepo string
useWrapper bool
}

func NewDepTreeManager(params *DepTreeParams) DepTreeManager {
return DepTreeManager{useWrapper: params.UseWrapper, depsRepo: params.DepsRepo, server: params.Server}
}

// The structure of a dependency tree of a module in a Gradle/Maven project, as created by the gradle-dep-tree and maven-dep-tree plugins.
type moduleDepTree struct {
Root string `json:"root"`
Nodes map[string]xray.DepTreeNode `json:"nodes"`
}

// Reads the output files of the gradle-dep-tree and maven-dep-tree plugins and returns them as a slice of GraphNodes.
// It takes the output of the plugin's run (which is a byte representation of a list of paths of the output files, separated by newlines) as input.
func getGraphFromDepTree(outputFilePaths string) (depsGraph []*xrayUtils.GraphNode, uniqueDepsMap map[string][]string, err error) {
modules, err := parseDepTreeFiles(outputFilePaths)
if err != nil {
return
}
uniqueDepsMap = map[string][]string{}
for _, module := range modules {
moduleTree, moduleUniqueDeps := GetModuleTreeAndDependencies(module)
depsGraph = append(depsGraph, moduleTree)
for depToAdd, depTypes := range moduleUniqueDeps {
uniqueDepsMap[depToAdd] = depTypes
}
}
return
}

// Returns a dependency tree and a flat list of the module's dependencies for the given module
func GetModuleTreeAndDependencies(module *moduleDepTree) (*xrayUtils.GraphNode, map[string][]string) {
moduleTreeMap := make(map[string]xray.DepTreeNode)
moduleDeps := module.Nodes
for depName, dependency := range moduleDeps {
dependencyId := GavPackageTypeIdentifier + depName
var childrenList []string
for _, childName := range dependency.Children {
childId := GavPackageTypeIdentifier + childName
childrenList = append(childrenList, childId)
}
moduleTreeMap[dependencyId] = xray.DepTreeNode{
Types: dependency.Types,
Children: childrenList,
}
}
return xray.BuildXrayDependencyTree(moduleTreeMap, GavPackageTypeIdentifier+module.Root)
}

func parseDepTreeFiles(jsonFilePaths string) ([]*moduleDepTree, error) {
outputFilePaths := strings.Split(strings.TrimSpace(jsonFilePaths), "\n")
var modules []*moduleDepTree
for _, path := range outputFilePaths {
results, err := parseDepTreeFile(path)
if err != nil {
return nil, err
}
modules = append(modules, results)
}
return modules, nil
}

func parseDepTreeFile(path string) (results *moduleDepTree, err error) {
depTreeJson, err := os.ReadFile(strings.TrimSpace(path))
if errorutils.CheckError(err) != nil {
return
}
results = &moduleDepTree{}
err = errorutils.CheckError(json.Unmarshal(depTreeJson, &results))
return
}

func getArtifactoryAuthFromServer(server *config.ServerDetails) (string, string, error) {
username, password, err := server.GetAuthenticationCredentials()
if err != nil {
return "", "", err
}
if username == "" {
return "", "", errorutils.CheckErrorf("a username is required for authenticating with Artifactory")
}
return username, password, nil
}

func (dtm *DepTreeManager) GetDepsRepo() string {
return dtm.depsRepo
}
66 changes: 66 additions & 0 deletions commands/audit/sca/java/deptreemanager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package java

import (
"os"
"path/filepath"
"reflect"
"strings"
"testing"

"github.com/jfrog/jfrog-cli-core/v2/utils/tests"
"github.com/stretchr/testify/assert"
)

func TestGetGradleGraphFromDepTree(t *testing.T) {
// Create and change directory to test workspace
tempDirPath, cleanUp := tests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "gradle", "gradle"))
defer cleanUp()
assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700))
expectedTree := map[string]map[string]string{
"org.jfrog.example.gradle:shared:1.0": {},
"org.jfrog.example.gradle:" + filepath.Base(tempDirPath) + ":1.0": {},
"org.jfrog.example.gradle:services:1.0": {},
"org.jfrog.example.gradle:webservice:1.0": {
"junit:junit:4.11": "",
"commons-io:commons-io:1.2": "",
"org.apache.wicket:wicket:1.3.7": "",
"org.jfrog.example.gradle:shared:1.0": "",
"org.jfrog.example.gradle:api:1.0": "",
"commons-lang:commons-lang:2.4": "",
"commons-collections:commons-collections:3.2": "",
},
"org.jfrog.example.gradle:api:1.0": {
"org.apache.wicket:wicket:1.3.7": "",
"org.jfrog.example.gradle:shared:1.0": "",
"commons-lang:commons-lang:2.4": "",
},
}
expectedUniqueDeps := []string{
"junit:junit:4.11",
"org.jfrog.example.gradle:webservice:1.0",
"org.jfrog.example.gradle:api:1.0",
"org.jfrog.example.gradle:" + filepath.Base(tempDirPath) + ":1.0",
"commons-io:commons-io:1.2",
"org.apache.wicket:wicket:1.3.7",
"org.jfrog.example.gradle:shared:1.0",
"org.jfrog.example.gradle:api:1.0",
"commons-collections:commons-collections:3.2",
"commons-lang:commons-lang:2.4",
"org.hamcrest:hamcrest-core:1.3",
"org.slf4j:slf4j-api:1.4.2",
}

manager := &gradleDepTreeManager{DepTreeManager{}}
outputFileContent, err := manager.runGradleDepTree()
assert.NoError(t, err)
depTree, uniqueDeps, err := getGraphFromDepTree(outputFileContent)
assert.NoError(t, err)
reflect.DeepEqual(uniqueDeps, expectedUniqueDeps)

for _, dependency := range depTree {
dependencyId := strings.TrimPrefix(dependency.Id, GavPackageTypeIdentifier)
depChild, exists := expectedTree[dependencyId]
assert.True(t, exists)
assert.Equal(t, len(depChild), len(dependency.Nodes))
}
}
Loading

0 comments on commit 6f6d5d3

Please sign in to comment.