From 0cdde30583e5ba270cf3f605fedc64511164f8b8 Mon Sep 17 00:00:00 2001 From: Ruud Senden <8635138+rsenden@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:52:38 +0200 Subject: [PATCH] feat: Add `fcli fod release wait-for` command to wait for release(s) to leave suspended state (resolves #624) --- .../release/cli/cmd/FoDReleaseCommands.java | 3 +- .../cli/cmd/FoDReleaseWaitForCommand.java | 48 +++++++++++++++++++ ...leaseByQualifiedNameOrIdResolverMixin.java | 27 +++++++++++ .../cli/fod/i18n/FoDMessages.properties | 21 +++++++- 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseWaitForCommand.java diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseCommands.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseCommands.java index 2f35f2c9b8..823d7db423 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseCommands.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseCommands.java @@ -27,7 +27,8 @@ FoDReleaseUpdateCommand.class, FoDReleaseDeleteCommand.class, FoDReleaseAssessmentTypeListCommand.class, - FoDReleaseScanListCommand.class + FoDReleaseScanListCommand.class, + FoDReleaseWaitForCommand.class } ) @DefaultVariablePropertyName("releaseId") diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseWaitForCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseWaitForCommand.java new file mode 100644 index 0000000000..bd85d00adb --- /dev/null +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/cmd/FoDReleaseWaitForCommand.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright 2021, 2023 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + *******************************************************************************/ + +package com.fortify.cli.fod.release.cli.cmd; + +import java.util.Set; + +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.common.rest.cli.cmd.AbstractWaitForCommand; +import com.fortify.cli.common.rest.wait.WaitHelper.WaitHelperBuilder; +import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin; +import com.fortify.cli.fod._common.session.cli.mixin.FoDUnirestInstanceSupplierMixin; +import com.fortify.cli.fod.release.cli.mixin.FoDReleaseByQualifiedNameOrIdResolverMixin; + +import kong.unirest.UnirestInstance; +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; + +@Command(name = OutputHelperMixins.WaitFor.CMD_NAME) +public final class FoDReleaseWaitForCommand extends AbstractWaitForCommand { + @Getter @Mixin private FoDUnirestInstanceSupplierMixin unirestInstanceSupplier; + @Mixin private FoDDelimiterMixin delimiterMixin; // Is automatically injected in resolver mixins + @Mixin private FoDReleaseByQualifiedNameOrIdResolverMixin.PositionalParameterMulti releaseResolver; + @Option(names={"-s", "--suspended"}, paramLabel="true|false", required=true, defaultValue="false", arity="1") + private boolean suspended; + + @Override + protected final WaitHelperBuilder configure(UnirestInstance unirest, WaitHelperBuilder builder) { + return builder + .recordsSupplier(releaseResolver::getReleaseDescriptorJsonNodes) + .currentStateProperty("suspended") + .knownStates("true", "false") + .failureStates() + .matchStates(Set.of(String.valueOf(suspended))); + } +} diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/mixin/FoDReleaseByQualifiedNameOrIdResolverMixin.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/mixin/FoDReleaseByQualifiedNameOrIdResolverMixin.java index c03d5b1a9a..b3305d06a8 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/mixin/FoDReleaseByQualifiedNameOrIdResolverMixin.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/release/cli/mixin/FoDReleaseByQualifiedNameOrIdResolverMixin.java @@ -13,6 +13,11 @@ package com.fortify.cli.fod.release.cli.mixin; +import java.util.Collection; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.JsonNode; import com.fortify.cli.common.cli.util.EnvSuffix; import com.fortify.cli.common.util.StringUtils; import com.fortify.cli.fod._common.cli.mixin.FoDDelimiterMixin; @@ -43,6 +48,23 @@ public String getReleaseId(UnirestInstance unirest) { return descriptor==null ? null : descriptor.getReleaseId(); } } + + public static abstract class AbstractFoDMultiQualifiedReleaseNameOrIdResolverMixin implements IFoDDelimiterMixinAware { + @Setter private FoDDelimiterMixin delimiterMixin; + public abstract String[] getQualifiedReleaseNamesOrIds(); + + public FoDReleaseDescriptor[] getReleaseDescriptors(UnirestInstance unirest, String... fields) { + return Stream.of(getQualifiedReleaseNamesOrIds()).map(nameOrId->FoDReleaseHelper.getReleaseDescriptor(unirest, nameOrId, delimiterMixin.getDelimiter(), true, fields)).toArray(FoDReleaseDescriptor[]::new); + } + + public Collection getReleaseDescriptorJsonNodes(UnirestInstance unirest, String... fields) { + return Stream.of(getReleaseDescriptors(unirest, fields)).map(FoDReleaseDescriptor::asJsonNode).collect(Collectors.toList()); + } + + public Integer[] getReleaseIds(UnirestInstance unirest) { + return Stream.of(getReleaseDescriptors(unirest, "releaseId")).map(FoDReleaseDescriptor::getReleaseId).toArray(Integer[]::new); + } + } public static class RequiredOption extends AbstractFoDQualifiedReleaseNameOrIdResolverMixin { @Option(names = {"--release", "--rel"}, required = true, paramLabel = "id|app[:ms]:rel", descriptionKey = "fcli.fod.release.resolver.name-or-id") @@ -63,4 +85,9 @@ public static class OptionalCopyFromOption extends AbstractFoDQualifiedReleaseNa @Option(names = {"--copy-from"}, required = false, paramLabel = "id|app[:ms]:rel", descriptionKey = "fcli.fod.release.resolver.copy-from.nameOrId") @Getter private String qualifiedReleaseNameOrId; } + + public static class PositionalParameterMulti extends AbstractFoDMultiQualifiedReleaseNameOrIdResolverMixin { + @Parameters(index = "0", arity = "1..", paramLabel="id|app[:ms]:rel", descriptionKey = "fcli.fod.release.resolver.multi-name-or-id") + @Getter private String[] qualifiedReleaseNamesOrIds; + } } diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties index 2ec1371237..12a81a0332 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties @@ -41,6 +41,7 @@ fcli.fod.scan.in-progress-action = The action to use if a scan is already in pro fcli.fod.scan.remediation-preference = The remediation preference to use. Valid values: ${COMPLETION-CANDIDATES}. fcli.fod.microservice.resolver.name = Microservice name in the format :. fcli.fod.release.resolver.name-or-id = Release id or [:]: name. +fcli.fod.release.resolver.multi-name-or-id = One or more release id's or [:]: names. fcli.fod.release.resolver.name = Release name in the format [:]:. fcli.fod.import.sbom-format = The SBOM format to import. If empty Cyclone DX format is assumed. fcli.fod.user.user-name-or-id = User id or username. Note that numeric values are always interpreted \ @@ -363,7 +364,11 @@ fcli.fod.release.create.usage.header = Create a new application release. \ fcli.fod.release.create.application-name = The id or name of the application to create the release on. fcli.fod.release.create.release-name = The name of the release to create for the application. fcli.fod.release.create.description = Description of the release. -fcli.fod.release.create.copy-from = The id or name of a release to copy existing state from. +fcli.fod.release.create.copy-from = The id or name of a release to copy existing state from. Note that \ + FoD will put the release in 'suspended' state until copying is complete, during which time scan \ + requests and other operations may be rejected. If you want to run any other operations on the release, \ + it is recommended to first invoke the `fcli fod release wait-for` command to wait until the release \ + leaves suspended state. fcli.fod.release.create.microservice = The id or name of the microservice to create the release on. fcli.fod.release.create.status = SDLC lifecycle status of the release. Valid values: ${COMPLETION-CANDIDATES}. fcli.fod.release.create.attr = Attribute id or name and its value to set on the release. \ @@ -387,6 +392,19 @@ fcli.fod.release.update.attr = Attribute id or name and its value to set on the fcli.fod.release.list-assessment-types.usage.header = List assessment types for a given release. fcli.fod.release.list-assessment-types.scan-types = Comma-separated list of scan types for which to list assessment types. Default value: ${DEFAULT-VALUE}. Valid values: ${COMPLETION-CANDIDATES}. fcli.fod.release.list-scans.usage.header = List scans for a given release. +fcli.fod.release.wait-for.usage.header = Wait for one or more scans to reach or exit suspended state. +fcli.fod.release.wait-for.usage.description.0 = Although this command offers a lot of options to \ + cover many different use cases, you can simply pass one or more release names or id's to wait until \ + those releases leave 'suspended' state. \ + %n%nMost common use case is to invoke this wait-for command after creating a new release through the \ + `fcli fod release create` command with the `--copy-from` option; FoD will put the newly created \ + release in 'suspended' state until copying is completed, during which time scan requests and other \ + operations may be rejected. \ + %n%nNote that contrary to other fcli wait-for commands, any options related to unknown or failure state \ + handling are not applicable to this wait-for command and will be ignored. +fcli.fod.release.wait-for.until = Wait until either any or all releases match. If neither --until or --while are specified, default is to wait until all releases match. +fcli.fod.release.wait-for.while = Wait while either any or all releases match. +fcli.fod.release.wait-for.suspended = Suspended state against which to match the given releases; may be `false` (default) or `true`. # fcli fod assessment-type fcli.fod.assessment-type.usage.header = Manage FoD assessment types. @@ -884,6 +902,7 @@ fcli.fod.app.output.table.options = applicationId,applicationName,fcliApplicatio fcli.fod.app.scan.output.table.options = scanId,scanType,analysisStatusType,applicationName,microserviceName,releaseName,startedDateTime,completedDateTime,scanMethodTypeName fcli.fod.microservice.output.table.options = microserviceId,microserviceName,applicationName fcli.fod.release.output.table.options = releaseId,releaseName,microserviceName,applicationName,sdlcStatusType +fcli.fod.release.wait-for.output.table.options = releaseId,releaseName,microserviceName,applicationName,suspended fcli.fod.release.scan.output.table.options = scanId,scanType,analysisStatusType,applicationName,microserviceName,releaseName,startedDateTime,completedDateTime,scanMethodTypeName fcli.fod.release.assessment-type.output.table.options = assessmentTypeId,name,scanType,frequencyType,unitInfo,entitlementId,entitlementDescription fcli.fod.entitlement.output.table.options = entitlementId,entitlementDescription,startDate,endDate,unitInfo