-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix malformed External ID generation, obtuse warning message and unmatched version resolution in Yarn detector #1290
base: master
Are you sure you want to change the base?
Conversation
@@ -46,7 +46,7 @@ public Extraction extract(File projectDir, File yarnLockFile, File rootPackageJs | |||
YarnLock yarnLock = readYarnLock(yarnLockFile); | |||
|
|||
YarnResult yarnResult; | |||
if (yarnLockOptions.getMonorepoMode()) { | |||
if (yarnLockOptions.getYarnIgnoreAllWorkspacesMode()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed for consistency.
@@ -8,17 +8,17 @@ public class YarnLockOptions { | |||
private final EnumListFilter<YarnDependencyType> yarnDependencyTypeFilter; | |||
private final List<String> excludedWorkspaceNamePatterns; | |||
private final List<String> includedWorkspaceNamePatterns; | |||
private final Boolean monorepoMode; | |||
private final Boolean yarnIgnoreAllWorkspacesMode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed for consistency.
|
||
public YarnLockOptions( | ||
EnumListFilter<YarnDependencyType> yarnDependencyTypeFilter, | ||
List<String> excludedWorkspaceNamePatterns, | ||
List<String> includedWorkspaceNamePatterns, | ||
Boolean monorepoMode) { | ||
Boolean yarnIgnoreAllWorkspacesMode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed for consistency.
this.yarnDependencyTypeFilter = yarnDependencyTypeFilter; | ||
this.excludedWorkspaceNamePatterns = excludedWorkspaceNamePatterns; | ||
this.includedWorkspaceNamePatterns = includedWorkspaceNamePatterns; | ||
this.monorepoMode = monorepoMode; | ||
this.yarnIgnoreAllWorkspacesMode = yarnIgnoreAllWorkspacesMode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed for consistency.
@@ -33,7 +33,7 @@ public List<String> getIncludedWorkspaceNamePatterns() { | |||
return includedWorkspaceNamePatterns; | |||
} | |||
|
|||
public Boolean getMonorepoMode() { | |||
return monorepoMode; | |||
public Boolean getYarnIgnoreAllWorkspacesMode() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed for consistency.
@@ -39,7 +37,7 @@ public class YarnTransformer { | |||
private final Logger logger = LoggerFactory.getLogger(this.getClass()); | |||
public static final String STRING_ID_NAME_VERSION_SEPARATOR = "@"; | |||
private final ExternalIdFactory externalIdFactory; | |||
private final Set<LazyId> unMatchedDependencies = new HashSet<>(); | |||
private final Map<LazyId, Optional<NameVersion>> unMatchedDependencies = new HashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used a map to retain NameVersion corresponding to LazyID to avoid lossy reconversion later.
@@ -50,10 +48,10 @@ public YarnTransformer(ExternalIdFactory externalIdFactory, EnumListFilter<YarnD | |||
public List<CodeLocation> generateCodeLocations(YarnLockResult yarnLockResult, List<NameVersion> externalDependencies) | |||
throws MissingExternalIdException { | |||
List<CodeLocation> codeLocations = new LinkedList<>(); | |||
logger.debug("Adding root dependencies for project: {}:{}", yarnLockResult.getRootPackageJson().getNameString(), yarnLockResult.getRootPackageJson().getVersionString()); | |||
logger.debug("Adding root dependencies for project: {}:{}, externalDeps: {}", yarnLockResult.getRootPackageJson().getNameString(), yarnLockResult.getRootPackageJson().getVersionString(), externalDependencies.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional data in debug logger on external dependencies available.
LazyBuilderMissingExternalIdHandler lazyBuilderHandler = getLazyBuilderHandler(externalDependencies); | ||
ExternalIdDependencyGraphBuilder rootGraphBuilder = new ExternalIdDependencyGraphBuilder(); | ||
addRootDependenciesForProjectOrWorkspace(yarnLockResult, yarnLockResult.getRootPackageJson(), rootGraphBuilder); | ||
addRootDependenciesForProject(yarnLockResult, yarnLockResult.getRootPackageJson(), rootGraphBuilder); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method addRootDependenciesForProject(...) belongs to 'ignore all workspaces' workflow.
@@ -83,29 +81,33 @@ private DependencyGraph buildGraphForProjectOrWorkspace( | |||
List<NameVersion> externalDependencies | |||
) throws MissingExternalIdException { | |||
LazyExternalIdDependencyGraphBuilder graphBuilder = new LazyExternalIdDependencyGraphBuilder(); | |||
addRootNodesToGraph(graphBuilder, projectOrWorkspacePackageJson, yarnLockResult.getWorkspaceData()); | |||
addRootDependenciesForProjectOrWorkspace(graphBuilder, projectOrWorkspacePackageJson, yarnLockResult.getWorkspaceData()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored code by removing need for addRootNodesToGraph(...)
for (YarnLockDependency dependency : entry.getDependencies()) { | ||
if (!isWorkspace(yarnLockResult.getWorkspaceData(), dependency)) { | ||
LazyId stringDependencyId = generateComponentDependencyId(dependency.getName(), dependency.getVersion()); | ||
|
||
if (yarnDependencyTypeFilter.shouldInclude(YarnDependencyType.NON_PRODUCTION) || !dependency.isOptional()) { | ||
yarnDependencies.put(stringDependencyId, new NameVersion(dependency.getName(), entry.getVersion())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retained NameVersion pairs for LazyId.
unMatchedDependencies.add(dependencyId); | ||
Optional<NameVersion> nameVersionOptional; | ||
if (!unMatchedDependencies.containsKey(dependencyId)) { | ||
String dependencyIdClean = dependencyId.toString().replace("\\/","/"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed escape character from string of dependency ID before use to reconvert to NameVersion.
if (!unMatchedDependencies.containsKey(dependencyId)) { | ||
String dependencyIdClean = dependencyId.toString().replace("\\/","/"); | ||
nameVersionOptional = getNameVersion(dependencyIdClean); | ||
logger.warn("No standard NPM package identification details are available for '{}' from the yarn.lock file", dependencyIdClean); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improved warning message about lack of standard NPM details to resolve remaining unmatched components.
logger.warn("No standard NPM package identification details are available for '{}' from the yarn.lock file", dependencyIdClean); | ||
unMatchedDependencies.put(dependencyId, nameVersionOptional); | ||
} else { | ||
nameVersionOptional = unMatchedDependencies.get(dependencyId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If matched, then use the retained NameVersion from map.
} | ||
lazilyGeneratedExternalId = generateComponentExternalId(dependencyId); | ||
return lazilyGeneratedExternalId; | ||
return generateComponentExternalId(nameVersionOptional.get().getName(), nameVersionOptional.get().getVersion()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExternalID generated from NameVersion avoids escape character issue.
Optional<YarnWorkspace> dependencyWorkspace = workspaceData.lookup(rootDependency.getKey(), rootDependency.getValue()); | ||
if (dependencyWorkspace.isPresent()) { | ||
logger.trace("Omitting dependency {}/{} because it's a workspace", rootDependency.getKey(), rootDependency.getValue()); | ||
private LazyBuilderMissingExternalIdHandler getLazyBuilderHandler(List<NameVersion> externalDependencies, Map<LazyId, NameVersion> yarnLockDependencies) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate method for default Yarn detector.
@@ -210,42 +212,68 @@ private LazyBuilderMissingExternalIdHandler getLazyBuilderHandler(List<NameVersi | |||
if (externalId.isPresent()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate method for ignore-all-workspaces Yarn detector workflow.
}; | ||
} | ||
|
||
private Optional<NameVersion> getNameVersion(String dependencyIdString) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converts given dependency ID string to NameVersion pair object.
} | ||
|
||
private void addRootNodesToGraph(LazyExternalIdDependencyGraphBuilder graphBuilder, NullSafePackageJson projectOrWorkspacePackageJson, YarnWorkspaces workspaceData) { | ||
populateGraphWithRootDependencies(graphBuilder, projectOrWorkspacePackageJson, workspaceData); | ||
private void addRootDependenciesForProject(YarnLockResult yarnLockResult, NullSafePackageJson projectPackageJson, LazyExternalIdDependencyGraphBuilder graphBuilder) throws MissingExternalIdException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method refactored.
} | ||
|
||
private void populateGraphWithRootDependencies(LazyExternalIdDependencyGraphBuilder graphBuilder, NullSafePackageJson rootPackageJson, YarnWorkspaces workspaceData) { | ||
private void addRootDependenciesForProjectOrWorkspace(LazyExternalIdDependencyGraphBuilder graphBuilder, NullSafePackageJson rootPackageJson, YarnWorkspaces workspaceData) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method refactored.
@@ -266,14 +294,10 @@ private void addRootDependenciesToGraph(LazyExternalIdDependencyGraphBuilder gra | |||
} | |||
|
|||
private LazyId generateComponentDependencyId(String name, String version) { | |||
return LazyId.fromString(name + STRING_ID_NAME_VERSION_SEPARATOR + version); | |||
return LazyId.fromNameAndVersion(name, version); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switch to NameVersion method to generate external ID.
@@ -1715,7 +1715,7 @@ private DetectProperties() { | |||
.setGroups(DetectGroup.PATHS, DetectGroup.GLOBAL) | |||
.build(); | |||
|
|||
public static final BooleanProperty DETECT_YARN_MONOREPO_MODE = | |||
public static final BooleanProperty DETECT_YARN_IGNORE_ALL_WORKSPACES_MODE = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed for consistency.
@@ -284,12 +284,12 @@ public SbtDetectableOptions createSbtDetectableOptions() { | |||
|
|||
public YarnLockOptions createYarnLockOptions() { | |||
Set<YarnDependencyType> excludedDependencyTypes = detectConfiguration.getValue(DetectProperties.DETECT_YARN_DEPENDENCY_TYPES_EXCLUDED).representedValueSet(); | |||
Boolean monorepoMode = detectConfiguration.getValue(DetectProperties.DETECT_YARN_MONOREPO_MODE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed for consistency.
EnumListFilter<YarnDependencyType> yarnDependencyTypeFilter = EnumListFilter.fromExcluded(excludedDependencyTypes); | ||
|
||
List<String> excludedWorkspaces = detectConfiguration.getValue(DetectProperties.DETECT_YARN_EXCLUDED_WORKSPACES); | ||
List<String> includedWorkspaces = detectConfiguration.getValue(DetectProperties.DETECT_YARN_INCLUDED_WORKSPACES); | ||
return new YarnLockOptions(yarnDependencyTypeFilter, excludedWorkspaces, includedWorkspaces, monorepoMode); | ||
return new YarnLockOptions(yarnDependencyTypeFilter, excludedWorkspaces, includedWorkspaces, yarnIgnoreAllWorkspacesMode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed for consistency.
...ble/src/main/java/com/blackduck/integration/detectable/detectables/yarn/YarnTransformer.java
Outdated
Show resolved
Hide resolved
Replaced string constants with existing enum references.
@andrian-sevastyanov @devmehtabd Please review the last committed changes. |
@Override | ||
public int compareTo(Version o) { | ||
if (this.major == o.major | ||
&& this.minor == o.minor) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we missing the patch comparison here or is this for some reason purposeful?
Description
The root cause for the issue is JSON: why are forward slashes escaped?
This issue is surfaced by the dependency on Apache Commons : ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE)
This returns a string with escaped forward slash in the JSON format. This is used in Stringable class of integration-common library which in turn is extended by ExternalID class in integration-bdio library.
When external ID is generated from name and version of entries in yarn.lock file, we use a factory method that generates a String. This same string is recorded and referred to again for potential matches as we build the dependency graph. When we do, we do not match because the String with escaped forward slash does not return the same component name for comparison.
To fix this, we have addressed it in the Yarn Detect code to remove the escape character for forward slash whenever we generate the external ID as String for comparison.
While the malformed ID is fixed, the warning message itself was not intuitive and is confusing. It is now reworded for better clarity to mean that the entries in the yarn.lock file that have not so far been resolved through package.json files, could not be resolved with any standard NPM packages either.
Additionally, the default workflow now provides advanced version resolution for unmatched components, if any, using the root yarn lock file. This addresses the requirement of IDETECT-4342 indirectly by resolving the warning messages of unresolved versions when default Yarn detector is used. Yarn 4 is support is unaffected.
Github Issues
IDETECT-4447, IDETECT-4314, IDETECT-4342