Skip to content
Merged
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 @@ -67,7 +67,7 @@ public static final FoDScanDescriptor startScanAdvanced(UnirestInstance unirest,
.queryString("remdiationScanPreferenceType", (req.getRemdiationScanPreferenceType() != null ?
FoDEnums.RemediationScanPreferenceType.valueOf(req.getRemdiationScanPreferenceType()) : FoDEnums.RemediationScanPreferenceType.NonRemediationScanOnly))
.queryString("inProgressScanActionType", (req.getInProgressScanActionType() != null ?
FoDEnums.InProgressScanActionType.valueOf(req.getInProgressScanActionType()) : FoDEnums.InProgressScanActionType.DoNotStartScan))
req.getInProgressScanActionType() : FoDEnums.InProgressScanActionType.DoNotStartScan.toString()))
.queryString("scanTool", req.getScanTool())
.queryString("scanToolVersion", req.getScanToolVersion())
.queryString("scanMethodType", req.getScanMethodType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins;
import com.fortify.cli.common.progress.cli.mixin.ProgressWriterFactoryMixin;
import com.fortify.cli.common.progress.helper.IProgressWriter;
import com.fortify.cli.common.rest.unirest.UnexpectedHttpResponseException;
import com.fortify.cli.common.util.FcliBuildProperties;
import com.fortify.cli.fod._common.scan.cli.cmd.AbstractFoDScanStartCommand;
import com.fortify.cli.fod._common.scan.cli.mixin.FoDRemediationScanPreferenceTypeMixins;
Expand All @@ -41,6 +42,10 @@ public class FoDSastScanStartCommand extends AbstractFoDScanStartCommand {

@Option(names = {"--notes"})
private String notes;
@Option(names = {"--in-progress-action"}, descriptionKey = "fcli.fod.sast-scan.start.in-progress-action")
private FoDEnums.InProgressScanActionType inProgressScanActionType;
@Option(names = {"--entitlement-preference"}, descriptionKey = "fcli.fod.scan.entitlement-preference")
private FoDEnums.EntitlementPreferenceType entitlementPreferenceType;
@Mixin private CommonOptionMixins.RequiredFile scanFileMixin;

@Mixin private FoDRemediationScanPreferenceTypeMixins.OptionalOption remediationScanType;
Expand All @@ -49,29 +54,63 @@ public class FoDSastScanStartCommand extends AbstractFoDScanStartCommand {
@Override
protected FoDScanDescriptor startScan(UnirestInstance unirest, FoDReleaseDescriptor releaseDescriptor) {
String relId = releaseDescriptor.getReleaseId();
Boolean isRemediation = false;

// if we have requested remediation scan use it to find appropriate assessment type
if (remediationScanType != null && remediationScanType.getRemediationScanPreferenceType() != null) {
if (remediationScanType.getRemediationScanPreferenceType().equals(FoDEnums.RemediationScanPreferenceType.RemediationScanIfAvailable) ||
remediationScanType.getRemediationScanPreferenceType().equals(FoDEnums.RemediationScanPreferenceType.RemediationScanOnly)) {
isRemediation = true;
}
}

validateScanSetup(unirest, relId);

FoDScanSastStartRequest startScanRequest = FoDScanSastStartRequest.builder()
.isRemediationScan(isRemediation)
FoDEnums.RemediationScanPreferenceType remediationPref = remediationScanType != null
? remediationScanType.getRemediationScanPreferenceType() : null;

boolean useAdvanced = entitlementPreferenceType != null || inProgressScanActionType != null;

FoDScanSastStartRequest.FoDScanSastStartRequestBuilder requestBuilder = FoDScanSastStartRequest.builder()
.scanMethodType("Other")
.notes(notes != null && !notes.isEmpty() ? notes : "")
.scanTool(FcliBuildProperties.INSTANCE.getFcliProjectName())
.scanToolVersion(FcliBuildProperties.INSTANCE.getFcliVersion())
.build();
.scanToolVersion(FcliBuildProperties.INSTANCE.getFcliVersion());

try (IProgressWriter progressWriter = progressWriterFactory.create()) {
if (useAdvanced) {
FoDEnums.InProgressScanActionType inProgressAction = inProgressScanActionType != null
? inProgressScanActionType : FoDEnums.InProgressScanActionType.Queue;
// FoD's start-scan-advanced expects 'CancelInProgressScan' rather than the enum's 'CancelScanInProgress'
String inProgressApiValue = inProgressAction == FoDEnums.InProgressScanActionType.CancelScanInProgress
? "CancelInProgressScan" : inProgressAction.name();
Comment on lines +63 to +77
FoDScanSastStartRequest startScanRequest = requestBuilder
.entitlementPreferenceType(entitlementPreferenceType != null ? entitlementPreferenceType.name() : null)
.purchaseEntitlement(false)
.remdiationScanPreferenceType(remediationPref != null ? remediationPref.name() : null)
.inProgressScanActionType(inProgressApiValue)
.build();
return FoDScanSastHelper.startScanAdvanced(unirest, releaseDescriptor, startScanRequest, scanFileMixin.getFile(), progressWriter);
}
boolean isRemediation = remediationPref != null
&& (remediationPref.equals(FoDEnums.RemediationScanPreferenceType.RemediationScanIfAvailable)
|| remediationPref.equals(FoDEnums.RemediationScanPreferenceType.RemediationScanOnly));
FoDScanSastStartRequest startScanRequest = requestBuilder
.isRemediationScan(isRemediation)
.build();
return FoDScanSastHelper.startScanWithDefaults(unirest, releaseDescriptor, startScanRequest, scanFileMixin.getFile(), progressWriter);
} catch (Exception e) {
throw translateScanInProgressException(e);
}
}

// FoD returns HTTP 422 (errorCode 2001) when a scan is already in progress and the
// in-progress action prevents starting a new one. Translate that into a concise,
// actionable message instead of surfacing the raw upload/HTTP exception.
private RuntimeException translateScanInProgressException(Exception e) {
for (Throwable t = e; t != null; t = t.getCause()) {
if (t instanceof UnexpectedHttpResponseException) {
UnexpectedHttpResponseException httpException = (UnexpectedHttpResponseException) t;
if (httpException.getStatus() == 422 && httpException.getMessage() != null
&& httpException.getMessage().toLowerCase().contains("another scan is in progress")) {
return new FcliSimpleException("Cannot start scan: another scan is already in progress for this release. "
+ "Use '--in-progress-action=Queue' to queue this scan, or "
+ "'--in-progress-action=CancelScanInProgress' to cancel the running scan and start a new one.");
}
}
}
return e instanceof RuntimeException ? (RuntimeException) e : new FcliSimpleException(e);
}

private void validateScanSetup(UnirestInstance unirest, String relId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ fcli.fod.sast-scan.start.remediation = Identify this scan as a remediation scan.
fcli.fod.sast-scan.start.skip-if-running = Check to see if static scan is already running before starting.
fcli.fod.sast-scan.start.entitlement-id = The Id of the entitlement to use for the scan.
fcli.fod.sast-scan.start.purchase-entitlement = Purchase an entitlement if one is not currently allocated or available.
fcli.fod.sast-scan.start.in-progress-action = The action to use if a scan is already in progress. Valid values: ${COMPLETION-CANDIDATES}. Defaults to 'Queue' when this or '--entitlement-preference' is specified; otherwise the FoD-side default applies.
fcli.fod.sast-scan.start.notes = Scan notes.
fcli.fod.sast-scan.start.file = Absolute path of the ScanCentral package (.Zip) file to upload.
fcli.fod.sast-scan.start.validate-entitlement = Validate if an entitlement has been set and is still valid.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,42 @@ class FoDScanSpec extends FcliBaseSpec {
}
}

def "start.sast-scan-help-shows-new-options"() {
def args = "fod sast-scan start --help"
when:
def result = Fcli.run(args)
then:
verifyAll(result.stdout) {
it.any { it.contains("--in-progress-action") }
it.any { it.contains("--entitlement-preference") }
it.any { it.contains("DoNotStartScan") }
it.any { it.contains("CancelScanInProgress") }
it.any { it.contains("Queue") }
}
}

def "start.sast-scan-in-progress-do-not-start"() {
// A scan was started above, so DoNotStartScan must be rejected with a friendly message
def args = "fod sast-scan start --release=fcli-1698140484524:v2 --file=$sastPackage --in-progress-action=DoNotStartScan"
when:
Fcli.run(args)
then:
def e = thrown(UnexpectedFcliResultException)
e.result.stderr.any { it.contains("another scan is already in progress") }
}

def "start.sast-scan-advanced-queue"() {
// Exercises the start-scan-advanced path; queues behind the in-progress scan
def args = "fod sast-scan start --release=fcli-1698140484524:v2 --file=$sastPackage --in-progress-action=Queue"
when:
def result = Fcli.run(args)
then:
verifyAll(result.stdout) {
size()>=2
it.last().contains("STARTED")
}
}

def "wait-for-sast"() {
def args = "fod sast-scan wait-for ::sastScan:: -i 2s --until=all-match --any-state=Completed,In_Progress,Queued"
when:
Expand Down
Loading