Skip to content

Commit

Permalink
chore: resolve exosystem based on package type
Browse files Browse the repository at this point in the history
Package ecosystems were previously determined based on file extension.
This would not scale as we expand the plugin with further ecosystems,
e.g. both `pypi` and `cocoapods` use the `.tar.gz` extension.

This commit switches to `packageType` field provided by Artifactory's
`RepositoryConfiguration` model in order to determine the ecosystem
without relying on file extensions.
  • Loading branch information
jacek-rzrz committed Nov 18, 2024
1 parent 60eb4d6 commit a77f112
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 286 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
import io.snyk.plugins.artifactory.configuration.UserAgent;
import io.snyk.plugins.artifactory.configuration.properties.ArtifactProperty;
import io.snyk.plugins.artifactory.configuration.ConfigurationModule;
import io.snyk.plugins.artifactory.ecosystem.Ecosystem;
import io.snyk.plugins.artifactory.exception.CannotScanException;
import io.snyk.plugins.artifactory.exception.SnykAPIFailureException;
import io.snyk.plugins.artifactory.exception.SnykRuntimeException;
import io.snyk.plugins.artifactory.scanner.ScannerModule;
import io.snyk.plugins.artifactory.scanner.*;
import io.snyk.sdk.SnykConfig;
import io.snyk.sdk.api.v1.SnykClient;
import io.snyk.sdk.api.v1.SnykResult;
Expand Down Expand Up @@ -63,7 +64,8 @@ public SnykPlugin(@Nonnull Repositories repositories, File pluginsDirectory) {
final SnykClient snykClient = createSnykClient(configurationModule, pluginVersion);

auditModule = new AuditModule();
scannerModule = new ScannerModule(configurationModule, repositories, snykClient);
ScannerResolver scannerResolver = ScannerResolver.setup(configurationModule, snykClient);
scannerModule = new ScannerModule(configurationModule, repositories, scannerResolver);

LOG.info("Plugin version: {}", pluginVersion);
} catch (Exception ex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package io.snyk.plugins.artifactory.scanner;
package io.snyk.plugins.artifactory.ecosystem;

import io.snyk.plugins.artifactory.configuration.PluginConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;

public enum Ecosystem {


MAVEN(PluginConfiguration.SCANNER_PACKAGE_TYPE_MAVEN),
NPM(PluginConfiguration.SCANNER_PACKAGE_TYPE_NPM),
PYPI(PluginConfiguration.SCANNER_PACKAGE_TYPE_PYPI),
;

private static final Logger LOG = LoggerFactory.getLogger(Ecosystem.class);

private final PluginConfiguration configProperty;

Ecosystem(PluginConfiguration configProperty) {
Expand All @@ -21,19 +26,14 @@ public PluginConfiguration getConfigProperty() {
return configProperty;
}

static Optional<Ecosystem> fromPackagePath(String path) {
if (path.endsWith(".jar")) {
return Optional.of(MAVEN);
}

if (path.endsWith(".tgz")) {
return Optional.of(NPM);
}

if (path.endsWith(".whl") || path.endsWith(".tar.gz") || path.endsWith(".zip") || path.endsWith(".egg")) {
return Optional.of(PYPI);
public static Optional<Ecosystem> fromPackageType(String artifactoryPackageType) {
switch (artifactoryPackageType.toLowerCase()) {
case "maven": return Optional.of(MAVEN);
case "npm": return Optional.of(NPM);
case "pypi": return Optional.of(PYPI);
}

LOG.error("Unknown package type: {}", artifactoryPackageType);
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.snyk.plugins.artifactory.ecosystem;

import org.artifactory.repo.RepoPath;

import java.util.Optional;

public interface EcosystemResolver {

Optional<Ecosystem> getFor(RepoPath repoPath);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.snyk.plugins.artifactory.ecosystem;

import org.artifactory.repo.RepoPath;
import org.artifactory.repo.Repositories;
import org.artifactory.repo.RepositoryConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;

public class RepositoryMetadataEcosystemResolver implements EcosystemResolver {

private static final Logger LOG = LoggerFactory.getLogger(RepositoryMetadataEcosystemResolver.class);

private final Repositories repositories;

public RepositoryMetadataEcosystemResolver(Repositories repositories) {
this.repositories = repositories;
}

@Override
public Optional<Ecosystem> getFor(RepoPath repoPath) {
RepositoryConfiguration repositoryConfiguration = repositories.getRepositoryConfiguration(repoPath.getRepoKey());
if(repositoryConfiguration == null) {
LOG.error("No repository configuration for {}", repoPath);
return Optional.empty();
}

String packageType = repositoryConfiguration.getPackageType();
if(packageType == null) {
LOG.error("No package type for {}", repoPath);
return Optional.empty();
}

return Ecosystem.fromPackageType(packageType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
import org.artifactory.fs.FileLayoutInfo;
import org.artifactory.repo.RepoPath;

interface PackageScanner {
public interface PackageScanner {
TestResult scan(FileLayoutInfo fileLayoutInfo, RepoPath repoPath);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import io.snyk.plugins.artifactory.configuration.PluginConfiguration;
import io.snyk.plugins.artifactory.configuration.properties.ArtifactProperties;
import io.snyk.plugins.artifactory.configuration.properties.RepositoryArtifactProperties;
import io.snyk.plugins.artifactory.exception.CannotScanException;
import io.snyk.plugins.artifactory.ecosystem.EcosystemResolver;
import io.snyk.plugins.artifactory.ecosystem.RepositoryMetadataEcosystemResolver;
import io.snyk.plugins.artifactory.model.Ignores;
import io.snyk.plugins.artifactory.model.MonitoredArtifact;
import io.snyk.plugins.artifactory.model.TestResult;
import io.snyk.plugins.artifactory.model.ValidationSettings;
import io.snyk.sdk.api.v1.SnykClient;
import org.artifactory.fs.FileLayoutInfo;
import org.artifactory.repo.RepoPath;
import org.artifactory.repo.Repositories;
Expand All @@ -21,25 +21,23 @@
import java.time.Duration;
import java.util.Optional;

import static java.lang.String.format;
import static java.util.Objects.requireNonNull;

public class ScannerModule {
private static final Logger LOG = LoggerFactory.getLogger(ScannerModule.class);
private final ConfigurationModule configurationModule;
private final Repositories repositories;
private final MavenScanner mavenScanner;
private final NpmScanner npmScanner;
private final PythonScanner pythonScanner;
private final EcosystemResolver ecosystemResolver;
private final ScannerResolver scannerResolver;
private final ArtifactResolver artifactResolver;

public ScannerModule(@Nonnull ConfigurationModule configurationModule, @Nonnull Repositories repositories, @Nonnull SnykClient snykClient) {
public ScannerModule(ConfigurationModule configurationModule, @Nonnull Repositories repositories, ScannerResolver scannerResolver) {
this.configurationModule = requireNonNull(configurationModule);
this.repositories = requireNonNull(repositories);

mavenScanner = new MavenScanner(configurationModule, snykClient);
npmScanner = new NpmScanner(configurationModule, snykClient);
pythonScanner = new PythonScanner(configurationModule, snykClient);
ecosystemResolver = new RepositoryMetadataEcosystemResolver(repositories);

this.scannerResolver = scannerResolver;

artifactResolver = shouldTestContinuously() ? new ArtifactCache(
durationHoursProperty(PluginConfiguration.TEST_FREQUENCY_HOURS, configurationModule),
Expand Down Expand Up @@ -68,7 +66,9 @@ private ArtifactProperties properties(RepoPath repoPath) {
}

private @NotNull Optional<MonitoredArtifact> runTest(RepoPath repoPath) {
return getScannerForPackageType(repoPath).map(scanner -> runTestWith(scanner, repoPath));
return ecosystemResolver.getFor(repoPath)
.flatMap(ecosystem -> scannerResolver.getFor(ecosystem))
.map(scanner -> runTestWith(scanner, repoPath));
}

private MonitoredArtifact runTestWith(PackageScanner scanner, RepoPath repoPath) {
Expand All @@ -88,33 +88,6 @@ private void filter(MonitoredArtifact artifact) {
return new MonitoredArtifact(repoPath.toString(), testResult, ignores);
}

protected Optional<PackageScanner> getScannerForPackageType(RepoPath repoPath) {
String path = Optional.ofNullable(repoPath.getPath())
.orElseThrow(() -> new CannotScanException("Path not provided."));
return getScannerForPackageType(path);
}

protected Optional<PackageScanner> getScannerForPackageType(String path) {
return Ecosystem.fromPackagePath(path).map(this::getScanner);
}

private PackageScanner getScanner(Ecosystem ecosystem) {
if (!configurationModule.getPropertyOrDefault(ecosystem.getConfigProperty()).equals("true")) {
throw new CannotScanException(format("Plugin Property \"%s\" is not \"true\".", ecosystem.getConfigProperty().propertyKey()));
}

switch (ecosystem) {
case MAVEN:
return mavenScanner;
case NPM:
return npmScanner;
case PYPI:
return pythonScanner;
default:
throw new IllegalStateException("Unsupported ecosystem: " + ecosystem.name());
}
}

private boolean shouldTestContinuously() {
return configurationModule.getPropertyOrDefault(PluginConfiguration.TEST_CONTINUOUSLY).equals("true");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.snyk.plugins.artifactory.scanner;

import io.snyk.plugins.artifactory.configuration.ConfigurationModule;
import io.snyk.plugins.artifactory.configuration.PluginConfiguration;
import io.snyk.plugins.artifactory.ecosystem.Ecosystem;
import io.snyk.sdk.api.v1.SnykClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

public class ScannerResolver {
private static final Logger LOG = LoggerFactory.getLogger(ScannerResolver.class);
private final Function<PluginConfiguration, String> getConfig;
private final Map<Ecosystem, PackageScanner> scannerByEcosystem = new HashMap<>();

public ScannerResolver(Function<PluginConfiguration, String> getConfig) {
this.getConfig = getConfig;
}

public ScannerResolver register(Ecosystem ecosystem, PackageScanner scanner) {
scannerByEcosystem.put(ecosystem, scanner);
return this;
}

public Optional<PackageScanner> getFor(Ecosystem ecosystem) {
PluginConfiguration configKey = ecosystem.getConfigProperty();
String configValue = getConfig.apply(configKey);
if (!"true".equals(configValue)) {
LOG.info("Snyk scanner disabled for {}. Config: {} = {}", ecosystem.name(), configKey.propertyKey(), configValue);
return Optional.empty();
}

PackageScanner scanner = scannerByEcosystem.get(ecosystem);

if (scanner == null) {
LOG.error("No scanner registered for {}", ecosystem.name());
}

return Optional.ofNullable(scanner);
}

public static ScannerResolver setup(ConfigurationModule configurationModule, SnykClient snykClient) {
return new ScannerResolver(configurationModule::getPropertyOrDefault)
.register(Ecosystem.MAVEN, new MavenScanner(configurationModule, snykClient))
.register(Ecosystem.NPM, new NpmScanner(configurationModule, snykClient))
.register(Ecosystem.PYPI, new PythonScanner(configurationModule, snykClient));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.snyk.plugins.artifactory.ecosystem;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

class EcosystemTest {

@Test
void ecosystemByPackageType() {
assertThat(Ecosystem.fromPackageType("maven")).contains(Ecosystem.MAVEN);
assertThat(Ecosystem.fromPackageType("npm")).contains(Ecosystem.NPM);
assertThat(Ecosystem.fromPackageType("pypi")).contains(Ecosystem.PYPI);
assertThat(Ecosystem.fromPackageType("nuget")).isEmpty();
}
}

This file was deleted.

Loading

0 comments on commit a77f112

Please sign in to comment.