Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fortify.cli.common.exception.FcliSimpleException;
import com.fortify.cli.common.json.JsonHelper;
import com.fortify.cli.common.json.producer.AbstractObjectNodeProducer.AbstractObjectNodeProducerBuilder;
import com.fortify.cli.common.json.producer.IObjectNodeProducer;
Expand All @@ -49,6 +48,7 @@
import kong.unirest.HttpRequest;
import kong.unirest.UnirestInstance;
import lombok.Getter;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
Expand All @@ -57,8 +57,8 @@
public class FoDIssueListCommand extends AbstractFoDOutputCommand implements IServerSideQueryParamGeneratorSupplier {
@Getter @Mixin private OutputHelperMixins.List outputHelper;
@Mixin private FoDDelimiterMixin delimiterMixin; // injected in resolvers
@Mixin private FoDAppResolverMixin.OptionalOption appResolver;
@Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.OptionalOption releaseResolver;
@ArgGroup(exclusive = true, multiplicity = "1", order = 1)
@Getter private TargetSpecifierArgGroup targetSpecifier = new TargetSpecifierArgGroup();
@Mixin private FoDFiltersParamMixin filterParamMixin;
@Mixin private FoDIssueEmbedMixin embedMixin;
@Mixin private FoDIssueIncludeMixin includeMixin;
Expand All @@ -75,23 +75,34 @@ public class FoDIssueListCommand extends AbstractFoDOutputCommand implements ISe
.add("severityString","severityString")
.add("category","category");

public static class TargetSpecifierArgGroup {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1) @Getter private AppTarget app = new AppTarget();
@ArgGroup(exclusive = false, multiplicity = "1", order = 2) @Getter private ReleaseTarget release = new ReleaseTarget();
}

public static class AppTarget extends FoDAppResolverMixin.AbstractFoDAppResolverMixin {
@Option(names = { "--app" }, required = true, descriptionKey = "fcli.fod.app.app-name-or-id") @Getter private String appNameOrId;
}

public static class ReleaseTarget extends FoDReleaseByQualifiedNameOrIdResolverMixin.AbstractFoDQualifiedReleaseNameOrIdResolverMixin {
@Option(names = { "--release", "--rel" }, required = true, paramLabel = "id|app[:ms]:rel", descriptionKey = "fcli.fod.release.resolver.name-or-id") @Getter private String qualifiedReleaseNameOrId;
}

@Override
protected IObjectNodeProducer getObjectNodeProducer(UnirestInstance unirest) {
boolean releaseSpecified = releaseResolver.getQualifiedReleaseNameOrId() != null;
boolean appSpecified = appResolver.getAppNameOrId() != null;
if ( releaseSpecified && appSpecified ) {
throw new FcliSimpleException("Cannot specify both an application and release");
}
if ( !releaseSpecified && !appSpecified ) {
throw new FcliSimpleException("Either an application or release must be specified");
var appGroup = targetSpecifier.getApp();
var releaseGroup = targetSpecifier.getRelease();

boolean appSpecified = appGroup != null && appGroup.getAppNameOrId() != null;
boolean releaseSpecified = releaseGroup != null && releaseGroup.getQualifiedReleaseNameOrId() != null;

if (releaseSpecified) {
releaseGroup.setDelimiterMixin(delimiterMixin);
}

var result = releaseSpecified
? singleReleaseProducerBuilder(unirest, releaseResolver.getReleaseId(unirest))
: applicationProducerBuilder(unirest, appResolver.getAppId(unirest));
// For consistent output, we should remove releaseId/releaseName when listing across multiple releases,
// but that breaks existing scripts that may rely on those fields, so for now, we only do this in
// applicationProducerBuilder(). TODO: Change in in fcli v4.0.
// return result.recordTransformer(this::removeReleaseProperties).build();
? singleReleaseProducerBuilder(unirest, releaseGroup.getReleaseId(unirest))
: applicationProducerBuilder(unirest, appGroup.getAppId(unirest));
return result.build();
}

Expand Down Expand Up @@ -225,17 +236,15 @@ private JsonNode enrichIssueRecord(UnirestInstance unirest, String releaseName,
}

private boolean isEffectiveFastOutput() {
boolean appSpecified = appResolver.getAppNameOrId() != null;
boolean releaseSpecified = releaseResolver.getQualifiedReleaseNameOrId() != null;
if ( !appSpecified || releaseSpecified ) { return false; }
var appGroup = targetSpecifier.getApp();
var releaseGroup = targetSpecifier.getRelease();

boolean appSpecified = appGroup != null && appGroup.getAppNameOrId() != null;
boolean releaseSpecified = releaseGroup != null && releaseGroup.getQualifiedReleaseNameOrId() != null;
if (!appSpecified || releaseSpecified) { return false; }
boolean fastOutputStyle = outputHelper.getRecordWriterStyle().isFastOutput();
boolean streamingSupported = outputHelper.isStreamingOutputSupported();
boolean recordConsumerConfigured = getRecordConsumer()!=null;
// Effective fast output requires:
// - application specified (multiple releases)
// - fast output style
// - no aggregation (merging requires full set)
// - streaming output or record consumer configured
boolean recordConsumerConfigured = getRecordConsumer() != null;
return fastOutputStyle && !aggregate && (streamingSupported || recordConsumerConfigured);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import kong.unirest.HttpResponse;
import kong.unirest.UnirestInstance;
import lombok.Getter;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
Expand All @@ -47,24 +48,40 @@
@CommandGroup("oss-components")
public final class FoDOssComponentsListCommand extends AbstractFoDJsonNodeOutputCommand {
private static final Logger LOG = LoggerFactory.getLogger(FoDOssComponentsListCommand.class);
@Getter
@Mixin
private OutputHelperMixins.TableWithQuery outputHelper;
@Mixin
private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins
@Mixin
private FoDAppResolverMixin.OptionalOption appResolver;
@Mixin
private FoDReleaseByQualifiedNameOrIdResolverMixin.OptionalOption releaseResolver;
@Option(names = "--scan-types", required = true, split = ",", defaultValue = "Debricked")
private FoDOpenSourceScanType[] scanTypes;
@Getter @Mixin private OutputHelperMixins.TableWithQuery outputHelper;
@Mixin private FoDDelimiterMixin delimiterMixin;
@ArgGroup(exclusive = true, multiplicity = "1", order = 1) @Getter private TargetSpecifierArgGroup targetSpecifier = new TargetSpecifierArgGroup();
@Option(names = "--scan-types", required = true, split = ",", defaultValue = "Debricked") private FoDOpenSourceScanType[] scanTypes;

public static class TargetSpecifierArgGroup {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1) @Getter private AppTarget app = new AppTarget();
@ArgGroup(exclusive = false, multiplicity = "1", order = 2) @Getter private ReleaseTarget release = new ReleaseTarget();
}

public static class AppTarget extends FoDAppResolverMixin.AbstractFoDAppResolverMixin {
@Option(names = { "--app" }, required = true, descriptionKey = "fcli.fod.app.app-name-or-id") @Getter private String appNameOrId;
}

public static class ReleaseTarget extends FoDReleaseByQualifiedNameOrIdResolverMixin.AbstractFoDQualifiedReleaseNameOrIdResolverMixin {
@Option(names = { "--release", "--rel" }, required = true, paramLabel = "id|app[:ms]:rel", descriptionKey = "fcli.fod.release.resolver.name-or-id") @Getter private String qualifiedReleaseNameOrId;
}

@Override
public JsonNode getJsonNode(UnirestInstance unirest) {
ArrayNode result = JsonHelper.getObjectMapper().createArrayNode();

var appGroup = targetSpecifier.getApp();
var releaseGroup = targetSpecifier.getRelease();

final String applicationId = (appGroup != null && appGroup.getAppNameOrId() != null)
? appGroup.getAppId(unirest)
: null;
final String releaseId = (releaseGroup != null && releaseGroup.getQualifiedReleaseNameOrId() != null)
? releaseGroup.getReleaseId(unirest)
: null;

Stream.of(scanTypes)
.map(t -> getForOpenSourceScanType(unirest, t, releaseResolver.getReleaseId(unirest),
appResolver.getAppId(unirest), false))
.map(t -> getForOpenSourceScanType(unirest, t, releaseId, applicationId, false))
.forEach(result::addAll);
return result;
}
Expand Down Expand Up @@ -100,7 +117,7 @@ private ArrayNode getForOpenSourceScanType(UnirestInstance unirest, FoDOpenSourc
if (failOnError) {
throw e;
}
LOG.error("Error retrieving OSS components for release " + releaseResolver.getReleaseId(unirest)
LOG.error("Error retrieving OSS components for release " + releaseId
+ " and scan type " + scanType.name() + ": " + e.getMessage());
return JsonHelper.getObjectMapper().createArrayNode();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins;
import com.fortify.cli.common.output.writer.record.RecordWriterStyle;
import com.fortify.cli.common.output.writer.record.RecordWriterStyle.RecordWriterStyleElement;
import com.fortify.cli.fod.app.cli.mixin.FoDAppResolverMixin;
import com.fortify.cli.fod.release.cli.mixin.FoDReleaseByQualifiedNameOrIdResolverMixin;

/**
* Tests for FoDIssueListCommand.isEffectiveFastOutput logic after migration to style-based fast-output.
Expand All @@ -39,10 +37,7 @@ public class FoDIssueListCommandEffectiveFastOutputTest {
void init() throws Exception {
cmd = new FoDIssueListCommand();
streamingStub = new StreamingStubOutputHelper();
setField(cmd, "outputHelper", streamingStub); // inject stub
// Provide empty mixins so reflection can set their private fields
setField(cmd, "appResolver", new FoDAppResolverMixin.OptionalOption());
setField(cmd, "releaseResolver", new FoDReleaseByQualifiedNameOrIdResolverMixin.OptionalOption());
setField(cmd, "outputHelper", streamingStub);
}

@Test
Expand Down Expand Up @@ -94,17 +89,15 @@ private void setField(Object target, String fieldName, Object value) throws Exce
}

private void setApp(String app) throws Exception {
Object appResolver = getField(cmd, "appResolver");
setField(appResolver, "appNameOrId", app);
var target = cmd.getTargetSpecifier();
var appGroup = target.getApp();
setField(appGroup, "appNameOrId", app);
}

private void setRelease(String rel) throws Exception {
Object relResolver = getField(cmd, "releaseResolver");
setField(relResolver, "qualifiedReleaseNameOrId", rel);
}
private Object getField(Object target, String fieldName) throws Exception {
Field f = target.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(target);
var target = cmd.getTargetSpecifier();
var releaseGroup = target.getRelease();
setField(releaseGroup, "qualifiedReleaseNameOrId", rel);
}

private boolean invokeIsEffectiveFastOutput() throws Exception {
Expand Down
Loading