Skip to content

Commit

Permalink
Merge pull request #609 from fortify/develop
Browse files Browse the repository at this point in the history
chore: Prepare for new release
  • Loading branch information
rsenden authored Sep 25, 2024
2 parents 0798d4c + c3670cc commit 5241640
Show file tree
Hide file tree
Showing 51 changed files with 1,289 additions and 164 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*******************************************************************************
* 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.common.action.cli.cmd;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.fortify.cli.common.action.cli.mixin.ActionSourceResolverMixin;
import com.fortify.cli.common.action.helper.ActionLoaderHelper;
import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler;
import com.fortify.cli.common.action.model.Action;
import com.fortify.cli.common.action.runner.ActionParameterHelper;
import com.fortify.cli.common.cli.cmd.AbstractRunnableCommand;
import com.fortify.cli.common.cli.mixin.CommonOptionMixins;
import com.fortify.cli.common.cli.util.SimpleOptionsParser.IOptionDescriptor;
import com.fortify.cli.common.util.FcliBuildPropertiesHelper;
import com.fortify.cli.common.util.StringUtils;

import lombok.SneakyThrows;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;

public abstract class AbstractActionAsciidocCommand extends AbstractRunnableCommand {
@Mixin private ActionSourceResolverMixin.OptionalOption actionSourceResolver;
@Mixin private CommonOptionMixins.OptionalFile outputFileMixin;
@Option(names= {"--manpage-dir", "-d"}, required = false, descriptionKey="fcli.action.asciidoc.manpage-dir")
private Path manpageDir;

@Override @SneakyThrows
public final Integer call() {
initMixins();
var contents = generateHeader();
contents += ActionLoaderHelper
.streamAsActions(actionSourceResolver.getActionSources(getType()), ActionValidationHandler.IGNORE)
.map(this::generateActionSection)
.collect(Collectors.joining("\n\n"));
contents = addLinks(contents);
var outputFile = outputFileMixin.getFile();
if ( outputFile==null ) {
System.out.println(contents);
} else {
// TODO Should we require confirmation is file already exists?
Files.writeString(outputFile.toPath(), contents, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING);
}
return 0;
}

private final String replaceVariables(String s) {
return s.replace("${version}", FcliBuildPropertiesHelper.getFcliBuildInfo().replace(':', ' '))
.replace("${type}", getType())
.replace("${typeLower}", getType().toLowerCase());
}

private final String generateHeader() {
return replaceVariables("""
= Fcli ${type} Actions
This manual page describes built-in fcli ${type} actions that can be run through
the `fcli ${typeLower} action run <action-name>` command.
""");
}

private final String generateActionSection(Action action) {
// TODO Generate proper options list in synopsis. We should have a re-usable method in
// ActionParameterHelper or other class for generating this, such that we can also
// show synopsis in `fcli * action help` output.
String name = action.getMetadata().getName();
return replaceVariables(String.format("""
== %s
%s
=== Synopsis
*fcli ${typeLower} action run %s [fcli ${typeLower} action run options] [action options, see below]*
=== Description
%s
=== Options
%s
""", name, action.getUsage().getHeader(), name, action.getUsage().getDescription(), generateOptionsSection(action)));
}

private final String generateOptionsSection(Action action) {
return ActionParameterHelper.getOptionDescriptors(action)
.stream().map(this::generateOptionDescription).collect(Collectors.joining("\n\n"));
}

private final String generateOptionDescription(IOptionDescriptor descriptor) {
return String.format("%s::\n%s",
descriptor.getOptionNamesAndAliasesString(", "),
StringUtils.indent(descriptor.getDescription(), " "));
}

private final String addLinks(String contents) {
if ( manpageDir==null ) { return contents; }
var manPages = listDir(manpageDir).stream().filter(s->s.matches("fcli-[\\w-]+-[\\w-]+-[\\w-]+.adoc"))
.map(s->s.replaceAll("\\.adoc", ""))
.collect(Collectors.toSet());
for ( var manPage : manPages ) {
var pattern = manPage.replace("-", "[ -]");
var replacement = String.format("link:manpage/%s.html[$1]", manPage);
contents = contents.replaceAll("(?<!`)("+pattern+")", replacement);
contents = contents.replaceAll("(`"+pattern+".*`)", replacement);
}
return contents;
}

private Set<String> listDir(Path dir) {
try (Stream<Path> stream = Files.list(dir)) {
return stream
.filter(file -> !Files.isDirectory(file))
.map(Path::getFileName)
.map(Path::toString)
.collect(Collectors.toSet());
} catch ( IOException e ) {
return new HashSet<>();
}
}

protected abstract String getType();


}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Stream;
Expand Down Expand Up @@ -73,19 +74,23 @@ public static final ActionLoadResult load(List<ActionSource> sources, String nam
return new ActionLoader(sources, actionValidationHandler).load(name);
}

public static final Stream<Action> streamAsActions(List<ActionSource> sources, ActionValidationHandler actionValidationHandler) {
return _stream(sources, actionValidationHandler, ActionLoadResult::getAction, a->a.getMetadata().getName());
}

public static final Stream<ObjectNode> streamAsJson(List<ActionSource> sources, ActionValidationHandler actionValidationHandler) {
return _streamAsJson(sources, actionValidationHandler);
return _stream(sources, actionValidationHandler, ActionLoadResult::getSummaryObjectNode, o->o.get("name").asText());
}

private static final Stream<ObjectNode> _streamAsJson(List<ActionSource> sources, ActionValidationHandler actionValidationHandler) {
Map<String, ObjectNode> result = new HashMap<>();
private static final <T> Stream<T> _stream(List<ActionSource> sources, ActionValidationHandler actionValidationHandler, Function<ActionLoadResult, T> asTypeFunction, Function<T, String> nameFunction) {
Map<String, T> result = new HashMap<>();
new ActionLoader(sources, actionValidationHandler)
.processActions(loadResult->{
result.putIfAbsent(loadResult.getMetadata().getName(), loadResult.getSummaryObjectNode());
result.putIfAbsent(loadResult.getMetadata().getName(), asTypeFunction.apply(loadResult));
return Break.FALSE;
});
return result.values().stream()
.sorted((a,b)->a.get("name").asText().compareTo(b.get("name").asText()));
.sorted((a,b)->nameFunction.apply(a).compareTo(nameFunction.apply(b)));
}

public static final String getSignatureStatusMessage(ActionMetadata metadata, SignatureStatus signatureStatus) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public final class ActionParameter implements IActionElement {
@JsonPropertyDescription("Optional boolean: All parameters are required by default, unless this property is set to false.")
@JsonProperty(required = false, defaultValue = "true") private boolean required = true;

@JsonPropertyDescription("Optional string: Allows for defining groups of parameters")
@JsonProperty(required = false) private String group;

public final void postLoad(Action action) {
Action.checkNotBlank("parameter name", name, this);
Action.checkNotNull("parameter description", getDescription(), this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import com.fasterxml.jackson.databind.node.TextNode;
import com.fasterxml.jackson.databind.node.ValueNode;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.formkiq.graalvm.annotations.Reflectable;
import com.fortify.cli.common.action.model.AbstractActionStepForEach;
import com.fortify.cli.common.action.model.Action;
import com.fortify.cli.common.action.model.ActionParameter;
Expand Down Expand Up @@ -207,6 +208,29 @@ public final void close() {

private final void configureSpelEvaluator(SimpleEvaluationContext context) {
SpelHelper.registerFunctions(context, ActionSpelFunctions.class);
context.setVariable("action", new ActionUtil());
}

@Reflectable
private final class ActionUtil {
@SuppressWarnings("unused")
public final String copyParametersFromGroup(String group) {
StringBuilder result = new StringBuilder();
for ( var p : action.getParameters() ) {
if ( group==null || group.equals(p.getGroup()) ) {
var val = parameters.get(p.getName());
if ( val!=null && StringUtils.isNotBlank(val.asText()) ) {
result
.append("\"--")
.append(p.getName())
.append("=")
.append(val.asText())
.append("\" ");
}
}
}
return result.toString();
}
}

public final ActionRunner addParameterConverter(String type, BiFunction<String, ParameterTypeConverterArgs, JsonNode> converter) {
Expand Down Expand Up @@ -956,12 +980,16 @@ public static final class ParameterTypeConverterArgs {

private static final Map<String, BiFunction<String, ParameterTypeConverterArgs, JsonNode>> createDefaultParameterConverters() {
Map<String, BiFunction<String, ParameterTypeConverterArgs, JsonNode>> result = new HashMap<>();
// TODO Most of these will likely fail in case value is null or empty
result.put("string", (v,a)->new TextNode(v));
result.put("boolean", (v,a)->BooleanNode.valueOf(Boolean.parseBoolean(v)));
result.put("int", (v,a)->IntNode.valueOf(Integer.parseInt(v)));
result.put("long", (v,a)->LongNode.valueOf(Long.parseLong(v)));
result.put("double", (v,a)->DoubleNode.valueOf(Double.parseDouble(v)));
result.put("float", (v,a)->FloatNode.valueOf(Float.parseFloat(v)));
result.put("array", (v,a)->StringUtils.isBlank(v)
? JsonHelper.toArrayNode(new String[] {})
: JsonHelper.toArrayNode(v.split(",")));
// TODO Add BigIntegerNode/DecimalNode/ShortNode support?
// TODO Add array support?
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ public static final ArrayNodeCollector arrayNodeCollector() {
return new ArrayNodeCollector();
}

public static final ArrayNode toArrayNode(String... objects) {
return Stream.of(objects).map(TextNode::new).collect(arrayNodeCollector());
public static final ArrayNode toArrayNode(String... strings) {
return Stream.of(strings).map(TextNode::new).collect(arrayNodeCollector());
}

public static final ArrayNode toArrayNode(JsonNode... objects) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ private final void addNodeWithStatus(Map<ObjectNode, String> nodesWithStatus, Js
}

private final void updateProgress(Map<ObjectNode, WaitStatus> recordsWithWaitStatus) {
if ( progressMonitor!=null ) {
if ( progressMonitor!=null && !recordsWithWaitStatus.isEmpty() ) {
progressMonitor.updateProgress(recordsWithWaitStatus);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ usage.footer.0 = %nCommands/options marked as PREVIEW are subject to change; pip
usage.footer.1 = %nFull command list: fcli util all-commands list
usage.footer.2 = Documentation: https://fortify.github.io/fcli
usage.footer.3 = %n(c) Copyright 2021-2024 Open Text
usage.parameterListHeading = %nCommand parameters:%n
usage.optionListHeading = %nCommand options:%n
usage.parameterListHeading = %nCommand parameters%n
usage.optionListHeading = %nCommand options%n

# Generic, non command-specific options
fcli.genericOptions.heading = Generic fcli options:%n
fcli.genericOptions.heading = Generic fcli options%n
help = Show this help message and exit. Use 'fcli <command> -h' to display help for subcommands.
version = Print version information and exit.
env-prefix = Environment variable prefix for resolving default option and parameter values. \
Expand All @@ -31,6 +31,9 @@ log-file = File where logging data will be written. Defaults to fcli.log in curr
fcli.action.nameOrLocation = The action to load; either simple name or local or remote action \
YAML file location. Note that custom actions are currently considered PREVIEW functionality, \
as explained in the 'fcli ${module} action -h' help output.
fcli.action.asciidoc.manpage-dir = Optional directory to write output. If directory contains fcli \
manual pages, any (full) fcli commands in the generated documentation will link to the corresponding \
fcli manual page.

fcli.action.run.action-parameter = Action parameter(s); see 'help' command output to \
list supported parameters.
Expand All @@ -53,8 +56,8 @@ fcli.action.sign.pubout = Public key output file. This option is required when g
new key pair (if given private key doesn't exist), and may optionally be used for outputting \
the public key if an already existing private key is being used.
fcli.action.resolver.from-zip = Optional local or remote zip-file from which to load the action if \
the action is specified as a simple name. This option will be ignored if action is specified as \
local or remote action YAML file location.
the action is specified as a simple name. For commands that take an action as input (like get, help \
or run), this option will be ignored if action is specified as local or remote action YAML file location.
fcli.action.resolver.pubkey = Optional public key to use for verifying action signature. Can \
be specified as one of: \
%n file:<local file>%n url:<url>%n string:<string value>%n env:<env-var name>\
Expand All @@ -74,7 +77,7 @@ fcli.action.on-invalid-version = Action to take if action schema version is not
output.header.signatureStatus = Signature\nStatus

# Generic, non command-specific output and query options
arggroup.output.heading = Output options:%n
arggroup.output.heading = Output options%n
output = Specify output format and options. Available output formats: ${COMPLETION-CANDIDATES}. \
The 'expr' output format takes a string containing '{property}' placeholders, other output \
formats take an optional, comma-separated list of properties to include in the output. Use \
Expand Down Expand Up @@ -132,7 +135,7 @@ progress=Configure progress output. Allowed values: ${COMPLETION-CANDIDATES}. De
${DEFAULT-VALUE}. Proper output of single-line and ansi depends on console capabilities.

# Login and connection options
arggroup.optional.session-name.heading = Session options:%n
arggroup.optional.session-name.heading = Session options%n
login.session = Name for this ${product} session. Default value: ${DEFAULT-VALUE}.
logout.session = Name of the ${product} session to be terminated. Default value: ${DEFAULT-VALUE}.
session = Name of the ${product} session to use for executing this command. Default value: ${DEFAULT-VALUE}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,4 +455,55 @@ public enum ApiSchemeType {
@JsonProperty("http,https")
HTTPandHTTPs;
}

public enum AttributeTypes {
All(0),
Application(1),
Vulnerability(2),
Microservice(3),
Release(4);

private final int _val;

AttributeTypes(int val) {
this._val = val;
}

public int getValue() {
return this._val;
}

public String toString() {
switch (this._val) {
case 0:
return "All";
case 1:
return "Application";
case 2:
return "Vulnerability";
case 3:
return "Microservice";
case 4:
default:
return "Release";
}
}

public static AttributeTypes fromInt(int val) {
switch (val) {
case 0:
return All;
case 1:
return Application;
case 2:
return Vulnerability;
case 3:
return Microservice;
case 4:
default:
return Release;
}
}
}

}
Loading

0 comments on commit 5241640

Please sign in to comment.