diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml index a95203c228..b42ae0f39a 100644 --- a/.github/workflows/functional-tests.yml +++ b/.github/workflows/functional-tests.yml @@ -54,20 +54,20 @@ jobs: mv artifact/fcli-ftest.jar . case "${{ matrix.type }}" in "java" ) - java -jar fcli-ftest.jar -Dft.fcli=build -Dft.run=core,config ;; + java -jar fcli-ftest.jar -Dft.fcli=build -Dft.run=core,config,tool ;; "jar" ) - java -jar fcli-ftest.jar -Dft.fcli=fcli.jar -Dft.run=core,config ;; + java -jar fcli-ftest.jar -Dft.fcli=fcli.jar -Dft.run=core,config,tool ;; "native" ) case "${{ matrix.os }}" in "ubuntu-latest" ) tar -zxvf fcli-linux.tgz - java -jar fcli-ftest.jar -Dft.fcli=./fcli -Dft.run=core,config ;; + java -jar fcli-ftest.jar -Dft.fcli=./fcli -Dft.run=core,config,tool ;; "windows-latest" ) 7z e fcli-windows.zip - java -jar fcli-ftest.jar -Dft.fcli=fcli.exe -Dft.run=core,config ;; + java -jar fcli-ftest.jar -Dft.fcli=fcli.exe -Dft.run=core,config,tool ;; "macos-latest" ) tar -zxvf fcli-mac.tgz - java -jar fcli-ftest.jar -Dft.fcli=./fcli -Dft.run=core,config ;; + java -jar fcli-ftest.jar -Dft.fcli=./fcli -Dft.run=core,config,tool ;; esac ;; esac @@ -153,4 +153,4 @@ jobs: name: test-log path: test-*.log - \ No newline at end of file + diff --git a/.gitignore b/.gitignore index e6d472f6e5..42bab027ab 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ build/ # We want to include everything in our functional test resources !**/ftest/resources/runtime/**/* +# automatically downloaded during gradle build +fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/config/tool-definitions.yaml.zip + ### STS ### .apt_generated .classpath diff --git a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/runner/util/FortifyCLIStaticInitializer.java b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/runner/util/FortifyCLIStaticInitializer.java index 1158f83fdc..1ab7330243 100644 --- a/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/runner/util/FortifyCLIStaticInitializer.java +++ b/fcli-core/fcli-app/src/main/java/com/fortify/cli/app/runner/util/FortifyCLIStaticInitializer.java @@ -30,6 +30,7 @@ import com.fortify.cli.sc_sast.scan.helper.SCSastControllerScanJobArtifactState; import com.fortify.cli.sc_sast.scan.helper.SCSastControllerScanJobState; import com.fortify.cli.ssc.artifact.helper.SSCArtifactStatus; +import com.fortify.cli.tool._common.helper.ToolUninstaller; import lombok.AccessLevel; import lombok.Getter; @@ -48,6 +49,7 @@ public final class FortifyCLIStaticInitializer { private static final FortifyCLIStaticInitializer instance = new FortifyCLIStaticInitializer(); public void initialize() { + ToolUninstaller.deleteAllPending(); initializeTrustStore(); initializeLocale(); initializeFoDProperties(); diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/cli/mixin/CommonOptionMixins.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/cli/mixin/CommonOptionMixins.java index 121a9d5a18..2488882254 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/cli/mixin/CommonOptionMixins.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/cli/mixin/CommonOptionMixins.java @@ -45,10 +45,12 @@ public void checkConfirmed(Object... promptArgs) { if (!confirmed) { CommandSpec spec = commandHelper.getCommandSpec(); if ( System.console()==null ) { - throw new ParameterException(spec.commandLine(), "Missing option: Confirm operation with -y / --confirm (interactive prompt not available)"); + throw new ParameterException(spec.commandLine(), + PicocliSpecHelper.getRequiredMessageString(spec, + "error.missing.confirmation", getPlainPrompt(spec, promptArgs).replace("\n", "\n "))); } else { String expectedResponse = PicocliSpecHelper.getRequiredMessageString(spec, "expectedConfirmPromptResponse"); - String response = System.console().readLine(getPrompt(promptArgs)); + String response = System.console().readLine(getPrompt(spec, promptArgs)); if ( response.equalsIgnoreCase(expectedResponse) ) { return; } else { @@ -58,19 +60,22 @@ public void checkConfirmed(Object... promptArgs) { } } - private String getPrompt(Object... promptArgs) { - CommandSpec spec = commandHelper.getCommandSpec(); - String promptFormat = PicocliSpecHelper.getMessageString(spec, "confirmPrompt"); - if ( StringUtils.isBlank(promptFormat) ) { + private String getPrompt(CommandSpec spec, Object... promptArgs) { + String prompt = getPlainPrompt(spec, promptArgs); + String promptOptions = PicocliSpecHelper.getRequiredMessageString(spec, "confirmPromptOptions"); + return String.format("%s (%s) ", prompt, promptOptions); + } + + private String getPlainPrompt(CommandSpec spec, Object... promptArgs) { + String prompt = PicocliSpecHelper.getMessageString(spec, "confirmPrompt", promptArgs); + if ( StringUtils.isBlank(prompt) ) { String[] descriptionLines = spec.optionsMap().get("-y").description(); if ( descriptionLines==null || descriptionLines.length<1 ) { throw new RuntimeException("No proper description found for generating prompt for --confirm option"); } - promptFormat = spec.optionsMap().get("-y").description()[0].replaceAll("[. ]+$", "")+"?"; + prompt = spec.optionsMap().get("-y").description()[0].replaceAll("[. ]+$", "")+"?"; } - String prompt = String.format(promptFormat, promptArgs); - String promptOptions = PicocliSpecHelper.getRequiredMessageString(spec, "confirmPromptOptions"); - return String.format("%s (%s) ", prompt, promptOptions); + return prompt; } } } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/OutputHelperMixins.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/OutputHelperMixins.java index 2c5fd714d7..bf7bbf32a2 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/OutputHelperMixins.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/OutputHelperMixins.java @@ -124,6 +124,11 @@ public static class List extends TableWithQuery { public static final String CMD_NAME = "list"; } + @Command(aliases = {"ls"}) + public static class ListNoQuery extends TableNoQuery { + public static final String CMD_NAME = "list"; + } + @Command(aliases = {"lsd"}) public static class ListDefinitions extends TableWithQuery { public static final String CMD_NAME = "list-definitions"; diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/writer/record/table/TableRecordWriter.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/writer/record/table/TableRecordWriter.java index 6c958aab2e..1c03f1ff0a 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/writer/record/table/TableRecordWriter.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/output/writer/record/table/TableRecordWriter.java @@ -68,7 +68,11 @@ private String getTable(String[] fields, String[][] data) { private String getHeader(String fieldName) { String header = getConfig().getMessageResolver().getMessageString("output.header."+fieldName); - return header!=null ? header : PropertyPathFormatter.humanReadable(fieldName); + return header!=null ? header : PropertyPathFormatter.humanReadable(getNormalizedFieldName(fieldName)); + } + + private String getNormalizedFieldName(String fieldName) { + return fieldName.replaceAll("String$", ""); } private String[] getFields(ObjectNode firstObjectNode) { diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/rest/unirest/UnirestHelper.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/rest/unirest/UnirestHelper.java new file mode 100644 index 0000000000..c0b1120438 --- /dev/null +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/rest/unirest/UnirestHelper.java @@ -0,0 +1,29 @@ +/** + * Copyright 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.rest.unirest; + +import java.io.File; +import java.nio.file.StandardCopyOption; + +import com.fortify.cli.common.http.proxy.helper.ProxyHelper; + +/** + * This class provides utility methods related to Unirest + */ +public class UnirestHelper { + public static final File download(String fcliModule, String url, File dest) { + GenericUnirestFactory.getUnirestInstance(fcliModule, u->ProxyHelper.configureProxy(u, fcliModule, url)) + .get(url).asFile(dest.getAbsolutePath(), StandardCopyOption.REPLACE_EXISTING).getBody(); + return dest; + } +} diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/FcliBuildPropertiesHelper.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/FcliBuildPropertiesHelper.java index 80b87e6edc..8c705a2dd7 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/FcliBuildPropertiesHelper.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/FcliBuildPropertiesHelper.java @@ -14,9 +14,13 @@ import java.io.IOException; import java.io.InputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Properties; public class FcliBuildPropertiesHelper { + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static final Properties buildProperties = loadProperties(); public static final Properties getBuildProperties() { @@ -31,15 +35,25 @@ public static final String getFcliVersion() { return buildProperties.getProperty("projectVersion", "unknown"); } - public static final String getFcliBuildDate() { + public static final String getFcliBuildDateString() { return buildProperties.getProperty("buildDate", "unknown"); } + public static final Date getFcliBuildDate() { + var dateString = getFcliBuildDateString(); + if ( !StringUtils.isBlank(dateString) && !"unknown".equals(dateString) ) { + try { + return DATE_FORMAT.parse(dateString); + } catch (ParseException ignore) {} + } + return null; + } + public static final String getFcliBuildInfo() { return String.format("%s version %s, built on %s" , FcliBuildPropertiesHelper.getFcliProjectName() , FcliBuildPropertiesHelper.getFcliVersion() - , FcliBuildPropertiesHelper.getFcliBuildDate()); + , FcliBuildPropertiesHelper.getFcliBuildDateString()); } private static final Properties loadProperties() { diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/FileUtils.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/FileUtils.java index 4402a32089..9fcb863905 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/FileUtils.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/FileUtils.java @@ -16,26 +16,55 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; import java.nio.file.CopyOption; +import java.nio.file.FileSystemException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.util.Comparator; import java.util.stream.Stream; +import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; + +import lombok.SneakyThrows; // TODO For now, methods provided in this class are only used by the tools module, // but potentially some methods or the full class could be moved to the common module. public final class FileUtils { private FileUtils() {} + public static final InputStream getResourceInputStream(String resourcePath) { + return Thread.currentThread().getContextClassLoader().getResourceAsStream(resourcePath); + } + + @SneakyThrows + public static final String readResourceAsString(String resourcePath, Charset charset) { + return new String(readResourceAsBytes(resourcePath), charset); + } + + @SneakyThrows + public static final byte[] readResourceAsBytes(String resourcePath) { + try ( InputStream in = getResourceInputStream(resourcePath) ) { + return in.readAllBytes(); + } + } + public static final void copyResource(String resourcePath, Path destinationFilePath, CopyOption... options) { - try ( InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourcePath) ) { + var parent = destinationFilePath.getParent(); + try { + Files.createDirectories(parent); + } catch (IOException e) { + throw new RuntimeException(String.format("Error creating directory %s", parent), e); + } + try ( InputStream in = getResourceInputStream(resourcePath) ) { Files.copy( in, destinationFilePath, options); } catch ( IOException e ) { throw new RuntimeException(String.format("Error copying resource %s to %s", resourcePath, destinationFilePath), e); @@ -47,37 +76,27 @@ public static final void copyResourceToDir(String resourcePath, Path destination copyResource(resourcePath, destinationPath.resolve(fileName), options); } - public static final String getFileDigest(File file, String algorithm) { - try { - MessageDigest digestInstance = MessageDigest.getInstance(algorithm); - return bytesToHex(DigestUtils.digest(digestInstance, file)); - } catch ( IOException | NoSuchAlgorithmException e ) { - throw new RuntimeException("Error calculating file digest for file "+file.getAbsolutePath(), e); + @SneakyThrows + public static final void moveFiles(Path sourcePath, Path targetPath, String regex) { + Files.createDirectories(targetPath); + try ( var ls = Files.list(sourcePath) ) { + ls.map(Path::toFile) + .map(File::getName) + .filter(name->name.matches(regex)) + .forEach(name->move(sourcePath.resolve(name), targetPath.resolve(name))); } } - public static final String getDigest(String inputName, InputStream input, String algorithm) { + public static final void move(Path source, Path target) { try { - MessageDigest digestInstance = MessageDigest.getInstance(algorithm); - return bytesToHex(DigestUtils.digest(digestInstance, input)); - } catch ( IOException | NoSuchAlgorithmException e ) { - throw new RuntimeException("Error calculating file digest for file "+inputName, e); - } - } - - private static final String bytesToHex(byte[] bytes) { - StringBuilder hexString = new StringBuilder(2 * bytes.length); - for (int i = 0; i < bytes.length; i++) { - String hex = Integer.toHexString(0xff & bytes[i]); - if(hex.length() == 1) { - hexString.append('0'); - } - hexString.append(hex); + Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(String.format("Error moving %s to %s", source, target), e); } - return hexString.toString(); } - public static final void extractZip(File zipFile, Path targetDir) throws IOException { + @SneakyThrows + public static final void extractZip(File zipFile, Path targetDir) { try (FileInputStream fis = new FileInputStream(zipFile); ZipInputStream zipIn = new ZipInputStream(fis)) { for (ZipEntry ze; (ze = zipIn.getNextEntry()) != null; ) { Path resolvedPath = targetDir.resolve(ze.getName()).normalize(); @@ -89,17 +108,72 @@ public static final void extractZip(File zipFile, Path targetDir) throws IOExcep Files.createDirectories(resolvedPath); } else { Files.createDirectories(resolvedPath.getParent()); - Files.copy(zipIn, resolvedPath); + Files.copy(zipIn, resolvedPath, StandardCopyOption.REPLACE_EXISTING); + } + } + } + } + + @SneakyThrows + public static final void extractTarGZ(File tgzFile, Path targetDir) { + try (InputStream source = Files.newInputStream(tgzFile.toPath()); + GZIPInputStream gzip = new GZIPInputStream(source); + TarArchiveInputStream tar = new TarArchiveInputStream(gzip)) { + + TarArchiveEntry entry; + while ((entry = tar.getNextEntry()) != null) { + Path extractTo = targetDir.resolve(entry.getName()); + if(entry.isDirectory()) { + Files.createDirectories(extractTo); + } else { + Files.copy(tar, extractTo, StandardCopyOption.REPLACE_EXISTING); } } } } - public static final void deleteRecursive(Path installPath) throws IOException { - try (Stream walk = Files.walk(installPath)) { + /** + * Recursively delete the given path. As a best practice, this method should + * only be invoked if {@link #isDirPathInUse(Path)} returns false. The + * deleteRecursive() method itself doesn't invoke {@link #isDirPathInUse(Path)} + * for performance reasons, as callers may wish to explicitly check whether + * any files are in use in order to perform some alternative action. + * @param path + */ + @SneakyThrows + public static final void deleteRecursive(Path path) { + try (Stream walk = Files.walk(path)) { walk.sorted(Comparator.reverseOrder()) .map(Path::toFile) .forEach(File::delete); } } + + @SneakyThrows + public static final boolean isDirPathInUse(Path path) { + if ( isDirPathInUseByCurrentExecutable(path) ) { return true; } + try (Stream walk = Files.walk(path)) { + return walk.anyMatch(FileUtils::isFilePathInUse); + } + } + + @SneakyThrows + public static final boolean isDirPathInUseByCurrentExecutable(Path path) { + var currentExecutablePath = Path.of(FileUtils.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + return currentExecutablePath.normalize().startsWith(path.normalize()); + } + + @SneakyThrows + public static final boolean isFilePathInUse(Path path) { + if ( path.toFile().isFile() ) { + try ( var fc = FileChannel.open(path, StandardOpenOption.APPEND) ) { + if ( fc.tryLock()==null ) { + return true; + } + } catch ( FileSystemException e ) { + return true; + } + } + return false; + } } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/PicocliSpecHelper.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/PicocliSpecHelper.java index ef38959a92..abbe5a2877 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/PicocliSpecHelper.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/PicocliSpecHelper.java @@ -54,7 +54,7 @@ public static final String getCommandGroup(CommandSpec cmdSpec) { return annotation==null ? null : annotation.value(); } - public static final String getMessageString(CommandSpec commandSpec, String keySuffix) { + public static final String getMessageString(CommandSpec commandSpec, String keySuffix, Object... args) { var group = getCommandGroup(commandSpec); Messages messages = getMessages(commandSpec); String value = null; @@ -64,7 +64,8 @@ public static final String getMessageString(CommandSpec commandSpec, String keyS commandSpec = commandSpec.parent(); } // If value is still null, try without any prefix - return value!=null ? value : getMessageString(messages, "", group, keySuffix); + value = value!=null ? value : getMessageString(messages, "", group, keySuffix); + return formatMessage(value, args); } private static final String getMessageString(Messages messages, String pfx, String group, String sfx) { @@ -75,8 +76,12 @@ private static final String getMessageString(Messages messages, String pfx, Stri return value!=null ? value : messages.getString(pfx+sfx, null); } - public static final String getRequiredMessageString(CommandSpec commandSpec, String keySuffix) { - String result = getMessageString(commandSpec, keySuffix); + private static final String formatMessage(String msg, Object... args) { + return msg==null || args==null || args.length==0 ? msg : String.format(msg, args); + } + + public static final String getRequiredMessageString(CommandSpec commandSpec, String keySuffix, Object... args) { + String result = getMessageString(commandSpec, keySuffix, args); if ( StringUtils.isBlank(result) ) { throw new RuntimeException("No resource bundle entry found for required key suffix: "+keySuffix); } diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/SemVerHelper.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/SemVerHelper.java new file mode 100644 index 0000000000..028d65487e --- /dev/null +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/util/SemVerHelper.java @@ -0,0 +1,60 @@ +/** + * Copyright 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.util; + +import java.lang.module.ModuleDescriptor.Version; +import java.util.Optional; +import java.util.regex.Pattern; + +public final class SemVerHelper { + private static final Pattern semverPattern = Pattern.compile("([1-9]\\d*)\\.(\\d+)\\.(\\d+)(?:-(.*))?"); + /** + * Loosely compare two semantic versions, returning -1 if first semver is lower than + * the second, 0 if they are the same, or 1 if first semver is higher than the + * second. Null, blank, or non-semver values are always considered lower than + * true semvers. + * + * @param semver1 + * @param semver2 + * @return + */ + public static final int compare(String semver1, String semver2) { + var semver1Matcher = semverPattern.matcher(semver1==null?"":semver1); + var semver2Matcher = semverPattern.matcher(semver2==null?"":semver2); + if ( (semver1==null && semver2==null) || semver1.equals(semver2) ) { + return 0; + } else if ( !semver1Matcher.matches() && !semver2Matcher.matches() ) { + return semver1.compareTo(semver2); + } else if ( semver1Matcher.matches() && !semver2Matcher.matches() ) { + return 1; + } else if ( !semver1Matcher.matches() && semver2Matcher.matches() ) { + return -1; + } else { + var version1 = Version.parse(semver1); + var version2 = Version.parse(semver2); + return version1.compareTo(version2); + } + } + + public static final Optional getMajor(String semver) { + var matcher = semverPattern.matcher(semver); + return !matcher.matches() ? Optional.empty() : Optional.of(matcher.group(1)); + } + + public static final Optional getMajorMinor(String semver) { + var matcher = semverPattern.matcher(semver); + return !matcher.matches() + ? Optional.empty() + : Optional.of(String.format("%s.%s", matcher.group(1), matcher.group(2))); + } +} diff --git a/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties b/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties index fa6716a6aa..9bf8c02f6a 100644 --- a/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties +++ b/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties @@ -2,6 +2,7 @@ error.missing.subcommand = Missing required subcommand error.missing.parameter = Missing required parameter: error.missing.option = Missing required option +error.missing.confirmation = Interactive console not available; use -y / --confirm option to confirm:\n %s\n error.unmatched.argument = Unmatched argument at index error.unmatched.command = Did you mean diff --git a/fcli-core/fcli-common/src/test/java/com/fortify/cli/common/util/SemVerHelperTest.java b/fcli-core/fcli-common/src/test/java/com/fortify/cli/common/util/SemVerHelperTest.java new file mode 100644 index 0000000000..deeedec2a2 --- /dev/null +++ b/fcli-core/fcli-common/src/test/java/com/fortify/cli/common/util/SemVerHelperTest.java @@ -0,0 +1,48 @@ +/** + * Copyright 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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * + * @author Ruud Senden + */ +public class SemVerHelperTest { + @ParameterizedTest + @CsvSource({ + ",,0", + "a,a,0", + "1.2.3,1.2.3,0", + "a,b,-1", + "b,a,1", + "a,1.2.3,-1", + "1.2.3,a,1", + "a,1.2.3-alpha1,-1", + "1.2.3-alpha1,a,1", + "1.2.0,1.2.1,-1", + "1.2.1,1.2.0,1", + "1.2.0,1.3.0,-1", + "1.3.0,1.2.0,1", + "1.0.0,2.0.0,-1", + "2.0.0,1.0.0,1", + "1.2.0,1.2.0-alpha1,1", + "1.2.0-alpha1,1.2.0,-1" + }) + public void testSemVerCompare(String semver1, String semver2, int expectedResult) throws Exception { + assertEquals(expectedResult, SemVerHelper.compare(semver1, semver2)); + } +} diff --git a/fcli-core/fcli-tool/build.gradle b/fcli-core/fcli-tool/build.gradle index 8e063597b9..76067b2f6d 100644 --- a/fcli-core/fcli-tool/build.gradle +++ b/fcli-core/fcli-tool/build.gradle @@ -1 +1,28 @@ -apply from: "${sharedGradleScriptsDir}/fcli-module.gradle" \ No newline at end of file +plugins { + id "de.undercouch.download" version "5.5.0" +} +apply from: "${sharedGradleScriptsDir}/fcli-module.gradle" + +ext.generatedToolDefinitionsDir = "${buildDir}/tool-definitions" +tasks.register('generateToolDefinitionResources') { + doLast { + def toolDefinitionsSource = "https://github.com/fortify-ps/tool-definitions/releases/download/v1/tool-definitions.yaml.zip" + def toolDefinitionsFile = "tool-definitions.yaml.zip" + def toolDefinitionsRelativeDir = "com/fortify/cli/tool/config" + def toolDefinitionsRelativeFile = "${toolDefinitionsRelativeDir}/${toolDefinitionsFile}" + def toolDefinitionsOutputDir = "${generatedToolDefinitionsDir}/${toolDefinitionsRelativeDir}" + def resourceConfigOutputDir = "${generatedToolDefinitionsDir}/META-INF/native-image/tool-definitions"; + mkdir "${toolDefinitionsOutputDir}" + mkdir "${resourceConfigOutputDir}" + download.run { + src toolDefinitionsSource + dest toolDefinitionsOutputDir + onlyIfModified true + useETag 'all' + } + def resourceConfigContents = '{"resources":[{"pattern":"'+toolDefinitionsRelativeFile+'"}]}'; + file("${resourceConfigOutputDir}/resource-config.json").text = resourceConfigContents; + println resourceConfigContents + } +} +sourceSets.main.output.dir generatedToolDefinitionsDir, builtBy: generateToolDefinitionResources \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolInstallCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolInstallCommand.java index 9d99cd74ac..1a2f13d83f 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolInstallCommand.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolInstallCommand.java @@ -13,171 +13,245 @@ package com.fortify.cli.tool._common.cli.cmd; import java.io.File; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.PosixFilePermissions; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.Stream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fortify.cli.common.cli.mixin.CommonOptionMixins; -import com.fortify.cli.common.http.proxy.helper.ProxyHelper; +import com.fortify.cli.common.cli.util.CommandGroup; +import com.fortify.cli.common.json.JsonHelper; import com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand; import com.fortify.cli.common.output.cli.cmd.IJsonNodeSupplier; import com.fortify.cli.common.output.transform.IActionCommandResultSupplier; -import com.fortify.cli.common.rest.unirest.GenericUnirestFactory; -import com.fortify.cli.common.util.FcliDataHelper; +import com.fortify.cli.common.progress.cli.mixin.ProgressWriterFactoryMixin; +import com.fortify.cli.common.util.DisableTest; +import com.fortify.cli.common.util.DisableTest.TestType; import com.fortify.cli.common.util.FileUtils; import com.fortify.cli.common.util.StringUtils; -import com.fortify.cli.tool._common.helper.ToolHelper; -import com.fortify.cli.tool._common.helper.ToolVersionCombinedDescriptor; -import com.fortify.cli.tool._common.helper.ToolVersionDownloadDescriptor; -import com.fortify.cli.tool._common.helper.ToolVersionInstallDescriptor; +import com.fortify.cli.tool._common.helper.ToolInstallationDescriptor; +import com.fortify.cli.tool._common.helper.ToolInstallationHelper; +import com.fortify.cli.tool._common.helper.ToolInstaller; +import com.fortify.cli.tool._common.helper.ToolInstaller.DigestMismatchAction; +import com.fortify.cli.tool._common.helper.ToolInstaller.ToolInstallationResult; +import com.fortify.cli.tool._common.helper.ToolUninstaller; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionVersionDescriptor; -import kong.unirest.UnirestInstance; import lombok.Getter; import lombok.SneakyThrows; +import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; +@CommandGroup("install") public abstract class AbstractToolInstallCommand extends AbstractOutputCommand implements IJsonNodeSupplier, IActionCommandResultSupplier { - private static final Logger LOG = LoggerFactory.getLogger(AbstractToolInstallCommand.class); - private static final Set binPermissions = PosixFilePermissions.fromString("rwxr-xr-x"); - @Getter @Option(names={"-v", "--version"}, required = true, descriptionKey="fcli.tool.install.version", defaultValue = "default") + private static final ObjectMapper OBJECTMAPPER = JsonHelper.getObjectMapper(); + @Option(names={"-v", "--version"}, required = true, descriptionKey="fcli.tool.install.version", defaultValue = "latest") private String version; - @Getter @Option(names={"-d", "--install-dir"}, required = false, descriptionKey="fcli.tool.install.install-dir") - private File installDir; - @Mixin private CommonOptionMixins.RequireConfirmation requireConfirmation; - @Getter @Option(names={"--on-digest-mismatch"}, required = false, descriptionKey="fcli.tool.install.on-digest-mismatch", defaultValue = "fail") + @ArgGroup(exclusive = true) + private InstallOrBaseDirArgGroup installOrBaseDirArgGroup = new InstallOrBaseDirArgGroup(); + @Option(names={"-p", "--platform"}, required = false, descriptionKey="fcli.tool.install.platform") + private String platform; + @Option(names={"--on-digest-mismatch"}, required = false, descriptionKey="fcli.tool.install.on-digest-mismatch", defaultValue = "fail") private DigestMismatchAction onDigestMismatch; + @DisableTest(TestType.MULTI_OPT_PLURAL_NAME) + @Option(names={"-u", "--uninstall"}, required = false, split=",", descriptionKey="fcli.tool.install.uninstall") + private Set versionsToUninstall = new HashSet<>(); + @Option(names={"--no-global-bin"}, required = false, negatable = true, descriptionKey="fcli.tool.install.global-bin") + private boolean installGlobalBin = true; + @Mixin private CommonOptionMixins.RequireConfirmation requireConfirmation; + @Mixin private ProgressWriterFactoryMixin progressWriterFactory; - private static enum DigestMismatchAction { - fail, warn + private static final class InstallOrBaseDirArgGroup { + @Option(names={"-d", "--install-dir"}, required = false, descriptionKey="fcli.tool.install.install-dir") + private File installDir; + @Option(names={"-b", "--base-dir"}, required = false, descriptionKey="fcli.tool.install.base-dir") + private File baseDir; } @Override public final JsonNode getJsonNode() { - String toolName = getToolName(); - ToolVersionDownloadDescriptor descriptor = ToolHelper.getToolDownloadDescriptor(toolName).getVersionOrDefault(version); - return downloadAndInstall(toolName, descriptor); + return install(); } @Override - public String getActionCommandResult() { + public final String getActionCommandResult() { return "INSTALLED"; } @Override - public boolean isSingular() { - return true; + public final boolean isSingular() { + return false; } - private final JsonNode downloadAndInstall(String toolName, ToolVersionDownloadDescriptor downloadDescriptor) { - try { - Path installPath = getInstallPathOrDefault(downloadDescriptor); - Path binPath = getBinPath(downloadDescriptor); - ToolVersionInstallDescriptor installDescriptor = new ToolVersionInstallDescriptor(downloadDescriptor, installPath, binPath); - emptyExistingInstallPath(installDescriptor.getInstallPath()); - File downloadedFile = download(downloadDescriptor); - checkDigest(downloadDescriptor, downloadedFile); - install(installDescriptor, downloadedFile); - ToolVersionCombinedDescriptor combinedDescriptor = ToolHelper.saveToolVersionInstallDescriptor(toolName, installDescriptor); - return new ObjectMapper().valueToTree(combinedDescriptor); - } catch ( IOException e ) { - throw new RuntimeException("Error installing "+getToolName(), e); + protected abstract String getToolName(); + protected abstract void postInstall(ToolInstaller toolInstaller, ToolInstallationResult installationResult); + protected abstract String getDefaultArtifactType(); + + private final ArrayNode install() { + try ( var progressWriter = progressWriterFactory.create() ) { + var preparer = new ToolInstallationPreparer(); + var installer = ToolInstaller.builder() + .defaultPlatform(getDefaultArtifactType()) + .onDigestMismatch(onDigestMismatch) + .preInstallAction(preparer) + .postInstallAction(this::postInstall) + .progressWriter(progressWriter) + .targetPathProvider(this::getTargetPath) + .globalBinPathProvider(this::getGlobalBinPath) + .toolName(getToolName()) + .requestedVersion(version) + .build(); + var installResult = StringUtils.isBlank(platform) ? installer.install() : installer.install(platform); + var result = OBJECTMAPPER.createArrayNode(); + result.add(OBJECTMAPPER.valueToTree(installResult.asOutputDescriptor())); + result.addAll(preparer.getToolInstallationOutputDescriptors()); + return result; } } - - private final File download(ToolVersionDownloadDescriptor descriptor) throws IOException { - File tempDownloadFile = File.createTempFile("fcli-tool-download", null); - tempDownloadFile.deleteOnExit(); - download(descriptor.getDownloadUrl(), tempDownloadFile); - return tempDownloadFile; - } - private final Void download(String downloadUrl, File destFile) { - UnirestInstance unirest = GenericUnirestFactory.getUnirestInstance("tool", - u->ProxyHelper.configureProxy(u, "tool", downloadUrl)); - unirest.get(downloadUrl).asFile(destFile.getAbsolutePath(), StandardCopyOption.REPLACE_EXISTING).getBody(); - return null; + private final Path getInstallPath() { + return installOrBaseDirArgGroup.installDir==null + ? null + : installOrBaseDirArgGroup.installDir.toPath(); } - protected void install(ToolVersionInstallDescriptor descriptor, File downloadedFile) throws IOException { - Path installPath = descriptor.getInstallPath(); - Files.createDirectories(installPath); - InstallType installType = getInstallType(); - switch (installType) { - // TODO Clean this up - case COPY: Files.copy(downloadedFile.toPath(), installPath.resolve(StringUtils.substringAfterLast(descriptor.getOriginalDownloadDescriptor().getDownloadUrl(), "/")), StandardCopyOption.REPLACE_EXISTING); break; - case EXTRACT_ZIP: FileUtils.extractZip(downloadedFile, installPath); break; - default: throw new RuntimeException("Unknown install type: "+installType.name()); + private final Path getBasePath() { + var basePath = installOrBaseDirArgGroup.baseDir==null + ? null + : installOrBaseDirArgGroup.baseDir.toPath(); + if ( getInstallPath()==null && basePath==null ) { + basePath = Path.of(System.getProperty("user.home"),"fortify", "tools"); } - downloadedFile.delete(); - postInstall(descriptor); - updateBinPermissions(descriptor.getBinPath()); + return basePath; } - @SneakyThrows - protected Path getInstallPathOrDefault(ToolVersionDownloadDescriptor descriptor) { - if ( installDir == null ) { - installDir = FcliDataHelper.getFortifyHomePath().resolve(String.format("tools/%s/%s", getToolName(), descriptor.getVersion())).toFile(); + private final Path getTargetPath(ToolInstaller toolInstaller) { + var installPath = getInstallPath(); + Path result = null; + if ( installPath!=null ) { + toolInstaller.getProgressWriter().writeWarning("WARN: --install-dir option is deprecated"); + result = installPath; + } else { + var basePath = getBasePath(); + result = basePath.resolve(String.format("%s/%s", getToolName(), toolInstaller.getToolVersion())); } - return installDir.getCanonicalFile().toPath(); + return result.normalize().toAbsolutePath(); } - - protected Path getBinPath(ToolVersionDownloadDescriptor descriptor) { - return getInstallPathOrDefault(descriptor).resolve("bin"); + + private final Path getGlobalBinPath(ToolInstaller toolInstaller) { + var basePath = getBasePath(); + return basePath==null || !installGlobalBin ? null : basePath.resolve("bin"); } - protected abstract String getToolName(); - protected abstract InstallType getInstallType(); - protected abstract void postInstall(ToolVersionInstallDescriptor installDescriptor) throws IOException; - - private final void emptyExistingInstallPath(Path installPath) throws IOException { - if ( Files.exists(installPath) && Files.list(installPath).findFirst().isPresent() ) { - requireConfirmation.checkConfirmed(); - FileUtils.deleteRecursive(installPath); + private final class ToolInstallationPreparer implements Consumer { + @Getter private final ArrayNode toolInstallationOutputDescriptors = OBJECTMAPPER.createArrayNode(); + private ToolInstaller installer; + private ToolUninstaller uninstaller; + + @Override + public void accept(ToolInstaller installer) { + this.installer = installer; + this.uninstaller = new ToolUninstaller(installer.getToolName()); + prepare(); } - } - - private final void checkDigest(ToolVersionDownloadDescriptor descriptor, File downloadedFile) { - String actualDigest = FileUtils.getFileDigest(downloadedFile, descriptor.getDigestAlgorithm()); - String expectedDigest = descriptor.getExpectedDigest(); - if ( !actualDigest.equals(expectedDigest) ) { - String msg = "Digest mismatch" - +"\n Expected: "+expectedDigest - +"\n Actual: "+actualDigest; - switch(onDigestMismatch) { - case fail: throw new IllegalStateException(msg); - case warn: LOG.warn(msg); + + @SneakyThrows + private final void prepare() { + Map requiredPreparations = new LinkedHashMap(); + addTargetDirPreparation(requiredPreparations); + addUninstallPreparations(requiredPreparations); + prepare(requiredPreparations); + } + + private final void prepare(Map requiredPreparations) { + if ( !requiredPreparations.isEmpty() ) { + // Generate message for prompt. This includes the required preparation actions + // from requiredPreparations, and for clarity, also the installation action. + String msg = String.format("\n %s\n Install %s %s to %s", + String.join("\n ", requiredPreparations.keySet()), + installer.getToolName(), installer.getToolVersion(), installer.getTargetPath()); + requireConfirmation.checkConfirmed(msg); + requiredPreparations.values().forEach(Runnable::run); } } - } - - private static final void updateBinPermissions(Path binPath) throws IOException { - try (Stream walk = Files.walk(binPath)) { - walk.forEach(AbstractToolInstallCommand::updateFilePermissions); + + @SneakyThrows + private final void addTargetDirPreparation(Map requiredPreparations) { + var targetPath = installer.getTargetPath(); + if ( Files.exists(targetPath) ) { + var existingVersionsWithSameTargetPath = getVersionsStream() + .filter(d->!isCandidateForUninstall(d)) + .filter(d->installer.hasMatchingTargetPath(d)) + .map(ToolDefinitionVersionDescriptor::getVersion) + .collect(Collectors.toList()); + var otherVersionsWithSameTargetPath = existingVersionsWithSameTargetPath.stream() + .filter(v->!v.equals(installer.getToolVersion())) + .collect(Collectors.toList()); + if ( !otherVersionsWithSameTargetPath.isEmpty() ) { + throw new IllegalStateException(String.format("Target path %s already in use for versions: %s\nUse --replace option to explicitly uninstall the existing versions", targetPath, String.join(", ", otherVersionsWithSameTargetPath))); + } else if ( existingVersionsWithSameTargetPath.isEmpty() ) { + // Basically we're moving the tool installation to a different directory + requiredPreparations.put("Clean target directory "+targetPath, ()->deleteRecursive(targetPath)); + } + } + } + + private final void addUninstallPreparations(Map requiredPreparations) { + if ( !versionsToUninstall.isEmpty() ) { + getVersionsStream() + .filter(this::isCandidateForUninstall) + .forEach(vd->addUninstallPreparation(vd, requiredPreparations)); + } + } + + private final void addUninstallPreparation(ToolDefinitionVersionDescriptor versionDescriptor, Map requiredPreparations) { + var toolName = installer.getToolName(); + var installationDescriptor = ToolInstallationDescriptor.load(toolName, versionDescriptor); + if ( installationDescriptor!=null ) { + var msg = String.format("Uninstall %s v%s from %s", toolName, versionDescriptor.getVersion(), installationDescriptor.getInstallDir()); + requiredPreparations.put(msg, ()->uninstall(versionDescriptor, installationDescriptor)); + } } - } - @SneakyThrows - private static final void updateFilePermissions(Path p) { - try { - Files.setPosixFilePermissions(p, binPermissions); - } catch ( UnsupportedOperationException e ) { - // Log warning? + private final void deleteRecursive(Path targetPath) { + installer.getProgressWriter().writeProgress("Cleaning target directory %s", targetPath); + FileUtils.deleteRecursive(targetPath); + } + + private final void uninstall(ToolDefinitionVersionDescriptor versionDescriptor, ToolInstallationDescriptor installationDescriptor) { + var toolName = installer.getToolName(); + var toolVersion = versionDescriptor.getVersion(); + var installPath = installationDescriptor.getInstallPath(); + installer.getProgressWriter().writeProgress("Uninstalling %s %s from %s", toolName, toolVersion, installPath); + var outputDescriptor = uninstaller.uninstall(versionDescriptor, installationDescriptor, installer.getVersionDescriptor()); + toolInstallationOutputDescriptors.add(OBJECTMAPPER.valueToTree(outputDescriptor)); + } + + private final Stream getVersionsStream() { + return installer.getDefinitionRootDescriptor().getVersionsStream(); + } + + /** + * The given version descriptor is considered a candidate for uninstall + * if all of the following conditions are met: + * - {@link ToolInstallationHelper#isCandidateForUninstall(String, Set, ToolDefinitionVersionDescriptor)} + * returns true + * - The version doesn't match the target version to be installed, + * or target path is different from existing installation + */ + private final boolean isCandidateForUninstall(ToolDefinitionVersionDescriptor d) { + var toolName = installer.getToolName(); + return ToolInstallationHelper.isCandidateForUninstall(toolName, versionsToUninstall, d) + && !(d.getVersion().equals(installer.getToolVersion()) && installer.hasMatchingTargetPath(d)); } - } - - protected static enum InstallType { - EXTRACT_ZIP, COPY } } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolListCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolListCommand.java index 42dc758ffe..bd52dad487 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolListCommand.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolListCommand.java @@ -18,23 +18,32 @@ import com.fortify.cli.common.json.JsonHelper; import com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand; import com.fortify.cli.common.output.cli.cmd.IJsonNodeSupplier; -import com.fortify.cli.tool._common.helper.ToolHelper; +import com.fortify.cli.tool._common.helper.ToolInstallationDescriptor; +import com.fortify.cli.tool._common.helper.ToolInstallationOutputDescriptor; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionVersionDescriptor; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionsHelper; public abstract class AbstractToolListCommand extends AbstractOutputCommand implements IJsonNodeSupplier { private static final ObjectMapper objectMapper = new ObjectMapper(); @Override public final JsonNode getJsonNode() { - String toolName = getToolName(); - return ToolHelper.getToolVersionCombinedDescriptorsStream(toolName) + return ToolDefinitionsHelper.getToolDefinitionRootDescriptor(getToolName()).getVersionsStream() + .map(this::createToolOutputDescriptor) .map(objectMapper::valueToTree) .collect(JsonHelper.arrayNodeCollector()); } @Override - public boolean isSingular() { + public final boolean isSingular() { return false; } protected abstract String getToolName(); + + private ToolInstallationOutputDescriptor createToolOutputDescriptor(ToolDefinitionVersionDescriptor versionDescriptor) { + var toolName = getToolName(); + var installationDescriptor = ToolInstallationDescriptor.load(toolName, versionDescriptor); + return new ToolInstallationOutputDescriptor(toolName, versionDescriptor, installationDescriptor, ""); + } } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolListPlatformsCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolListPlatformsCommand.java new file mode 100644 index 0000000000..59b3a74333 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolListPlatformsCommand.java @@ -0,0 +1,51 @@ +/** + * Copyright 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.tool._common.cli.cmd; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fortify.cli.common.json.JsonHelper; +import com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand; +import com.fortify.cli.common.output.cli.cmd.IJsonNodeSupplier; +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionsHelper; + +import lombok.Getter; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; + +public abstract class AbstractToolListPlatformsCommand extends AbstractOutputCommand implements IJsonNodeSupplier { + @Getter @Mixin private OutputHelperMixins.TableWithQuery outputHelper; + @Getter @Option(names={"-v", "--version"}, required = true, descriptionKey="fcli.tool.list-platforms.version", defaultValue = "latest") + private String version; + + @Override + public final JsonNode getJsonNode() { + return ToolDefinitionsHelper.getToolDefinitionRootDescriptor(getToolName()) + .getVersion(version).getBinaries().keySet().stream() + .map(this::createObjectNode) + .collect(JsonHelper.arrayNodeCollector()); + } + + @Override + public final boolean isSingular() { + return false; + } + + protected abstract String getToolName(); + + private final ObjectNode createObjectNode(String platform) { + return JsonHelper.getObjectMapper().createObjectNode() + .put("platform", platform); + } +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolUninstallCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolUninstallCommand.java index 4b2cff9341..a323d817d5 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolUninstallCommand.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/cli/cmd/AbstractToolUninstallCommand.java @@ -12,59 +12,123 @@ *******************************************************************************/ package com.fortify.cli.tool._common.cli.cmd; -import java.io.IOException; -import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fortify.cli.common.cli.mixin.CommonOptionMixins; +import com.fortify.cli.common.cli.util.CommandGroup; +import com.fortify.cli.common.json.JsonHelper; import com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand; import com.fortify.cli.common.output.cli.cmd.IJsonNodeSupplier; import com.fortify.cli.common.output.transform.IActionCommandResultSupplier; -import com.fortify.cli.common.util.FileUtils; -import com.fortify.cli.tool._common.helper.ToolHelper; -import com.fortify.cli.tool._common.helper.ToolVersionCombinedDescriptor; +import com.fortify.cli.common.progress.cli.mixin.ProgressWriterFactoryMixin; +import com.fortify.cli.common.progress.helper.IProgressWriterI18n; +import com.fortify.cli.tool._common.helper.ToolInstallationDescriptor; +import com.fortify.cli.tool._common.helper.ToolInstallationHelper; +import com.fortify.cli.tool._common.helper.ToolUninstaller; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionRootDescriptor; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionVersionDescriptor; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionsHelper; import lombok.Getter; +import lombok.SneakyThrows; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; +@CommandGroup("uninstall") public abstract class AbstractToolUninstallCommand extends AbstractOutputCommand implements IJsonNodeSupplier, IActionCommandResultSupplier { - @Getter @Option(names={"-v", "--version"}, required = true, descriptionKey="fcli.tool.uninstall.version", defaultValue = "default") - private String version; + private static final ObjectMapper OBJECTMAPPER = JsonHelper.getObjectMapper(); + @Getter @Option(names={"-v", "--versions"}, required = true, split=",", descriptionKey="fcli.tool.uninstall.versions") + private Set versionsToUninstall; @Mixin private CommonOptionMixins.RequireConfirmation requireConfirmation; + @Mixin private ProgressWriterFactoryMixin progressWriterFactory; @Override public final JsonNode getJsonNode() { - String toolName = getToolName(); - ToolVersionCombinedDescriptor descriptor = ToolHelper.loadToolVersionCombinedDescriptor(toolName, version); - if ( descriptor==null ) { - throw new IllegalArgumentException("Tool installation not found"); + try ( var progressWriter = progressWriterFactory.create() ) { + var runner = new ToolUninstallationRunner(progressWriter, getToolName()); + runner.run(); + return runner.getToolInstallationOutputDescriptors(); } - Path installPath = descriptor.getInstallPath(); - if ( installPath==null ) { - throw new IllegalStateException("Tool installation path not found"); - } - requireConfirmation.checkConfirmed(); - try { - FileUtils.deleteRecursive(installPath); - } catch ( IOException e ) { - throw new RuntimeException("Error deleting tool installation; please manually delete the "+installPath+" directory", e); - } finally { - ToolHelper.deleteToolVersionInstallDescriptor(toolName, version); - } - return new ObjectMapper().valueToTree(descriptor); } - + @Override - public String getActionCommandResult() { + public final String getActionCommandResult() { return "UNINSTALLED"; } @Override - public boolean isSingular() { - return true; + public final boolean isSingular() { + return false; } protected abstract String getToolName(); + + // TODO Remove code duplication between this class and AbstractToolInstallCommand::ToolInstallationPreparer + private final class ToolUninstallationRunner { + @Getter private final ArrayNode toolInstallationOutputDescriptors = OBJECTMAPPER.createArrayNode(); + private final IProgressWriterI18n progressWriter; + private final String toolName; + private final ToolUninstaller uninstaller; + private final ToolDefinitionRootDescriptor definitionRootDescriptor; + + private ToolUninstallationRunner(IProgressWriterI18n progressWriter, String toolName) { + this.progressWriter = progressWriter; + this.toolName = toolName; + this.uninstaller = new ToolUninstaller(toolName); + this.definitionRootDescriptor = ToolDefinitionsHelper.getToolDefinitionRootDescriptor(toolName); + } + + @SneakyThrows + public final void run() { + Map actions = new LinkedHashMap(); + addUninstallActions(actions); + run(actions); + } + + private final void run(Map actions) { + if ( !actions.isEmpty() ) { + String msg = String.format("\n %s", String.join("\n ", actions.keySet())); + requireConfirmation.checkConfirmed(msg); + actions.values().forEach(Runnable::run); + } + } + + private final void addUninstallActions(Map actions) { + if ( !versionsToUninstall.isEmpty() ) { + getVersionsStream() + .filter(this::isCandidateForUninstall) + .forEach(vd->addUninstallPreparation(vd, actions)); + } + } + + private final void addUninstallPreparation(ToolDefinitionVersionDescriptor versionDescriptor, Map actions) { + var installationDescriptor = ToolInstallationDescriptor.load(toolName, versionDescriptor); + if ( installationDescriptor!=null ) { + var msg = String.format("Uninstall %s v%s from %s", toolName, versionDescriptor.getVersion(), installationDescriptor.getInstallDir()); + actions.put(msg, ()->uninstall(versionDescriptor, installationDescriptor)); + } + } + + private final void uninstall(ToolDefinitionVersionDescriptor versionDescriptor, ToolInstallationDescriptor installationDescriptor) { + var toolVersion = versionDescriptor.getVersion(); + var installPath = installationDescriptor.getInstallPath(); + progressWriter.writeProgress("Uninstalling %s %s from %s", toolName, toolVersion, installPath); + var outputDescriptor = uninstaller.uninstall(versionDescriptor, installationDescriptor); + toolInstallationOutputDescriptors.add(OBJECTMAPPER.valueToTree(outputDescriptor)); + } + + private final Stream getVersionsStream() { + return definitionRootDescriptor.getVersionsStream(); + } + + private final boolean isCandidateForUninstall(ToolDefinitionVersionDescriptor d) { + return ToolInstallationHelper.isCandidateForUninstall(toolName, versionsToUninstall, d); + } + } } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/SignatureHelper.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/SignatureHelper.java new file mode 100644 index 0000000000..388d13924c --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/SignatureHelper.java @@ -0,0 +1,91 @@ +package com.fortify.cli.tool._common.helper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SignatureHelper { + private static final Logger LOG = LoggerFactory.getLogger(SignatureHelper.class); + private static String pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArij9U9yJVNc53oEMFWYp" + + "NrXUG1UoRZseDh/p34q1uywD70RGKKWZvXIcUAZZwbZtCu4i0UzsrKRJeUwqanbc" + + "woJvYanp6lc3DccXUN1w1Y0WOHOaBxiiK3B1TtEIH1cK/X+ZzazPG5nX7TSGh8Tp" + + "/uxQzUFli2mDVLqaP62/fB9uJ2joX9Gtw8sZfuPGNMRoc8IdhjagbFkhFT7WCZnk" + + "FH/4Co007lmXLAe12lQQqR/pOTeHJv1sfda1xaHtj4/Tcrq04Kx0ZmGAd5D9lA92" + + "8pdBbzoe/mI5/Sk+nIY3AHkLXB9YAaKJf//Wb1yiP1/hchtVkfXyIaGM+cVyn7AN" + + "VQIDAQAB"; + + public static final void verifyFileSignature(File destFile, String expectedSignature, boolean throwOnFailure) { + try { + Signature signature = createSignature(); + updateSignature(signature, destFile); + verifySignature(signature, expectedSignature); + } catch (Exception e) { + handleSignatureException(e, throwOnFailure); + } + } + + private static Signature createSignature() throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decodeBase64(pubKey)); + KeyFactory kf = KeyFactory.getInstance("RSA"); + PublicKey pub = kf.generatePublic(spec); + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initVerify(pub); + return signature; + } + + private static void updateSignature(Signature signature, File destFile) + throws IOException, SignatureException, FileNotFoundException { + try ( var is = new FileInputStream(destFile); ) { + byte[] buffer = new byte[4096]; + int read = 0; + while ( (read = is.read(buffer)) > 0 ) { + signature.update(buffer, 0, read); + } + } + } + + private static void verifySignature(Signature signature, String expectedSignature) + throws SignatureException, SignatureMismatchException { + if(!signature.verify(Base64.decodeBase64(expectedSignature))) { + String msg = "Signature mismatch" + +"\n Expected: "+expectedSignature + +"\n Actual: "+signature.hashCode(); + throw new SignatureMismatchException(msg); + } + } + + private static void handleSignatureException(Exception e, boolean throwOnFailure) { + if(!throwOnFailure) { + LOG.warn("Signature verification failed", e); + } else { + if ( e instanceof RuntimeException ) { + throw (RuntimeException)e; + } else { + throw new IllegalStateException("Signature verification failed", e); + } + } + } + + public static final class SignatureMismatchException extends IllegalStateException { + private static final long serialVersionUID = 1L; + public SignatureMismatchException(String msg) { + super(msg); + } + } + + +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolDownloadDescriptor.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolDownloadDescriptor.java deleted file mode 100644 index 1fc8346afe..0000000000 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolDownloadDescriptor.java +++ /dev/null @@ -1,68 +0,0 @@ -/******************************************************************************* - * 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.tool._common.helper; - -import java.util.stream.Stream; - -import com.formkiq.graalvm.annotations.Reflectable; -import com.fortify.cli.common.util.StringUtils; - -import lombok.Data; -import lombok.NoArgsConstructor; - -@Reflectable @NoArgsConstructor -@Data -public class ToolDownloadDescriptor { - private String defaultDownloadUrl; - private String defaultVersion; - private ToolVersionDownloadDescriptor[] versions; - - public final ToolVersionDownloadDescriptor[] getVersions() { - return getVersionsStream().toArray(ToolVersionDownloadDescriptor[]::new); - } - - public final Stream getVersionsStream() { - return Stream.of(versions) - .map(this::updateDownloadUrl) - .map(this::addIsDefaultVersion); - } - - public final ToolVersionDownloadDescriptor getVersion(String version) { - var lookupVersion = (version.replaceFirst("^v", "")+".").replaceFirst("\\.\\.$", "."); - return getVersionsStream() - .filter(v->(v.getVersion()+".").startsWith(lookupVersion)) - .findFirst().orElseThrow(()->new IllegalArgumentException("Version "+version+" not defined")); - } - - public final ToolVersionDownloadDescriptor getVersionOrDefault(String versionName) { - if ( StringUtils.isBlank(versionName) || "default".equals(versionName) || "latest".equals(versionName) ) { - versionName = defaultVersion; - } - return getVersion(versionName); - } - - private final ToolVersionDownloadDescriptor updateDownloadUrl(ToolVersionDownloadDescriptor versionDescriptor) { - if ( StringUtils.isBlank(versionDescriptor.getDownloadUrl()) ) { - versionDescriptor.setDownloadUrl(defaultDownloadUrl); - } - versionDescriptor.setDownloadUrl(versionDescriptor.getDownloadUrl().replaceAll("\\{toolVersion\\}", versionDescriptor.getVersion())); - return versionDescriptor; - } - - private final ToolVersionDownloadDescriptor addIsDefaultVersion(ToolVersionDownloadDescriptor versionDescriptor) { - if ( versionDescriptor.getVersion().equals(defaultVersion) ) { - versionDescriptor.setIsDefaultVersion("Yes"); - } - return versionDescriptor; - } -} \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolHelper.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolHelper.java deleted file mode 100644 index 6c2d44ecd2..0000000000 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolHelper.java +++ /dev/null @@ -1,78 +0,0 @@ -/******************************************************************************* - * 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.tool._common.helper; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.util.stream.Stream; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.fortify.cli.common.util.FcliDataHelper; - -public final class ToolHelper { - private static final ObjectMapper yamlObjectMapper = new ObjectMapper(new YAMLFactory()); - - public static final ToolDownloadDescriptor getToolDownloadDescriptor(String toolName) { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - String resourceFile = getResourceFile(toolName, String.format("%s.yaml", toolName)); - try ( InputStream file = classLoader.getResourceAsStream(resourceFile) ) { - return yamlObjectMapper.readValue(file, ToolDownloadDescriptor.class); - } catch (IOException e) { - throw new RuntimeException("Error loading resource file: "+resourceFile, e); - } - } - - public static final ToolVersionCombinedDescriptor saveToolVersionInstallDescriptor(String toolName, ToolVersionInstallDescriptor installDescriptor) { - ToolVersionDownloadDescriptor downloadDescriptor = installDescriptor.getOriginalDownloadDescriptor(); - FcliDataHelper.saveFile(getInstallDescriptorPath(toolName, downloadDescriptor.getVersion()), installDescriptor, true); - return new ToolVersionCombinedDescriptor(toolName, downloadDescriptor, installDescriptor); - } - - public static final ToolVersionInstallDescriptor loadToolVersionInstallDescriptor(String toolName, String version) { - return FcliDataHelper.readFile(getInstallDescriptorPath(toolName, version), ToolVersionInstallDescriptor.class, false); - } - - public static final ToolVersionCombinedDescriptor loadToolVersionCombinedDescriptor(String toolName, String version) { - version = getToolDownloadDescriptor(toolName).getVersionOrDefault(version).getVersion(); - ToolVersionInstallDescriptor installDescriptor = loadToolVersionInstallDescriptor(toolName, version); - return installDescriptor==null ? null : new ToolVersionCombinedDescriptor(toolName, getToolDownloadDescriptor(toolName).getVersion(version), installDescriptor); - } - - public static final void deleteToolVersionInstallDescriptor(String toolName, String version) { - FcliDataHelper.deleteFile(getInstallDescriptorPath(toolName, version), true); - } - - public static final ToolVersionCombinedDescriptor[] getToolVersionCombinedDescriptors(String toolName) { - return getToolVersionCombinedDescriptorsStream(toolName) - .toArray(ToolVersionCombinedDescriptor[]::new); - } - - public static final Stream getToolVersionCombinedDescriptorsStream(String toolName) { - return getToolDownloadDescriptor(toolName).getVersionsStream() - .map(d->new ToolVersionCombinedDescriptor(toolName, d, loadToolVersionInstallDescriptor(toolName, d.getVersion()))); - } - - public static final String getResourceDir(String toolName) { - return String.format("com/fortify/cli/tool/%s", toolName); - } - - public static final String getResourceFile(String toolName, String fileName) { - return String.format("%s/%s", getResourceDir(toolName), fileName); - } - - private static final Path getInstallDescriptorPath(String toolName, String version) { - return FcliDataHelper.getFcliStatePath().resolve("tools").resolve(toolName).resolve(version); - } -} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstallationDescriptor.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstallationDescriptor.java new file mode 100644 index 0000000000..339bac2d20 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstallationDescriptor.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright 2021, 2022 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.tool._common.helper; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.util.FcliDataHelper; +import com.fortify.cli.common.util.StringUtils; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionVersionDescriptor; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * This class represents a single tool installation, containing information about the + * installation location. It doesn't include the actual tool name or version, as this + * is represented by the directory name (tool name) and file name (version) where the + * serialized installation descriptors are stored. + */ +@JsonIgnoreProperties(ignoreUnknown=true) +@Reflectable @NoArgsConstructor @AllArgsConstructor +@Data +public class ToolInstallationDescriptor { + private String installDir; + private String binDir; + + public ToolInstallationDescriptor(Path installPath, Path binPath) { + this.installDir = installPath.toAbsolutePath().toString(); + this.binDir = binPath.toAbsolutePath().toString(); + } + + public static final ToolInstallationDescriptor load(String toolName, ToolDefinitionVersionDescriptor versionDescriptor) { + var result = FcliDataHelper.readFile(getInstallDescriptorPath(toolName, versionDescriptor.getVersion()), ToolInstallationDescriptor.class, false); + // Check for stale descriptor + if ( result!=null && !Files.exists(result.getInstallPath()) ) { + delete(toolName, versionDescriptor); + result = null; + } + return result; + } + + public static final void delete(String toolName, ToolDefinitionVersionDescriptor versionDescriptor) { + FcliDataHelper.deleteFile(getInstallDescriptorPath(toolName, versionDescriptor.getVersion()), true); + } + + public final void save(String toolName, ToolDefinitionVersionDescriptor versionDescriptor) { + FcliDataHelper.saveFile(getInstallDescriptorPath(toolName, versionDescriptor.getVersion()), this, true); + } + + public Path getInstallPath() { + return asPath(installDir); + } + + public Path getBinPath() { + return asPath(binDir); + } + + private static final Path asPath(String dir) { + return StringUtils.isNotBlank(dir) ? Paths.get(dir) : null; + } + + private static final Path getInstallDescriptorPath(String toolName, String version) { + return getInstallDescriptorsDirPath(toolName).resolve(version); + } + + private static Path getInstallDescriptorsDirPath(String toolName) { + return ToolInstallationHelper.getToolsStatePath().resolve(toolName); + } + +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstallationHelper.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstallationHelper.java new file mode 100644 index 0000000000..f298ef0a38 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstallationHelper.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.tool._common.helper; + +import java.nio.file.Path; +import java.util.Set; + +import com.fortify.cli.common.util.FcliDataHelper; +import com.fortify.cli.common.util.SemVerHelper; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionVersionDescriptor; + +public final class ToolInstallationHelper { + public static final String getResourcePath(String subPath) { + return String.format("com/fortify/cli/tool/%s", subPath); + } + + public static final String getResourcePath(String toolName, String subPath) { + return getResourcePath(String.format("%s/%s", toolName.replace("-", "_"), subPath)); + } + + public static final Path getToolsStatePath() { + return FcliDataHelper.getFcliStatePath().resolve("tools"); + } + + /** + * The given version descriptor is considered a candidate for uninstall + * if all of the following conditions are met: + * - The full version is listed in --uninstall + * - The major version is listed in --uninstall + * - The major & minor version is listed in --uninstall + * - An installation descriptor for the version exists + */ + public static final boolean isCandidateForUninstall(String toolName, Set versionsToUninstall, ToolDefinitionVersionDescriptor versionDescriptor) { + var version = versionDescriptor.getVersion(); + return (versionsToUninstall.contains("all") + || versionsToUninstall.contains(version) + || versionsToUninstall.contains(SemVerHelper.getMajor(version).orElse("N/A")) + || versionsToUninstall.contains(SemVerHelper.getMajorMinor(version).orElse("N/A"))) + && ToolInstallationDescriptor.load(toolName, versionDescriptor)!=null; + } +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstallationOutputDescriptor.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstallationOutputDescriptor.java new file mode 100644 index 0000000000..ee4233c6f1 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstallationOutputDescriptor.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.tool._common.helper; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.util.StringUtils; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionArtifactDescriptor; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionVersionDescriptor; + +import lombok.Data; + +/** + * This descriptor defines the structure used as output for the various + * tool commands. + * + * @author Ruud Senden + */ +@Reflectable // We only serialize, not de-serialize, so no need for no-args contructor +@Data +public class ToolInstallationOutputDescriptor { + private final String name; + private final String version; + private final String[] aliases; + private final String aliasesString; + private final String stable; + private Map binaries; + private final String installDir; + private final String binDir; + private final String installed; + private final String __action__; + + public ToolInstallationOutputDescriptor(String toolName, ToolDefinitionVersionDescriptor versionDescriptor, ToolInstallationDescriptor installationDescriptor, String action) { + this.name = toolName; + this.version = versionDescriptor.getVersion(); + this.aliases = reverse(versionDescriptor.getAliases()); + this.aliasesString = String.join(", ", aliases); + this.stable = versionDescriptor.isStable()?"Yes":"No"; + this.binaries = versionDescriptor.getBinaries(); + this.installDir = installationDescriptor==null ? null : installationDescriptor.getInstallDir(); + this.binDir = installationDescriptor==null ? null : installationDescriptor.getBinDir(); + this.installed = StringUtils.isBlank(this.installDir) ? "No" : "Yes"; + this.__action__ = action; + } + + private static final String[] reverse(String[] array) { + List list = Arrays.asList(array); + Collections.reverse(list); + return list.toArray(String[]::new); + } +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstaller.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstaller.java new file mode 100644 index 0000000000..148048c5c9 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolInstaller.java @@ -0,0 +1,330 @@ +/******************************************************************************* + * 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.tool._common.helper; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import com.fortify.cli.common.progress.helper.IProgressWriterI18n; +import com.fortify.cli.common.rest.unirest.UnirestHelper; +import com.fortify.cli.common.util.FileUtils; +import com.fortify.cli.common.util.StringUtils; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionArtifactDescriptor; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionRootDescriptor; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionVersionDescriptor; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionsHelper; + +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.SneakyThrows; + +@Builder +public final class ToolInstaller { + private static final Set binPermissions = PosixFilePermissions.fromString("rwxr-xr-x"); + @Getter private final String toolName; + @Getter private final String requestedVersion; + @Getter private final String defaultPlatform; + @Getter private final Function targetPathProvider; + @Getter private final Function globalBinPathProvider; + @Getter private final DigestMismatchAction onDigestMismatch; + @Getter private final Consumer preInstallAction; + @Getter private final BiConsumer postInstallAction; + @Getter private final IProgressWriterI18n progressWriter; + private final LazyObject _definitionRootDescriptor = new LazyObject<>(); + private final LazyObject _versionDescriptor = new LazyObject<>(); + private final LazyObject _previousInstallationDescriptor = new LazyObject<>(); + private final LazyObject _targetPath = new LazyObject<>(); + private final LazyObject _globalBinPath = new LazyObject<>(); + + @Data + public static final class ToolInstallationResult { + private final String toolName; + private final ToolDefinitionVersionDescriptor versionDescriptor; + private final ToolDefinitionArtifactDescriptor artifactDescriptor; + private final ToolInstallationDescriptor installationDescriptor; + + public final ToolInstallationOutputDescriptor asOutputDescriptor() { + return new ToolInstallationOutputDescriptor(toolName, versionDescriptor, installationDescriptor, "INSTALLED"); + } + } + + public static enum DigestMismatchAction { + fail, warn + } + + public static enum BinScriptType { + bash, bat + } + + public final ToolDefinitionRootDescriptor getDefinitionRootDescriptor() { + return _definitionRootDescriptor.get(()->ToolDefinitionsHelper.getToolDefinitionRootDescriptor(toolName)); + } + + public final ToolDefinitionVersionDescriptor getVersionDescriptor() { + return _versionDescriptor.get(()->getDefinitionRootDescriptor().getVersionOrDefault(requestedVersion)); + } + + public final ToolInstallationDescriptor getPreviousInstallationDescriptor() { + return _previousInstallationDescriptor.get(()->ToolInstallationDescriptor.load(toolName, getVersionDescriptor())); + } + + public final Path getTargetPath() { + return _targetPath.get(()->targetPathProvider.apply(this)); + } + + public final Path getBinPath() { + return getTargetPath().resolve("bin"); + } + + public final Path getGlobalBinPath() { + return _globalBinPath.get(()->globalBinPathProvider.apply(this)); + } + + public final String getToolVersion() { + return getVersionDescriptor().getVersion(); + } + + public final boolean hasMatchingTargetPath(ToolDefinitionVersionDescriptor versionDescriptor) { + var installationDescriptor = ToolInstallationDescriptor.load(toolName, versionDescriptor); + var currentToolInstallPath = installationDescriptor==null ? null: installationDescriptor.getInstallPath().normalize(); + var targetToolInstallPath = getTargetPath().normalize(); + return targetToolInstallPath.equals(currentToolInstallPath) && Files.exists(targetToolInstallPath); + } + + public final ToolInstallationResult install() { + var artifactDescriptor = getArtifactDescriptor(ToolPlatformHelper.getPlatform()) + .orElseGet(()->getArtifactDescriptor(defaultPlatform) + .orElseThrow(()->new IllegalStateException("Appropriate artifact for system platform cannot be determined automatically, please specify platform explicitly"))); + return install(artifactDescriptor); + } + + public final ToolInstallationResult install(String platform) { + var artifactDescriptor = getArtifactDescriptor(platform) + .orElseThrow(()->new IllegalStateException(String.format("No matching artifact found for platform %s", platform))); + return install(artifactDescriptor); + } + + /** + * This method can be called by Tool*InstallCommands to install bin-scripts for + * Java-based tools. It will install both the tool version specific bin-scripts + * and global bin-scripts (if applicable). If global bin scripts already exist, + * they will be replaced. If tool version specific bin-scripts already exist, + * they will not be replaced, for the following reasons: + *
    + *
  • If the script already exists, it means that we're doing an update instead + * of full install, so we want to make sure that the scripts match the current + * install. For example, suppose fcli was first installed using --platform linux/x64 + * and later 're-installed' with --platform java, we'd be installing scripts for + * Java even though we didn't actually install the jar-file.
  • + *
  • On Windows, updating existing batch files while running can cause strange behavior. + * For example, suppose fcli 3.0.0 was installed from fcli 2.2.0, and a 're-install' + * for fcli 3.0.0 is done using fcli 3.0.0. If fcli 3.0.0 would overwrite the existing + * batch files with different contents, this could cause incorrect behavior and likely + * error messages once Windows resumes batch file execution once fcli has finished.
  • + *
+ * @param scriptBaseName Base name (without extension) for the scripts to be installed + * @param binScriptTargetJar Path to the jar-file, relative to the tool installation directory + */ + public final void installJavaBinScripts(String scriptBaseName, String binScriptTargetJar) { + var scriptTargetFilePath = getTargetPath().resolve(binScriptTargetJar); + if ( !Files.exists(scriptTargetFilePath) ) { + throw new IllegalStateException("Cannot install bin scripts; target jar doesn't exist: "+scriptTargetFilePath); + } + for ( var type : BinScriptType.values() ) { + var resourceFile = ToolInstallationHelper.getResourcePath("extra-files/java-bin/"+type.name()); + var replacements = getResourceReplacementsMap(getTargetPath(), scriptTargetFilePath); + var scriptName = type==BinScriptType.bash ? scriptBaseName : scriptBaseName+".bat"; + installResource(resourceFile, getBinPath().resolve(scriptName), false, replacements); + installGlobalBinScript(type, scriptName, "bin/"+scriptName); + } + } + + /** + * Install a global bin-script of the given {@link BinScriptType} with the given globalBinScriptName. + * The installed script will invoke the given globalBinScriptTarget. The script will only be installed + * if a global bin path has been configured. Note that for Java-based tools, the + * {@link #installJavaBinScripts(String, String)} method should be used instead, which installs + * both tool version specific bin-scripts and corresponding global bin-scripts. + * @param type Type of the bin script; bash or bat + * @param globalBinScriptName Name of the script to be installed to the global bin directory + * @param globalBinScriptTarget Target script/executable to be invoked by the global bin-script, + * relative to the tool installation directory. + */ + @SneakyThrows + public final void installGlobalBinScript(BinScriptType type, String globalBinScriptName, String globalBinScriptTarget) { + var globalBinPath = getGlobalBinPath(); + if ( globalBinPath!=null ) { + var resourceFile = ToolInstallationHelper.getResourcePath("extra-files/global-bin/"+type.name()); + var globalBinScriptPath = globalBinPath.resolve(globalBinScriptName); + var scriptTargetFilePath = getTargetPath().resolve(globalBinScriptTarget); + if ( Files.exists(scriptTargetFilePath) ) { + var replacements = getResourceReplacementsMap(globalBinPath.getParent(), scriptTargetFilePath); + installResource(resourceFile, globalBinScriptPath, true, replacements); + updateFilePermissions(globalBinScriptPath); + } + } + } + + + private final ToolInstallationResult install(ToolDefinitionArtifactDescriptor artifactDescriptor) { + try { + preInstallAction.accept(this); + var versionDescriptor = getVersionDescriptor(); + warnIfDifferentTargetPath(); + if ( !hasMatchingTargetPath(getVersionDescriptor()) ) { + checkEmptyTargetPath(); + downloadAndExtract(artifactDescriptor); + } + var result = new ToolInstallationResult(toolName, versionDescriptor, artifactDescriptor, createAndSaveInstallationDescriptor()); + progressWriter.writeProgress("Running post-install actions"); + postInstallAction.accept(this, result); + updateBinPermissions(result.getInstallationDescriptor().getBinPath()); + return result; + } catch ( IOException e ) { + throw new RuntimeException("Error installing "+toolName, e); + } + } + + private void downloadAndExtract(ToolDefinitionArtifactDescriptor artifactDescriptor) throws IOException { + progressWriter.writeProgress("Downloading tool binaries"); + File downloadedFile = download(artifactDescriptor); + progressWriter.writeProgress("Verifying signature"); + SignatureHelper.verifyFileSignature(downloadedFile, artifactDescriptor.getRsa_sha256(), onDigestMismatch == DigestMismatchAction.fail); + progressWriter.writeProgress("Installing tool binaries"); + copyOrExtract(artifactDescriptor, downloadedFile); + } + + private static final File download(ToolDefinitionArtifactDescriptor artifactDescriptor) throws IOException { + File tempDownloadFile = File.createTempFile("fcli-tool-download", null); + tempDownloadFile.deleteOnExit(); + UnirestHelper.download("tool", artifactDescriptor.getDownloadUrl(), tempDownloadFile); + return tempDownloadFile; + } + + private final void copyOrExtract(ToolDefinitionArtifactDescriptor artifactDescriptor, File downloadedFile) throws IOException { + Path targetPath = getTargetPath(); + Files.createDirectories(targetPath); + var artifactName = artifactDescriptor.getName(); + if (artifactName.endsWith("gz") || artifactName.endsWith(".tar.gz")) { + FileUtils.extractTarGZ(downloadedFile, targetPath); + } else if (artifactDescriptor.getName().endsWith("zip")) { + FileUtils.extractZip(downloadedFile, targetPath); + } else { + Files.copy(downloadedFile.toPath(), targetPath.resolve(artifactDescriptor.getName()), StandardCopyOption.REPLACE_EXISTING); + } + downloadedFile.delete(); + } + + private final ToolInstallationDescriptor createAndSaveInstallationDescriptor() { + var installPath = getTargetPath(); + var binPath = getBinPath(); + var installationDescriptor = new ToolInstallationDescriptor(installPath, binPath); + installationDescriptor.save(toolName, getVersionDescriptor()); + return installationDescriptor; + } + + private final Optional getArtifactDescriptor(String platform) { + return StringUtils.isBlank(platform) + ? Optional.empty() + : Optional.ofNullable(getVersionDescriptor().getBinaries().get(platform)); + } + + @SneakyThrows + private final void warnIfDifferentTargetPath() { + var oldDescriptor = getPreviousInstallationDescriptor(); + var targetPath = getTargetPath(); + if ( oldDescriptor!=null && !oldDescriptor.getInstallPath().toAbsolutePath().equals(targetPath.toAbsolutePath()) ) { + String msg = "WARN: This tool version was previously installed in another directory." + + "\n Fcli will only track the latest installation directory; you may" + + "\n want to manually remove the old installation directory." + + "\n Old: "+oldDescriptor.getInstallDir() + + "\n New: "+targetPath; + progressWriter.writeWarning(msg); + } + } + + private final void checkEmptyTargetPath() throws IOException { + var targetPath = getTargetPath(); + if ( Files.exists(targetPath) && Files.list(targetPath).findFirst().isPresent() ) { + throw new IllegalStateException("Non-empty target path "+targetPath+" already exists"); + } + } + + private static final void updateBinPermissions(Path binPath) throws IOException { + try (Stream walk = Files.walk(binPath)) { + walk.forEach(ToolInstaller::updateFilePermissions); + } + } + + // TODO Move this method to FileUtils or similar, as it's also used by AbstractToolInstallCommand + @SneakyThrows + public static final void updateFilePermissions(Path p) { + try { + Files.setPosixFilePermissions(p, binPermissions); + } catch ( UnsupportedOperationException e ) { + // Log warning? + } + } + + // TODO Is there a standard Java class for this? + private static final class LazyObject { + private T value = null; + public T get(Supplier supplier) { + if ( value==null ) { + value = supplier.get(); + } + return value; + } + } + + @SneakyThrows + private final void installResource(String resourceFile, Path targetPath, boolean replaceExisting, Map replacements) { + if ( replaceExisting || !Files.exists(targetPath) ) { + String contents = FileUtils.readResourceAsString(resourceFile, StandardCharsets.US_ASCII); + if ( replacements!=null ) { + contents = replacements.entrySet().stream() + .map(entry -> (Function) data -> data.replace(entry.getKey(), entry.getValue())) + .reduce(Function.identity(), Function::andThen) + .apply(contents); + } + Files.createDirectories(targetPath.getParent()); + Files.write(targetPath, contents.getBytes("ASCII")); + } + } + + private final Map getResourceReplacementsMap(Path basePath, Path targetPath) { + Map result = new HashMap<>(); + var relativePath = basePath.toAbsolutePath().normalize().relativize(targetPath.toAbsolutePath().normalize()); + var relativePathString = relativePath.toString(); + result.put("{{relativeBashTargetPath}}", relativePathString); + result.put("{{relativeBatTargetPath}}", relativePathString.replace('/', '\\')); + return result; + } + +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolPlatformHelper.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolPlatformHelper.java new file mode 100644 index 0000000000..75bb9dd8c4 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolPlatformHelper.java @@ -0,0 +1,136 @@ +package com.fortify.cli.tool._common.helper; + +import java.util.Locale; + +public class ToolPlatformHelper { + public static final String getOSString() { + return normalizeOs(System.getProperty("os.name", "unknown")); + } + + public static final String getArchString() { + return normalizeArch(System.getProperty("os.arch", "unknown")); + } + + public static final String getPlatform() { + return String.format("%s/%s", getOSString(), getArchString()); + } + + private static String normalizeOs(String value) { + value = normalize(value); + if (value.startsWith("aix")) { + return "linux"; + } + if (value.startsWith("hpux")) { + return "hpux"; + } + if (value.startsWith("os400")) { + // Avoid the names such as os4000 + if (value.length() <= 5 || !Character.isDigit(value.charAt(5))) { + return "os400"; + } + } + if (value.startsWith("linux")) { + return "linux"; + } + if (value.startsWith("mac") || value.startsWith("osx") || value.contains("darwin")) { + return "darwin"; + } + if (value.startsWith("freebsd")) { + return "linux"; + } + if (value.startsWith("openbsd")) { + return "linux"; + } + if (value.startsWith("netbsd")) { + return "linux"; + } + if (value.startsWith("solaris") || value.startsWith("sunos")) { + return "linux"; + } + if (value.startsWith("windows")) { + return "windows"; + } + if (value.startsWith("zos")) { + return "linux"; + } + return value; + } + + private static String normalizeArch(String value) { + value = normalize(value); + if (value.matches("^(x8664|amd64|ia32e|em64t|x64)$")) { + return "x64"; + } + if (value.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) { + return "x86"; + } + if (value.matches("^(ia64w?|itanium64)$")) { + return "itanium_64"; + } + if ("ia64n".equals(value)) { + return "itanium_32"; + } + if (value.matches("^(sparc|sparc32)$")) { + return "sparc_32"; + } + if (value.matches("^(sparcv9|sparc64)$")) { + return "sparc_64"; + } + if (value.matches("^(arm|arm32)$")) { + return "arm_32"; + } + if ("aarch64".equals(value)) { + return "arm64"; + } + if (value.matches("^(mips|mips32)$")) { + return "mips_32"; + } + if (value.matches("^(mipsel|mips32el)$")) { + return "mipsel_32"; + } + if ("mips64".equals(value)) { + return "mips_64"; + } + if ("mips64el".equals(value)) { + return "mipsel_64"; + } + if (value.matches("^(ppc|ppc32)$")) { + return "ppc_32"; + } + if (value.matches("^(ppcle|ppc32le)$")) { + return "ppcle_32"; + } + if ("ppc64".equals(value)) { + return "ppc_64"; + } + if ("ppc64le".equals(value)) { + return "ppcle_64"; + } + if ("s390".equals(value)) { + return "s390_32"; + } + if ("s390x".equals(value)) { + return "s390_64"; + } + if (value.matches("^(riscv|riscv32)$")) { + return "riscv"; + } + if ("riscv64".equals(value)) { + return "riscv64"; + } + if ("e2k".equals(value)) { + return "e2k"; + } + if ("loongarch64".equals(value)) { + return "loongarch_64"; + } + return value; + } + + private static String normalize(String value) { + if (value == null) { + return ""; + } + return value.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", ""); + } +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolUninstaller.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolUninstaller.java new file mode 100644 index 0000000000..5da340aeb5 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolUninstaller.java @@ -0,0 +1,95 @@ +/** + * Copyright 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.tool._common.helper; + +import java.nio.file.Path; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fortify.cli.common.json.JsonHelper; +import com.fortify.cli.common.util.FcliDataHelper; +import com.fortify.cli.common.util.FileUtils; +import com.fortify.cli.common.util.SemVerHelper; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionVersionDescriptor; + +import lombok.Data; + +@Data +public class ToolUninstaller { + private static final Logger LOG = LoggerFactory.getLogger(ToolUninstaller.class); + private static final Path deleteOnStartPath = ToolInstallationHelper.getToolsStatePath().resolve("delete-on-start.json"); + private final String toolName; + + public final ToolInstallationOutputDescriptor uninstall(ToolDefinitionVersionDescriptor versionDescriptor, ToolInstallationDescriptor installationDescriptor) { + return uninstall(versionDescriptor, installationDescriptor, null); + } + + // TODO Remove/update global bin script to point to latest remaining installation? + public final ToolInstallationOutputDescriptor uninstall(ToolDefinitionVersionDescriptor versionDescriptor, ToolInstallationDescriptor installationDescriptor, ToolDefinitionVersionDescriptor replacementVersion) { + var installPath = installationDescriptor.getInstallPath(); + var action = "UNINSTALLED"; + if ( !FileUtils.isDirPathInUse(installPath) ) { + FileUtils.deleteRecursive(installPath); + } else if (replacementVersion==null || SemVerHelper.compare(replacementVersion.getVersion(), "2.2.0")<0 ) { + action = "MANUAL_DELETE_REQUIRED"; + } else { + action = "PENDING_FCLI_RESTART"; + savePendingDelete(installPath); + } + ToolInstallationDescriptor.delete(toolName, versionDescriptor); + return new ToolInstallationOutputDescriptor(toolName, versionDescriptor, installationDescriptor, action); + } + + private void savePendingDelete(Path installPath) { + var deleteOnStartArray = FcliDataHelper.readFile(deleteOnStartPath, ArrayNode.class, false); + if ( deleteOnStartArray==null ) { + deleteOnStartArray = JsonHelper.getObjectMapper().createArrayNode(); + } + deleteOnStartArray.add(installPath.toAbsolutePath().toString()); + FcliDataHelper.saveFile(deleteOnStartPath, deleteOnStartArray, false); + } + + public static final void deleteAllPending() { + var deleteOnStartArray = FcliDataHelper.readFile(deleteOnStartPath, ArrayNode.class, false); + if ( deleteOnStartArray!=null ) { + var failingDirsArray = JsonHelper.stream(deleteOnStartArray) + .map(ToolUninstaller::deletePending) + .filter(Objects::nonNull) + .collect(JsonHelper.arrayNodeCollector()); + if ( failingDirsArray.isEmpty() ) { + FcliDataHelper.deleteFile(deleteOnStartPath, true); + } else { + FcliDataHelper.saveFile(deleteOnStartPath, failingDirsArray, true); + } + } + } + + public static final JsonNode deletePending(JsonNode dirNode) { + var dirPath = Path.of(dirNode.asText()); + if ( !FileUtils.isDirPathInUse(dirPath) ) { + try { + FileUtils.deleteRecursive(dirPath); + } catch ( Exception e ) { + LOG.warn("WARN: Error on pending delete; please delete manually: "+dirPath); + } + return null; + } + return dirNode; + } + + +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolVersionCombinedDescriptor.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolVersionCombinedDescriptor.java deleted file mode 100644 index 2bb1d23500..0000000000 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolVersionCombinedDescriptor.java +++ /dev/null @@ -1,94 +0,0 @@ -/******************************************************************************* - * 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.tool._common.helper; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.formkiq.graalvm.annotations.Reflectable; -import com.fortify.cli.common.util.StringUtils; - -import lombok.Data; - -@Reflectable // We only serialize, not de-serialize, so no need for no-args contructor -@Data -public class ToolVersionCombinedDescriptor { - private final String name; - @JsonIgnore private final ToolVersionDownloadDescriptor downloadDescriptor; - @JsonIgnore private final ToolVersionInstallDescriptor installDescriptor; - - public String getVersion() { - return getInstalledOrDefaultDownloadDescriptor().getVersion(); - } - - public String getDownloadUrl() { - return getInstalledOrDefaultDownloadDescriptor().getDownloadUrl(); - } - - public String getDigest() { - return getInstalledOrDefaultDownloadDescriptor().getDigest(); - } - - public String getIsDefaultVersion() { - // To determine whether a version is the default version, we - // need to use the configured download descriptor, not the - // download descriptor stored during tool installation. - return getDownloadDescriptor().getIsDefaultVersion(); - } - - public String getInstalled() { - return StringUtils.isBlank(getInstallDir()) ? "No" : "Yes"; - } - - public String getInstallDir() { - return getDir(ToolVersionInstallDescriptor::getInstallDir); - } - - public Path getInstallPath() { - return getPath(ToolVersionInstallDescriptor::getInstallPath); - } - - public String getBinDir() { - return getDir(ToolVersionInstallDescriptor::getBinDir); - } - - public Path getBinPath() { - return getPath(ToolVersionInstallDescriptor::getBinPath); - } - - private String getDir(Function f) { - if ( installDescriptor!=null ) { - String dir = f.apply(installDescriptor); - if ( Files.exists(Paths.get(dir)) ) { return dir; } - } - return null; - } - - private Path getPath(Function f) { - if ( installDescriptor!=null ) { - Path path = f.apply(installDescriptor); - if ( Files.exists(path) ) { return path; } - } - return null; - } - - @JsonIgnore - private ToolVersionDownloadDescriptor getInstalledOrDefaultDownloadDescriptor() { - return installDescriptor==null || installDescriptor.getOriginalDownloadDescriptor()==null - ? downloadDescriptor : installDescriptor.getOriginalDownloadDescriptor(); - } - -} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolVersionInstallDescriptor.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolVersionInstallDescriptor.java deleted file mode 100644 index c07239fecd..0000000000 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolVersionInstallDescriptor.java +++ /dev/null @@ -1,55 +0,0 @@ -/******************************************************************************* - * Copyright 2021, 2022 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.tool._common.helper; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.formkiq.graalvm.annotations.Reflectable; -import com.fortify.cli.common.util.StringUtils; - -import lombok.Data; -import lombok.NoArgsConstructor; - -@Reflectable @NoArgsConstructor -@Data -public class ToolVersionInstallDescriptor { - private ToolVersionDownloadDescriptor originalDownloadDescriptor; - private String installDir; - private String binDir; - @JsonIgnore Path installPath; - @JsonIgnore Path binPath; - - public ToolVersionInstallDescriptor(ToolVersionDownloadDescriptor originalDownloadDescriptor, Path installPath, Path binPath) { - this.originalDownloadDescriptor = originalDownloadDescriptor; - this.installPath = installPath.toAbsolutePath(); - this.installDir = installPath.toString(); - this.binPath = binPath.toAbsolutePath(); - this.binDir = binPath.toString(); - } - - public Path getInstallPath() { - if ( installPath==null && StringUtils.isNotBlank(installDir) ) { - installPath = Paths.get(installDir).toAbsolutePath(); - } - return installPath; - } - - public Path getBinPath() { - if ( binPath==null && StringUtils.isNotBlank(binDir) ) { - binPath = Paths.get(binDir).toAbsolutePath(); - } - return binPath; - } -} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_main/cli/cmd/ToolCommands.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_main/cli/cmd/ToolCommands.java index 2c7b003b87..ad2f5997e7 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_main/cli/cmd/ToolCommands.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_main/cli/cmd/ToolCommands.java @@ -14,6 +14,9 @@ import com.fortify.cli.common.cli.cmd.AbstractContainerCommand; import com.fortify.cli.tool.bugtracker_utility.cli.cmd.ToolBugTrackerUtilityCommands; +import com.fortify.cli.tool.debricked_cli.cli.cmd.ToolDebrickedCliCommands; +import com.fortify.cli.tool.definitions.cli.cmd.ToolDefinitionsCommands; +import com.fortify.cli.tool.fcli.cli.cmd.ToolFcliCommands; import com.fortify.cli.tool.fod_uploader.cli.cmd.ToolFoDUploaderCommands; import com.fortify.cli.tool.sc_client.cli.cmd.ToolSCClientCommands; import com.fortify.cli.tool.vuln_exporter.cli.cmd.ToolVulnExporterCommands; @@ -25,9 +28,12 @@ resourceBundle = "com.fortify.cli.tool.i18n.ToolMessages", subcommands = { ToolBugTrackerUtilityCommands.class, + ToolDebrickedCliCommands.class, + ToolFcliCommands.class, ToolFoDUploaderCommands.class, ToolSCClientCommands.class, - ToolVulnExporterCommands.class + ToolVulnExporterCommands.class, + ToolDefinitionsCommands.class } ) public class ToolCommands extends AbstractContainerCommand {} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityCommands.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityCommands.java index 332f8dda10..0ce04c4eb0 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityCommands.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityCommands.java @@ -22,6 +22,7 @@ subcommands = { ToolBugTrackerUtilityInstallCommand.class, ToolBugTrackerUtilityListCommand.class, + ToolBugTrackerUtilityListPlatformsCommand.class, ToolBugTrackerUtilityUninstallCommand.class } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityInstallCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityInstallCommand.java index 6b4edf2516..8876889793 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityInstallCommand.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityInstallCommand.java @@ -17,12 +17,12 @@ import java.nio.file.Path; import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; -import com.fortify.cli.common.util.FileUtils; import com.fortify.cli.tool._common.cli.cmd.AbstractToolInstallCommand; -import com.fortify.cli.tool._common.helper.ToolHelper; -import com.fortify.cli.tool._common.helper.ToolVersionInstallDescriptor; +import com.fortify.cli.tool._common.helper.ToolInstaller; +import com.fortify.cli.tool._common.helper.ToolInstaller.ToolInstallationResult; import lombok.Getter; +import lombok.SneakyThrows; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; @@ -32,24 +32,24 @@ public class ToolBugTrackerUtilityInstallCommand extends AbstractToolInstallComm @Getter private String toolName = ToolBugTrackerUtilityCommands.TOOL_NAME; @Override - protected InstallType getInstallType() { - return InstallType.EXTRACT_ZIP; + protected String getDefaultArtifactType() { + return "java"; } - @Override - protected void postInstall(ToolVersionInstallDescriptor descriptor) throws IOException { - Path binPath = descriptor.getBinPath(); - Files.createDirectories(binPath); - FileUtils.copyResourceToDir(ToolHelper.getResourceFile(getToolName(), "extra-files/bin/FortifyBugTrackerUtility"), binPath); - FileUtils.copyResourceToDir(ToolHelper.getResourceFile(getToolName(), "extra-files/bin/FortifyBugTrackerUtility.bat"), binPath); - - String version = descriptor.getOriginalDownloadDescriptor().getVersion(); - String jarName = String.format("FortifyBugTrackerUtility-%s.jar", version); - - //we are renaming the jar to remove the version reference - //this allows us to use pre-written bat/bash wrappers rather than having to dynamically generate those - descriptor.getInstallPath().resolve(jarName).toFile().renameTo( - descriptor.getInstallPath().resolve("FortifyBugTrackerUtility.jar").toFile()); - + @Override @SneakyThrows + protected void postInstall(ToolInstaller installer, ToolInstallationResult installationResult) { + var targetPath = installer.getTargetPath(); + renameJar(targetPath); + installer.installJavaBinScripts("FortifyBugTrackerUtility", "FortifyBugTrackerUtility.jar"); + } + + private void renameJar(Path targetPath) throws IOException { + var jarFiles = Files.find(targetPath, 1, + (p,a)->p.toFile().getName().matches("FortifyBugTrackerUtility.*\\.jar")) + .toList(); + if ( jarFiles.size()!=1 ) { + throw new IllegalStateException("Unexpected number of files matching FortifyBugTrackerUtility*.jar: "+jarFiles.size()); + } + Files.move(jarFiles.get(0), targetPath.resolve("FortifyBugTrackerUtility.jar")); } } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityListPlatformsCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityListPlatformsCommand.java new file mode 100644 index 0000000000..d17a2fba68 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/bugtracker_utility/cli/cmd/ToolBugTrackerUtilityListPlatformsCommand.java @@ -0,0 +1,24 @@ +/** + * Copyright 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.tool.bugtracker_utility.cli.cmd; + +import com.fortify.cli.common.cli.util.CommandGroup; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolListPlatformsCommand; + +import lombok.Getter; +import picocli.CommandLine.Command; + +@Command(name = "list-platforms", aliases = {"lsp"}) @CommandGroup("list-platforms") +public class ToolBugTrackerUtilityListPlatformsCommand extends AbstractToolListPlatformsCommand { + @Getter private String toolName = ToolBugTrackerUtilityCommands.TOOL_NAME; +} diff --git a/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/GitHubCommands.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliCommands.java similarity index 60% rename from fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/GitHubCommands.java rename to fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliCommands.java index 6759f1fb65..bf71184abc 100644 --- a/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/GitHubCommands.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliCommands.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright 2021, 2023 Open Text. + * Copyright 2021, 2022 Open Text. * * The only warranties for products and services of Open Text * and its affiliates and licensors ("Open Text") are as may @@ -10,17 +10,23 @@ * herein. The information contained herein is subject to change * without notice. *******************************************************************************/ -package com.fortify.cli.util.github.cli.cmd; +package com.fortify.cli.tool.debricked_cli.cli.cmd; import com.fortify.cli.common.cli.cmd.AbstractContainerCommand; import picocli.CommandLine.Command; @Command( - name = "github", hidden=true, + name = ToolDebrickedCliCommands.TOOL_NAME, + aliases = {"dcli"}, subcommands = { - GitHubListReleasesCommand.class, - GitHubListReleaseAssetsCommand.class, + ToolDebrickedCliInstallCommand.class, + ToolDebrickedCliListCommand.class, + ToolDebrickedCliListPlatformsCommand.class, + ToolDebrickedCliUninstallCommand.class } + ) -public class GitHubCommands extends AbstractContainerCommand {} +public class ToolDebrickedCliCommands extends AbstractContainerCommand { + static final String TOOL_NAME = "debricked-cli"; +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliInstallCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliInstallCommand.java new file mode 100644 index 0000000000..4223252ae1 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliInstallCommand.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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.tool.debricked_cli.cli.cmd; + +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.common.util.FileUtils; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolInstallCommand; +import com.fortify.cli.tool._common.helper.ToolInstaller; +import com.fortify.cli.tool._common.helper.ToolInstaller.BinScriptType; +import com.fortify.cli.tool._common.helper.ToolInstaller.ToolInstallationResult; + +import lombok.Getter; +import lombok.SneakyThrows; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; + +@Command(name = OutputHelperMixins.Install.CMD_NAME) +public class ToolDebrickedCliInstallCommand extends AbstractToolInstallCommand { + @Getter @Mixin private OutputHelperMixins.Install outputHelper; + @Getter private String toolName = ToolDebrickedCliCommands.TOOL_NAME; + + @Override + protected String getDefaultArtifactType() { + return ""; + } + + @Override @SneakyThrows + protected void postInstall(ToolInstaller installer, ToolInstallationResult installationResult) { + FileUtils.moveFiles(installer.getTargetPath(), installer.getBinPath(), "debricked(\\.exe)?"); + installer.installGlobalBinScript(BinScriptType.bash, "debricked", "bin/debricked"); + installer.installGlobalBinScript(BinScriptType.bat, "debricked.bat", "bin/debricked.exe"); + } +} diff --git a/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/GitHubListReleasesCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliListCommand.java similarity index 57% rename from fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/GitHubListReleasesCommand.java rename to fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliListCommand.java index 74c80c46b1..b814e68fec 100644 --- a/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/GitHubListReleasesCommand.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliListCommand.java @@ -10,28 +10,17 @@ * herein. The information contained herein is subject to change * without notice. *******************************************************************************/ -package com.fortify.cli.util.github.cli.cmd; +package com.fortify.cli.tool.debricked_cli.cli.cmd; -import com.fortify.cli.common.output.cli.cmd.IBaseRequestSupplier; import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolListCommand; -import kong.unirest.HttpRequest; import lombok.Getter; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; -@Command(name = "list-releases", aliases = "lsr") -public class GitHubListReleasesCommand extends AbstractGitHubRepoCommand implements IBaseRequestSupplier { - @Getter @Mixin private OutputHelperMixins.TableWithQuery outputHelper; - - @Override - public HttpRequest getBaseRequest() { - var endpoint = getRepoEndpointUrl("/releases"); - return getUnirestInstance().get(endpoint); - } - - @Override - public boolean isSingular() { - return false; - } +@Command(name = OutputHelperMixins.List.CMD_NAME) +public class ToolDebrickedCliListCommand extends AbstractToolListCommand { + @Getter @Mixin private OutputHelperMixins.List outputHelper; + @Getter private String toolName = ToolDebrickedCliCommands.TOOL_NAME; } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliListPlatformsCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliListPlatformsCommand.java new file mode 100644 index 0000000000..44908842e4 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliListPlatformsCommand.java @@ -0,0 +1,24 @@ +/** + * Copyright 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.tool.debricked_cli.cli.cmd; + +import com.fortify.cli.common.cli.util.CommandGroup; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolListPlatformsCommand; + +import lombok.Getter; +import picocli.CommandLine.Command; + +@Command(name = "list-platforms", aliases = {"lsp"}) @CommandGroup("list-platforms") +public class ToolDebrickedCliListPlatformsCommand extends AbstractToolListPlatformsCommand { + @Getter private String toolName = ToolDebrickedCliCommands.TOOL_NAME; +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliUninstallCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliUninstallCommand.java new file mode 100644 index 0000000000..9ba36b2469 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/debricked_cli/cli/cmd/ToolDebrickedCliUninstallCommand.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * 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.tool.debricked_cli.cli.cmd; + +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolUninstallCommand; + +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; + +@Command(name = OutputHelperMixins.Uninstall.CMD_NAME) +public class ToolDebrickedCliUninstallCommand extends AbstractToolUninstallCommand { + @Getter @Mixin private OutputHelperMixins.Uninstall outputHelper; + @Getter private String toolName = ToolDebrickedCliCommands.TOOL_NAME; + +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsCommands.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsCommands.java new file mode 100644 index 0000000000..6f337a6069 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsCommands.java @@ -0,0 +1,17 @@ +package com.fortify.cli.tool.definitions.cli.cmd; +import com.fortify.cli.common.cli.cmd.AbstractContainerCommand; + +import picocli.CommandLine.Command; + +@Command( + name = "definitions", + aliases = {}, + subcommands = { + ToolDefinitionsListCommand.class, + ToolDefinitionsUpdateCommand.class, + ToolDefinitionsResetCommand.class, + } +) + +public class ToolDefinitionsCommands extends AbstractContainerCommand { +} \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsListCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsListCommand.java new file mode 100644 index 0000000000..088d51a0f3 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsListCommand.java @@ -0,0 +1,27 @@ +package com.fortify.cli.tool.definitions.cli.cmd; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fortify.cli.common.json.JsonHelper; +import com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand; +import com.fortify.cli.common.output.cli.cmd.IJsonNodeSupplier; +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionsHelper; + +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; + +@Command(name=OutputHelperMixins.ListNoQuery.CMD_NAME) +public class ToolDefinitionsListCommand extends AbstractOutputCommand implements IJsonNodeSupplier { + @Mixin @Getter private OutputHelperMixins.ListNoQuery outputHelper; + + @Override + public JsonNode getJsonNode() { + return JsonHelper.getObjectMapper().valueToTree(ToolDefinitionsHelper.getOutputDescriptors()); + } + + @Override + public boolean isSingular() { + return false; + } +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsResetCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsResetCommand.java new file mode 100644 index 0000000000..be04067976 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsResetCommand.java @@ -0,0 +1,33 @@ +package com.fortify.cli.tool.definitions.cli.cmd; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fortify.cli.common.json.JsonHelper; +import com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand; +import com.fortify.cli.common.output.cli.cmd.IJsonNodeSupplier; +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.common.output.transform.IActionCommandResultSupplier; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionsHelper; + +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; + +@Command(name="reset") +public class ToolDefinitionsResetCommand extends AbstractOutputCommand implements IJsonNodeSupplier, IActionCommandResultSupplier { + @Mixin @Getter private OutputHelperMixins.TableNoQuery outputHelper; + + @Override + public JsonNode getJsonNode() { + return JsonHelper.getObjectMapper().valueToTree(ToolDefinitionsHelper.reset()); + } + + @Override + public boolean isSingular() { + return false; + } + + @Override + public String getActionCommandResult() { + return "RESET"; + } +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsUpdateCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsUpdateCommand.java new file mode 100644 index 0000000000..8b86500bd6 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/cli/cmd/ToolDefinitionsUpdateCommand.java @@ -0,0 +1,37 @@ +package com.fortify.cli.tool.definitions.cli.cmd; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fortify.cli.common.json.JsonHelper; +import com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand; +import com.fortify.cli.common.output.cli.cmd.IJsonNodeSupplier; +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.common.output.transform.IActionCommandResultSupplier; +import com.fortify.cli.tool.definitions.helper.ToolDefinitionsHelper; + +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; + +@Command(name=OutputHelperMixins.Update.CMD_NAME) +public class ToolDefinitionsUpdateCommand extends AbstractOutputCommand implements IJsonNodeSupplier, IActionCommandResultSupplier { + @Mixin @Getter private OutputHelperMixins.Update outputHelper; + private static final String DEFAULT_URL = "https://github.com/fortify-ps/tool-definitions/releases/download/v1/tool-definitions.yaml.zip"; + @Getter @Option(names={"-s", "--source"}, required = false, descriptionKey="fcli.tool.definitions.update.definitions-source") + private String source = DEFAULT_URL; + + @Override + public JsonNode getJsonNode() { + return JsonHelper.getObjectMapper().valueToTree(ToolDefinitionsHelper.updateToolDefinitions(source)); + } + + @Override + public boolean isSingular() { + return false; + } + + @Override + public String getActionCommandResult() { + return "UPDATED"; + } +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionArtifactDescriptor.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionArtifactDescriptor.java new file mode 100644 index 0000000000..13fad1f38e --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionArtifactDescriptor.java @@ -0,0 +1,20 @@ +package com.fortify.cli.tool.definitions.helper; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.formkiq.graalvm.annotations.Reflectable; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * This class represents the contents of an artifact definition in a tool + * definition YAML file. + */ +@JsonIgnoreProperties(ignoreUnknown=true) +@Reflectable @NoArgsConstructor +@Data +public final class ToolDefinitionArtifactDescriptor { + private String name; + private String downloadUrl; + private String rsa_sha256; +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionRootDescriptor.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionRootDescriptor.java new file mode 100644 index 0000000000..15e9a8c847 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionRootDescriptor.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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.tool.definitions.helper; + +import java.util.Arrays; +import java.util.stream.Stream; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.formkiq.graalvm.annotations.Reflectable; +import com.fortify.cli.common.util.StringUtils; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * This class represents the contents of a tool definition YAML file, usually + * deserialized from [tool-name].yaml loaded from tool-definitions.yaml.zip. + */ +@JsonIgnoreProperties(ignoreUnknown=true) +@Reflectable @NoArgsConstructor +@Data +public class ToolDefinitionRootDescriptor { + private String schema_version; + private ToolDefinitionVersionDescriptor[] versions; + + public final ToolDefinitionVersionDescriptor[] getVersions() { + return getVersionsStream().toArray(ToolDefinitionVersionDescriptor[]::new); + } + + public final Stream getVersionsStream() { + return Stream.of(versions); + } + + public final ToolDefinitionVersionDescriptor getVersion(String versionOrAlias) { + return getVersionsStream() + .filter(v-> (v.getVersion().equals(versionOrAlias) || Arrays.stream(v.getAliases()).anyMatch(versionOrAlias::equals)) ) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Version or alias "+versionOrAlias+" not found")); + } + + public final ToolDefinitionVersionDescriptor getVersionOrDefault(String versionOrAlias) { + if ( StringUtils.isBlank(versionOrAlias) || "default".equals(versionOrAlias)) { + versionOrAlias = "latest"; + } + return getVersion(versionOrAlias); + } + +} \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolVersionDownloadDescriptor.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionVersionDescriptor.java similarity index 60% rename from fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolVersionDownloadDescriptor.java rename to fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionVersionDescriptor.java index fd1a2a087a..cf86c4eca5 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/_common/helper/ToolVersionDownloadDescriptor.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionVersionDescriptor.java @@ -10,30 +10,26 @@ * herein. The information contained herein is subject to change * without notice. *******************************************************************************/ -package com.fortify.cli.tool._common.helper; +package com.fortify.cli.tool.definitions.helper; -import com.fasterxml.jackson.annotation.JsonIgnore; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.formkiq.graalvm.annotations.Reflectable; -import com.fortify.cli.common.util.StringUtils; import lombok.Data; import lombok.NoArgsConstructor; +/** + * This class represents the contents of an artifact definition in a tool + * definition YAML file. + */ +@JsonIgnoreProperties(ignoreUnknown=true) @Reflectable @NoArgsConstructor @Data -public final class ToolVersionDownloadDescriptor { +public final class ToolDefinitionVersionDescriptor { private String version; - private String downloadUrl; - private String digest; - private String isDefaultVersion = "No"; - - @JsonIgnore - public final String getDigestAlgorithm() { - return StringUtils.substringBefore(digest, ":"); - } - - @JsonIgnore - public final String getExpectedDigest() { - return StringUtils.substringAfter(digest, ":"); - } + private String[] aliases; + private boolean stable; + private Map binaries; } \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionsHelper.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionsHelper.java new file mode 100644 index 0000000000..5275f7c952 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionsHelper.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * 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.tool.definitions.helper; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fortify.cli.common.rest.unirest.UnirestHelper; +import com.fortify.cli.common.util.FcliBuildPropertiesHelper; +import com.fortify.cli.common.util.FcliDataHelper; +import com.fortify.cli.common.util.FileUtils; + +import lombok.SneakyThrows; + +public final class ToolDefinitionsHelper { + private static final String ZIP_FILE_NAME = "tool-definitions.yaml.zip"; + public static final Path DEFINITIONS_STATE_DIR = FcliDataHelper.getFcliStatePath().resolve("tool"); + public static final Path DEFINITIONS_STATE_ZIP = DEFINITIONS_STATE_DIR.resolve(ZIP_FILE_NAME); + private static final String DEFINITIONS_INTERNAL_ZIP = "com/fortify/cli/tool/config/"+ZIP_FILE_NAME; + private static final Path DESCRIPTOR_PATH = ToolDefinitionsHelper.DEFINITIONS_STATE_DIR.resolve("state.json"); + private static final ObjectMapper yamlObjectMapper = new ObjectMapper(new YAMLFactory()); + + public static final List getOutputDescriptors() { + List result = new ArrayList<>(); + addZipOutputDescriptor(result); + addYamlOutputDescriptors(result); + return result; + } + + @SneakyThrows + public static final List updateToolDefinitions(String source) { + createDefinitionsStateDir(ToolDefinitionsHelper.DEFINITIONS_STATE_DIR); + var zip = ToolDefinitionsHelper.DEFINITIONS_STATE_ZIP; + var descriptor = update(source, zip); + FcliDataHelper.saveFile(DESCRIPTOR_PATH, descriptor, true); + return getOutputDescriptors(); + } + + @SneakyThrows + public static final List reset() { + if ( Files.exists(DEFINITIONS_STATE_ZIP) ) { + Files.delete(DEFINITIONS_STATE_ZIP); + FcliDataHelper.deleteFile(DESCRIPTOR_PATH, false); + } + return getOutputDescriptors(); + } + + private static final void createDefinitionsStateDir(Path dir) throws IOException { + if( !Files.exists(dir) ) { + Files.createDirectories(dir); + } + } + + private static FileTime getModifiedTime(Path path) throws IOException { + BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class); + return attr.lastModifiedTime(); + } + + private static final ToolDefinitionsStateDescriptor update(String source, Path dest) throws IOException { + try { + UnirestHelper.download("tool", new URL(source).toString(), dest.toFile()); + } catch ( MalformedURLException e ) { + Files.copy(Path.of(source), dest, StandardCopyOption.REPLACE_EXISTING); + } + return new ToolDefinitionsStateDescriptor(source, new Date(getModifiedTime(dest).toMillis())); + } + + public static final ToolDefinitionRootDescriptor getToolDefinitionRootDescriptor(String toolName) { + String yamlFileName = toolName + ".yaml"; + try ( InputStream is = getToolDefinitionsInputStream(); ZipInputStream zis = new ZipInputStream(is) ) { + ZipEntry entry; + while ( (entry = zis.getNextEntry())!=null ) { + if ( yamlFileName.equals(entry.getName()) ) { + return yamlObjectMapper.readValue(zis, ToolDefinitionRootDescriptor.class); + } + } + throw new IllegalStateException("No tool definitions found for "+toolName); + } catch (IOException e) { + throw new RuntimeException("Error loading tool definitions", e); + } + } + + private static final InputStream getToolDefinitionsInputStream() throws IOException { + return Files.exists(DEFINITIONS_STATE_ZIP) + ? Files.newInputStream(DEFINITIONS_STATE_ZIP) + : FileUtils.getResourceInputStream(DEFINITIONS_INTERNAL_ZIP); + } + + private static final void addZipOutputDescriptor(List result) { + var stateDescriptor = FcliDataHelper.readFile(DESCRIPTOR_PATH, ToolDefinitionsStateDescriptor.class, false); + if ( stateDescriptor!=null ) { + result.add(new ToolDefinitionsOutputDescriptor(ZIP_FILE_NAME, stateDescriptor)); + } else { + result.add(new ToolDefinitionsOutputDescriptor(ZIP_FILE_NAME, "INTERNAL", FcliBuildPropertiesHelper.getFcliBuildDate())); + } + } + + private static final void addYamlOutputDescriptors(List result) { + try ( InputStream is = getToolDefinitionsInputStream(); ZipInputStream zis = new ZipInputStream(is) ) { + ZipEntry entry; + while ( (entry = zis.getNextEntry())!=null ) { + var name = Path.of(entry.getName()).getFileName().toString(); // Should already be just a file name, but just in case + var source = ZIP_FILE_NAME; + var lastModified = new Date(entry.getLastModifiedTime().toMillis()); + result.add(new ToolDefinitionsOutputDescriptor(name, source, lastModified)); + } + } catch (IOException e) { + throw new RuntimeException("Error loading tool definitions", e); + } + } + +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionsOutputDescriptor.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionsOutputDescriptor.java new file mode 100644 index 0000000000..cd21901111 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionsOutputDescriptor.java @@ -0,0 +1,52 @@ +/** + * Copyright 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.tool.definitions.helper; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.formkiq.graalvm.annotations.Reflectable; + +import lombok.Data; + +@Reflectable // We only serialize, not deserialize, so no no-args contructor +@Data +public final class ToolDefinitionsOutputDescriptor { + private final String name; + private final String source; + private final String lastUpdate; + + public ToolDefinitionsOutputDescriptor(String name, ToolDefinitionsStateDescriptor stateDescriptor) { + this(name, stateDescriptor.getSource(), stateDescriptor.getLastUpdate()); + } + + public ToolDefinitionsOutputDescriptor(String name, String source, Date lastUpdate) { + this.name = name; + this.source = getFormattedString(source); + this.lastUpdate = lastUpdate==null ? null : new SimpleDateFormat("yyyy-MM-dd HH:mm").format(lastUpdate); + } + + private static final String getFormattedString(String str) { + List parts = new ArrayList<>(); + int size = 29, length = str.length(); + for(int i = 0, end, goodPos; i < length; i = end) { + end = Math.min(length, i + size); + goodPos = str.lastIndexOf('/', end); + if(goodPos <= i) goodPos = end; else end = goodPos + 1; + parts.add(str.substring(i, goodPos)); + } + return String.join("\n/", parts); + } +} \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionsStateDescriptor.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionsStateDescriptor.java new file mode 100644 index 0000000000..27e1b96ec7 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/definitions/helper/ToolDefinitionsStateDescriptor.java @@ -0,0 +1,28 @@ +/** + * Copyright 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.tool.definitions.helper; + +import java.util.Date; + +import com.formkiq.graalvm.annotations.Reflectable; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Reflectable @NoArgsConstructor @AllArgsConstructor +@Data +public final class ToolDefinitionsStateDescriptor{ + private String source; + private Date lastUpdate; +} \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliCommands.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliCommands.java new file mode 100644 index 0000000000..74812401b5 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliCommands.java @@ -0,0 +1,19 @@ +package com.fortify.cli.tool.fcli.cli.cmd; + +import com.fortify.cli.common.cli.cmd.AbstractContainerCommand; + +import picocli.CommandLine.Command; + +@Command( + name = ToolFcliCommands.TOOL_NAME, + subcommands = { + ToolFcliInstallCommand.class, + ToolFcliListCommand.class, + ToolFcliListPlatformsCommand.class, + ToolFcliUninstallCommand.class + } + +) +public class ToolFcliCommands extends AbstractContainerCommand { + static final String TOOL_NAME = "fcli"; +} \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliInstallCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliInstallCommand.java new file mode 100644 index 0000000000..1bf2926ef5 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliInstallCommand.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * 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.tool.fcli.cli.cmd; + +import java.nio.file.Files; +import java.nio.file.Path; + +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.common.util.FileUtils; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolInstallCommand; +import com.fortify.cli.tool._common.helper.ToolInstaller; +import com.fortify.cli.tool._common.helper.ToolInstaller.BinScriptType; +import com.fortify.cli.tool._common.helper.ToolInstaller.ToolInstallationResult; + +import lombok.Getter; +import lombok.SneakyThrows; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; + +@Command(name = OutputHelperMixins.Install.CMD_NAME) +public class ToolFcliInstallCommand extends AbstractToolInstallCommand { + @Getter @Mixin private OutputHelperMixins.Install outputHelper; + @Getter private String toolName = ToolFcliCommands.TOOL_NAME; + + @Override + protected String getDefaultArtifactType() { + return "java"; + } + + @Override @SneakyThrows + protected void postInstall(ToolInstaller installer, ToolInstallationResult installationResult) { + Path installPath = installer.getTargetPath(); + FileUtils.moveFiles(installPath, installer.getBinPath(), "fcli(_completion)?(\\.exe)?"); + if ( Files.exists(installPath.resolve("fcli.jar")) ) { + installer.installJavaBinScripts("fcli", "fcli.jar"); + } else { + installer.installGlobalBinScript(BinScriptType.bash, "fcli", "bin/fcli"); + installer.installGlobalBinScript(BinScriptType.bash, "fcli_completion", "bin/fcli_completion"); + installer.installGlobalBinScript(BinScriptType.bat, "fcli.bat", "bin/fcli.exe"); + } + } +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliListCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliListCommand.java new file mode 100644 index 0000000000..db1f4a6700 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliListCommand.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * 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.tool.fcli.cli.cmd; + +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolListCommand; + +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; + +@Command(name = OutputHelperMixins.List.CMD_NAME) +public class ToolFcliListCommand extends AbstractToolListCommand { + @Getter @Mixin private OutputHelperMixins.List outputHelper; + @Getter private String toolName = ToolFcliCommands.TOOL_NAME; +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliListPlatformsCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliListPlatformsCommand.java new file mode 100644 index 0000000000..94e4da6530 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliListPlatformsCommand.java @@ -0,0 +1,24 @@ +/** + * Copyright 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.tool.fcli.cli.cmd; + +import com.fortify.cli.common.cli.util.CommandGroup; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolListPlatformsCommand; + +import lombok.Getter; +import picocli.CommandLine.Command; + +@Command(name = "list-platforms", aliases = {"lsp"}) @CommandGroup("list-platforms") +public class ToolFcliListPlatformsCommand extends AbstractToolListPlatformsCommand { + @Getter private String toolName = ToolFcliCommands.TOOL_NAME; +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliUninstallCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliUninstallCommand.java new file mode 100644 index 0000000000..a844d3c653 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fcli/cli/cmd/ToolFcliUninstallCommand.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * 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.tool.fcli.cli.cmd; + +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolUninstallCommand; + +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; + +@Command(name = OutputHelperMixins.Uninstall.CMD_NAME) +public class ToolFcliUninstallCommand extends AbstractToolUninstallCommand { + @Getter @Mixin private OutputHelperMixins.Uninstall outputHelper; + @Getter private String toolName = ToolFcliCommands.TOOL_NAME; +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderCommands.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderCommands.java index 130742f2b3..b391520665 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderCommands.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderCommands.java @@ -22,6 +22,7 @@ subcommands = { ToolFoDUploaderInstallCommand.class, ToolFoDUploaderListCommand.class, + ToolFoDUploaderListPlatformsCommand.class, ToolFoDUploaderUninstallCommand.class } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderInstallCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderInstallCommand.java index 6db07fda84..6cbf182ada 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderInstallCommand.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderInstallCommand.java @@ -12,17 +12,13 @@ *******************************************************************************/ package com.fortify.cli.tool.fod_uploader.cli.cmd; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; -import com.fortify.cli.common.util.FileUtils; import com.fortify.cli.tool._common.cli.cmd.AbstractToolInstallCommand; -import com.fortify.cli.tool._common.helper.ToolHelper; -import com.fortify.cli.tool._common.helper.ToolVersionInstallDescriptor; +import com.fortify.cli.tool._common.helper.ToolInstaller; +import com.fortify.cli.tool._common.helper.ToolInstaller.ToolInstallationResult; import lombok.Getter; +import lombok.SneakyThrows; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; @@ -32,15 +28,12 @@ public class ToolFoDUploaderInstallCommand extends AbstractToolInstallCommand { @Getter private String toolName = ToolFoDUploaderCommands.TOOL_NAME; @Override - protected InstallType getInstallType() { - return InstallType.COPY; + protected String getDefaultArtifactType() { + return "java"; } - @Override - protected void postInstall(ToolVersionInstallDescriptor descriptor) throws IOException { - Path binPath = descriptor.getBinPath(); - Files.createDirectories(binPath); - FileUtils.copyResourceToDir(ToolHelper.getResourceFile(getToolName(), "extra-files/bin/FoDUpload"), binPath); - FileUtils.copyResourceToDir(ToolHelper.getResourceFile(getToolName(), "extra-files/bin/FoDUpload.bat"), binPath); + @Override @SneakyThrows + protected void postInstall(ToolInstaller installer, ToolInstallationResult installationResult) { + installer.installJavaBinScripts("FoDUpload", "FodUpload.jar"); } } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderListPlatformsCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderListPlatformsCommand.java new file mode 100644 index 0000000000..aa773fcf5f --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/fod_uploader/cli/cmd/ToolFoDUploaderListPlatformsCommand.java @@ -0,0 +1,24 @@ +/** + * Copyright 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.tool.fod_uploader.cli.cmd; + +import com.fortify.cli.common.cli.util.CommandGroup; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolListPlatformsCommand; + +import lombok.Getter; +import picocli.CommandLine.Command; + +@Command(name = "list-platforms", aliases = {"lsp"}) @CommandGroup("list-platforms") +public class ToolFoDUploaderListPlatformsCommand extends AbstractToolListPlatformsCommand { + @Getter private String toolName = ToolFoDUploaderCommands.TOOL_NAME; +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientCommands.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientCommands.java index 36427d50f0..f0251a2b9b 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientCommands.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientCommands.java @@ -22,6 +22,7 @@ subcommands = { ToolSCClientInstallCommand.class, ToolSCClientListCommand.class, + ToolSCClientListPlatformsCommand.class, ToolSCClientUninstallCommand.class } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientInstallCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientInstallCommand.java index 3f100856d3..8c26e74bb9 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientInstallCommand.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientInstallCommand.java @@ -21,9 +21,12 @@ import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; import com.fortify.cli.common.util.StringUtils; import com.fortify.cli.tool._common.cli.cmd.AbstractToolInstallCommand; -import com.fortify.cli.tool._common.helper.ToolVersionInstallDescriptor; +import com.fortify.cli.tool._common.helper.ToolInstaller; +import com.fortify.cli.tool._common.helper.ToolInstaller.BinScriptType; +import com.fortify.cli.tool._common.helper.ToolInstaller.ToolInstallationResult; import lombok.Getter; +import lombok.SneakyThrows; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; import picocli.CommandLine.Option; @@ -35,14 +38,17 @@ public class ToolSCClientInstallCommand extends AbstractToolInstallCommand { @Option(names= {"-t", "--client-auth-token"}) private String clientAuthToken; @Override - protected InstallType getInstallType() { - return InstallType.EXTRACT_ZIP; + protected String getDefaultArtifactType() { + return "java"; } - @Override - protected void postInstall(ToolVersionInstallDescriptor descriptor) throws IOException { - // Updating bin permissions is handled by parent class - updateClientAuthToken(descriptor.getInstallPath()); + @Override @SneakyThrows + protected void postInstall(ToolInstaller installer, ToolInstallationResult installationResult) { + updateClientAuthToken(installer.getTargetPath()); + installer.installGlobalBinScript(BinScriptType.bash, "scancentral", "bin/scancentral"); + installer.installGlobalBinScript(BinScriptType.bat, "scancentral.bat", "bin/scancentral.bat"); + installer.installGlobalBinScript(BinScriptType.bash, "pwtool", "bin/pwtool"); + installer.installGlobalBinScript(BinScriptType.bat, "pwtool.bat", "bin/pwtool.bat"); } private void updateClientAuthToken(Path installPath) throws IOException { diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientListPlatformsCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientListPlatformsCommand.java new file mode 100644 index 0000000000..0caca76012 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/sc_client/cli/cmd/ToolSCClientListPlatformsCommand.java @@ -0,0 +1,24 @@ +/** + * Copyright 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.tool.sc_client.cli.cmd; + +import com.fortify.cli.common.cli.util.CommandGroup; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolListPlatformsCommand; + +import lombok.Getter; +import picocli.CommandLine.Command; + +@Command(name = "list-platforms", aliases = {"lsp"}) @CommandGroup("list-platforms") +public class ToolSCClientListPlatformsCommand extends AbstractToolListPlatformsCommand { + @Getter private String toolName = ToolSCClientCommands.TOOL_NAME; +} diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterCommands.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterCommands.java index 5da3e66b58..c616a3ee79 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterCommands.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterCommands.java @@ -22,6 +22,7 @@ subcommands = { ToolVulnExporterInstallCommand.class, ToolVulnExporterListCommand.class, + ToolVulnExporterListPlatformsCommand.class, ToolVulnExporterUninstallCommand.class } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterInstallCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterInstallCommand.java index 7b9c573794..6ed6defd2d 100644 --- a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterInstallCommand.java +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterInstallCommand.java @@ -12,17 +12,13 @@ *******************************************************************************/ package com.fortify.cli.tool.vuln_exporter.cli.cmd; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; -import com.fortify.cli.common.util.FileUtils; import com.fortify.cli.tool._common.cli.cmd.AbstractToolInstallCommand; -import com.fortify.cli.tool._common.helper.ToolHelper; -import com.fortify.cli.tool._common.helper.ToolVersionInstallDescriptor; +import com.fortify.cli.tool._common.helper.ToolInstaller; +import com.fortify.cli.tool._common.helper.ToolInstaller.ToolInstallationResult; import lombok.Getter; +import lombok.SneakyThrows; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; @@ -32,15 +28,12 @@ public class ToolVulnExporterInstallCommand extends AbstractToolInstallCommand { @Getter private String toolName = ToolVulnExporterCommands.TOOL_NAME; @Override - protected InstallType getInstallType() { - return InstallType.EXTRACT_ZIP; + protected String getDefaultArtifactType() { + return "java"; } - @Override - protected void postInstall(ToolVersionInstallDescriptor descriptor) throws IOException { - Path binPath = descriptor.getBinPath(); - Files.createDirectories(binPath); - FileUtils.copyResourceToDir(ToolHelper.getResourceFile(getToolName(), "extra-files/bin/FortifyVulnerabilityExporter"), binPath); - FileUtils.copyResourceToDir(ToolHelper.getResourceFile(getToolName(), "extra-files/bin/FortifyVulnerabilityExporter.bat"), binPath); + @Override @SneakyThrows + protected void postInstall(ToolInstaller installer, ToolInstallationResult installationResult) { + installer.installJavaBinScripts("FortifyVulnerabilityExporter", "FortifyVulnerabilityExporter.jar"); } } diff --git a/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterListPlatformsCommand.java b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterListPlatformsCommand.java new file mode 100644 index 0000000000..191af0b013 --- /dev/null +++ b/fcli-core/fcli-tool/src/main/java/com/fortify/cli/tool/vuln_exporter/cli/cmd/ToolVulnExporterListPlatformsCommand.java @@ -0,0 +1,24 @@ +/** + * Copyright 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.tool.vuln_exporter.cli.cmd; + +import com.fortify.cli.common.cli.util.CommandGroup; +import com.fortify.cli.tool._common.cli.cmd.AbstractToolListPlatformsCommand; + +import lombok.Getter; +import picocli.CommandLine.Command; + +@Command(name = "list-platforms", aliases = {"lsp"}) @CommandGroup("list-platforms") +public class ToolVulnExporterListPlatformsCommand extends AbstractToolListPlatformsCommand { + @Getter private String toolName = ToolVulnExporterCommands.TOOL_NAME; +} diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/bugtracker-utility/bugtracker-utility.yaml b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/bugtracker-utility/bugtracker-utility.yaml deleted file mode 100644 index f12544033b..0000000000 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/bugtracker-utility/bugtracker-utility.yaml +++ /dev/null @@ -1,30 +0,0 @@ -#Versions must be listed in descending order to guarantee proper version selection when users provide a partial version number -defaultDownloadUrl: https://github.com/fortify-ps/FortifyBugTrackerUtility/releases/download/{toolVersion}/FortifyBugTrackerUtility-{toolVersion}-dist.zip -defaultVersion: 4.12 -versions: - - version: 4.12 - digest: SHA-256:3c5142c63be6a6338f827e26ee79fdb70566dffd5a31680dce05544452c8a56a - - version: 4.11 - digest: SHA-256:7beec363d7395cc44e3ffc305bec3f879be506e7b55c79b9852522760da67ea8 - - version: 4.10 - digest: SHA-256:5fd23f7a341eccb36cdc33c47cef3bdc9aef439f3cb474879945872744c38ac2 - - version: 4.9 - digest: SHA-256:01b039972acdc894ba18e80801e6dad363849b19c2e01cb4f502bd068879ade8 - - version: 4.8 - digest: SHA-256:1de4e7dcbbbb8e92d37c8d31093e1b96fb3f7deb1182c734c0c72c999bb3b663 - - version: 4.7 - digest: SHA-256:06d965ae7b239ed26339eb7f06105399b60de2ef7fd9f7c2eed8c22f5b875869 - - version: 4.6 - digest: SHA-256:d4be8fb28120223a0d150b3e32f996d334fc1156a0667f146614a7a3b91f875d - - version: 4.5 - digest: SHA-256:c4211194c496dd9a1e6eb5989502034a7513fef6888690012e9fe4d407f3159d - - version: 4.4 - digest: SHA-256:3795100a6f52dcd1a2a69ee62c6350e6c99fc7252ba924142f03b417b8ba398d - - version: 4.3 - digest: SHA-256:36193b3706efaebc68a6d88a335a816b891971e1685037bb0a3b5c3a5d13d63f - - version: 4.2 - digest: SHA-256:842abff06c181a3a3d2953b3ef8903909c09cbd07b9907ca7dec164f4811d9e9 - - version: 4.1 - digest: SHA-256:2507565e372d4d805f02081cc2b3d5f21a872369adaacddad93bc1d8994876a1 - - version: 4.0 - digest: SHA-256:db3cb473c555ca33ef01cc21833164a6a5cd78a9aa901ca1cfde359bcd652636 \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/bugtracker-utility/extra-files/bin/FortifyBugTrackerUtility.bat b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/bugtracker-utility/extra-files/bin/FortifyBugTrackerUtility.bat deleted file mode 100644 index f31b9cb9cf..0000000000 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/bugtracker-utility/extra-files/bin/FortifyBugTrackerUtility.bat +++ /dev/null @@ -1,13 +0,0 @@ -@rem *************************************************************************** -@rem Copyright 2021, 2022 Open Text. -@rem -@rem The only warranties for products and services of Open Text -@rem and its affiliates and licensors ("Open Text") are as may -@rem be set forth in the express warranty statements accompanying -@rem such products and services. Nothing herein should be construed -@rem as constituting an additional warranty. Open Text shall not be -@rem liable for technical or editorial errors or omissions contained -@rem herein. The information contained herein is subject to change -@rem without notice. -@rem *************************************************************************** -java -jar %~dp0\..\FortifyBugTrackerUtility.jar %* \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/bugtracker-utility/extra-files/bin/FortifyBugTrackerUtility b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/global-bin/bash similarity index 55% rename from fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/bugtracker-utility/extra-files/bin/FortifyBugTrackerUtility rename to fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/global-bin/bash index 2ea298519e..17adae7101 100644 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/bugtracker-utility/extra-files/bin/FortifyBugTrackerUtility +++ b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/global-bin/bash @@ -1,3 +1,4 @@ #!/bin/bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -java -jar ${SCRIPT_DIR}/../FortifyBugTrackerUtility.jar "$@" \ No newline at end of file +TOOLS_HOME=${SCRIPT_DIR}/.. +${TOOLS_HOME}/{{relativeBashTargetPath}} "$@" diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/global-bin/bat b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/global-bin/bat new file mode 100644 index 0000000000..cdd8e8f5ae --- /dev/null +++ b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/global-bin/bat @@ -0,0 +1,3 @@ +@ECHO OFF +set TOOLS_HOME=%~dp0.. +%TOOLS_HOME%\{{relativeBatTargetPath}} %* diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/fod-uploader/extra-files/bin/FoDUpload b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/java-bin/bash similarity index 52% rename from fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/fod-uploader/extra-files/bin/FoDUpload rename to fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/java-bin/bash index db1549e21f..8d82898816 100644 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/fod-uploader/extra-files/bin/FoDUpload +++ b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/java-bin/bash @@ -1,3 +1,4 @@ #!/bin/bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -java -jar ${SCRIPT_DIR}/../FodUpload.jar "$@" \ No newline at end of file +TOOLS_HOME=${SCRIPT_DIR}/.. +java -jar ${TOOLS_HOME}/{{relativeBashTargetPath}} "$@" diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/java-bin/bat b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/java-bin/bat new file mode 100644 index 0000000000..1eb10b942e --- /dev/null +++ b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/extra-files/java-bin/bat @@ -0,0 +1,3 @@ +@ECHO OFF +set TOOLS_HOME=%~dp0.. +java -jar %TOOLS_HOME%\{{relativeBatTargetPath}} %* diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/fod-uploader/extra-files/bin/FoDUpload.bat b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/fod-uploader/extra-files/bin/FoDUpload.bat deleted file mode 100644 index 52aeaf4a51..0000000000 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/fod-uploader/extra-files/bin/FoDUpload.bat +++ /dev/null @@ -1,13 +0,0 @@ -@rem *************************************************************************** -@rem Copyright 2021, 2022 Open Text. -@rem -@rem The only warranties for products and services of Open Text -@rem and its affiliates and licensors ("Open Text") are as may -@rem be set forth in the express warranty statements accompanying -@rem such products and services. Nothing herein should be construed -@rem as constituting an additional warranty. Open Text shall not be -@rem liable for technical or editorial errors or omissions contained -@rem herein. The information contained herein is subject to change -@rem without notice. -@rem *************************************************************************** -java -jar %~dp0\..\FodUpload.jar %* \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/fod-uploader/fod-uploader.yaml b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/fod-uploader/fod-uploader.yaml deleted file mode 100644 index 3d1cbfb781..0000000000 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/fod-uploader/fod-uploader.yaml +++ /dev/null @@ -1,28 +0,0 @@ -#Versions must be listed in descending order to guarantee proper version selection when users provide a partial version number -defaultDownloadUrl: https://github.com/fod-dev/fod-uploader-java/releases/download/v{toolVersion}/FodUpload.jar -defaultVersion: 5.4.0 -versions: - - version: 5.4.0 - digest: SHA-256:7d3fc9ada2df3cd5ed6159685d36656d04cef6ddda69bff2110702c052e64fce - - version: 5.3.1 - digest: SHA-256:9ac7d8c093511796b9d69c24e7ef17b763614c24b27787eff27ed7960d9105f4 - - version: 5.3.0 - digest: SHA-256:302e394dd0a84321a388ae0a802c5847dcfe52284e2fe9d7d524952aea8214f0 - - version: 5.2.1 - digest: SHA-256:f52e070309cc5539ed1937cd16c370ccf5c09d5cf4e80708766ab43959d4fa20 - - version: 5.2.0 - digest: SHA-256:1e08c0fc1269e39fe502fe3ab301182e2ef2a8884328ac2beec092873d308100 - - version: 5.0.1 - digest: SHA-256:fc31198af03c074bc9190b85d6b9acf42495163f2d7db763c8bb4ed1df6b7d92 - - version: 5.0.0 - digest: SHA-256:7d06869581879ffcb055ab0c2f771d69e21f90caa4a22440d918a2f18b0a3125 - - version: 4.0.4 - digest: SHA-256:adf474e314e2ba2b2e2d8a63ef8e5bc513f49648c1979af2ce04a3191edd6130 - - version: 4.0.3 - digest: SHA-256:4c69b360752a74d5cd728fd8f5b519bbed4af5d6fff47dd3e849a924ba31d0c1 - - version: 4.0.2 - digest: SHA-256:47b3056c7dcee7b790670458f65a21757c422e67b178d6ac907ed32aead004be - - version: 4.0.1 - digest: SHA-256:7c5abde97d07288ae146160c4eee14091606b26590536b00d5e7b9bba31f0507 - - version: 4.0.0 - digest: SHA-256:6921614493c070d1788c9326a43ce7d65cad5427d7de47dd2548be74c1defa7c \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/i18n/ToolMessages.properties b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/i18n/ToolMessages.properties index 78d6aeafca..ea4ef7d35e 100644 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/i18n/ToolMessages.properties +++ b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/i18n/ToolMessages.properties @@ -4,59 +4,162 @@ usage.header = usage.description = +# Shared resources +fcli.tool.install.platform = By default, fcli will try to install tool binaries that match the current operating system \ + & architecture. Use this option to override automatic platform detection. The list-platforms command lists available \ + platforms. +fcli.tool.install.version = Tool version to install; see output of list command to view available versions. \ + Default value: ${DEFAULT-VALUE} +fcli.tool.install.install-dir = Tool installation directory. Deprecated, use --tools-dir instead. +fcli.tool.install.base-dir = Directory under which the tool will be installed. Defaults to ~/fortify. \ + Tools will be installed to //. +fcli.tool.install.on-digest-mismatch = Action to take if there is a digest mismatch. Allowed values: \ + ${COMPLETION-CANDIDATES}. Default action is to fail. +fcli.tool.install.uninstall = Uninstall the given versions while installing the new version. Accepts \ + 'all' to uninstall all existing versions, or a comma-separated list of version numbers. Version numbers may be \ + specified as [.[.]]. +fcli.tool.install.global-bin = By default, wrapper scripts will be installed to /bin; use --no-global-bin \ + to skip installing/updating these wrapper scripts. +fcli.tool.uninstall.versions = One or more tool version to uninstall. Accepts \ + 'all' to uninstall all existing versions, or a comma-separated list of version numbers. Version numbers may be \ + specified as [.[.]]. +fcli.tool.list-platforms.version = Tool version for which to list available platforms. +fcli.tool.install.confirmPrompt = Actions to be performed: %s\nConfirm? +fcli.tool.definitions.update.definitions-source = URL or file to get the tool definitions from. If not specified, \ + default tool definitions provided by OpenText are downloaded from a predefined URL. +fcli.tool.definitions.update.max-definitions-age = Only update tool definitions if the current definitions are older \ + than the specified maximum age, for example 0s (0 seconds, force update), 4h (4 hours) or 1d (1 day), to avoid \ + downloading the definitions multiple times in a short period of time. Default value: ${DEFAULT-VALUE} +fcli.tool.uninstall.confirmPrompt = Actions to be performed: %s\nConfirm? + +fcli.tool.install.generic-description = This command allows for tool installation and upgrade. All tools are installed \ + to //. The base directory can be specified using the --base-dir option, defaulting \ + to /fortify/tools if not specified. Unless the --no-global-bin option is specified, wrapper scripts for \ + invoking each tool will be installed to /bin, allowing users to add this directory to the system PATH for \ + easy tool invocations. \ + \n\nWhen installing a new version, older versions can optionally be automatically un-installed using the --uninstall \ + option, which is basically the equivalent of an upgrade. When trying to install a version that's already been installed, \ + only some post-installation tasks will be re-run, like installing the global bin scripts and performing any tool \ + configuration if applicable. \ + \n\nSample invocations: \ + \n\nInstall latest tool version, keep older versions: \ + \n fcli tool install -v latest \ + \n\nInstall latest tool version (only re-running post-install if already existing), uninstall all other tool versions: \ + \n fcli tool install -v latest --uninstall all \ + \n\nInstall latest v2 version, uninstall all other v2 versions: \ + \n fcli tool install -v 2 --uninstall 2 \ + \n\nInstall latest and latest v1, have global bin-scripts point to latest: \ + \n fcli tool install -v latest \ + \n fcli tool install -v 1 --no-global-bin + +fcli.tool.uninstall.generic-global-bin-description = Note that global bin scripts, if installed, will not be updated \ + or removed by this command. If you un-install the version to which the global bin-script is currently pointing, \ + the global bin script will cease functioning. To update the global bin script to point to an existing version, \ + please use the install command. + # fcli tool fcli.tool.usage.header = Install and manage other Fortify tools and utilities. fcli.tool.usage.description = The commands in this module allow for installing other Fortify tools like FoD Uploader, ScanCentral Client and FortifyVulnerabilityExporter, and managing those installations. -# Shared options -fcli.tool.install.version = Tool version to install; see output of list command to view available versions. If not specified, the default version as shown in the output of the list command will be installed. -fcli.tool.install.install-dir = Tool installation directory. -fcli.tool.install.on-digest-mismatch = Action to take if there is a digest mismatch. Allowed values: ${COMPLETION-CANDIDATES}. Default action is to fail. When installing 'latest', you may consider setting this option to 'warn' to avoid failures if fcli hasn't been updated yet with the latest digest. Please consider submitting an issue on the fcli issue tracker if digest check consistently fails. -fcli.tool.uninstall.version = Tool version to uninstall. -fcli.tool.output.header.isDefaultVersion = Default +#fcli tool definitions +fcli.tool.definitions.generic-description = Tool definitions list the available versions, corresponding download \ + location, and other details for each tool. These tool definitions are used by the various 'fcli tool' commands \ + to identify what tool versions are currently available and where to download them from. By default, each fcli \ + release ships with the latest tool definitions that were available at the time that release was built. +fcli.tool.definitions.usage.header = Manage tool definitions +fcli.tool.definitions.usage.description.0 = ${fcli.tool.definitions.generic-description}\n +fcli.tool.definitions.usage.description.1 = The commands in this module allow for managing tool definitions, like \ + updating tool definitions from a URL or local file. +fcli.tool.definitions.list.usage.header = List tool definitions +fcli.tool.definitions.update.usage.header = Update tool definitions +fcli.tool.definitions.update.usage.description.0 = ${fcli.tool.definitions.generic-description}\n +fcli.tool.definitions.update.usage.description.1 = The update command allows for updating the tool definitions from \ + a URL or local file. This allows for the current fcli installation to be aware of new tool versions that were \ + released after the current fcli release was built, and also allows users to use a custom tool definitions bundle.\n +fcli.tool.definitions.update.usage.description.2 = For example, if it's not allowed to download tool \ + installation bundles from public sites like github.com or tools.fortify.com, companies can host tool installation \ + bundles internally and provide a tool definitions bundle that points to the internally hosted installation bundles. \ + At the same time, companies can restrict which versions of each tool users are allowed to install. +fcli.tool.definitions.reset.usage.header = Reset tool definitions +fcli.tool.definitions.reset.usage.description = This command removes any tool definition updates, reverting to \ + the tool definitions originally shipped with this fcli release. # fcli tool bugtracker-utility -fcli.tool.bugtracker-utility.usage.header = Manage Fortify on Demand (FoD) Uploader installations. (https://github.com/fod-dev/fod-uploader-java) +fcli.tool.bugtracker-utility.usage.header = Manage FortifyBugTrackerUtility installations. (https://github.com/fortify-ps/FortifyBugTrackerUtility) fcli.tool.bugtracker-utility.install.usage.header = Download and install FortifyBugTrackerUtility. -fcli.tool.bugtracker-utility.install.confirm.0 = Confirm replacing existing FortifyBugTrackerUtility installation. -fcli.tool.bugtracker-utility.install.confirm.1 = If a non-empty destination directory exists, the installation will fail unless this option is specified. -fcli.tool.bugtracker-utility.list.usage.header = List FortifyBugTrackerUtility available and installed versions. If you don't see the latest version(s) listed, please submit an issue on the fcli issue tracker to request adding support for the missing versions. +fcli.tool.bugtracker-utility.install.usage.description = ${fcli.tool.install.generic-description} +fcli.tool.bugtracker-utility.install.confirm = Automatically confirm all prompts (cleaning the target directory, uninstalling other versions). +fcli.tool.bugtracker-utility.list.usage.header = List available and installed FortifyBugTrackerUtility versions. +fcli.tool.bugtracker-utility.list.usage.description = Use the 'fcli tool config update' command to update the list of available versions. +fcli.tool.bugtracker-utility.list-platforms.usage.header = List available platforms for FortifyBugTrackerUtility fcli.tool.bugtracker-utility.uninstall.usage.header = Uninstall FortifyBugTrackerUtility. -fcli.tool.bugtracker-utility.uninstall.usage.description = This command removes a FortifyBugTrackerUtility installation that was previously installed using the 'fcli tool bugtracker-utility install' command. +fcli.tool.bugtracker-utility.uninstall.usage.description = This command removes one or more FortifyBugTrackerUtility installations that were previously installed using the 'fcli tool bugtracker-utility install' command. ${fcli.tool.uninstall.generic-global-bin-description} fcli.tool.bugtracker-utility.uninstall.confirm = Confirm removal of FortifyBugTrackerUtility. +# fcli tool debricked +fcli.tool.debricked-cli.usage.header = Manage Debricked CLI installations. (https://github.com/debricked/cli) +fcli.tool.debricked-cli.install.usage.header = Download and install the Debricked CLI. +fcli.tool.debricked-cli.install.usage.description = ${fcli.tool.install.generic-description} +fcli.tool.debricked-cli.install.confirm = Automatically confirm all prompts (cleaning the target directory, uninstalling other versions). +fcli.tool.debricked-cli.list.usage.header = List available and installed Debricked CLI versions. +fcli.tool.debricked-cli.list.usage.description = Use the 'fcli tool config update' command to update the list of available versions. +fcli.tool.debricked-cli.list-platforms.usage.header = List available platforms for Debricked CLI +fcli.tool.debricked-cli.uninstall.usage.header = Uninstall Debricked CLI. +fcli.tool.debricked-cli.uninstall.usage.description = This command removes one or more Debricked CLI installations that were previously installed using the 'fcli tool debricked install' command. ${fcli.tool.uninstall.generic-global-bin-description} +fcli.tool.debricked-cli.uninstall.confirm = Confirm removal of Debricked CLI. + +# fcli tool fcli +fcli.tool.fcli.usage.header = Manage fcli installations. (https://github.com/fortify/fcli) +fcli.tool.fcli.install.usage.header = Download and install fcli. +fcli.tool.fcli.install.usage.description = ${fcli.tool.install.generic-description} +fcli.tool.fcli.install.confirm = Automatically confirm all prompts (cleaning the target directory, uninstalling other versions). +fcli.tool.fcli.list.usage.header = List available and installed fcli versions. +fcli.tool.fcli.list.usage.description = Use the 'fcli tool config update' command to update the list of available versions. +fcli.tool.fcli.list-platforms.usage.header = List available platforms for fcli. +fcli.tool.fcli.uninstall.usage.header = Uninstall fcli. +fcli.tool.fcli.uninstall.usage.description = This command removes one or more fcli installations that were previously installed using the 'fcli tool fcli install' command. ${fcli.tool.uninstall.generic-global-bin-description} +fcli.tool.fcli.uninstall.confirm = Confirm removal of fcli. + # fcli tool fod-uploader fcli.tool.fod-uploader.usage.header = Manage Fortify on Demand (FoD) Uploader installations. (https://github.com/fod-dev/fod-uploader-java) fcli.tool.fod-uploader.install.usage.header = Download and install Fortify on Demand Uploader. -fcli.tool.fod-uploader.install.confirm.0 = Confirm replacing existing FoD Uploader installation. -fcli.tool.fod-uploader.install.confirm.1 = If a non-empty destination directory exists, the installation will fail unless this option is specified. -fcli.tool.fod-uploader.list.usage.header = List Fortify on Demand Uploader available and installed versions. If you don't see the latest version(s) listed, please submit an issue on the fcli issue tracker to request adding support for the missing versions. +fcli.tool.fod-uploader.install.usage.description = ${fcli.tool.install.generic-description} +fcli.tool.fod-uploader.install.confirm = Automatically confirm all prompts (cleaning the target directory, uninstalling other versions). +fcli.tool.fod-uploader.list.usage.header = List available and installed FoD Uploader versions. +fcli.tool.fod-uploader.list.usage.description = Use the 'fcli tool config update' command to update the list of available versions. +fcli.tool.fod-uploader.list-platforms.usage.header = List available platforms for FoD Uploader. fcli.tool.fod-uploader.uninstall.usage.header = Uninstall Fortify on Demand Uploader. -fcli.tool.fod-uploader.uninstall.usage.description = This command removes a Fortify on Demand Uploader installation that was previously installed using the 'fcli tool fod-uploader install' command. +fcli.tool.fod-uploader.uninstall.usage.description = This command removes one or more Fortify on Demand Uploader installations that were previously installed using the 'fcli tool fod-uploader install' command. ${fcli.tool.uninstall.generic-global-bin-description} fcli.tool.fod-uploader.uninstall.confirm = Confirm removal of Fortify on Demand Uploader. # fcli tool sc-client fcli.tool.sc-client.usage.header = Manage ScanCentral SAST Client installations. fcli.tool.sc-client.install.usage.header = Download and install ScanCentral SAST Client. -fcli.tool.sc-client.install.confirm.0 = Confirm replacing existing ScanCentral SAST Client installation. -fcli.tool.sc-client.install.confirm.1 = If a non-empty destination directory exists, the installation will fail unless this option is specified. +fcli.tool.sc-client.install.usage.description = ${fcli.tool.install.generic-description} +fcli.tool.sc-client.install.confirm = Automatically confirm all prompts (cleaning the target directory, uninstalling other versions). fcli.tool.sc-client.install.client-auth-token = ScanCentral SAST client_auth_token used for authenticating with ScanCentral SAST Controller. -fcli.tool.sc-client.list.usage.header = List ScanCentral SAST Client available and installed versions. If you don't see the latest version(s) listed, please submit an issue on the fcli issue tracker to request adding support for the missing versions. +fcli.tool.sc-client.list.usage.header = List available and installed ScanCentral SAST Client versions. +fcli.tool.sc-client.list.usage.description = Use the 'fcli tool config update' command to update the list of available versions. +fcli.tool.sc-client.list-platforms.usage.header = List available platforms for ScanCentral SAST Client. fcli.tool.sc-client.uninstall.usage.header = Uninstall ScanCentral SAST Client. -fcli.tool.sc-client.uninstall.usage.description = This command removes a ScanCentral Client installation that was previously installed using the 'fcli tool sc-client install' command. +fcli.tool.sc-client.uninstall.usage.description = This command removes one or more ScanCentral Client installations that were previously installed using the 'fcli tool sc-client install' command. ${fcli.tool.uninstall.generic-global-bin-description} fcli.tool.sc-client.uninstall.confirm = Confirm removal of ScanCentral SAST Client. # fcli tool vuln-exporter fcli.tool.vuln-exporter.usage.header = Manage Fortify Vulnerability Exporter installations. (https://github.com/fortify/FortifyVulnerabilityExporter) fcli.tool.vuln-exporter.install.usage.header = Download and install Fortify Vulnerability Exporter. -fcli.tool.vuln-exporter.install.confirm.0 = Confirm replacing existing Fortify Vulnerability Exporter installation. -fcli.tool.vuln-exporter.install.confirm.1 = If a non-empty destination directory exists, the installation will fail unless this option is specified. -fcli.tool.vuln-exporter.list.usage.header = List Fortify Vulnerability Exporter available and installed versions. If you don't see the latest version(s) listed, please submit an issue on the fcli issue tracker to request adding support for the missing versions. +fcli.tool.vuln-exporter.install.usage.description = ${fcli.tool.install.generic-description} +fcli.tool.vuln-exporter.install.confirm = Automatically confirm all prompts (cleaning the target directory, uninstalling other versions). +fcli.tool.vuln-exporter.list.usage.header = List available and installed Fortify Vulnerability Exporter versions. +fcli.tool.vuln-exporter.list.usage.description = Use the 'fcli tool config update' command to update the list of available versions. +fcli.tool.vuln-exporter.list-platforms.usage.header = List available platforms for Fortify Vulnerability Exporter. fcli.tool.vuln-exporter.uninstall.usage.header = Uninstall Fortify Vulnerability Exporter. -fcli.tool.sc-client.uninstall.usage.description = This command removes a Fortify Vulnerability Exporter installation that was previously installed using the 'fcli tool vuln-exporter install' command. +fcli.tool.sc-client.uninstall.usage.description = This command removes one or more Fortify Vulnerability Exporter installations that were previously installed using the 'fcli tool vuln-exporter install' command. ${fcli.tool.uninstall.generic-global-bin-description} fcli.tool.vuln-exporter.uninstall.confirm = Confirm removal of Fortify Vulnerability Exporter. ################################################################################################################# # The following are technical properties that shouldn't be internationalized #################################### ################################################################################################################# -fcli.tool.output.table.options = name,version,isDefaultVersion,installed,installDir,binDir \ No newline at end of file +fcli.tool.output.table.options = name,version,aliasesString,stable,installDir +fcli.tool.list-platforms.output.table.options = platform +fcli.tool.definitions.output.table.options = name,source,lastUpdate diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/sc-client/sc-client.yaml b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/sc-client/sc-client.yaml deleted file mode 100644 index 35e221787b..0000000000 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/sc-client/sc-client.yaml +++ /dev/null @@ -1,32 +0,0 @@ -#Versions must be listed in descending order to guarantee proper version selection when users provide a partial version number -defaultDownloadUrl: https://tools.fortify.com/scancentral/Fortify_ScanCentral_Client_{toolVersion}_x64.zip -defaultVersion: 23.1.0 -versions: - - version: 23.1.0 - digest: SHA-256:012e00fab914495ad235e8658207702869e02fffbd4d2adfcc3c2baf50fe8de9 - - version: 22.2.1 - digest: SHA-256:1b63e4e8bb0f9314d7ad1b0b925b0fbf878bfa37a1436774357de36fefbeeedf - - version: 22.2.0 - digest: SHA-256:44d49bddbf73703cbca619eff05c4d5aff6725b0b1809717722b63c0a3f4c9ae - - version: 22.1.2 - digest: SHA-256:534034b322811b6eb15289c5ae538837c615f2d94ef17f24a1d5c26f492b98e0 - - version: 22.1.0 - digest: SHA-256:dc57f68e1b89578ee7b9230c6ff5ea268480e8ca6d847f3ddbdb0bb95065e107 - - version: 21.2.3 - digest: SHA-256:96b6678e0a133f2589ac1df7b7ff34141630c2c697ea17be3a610bccb3b0d97e - - version: 21.2.2 - digest: SHA-256:e38dcd114153ff9e3f570f2dae2a9e5ebf30b10c9900ae8b96a03e22bc35a487 - - version: 21.2.0 - digest: SHA-256:055e26cb8ee81f148536a9e94b64a07d388a243291b48293df17d7ee2b4f2d58 - - version: 21.1.4 - digest: SHA-256:c12b4725fa1bcaf718d05a7dcd5fb44b90139b21fa4009fa3b1c170b886df80d - - version: 21.1.3 - digest: SHA-256:176e6ec213cb958b6828ca8601dc482d5a4ba5126f79e3249bf4e58d6982aea0 - - version: 21.1.2 - digest: SHA-256:313d37acc652edba9657fbc8fed1d811ad0df014636f02314a97865c48244dd6 - - version: 20.2.4 - digest: SHA-256:a0a6acba48b7b3f989db191053d336410b124cb493f006abcba09cc70311b095 - - version: 20.2.0 - digest: SHA-256:c559e1e08c0d90af71e77bdbb806731f818f59d4b3da7e41c02a307495c38d06 - - version: 20.1.0 - digest: SHA-256:4a315c9ab9c61978b47772945e29063545478ae2f0e4574c0111bce891c04eb5 \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/vuln-exporter/extra-files/bin/FortifyVulnerabilityExporter b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/vuln-exporter/extra-files/bin/FortifyVulnerabilityExporter deleted file mode 100644 index ea1aa54492..0000000000 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/vuln-exporter/extra-files/bin/FortifyVulnerabilityExporter +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -java -jar ${SCRIPT_DIR}/../FortifyVulnerabilityExporter.jar "$@" \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/vuln-exporter/extra-files/bin/FortifyVulnerabilityExporter.bat b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/vuln-exporter/extra-files/bin/FortifyVulnerabilityExporter.bat deleted file mode 100644 index 9e62db14ab..0000000000 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/vuln-exporter/extra-files/bin/FortifyVulnerabilityExporter.bat +++ /dev/null @@ -1,13 +0,0 @@ -@rem *************************************************************************** -@rem Copyright 2021, 2022 Open Text. -@rem -@rem The only warranties for products and services of Open Text -@rem and its affiliates and licensors ("Open Text") are as may -@rem be set forth in the express warranty statements accompanying -@rem such products and services. Nothing herein should be construed -@rem as constituting an additional warranty. Open Text shall not be -@rem liable for technical or editorial errors or omissions contained -@rem herein. The information contained herein is subject to change -@rem without notice. -@rem *************************************************************************** -java -jar %~dp0\..\FortifyVulnerabilityExporter.jar %* \ No newline at end of file diff --git a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/vuln-exporter/vuln-exporter.yaml b/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/vuln-exporter/vuln-exporter.yaml deleted file mode 100644 index 6495c6942f..0000000000 --- a/fcli-core/fcli-tool/src/main/resources/com/fortify/cli/tool/vuln-exporter/vuln-exporter.yaml +++ /dev/null @@ -1,56 +0,0 @@ -#Versions must be listed in descending order to guarantee proper version selection when users provide a partial version number -defaultDownloadUrl: https://github.com/fortify/FortifyVulnerabilityExporter/releases/download/v{toolVersion}/FortifyVulnerabilityExporter.zip -defaultVersion: 2.0.4 -versions: - - version: 2.0.4 - digest: SHA-256:e4793235e2d1152a98975b5aa3baee4ab440ae08ba03e320ec0c4882cd325a2b - - version: 2.0.3 - digest: SHA-256:6f3b83b1046dc76038e8fb2121c4dbf5c976fa4cb10c3826d591f6fb31c14bee - - version: 2.0.2 - digest: SHA-256:d5ae837278fd01cf876064f62799c13ecc764b1b9aef5a84d25ca8abe51874fc - - version: 2.0.1 - digest: SHA-256:2d46b4c808e6fa594d3179e0e441a5f2f22fde3ba0624e269aba0109816a3c81 - - version: 2.0.0 - digest: SHA-256:fae3d3adbd0629ede5013227e33da2f25786006530b647a9f301068707a102e5 - - version: 1.8.0 - digest: SHA-256:1f3a710003c81a9e767f2c90bbfca3f2250b01ad3a50b181a04eb7d025c05da6 - - version: 1.7.0 - digest: SHA-256:d04e71cd9049475911094f43bcf96184763cc0e8989fb329cc18117f8a96d98d - - version: 1.6.0 - digest: SHA-256:48dffe599c8712393c7491f5c824de1a17afea0d0a5b4c8f0c657674ff2c6bb9 - - version: 1.5.5 - digest: SHA-256:6a6fe679768a8f079f00befe1daf6a606b091bf199829d66fe4f8b28de40e67c - - version: 1.5.4 - digest: SHA-256:98a1deb443859018de25c853bb90001088819be556495b309a7e34c798c8da33 - - version: 1.5.3 - digest: SHA-256:86ce98782823354a3bfc0fe770ec8344df616b69316b73d2319e875a9d2bb8d2 - - version: 1.5.2 - digest: SHA-256:86eaa462085f8a0b178a4a697e13332ce1954a9e3d886a7d8c710ce5b53a3a07 - - version: 1.5.1 - digest: SHA-256:8db8d6db6c7d3e6ddeee0722179cb0f413a0d3dcefa19a21db5461a0b0c410d4 - - version: 1.5.0 - digest: SHA-256:cc86851b0b5adada0b04f43b93693a637ab3735e85d25246f859535aeb7835cf - - version: 1.4.1 - digest: SHA-256:6e9fa005364513593ab820c79d2c1f05283ff9769fb313c669f0159c395e3d4c - - version: 1.4.0 - digest: SHA-256:63b6e90c5a06f3db6d913121a5fb8a7578d2a1c65a4bdc8cfd6fd0aef286d296 - - version: 1.3.1 - digest: SHA-256:8b3f1d8696ed183a9ae0e005bc4165571b0be3e80e59038eb16a446e3ef5e91b - - version: 1.3.0 - digest: SHA-256:28515fd51112b803a1d154884efd3743013895bb3f0037e4eedb2a8b18659bb7 - - version: 1.2.1 - digest: SHA-256:1badaf7f91be4482d7666d398893e1b68e24d446811843bee74aea5144f0fe1d - - version: 1.2.0 - digest: SHA-256:05acd1451bcd5b7639e5abb43d42544a6ff5b53d3e9f6c49891094a4d9d6fc6a - - version: 1.1.3 - digest: SHA-256:07b885a3690d111a0bc3b4e40581cd148bd36435255f1a7638c574ebeb8975e1 - - version: 1.1.2 - digest: SHA-256:5c27aa10b9fa8cd6b49317543f9fb0ae8e53d6d099f62daadd3b8086f3e56500 - - version: 1.1.1 - digest: SHA-256:d27c234a0a85ac79ab0f317777a151b70c8899f3c8bdf909779823555227d98f - - version: 1.1.0 - digest: SHA-256:05a6fed8ded797ab4afb0bdede2a53fa830c218f9944cc076fcaab316505d20d - - version: 1.0.1 - digest: SHA-256:23f499053ed2663084ca509d292d4190c1a609941371d7094a7b3cf474363d4f - - version: 1.0.0 - digest: SHA-256:2edf4bc065e48cb8a2571752dfbe5a944495824e8a3c899e06206a24bf15562c diff --git a/fcli-core/fcli-tool/src/test/java/com/fortify/cli/tool/versionHandling/VersionHandlingTest.java b/fcli-core/fcli-tool/src/test/java/com/fortify/cli/tool/versionHandling/VersionHandlingTest.java deleted file mode 100644 index d610b39119..0000000000 --- a/fcli-core/fcli-tool/src/test/java/com/fortify/cli/tool/versionHandling/VersionHandlingTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.fortify.cli.tool.versionHandling; - -import java.io.InputStream; - -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.fortify.cli.tool._common.helper.ToolDownloadDescriptor; -import com.fortify.cli.tool._common.helper.ToolHelper; - -import org.junit.jupiter.api.Assertions; - - -public class VersionHandlingTest { - private static final ObjectMapper yamlObjectMapper = new ObjectMapper(new YAMLFactory()); - String toolName = "sc-client"; - - @Test - public void testVersionHandling() throws Exception { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - String resourceFile = ToolHelper.getResourceFile(toolName, String.format("%s.yaml", toolName)); - try (InputStream file = classLoader.getResourceAsStream(resourceFile)) { - - var downloadDescriptor = yamlObjectMapper.readValue(file, ToolDownloadDescriptor.class); - var d = downloadDescriptor.getVersion("20"); - var d2 = downloadDescriptor.getVersion("20."); - var d3 = downloadDescriptor.getVersion("21.1"); - var d4 = downloadDescriptor.getVersion("21.1.2"); - - Assertions.assertEquals(d.getVersion(), "20.2.4"); - Assertions.assertEquals(d2.getVersion(), "20.2.4"); - Assertions.assertEquals(d3.getVersion(), "21.1.4"); - Assertions.assertEquals(d4.getVersion(), "21.1.2"); - } catch(Exception e) { - throw e; - } - } -} - diff --git a/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/_main/cli/cmd/UtilCommands.java b/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/_main/cli/cmd/UtilCommands.java index 98ee15cde9..c18463ced7 100644 --- a/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/_main/cli/cmd/UtilCommands.java +++ b/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/_main/cli/cmd/UtilCommands.java @@ -16,7 +16,6 @@ import com.fortify.cli.util.all_commands.cli.cmd.AllCommandsCommands; import com.fortify.cli.util.autocomplete.cli.cmd.AutoCompleteCommands; import com.fortify.cli.util.crypto.cli.cmd.CryptoCommands; -import com.fortify.cli.util.github.cli.cmd.GitHubCommands; import com.fortify.cli.util.sample_data.cli.cmd.SampleDataCommands; import com.fortify.cli.util.state.cli.cmd.StateCommands; import com.fortify.cli.util.variable.cli.cmd.VariableCommands; @@ -30,7 +29,6 @@ AllCommandsCommands.class, AutoCompleteCommands.class, CryptoCommands.class, - GitHubCommands.class, SampleDataCommands.class, StateCommands.class, VariableCommands.class diff --git a/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/AbstractGitHubRepoCommand.java b/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/AbstractGitHubRepoCommand.java deleted file mode 100644 index 69c860780e..0000000000 --- a/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/AbstractGitHubRepoCommand.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright 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.util.github.cli.cmd; - -import org.apache.http.client.utils.URIBuilder; - -import com.fortify.cli.common.output.cli.cmd.AbstractOutputCommand; -import com.fortify.cli.common.rest.github.GitHubPagingHelper; -import com.fortify.cli.common.rest.paging.INextPageUrlProducer; -import com.fortify.cli.common.rest.paging.INextPageUrlProducerSupplier; -import com.fortify.cli.common.rest.unirest.GenericUnirestFactory; -import com.fortify.cli.common.rest.unirest.IUnirestInstanceSupplier; - -import kong.unirest.UnirestInstance; -import lombok.Getter; -import lombok.SneakyThrows; -import picocli.CommandLine.Option; - -/** - * - * @author Ruud Senden - */ -public abstract class AbstractGitHubRepoCommand extends AbstractOutputCommand implements INextPageUrlProducerSupplier, IUnirestInstanceSupplier { - @Getter @Option(names={"--api-url"}, defaultValue = "https://api.github.com", required = true, descriptionKey = "fcli.util.github.api-url") - private String apiUrl; - @Getter @Option(names={"--repo", "-r"}, required = true, descriptionKey = "fcli.util.github.repo") - private String repo; - - @Override - public UnirestInstance getUnirestInstance() { - return GenericUnirestFactory.getUnirestInstance(apiUrl, null); - } - - @Override - public INextPageUrlProducer getNextPageUrlProducer() { - return GitHubPagingHelper.nextPageUrlProducer(); - } - - @SneakyThrows - protected String getRepoEndpointUrl(String endpoint) { - endpoint = endpoint.replaceAll("^/", ""); - return new URIBuilder(apiUrl).setPath(String.format("/repos/%s/%s", repo, endpoint)).build().toString(); - } -} \ No newline at end of file diff --git a/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/GitHubListReleaseAssetsCommand.java b/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/GitHubListReleaseAssetsCommand.java deleted file mode 100644 index b0848e3612..0000000000 --- a/fcli-core/fcli-util/src/main/java/com/fortify/cli/util/github/cli/cmd/GitHubListReleaseAssetsCommand.java +++ /dev/null @@ -1,95 +0,0 @@ -/******************************************************************************* - * 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.util.github.cli.cmd; - -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.json.JsonHelper; -import com.fortify.cli.common.output.cli.cmd.IBaseRequestSupplier; -import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; -import com.fortify.cli.common.output.transform.IInputTransformer; -import com.fortify.cli.common.output.transform.IRecordTransformer; -import com.fortify.cli.common.progress.cli.mixin.ProgressWriterFactoryMixin; -import com.fortify.cli.common.util.FileUtils; -import com.fortify.cli.common.util.StringUtils; - -import kong.unirest.HttpRequest; -import kong.unirest.RawResponse; -import lombok.Getter; -import picocli.CommandLine.Command; -import picocli.CommandLine.Mixin; -import picocli.CommandLine.Option; - -@Command(name = "list-release-assets", aliases = "lsra") -public class GitHubListReleaseAssetsCommand extends AbstractGitHubRepoCommand implements IBaseRequestSupplier, IInputTransformer, IRecordTransformer { - @Getter @Mixin private OutputHelperMixins.TableWithQuery outputHelper; - @Mixin private ProgressWriterFactoryMixin progressWriterFactory; - @Option(names = {"--digest", "-d"}, required = false) private String digestAlgorithm; - - @Override - public HttpRequest getBaseRequest() { - var endpoint = getRepoEndpointUrl("/releases"); - return getUnirestInstance().get(endpoint); - } - - @Override - public JsonNode transformRecord(JsonNode record) { - return addDigest((ObjectNode)record); - } - - @Override - public JsonNode transformInput(JsonNode input) { - ArrayNode result = JsonHelper.getObjectMapper().createArrayNode(); - input.forEach(release->addReleaseAssets(result, (ObjectNode)release)); - return result; - } - - private void addReleaseAssets(ArrayNode result, ObjectNode release) { - release.get("assets").forEach(asset->addReleaseAsset(result, release, (ObjectNode)asset)); - release.remove("assets"); - } - - private void addReleaseAsset(ArrayNode result, ObjectNode release, ObjectNode asset) { - asset.set("release", release); - // We already filter assets here, to avoid calculating hashes for non-matching assets - if ( outputHelper.getOutputWriterFactory().getQueryExpression().matches(asset) ) { - result.add(asset); - } - } - - private ObjectNode addDigest(ObjectNode asset) { - String digest_algorithm = "N/A"; - String digest = "Not Calculated"; - if ( StringUtils.isNotBlank(digestAlgorithm) ) { - try (var pw = progressWriterFactory.create()) { - var downloadUrl = asset.get("browser_download_url").asText(); - pw.writeProgress("Calculating digest for %s", downloadUrl); - digest_algorithm = this.digestAlgorithm; - digest = getUnirestInstance().get(downloadUrl).asObject(raw->calculateDigest(downloadUrl, raw)).getBody(); - } - } - asset.put("digest_algorithm", digest_algorithm); - asset.put("digest", digest); - return asset; - } - - private String calculateDigest(String downloadUrl, RawResponse rawResponse) { - return FileUtils.getDigest(downloadUrl, rawResponse.getContent(), digestAlgorithm); - } - - @Override - public boolean isSingular() { - return false; - } -} diff --git a/fcli-other/fcli-bom/build.gradle b/fcli-other/fcli-bom/build.gradle index 2d02f1cb07..236a6cc0ed 100644 --- a/fcli-other/fcli-bom/build.gradle +++ b/fcli-other/fcli-bom/build.gradle @@ -52,5 +52,8 @@ dependencies { api 'org.junit.jupiter:junit-jupiter-api:5.9.3' api 'org.junit.jupiter:junit-jupiter-params:5.9.3' api 'org.junit.jupiter:junit-jupiter-engine:5.9.3' + + //required for unpacking tar.gz (debricked cli) + api('org.apache.commons:commons-compress:1.25.0') } } \ No newline at end of file diff --git a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/ssc/SSCAttributeSpec.groovy b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/ssc/SSCAttributeSpec.groovy index 4de9c5c9bb..3d5a3583bb 100644 --- a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/ssc/SSCAttributeSpec.groovy +++ b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/ssc/SSCAttributeSpec.groovy @@ -24,7 +24,7 @@ class SSCAttributeSpec extends FcliBaseSpec { then: verifyAll(result.stdout) { size()>=2 - it[0].replace(" ","").equals("IdCategoryGuidNameValuestring"); + it[0].replace(" ","").equals("IdCategoryGuidNameValue"); } } @@ -36,7 +36,7 @@ class SSCAttributeSpec extends FcliBaseSpec { then: verifyAll(result.stdout) { size()==3 - it[0].replace(" ","").equals("IdCategoryGuidNameValuestring"); + it[0].replace(" ","").equals("IdCategoryGuidNameValue"); } } diff --git a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolBugTrackerUtilitySpec.groovy b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolBugTrackerUtilitySpec.groovy index 9ba3f8372a..f1f8d91c43 100644 --- a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolBugTrackerUtilitySpec.groovy +++ b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolBugTrackerUtilitySpec.groovy @@ -12,32 +12,34 @@ */ package com.fortify.cli.ftest.tool -import static com.fortify.cli.ftest._common.spec.FcliSessionType.SSC +import java.nio.file.Files +import java.nio.file.Path import com.fortify.cli.ftest._common.Fcli import com.fortify.cli.ftest._common.spec.FcliBaseSpec -import com.fortify.cli.ftest._common.spec.FcliSession import com.fortify.cli.ftest._common.spec.Prefix -import com.fortify.cli.ftest.ssc._common.SSCRoleSupplier -import com.fortify.cli.ftest.ssc._common.SSCRoleSupplier.SSCRole -import spock.lang.AutoCleanup -import spock.lang.Requires +import com.fortify.cli.ftest._common.spec.TempDir + import spock.lang.Shared import spock.lang.Stepwise @Prefix("tool.bugtracker-utility") @Stepwise class ToolBugTrackerUtilitySpec extends FcliBaseSpec { - + @Shared @TempDir("fortify/tools") String baseDir; + @Shared String version = "4.12.0" + @Shared Path globalBinScript = Path.of(baseDir).resolve("bin/FortifyBugTrackerUtility.bat"); + @Shared Path binScript = Path.of(baseDir).resolve("bugtracker-utility/${version}/bin/FortifyBugTrackerUtility.bat"); def "install"() { - def args = "tool bugtracker-utility install -y latest" + def args = "tool bugtracker-utility install -y -v=${version} --progress=none -b ${baseDir}" when: def result = Fcli.run(args) then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindirAction") - it[1].replace(" ", "").contains("YesYes") - it[1].contains("INSTALLED") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" INSTALLED") + Files.exists(binScript); + Files.exists(globalBinScript); } } @@ -48,21 +50,23 @@ class ToolBugTrackerUtilitySpec extends FcliBaseSpec { then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindir") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldir") it[1].replace(" ", "").startsWith("bugtracker-utility") - it[1].replace(" ", "").contains("YesYes") } } def "uninstall"() { - def args = "tool bugtracker-utility uninstall -y default" + def args = "tool bugtracker-utility uninstall -y --progress=none -v=${version}" when: def result = Fcli.run(args) then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindirAction") - it[1].replace(" ", "").contains("YesNoN/AN/AUNINSTALLED") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" UNINSTALLED") + // TODO fcli currently doesn't delete/update global bin script + Files.exists(globalBinScript); + !Files.exists(binScript); } } diff --git a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolDebrickedSpec.groovy b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolDebrickedSpec.groovy new file mode 100644 index 0000000000..6df8205b2c --- /dev/null +++ b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolDebrickedSpec.groovy @@ -0,0 +1,74 @@ +/** + * Copyright 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.ftest.tool + +import java.nio.file.Files +import java.nio.file.Path + +import com.fortify.cli.ftest._common.Fcli +import com.fortify.cli.ftest._common.spec.FcliBaseSpec +import com.fortify.cli.ftest._common.spec.Prefix +import com.fortify.cli.ftest._common.spec.TempDir + +import spock.lang.Shared +import spock.lang.Stepwise + +@Prefix("tool.debricked") @Stepwise +class ToolDebrickedSpec extends FcliBaseSpec { + @Shared @TempDir("fortify/tools") String baseDir; + @Shared String version = "1.7.12" + @Shared Path globalBinScript = Path.of(baseDir).resolve("bin/debricked.bat"); + @Shared Path binScript = Path.of(baseDir).resolve("debricked-cli/${version}/bin/debricked.exe"); + + def "install"() { + def args = "tool debricked-cli install -y -v=${version} --progress=none -b ${baseDir} --platform windows/x64" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" INSTALLED") + Files.exists(binScript); + Files.exists(globalBinScript); + } + } + + def "listVersions"() { + def args = "tool debricked-cli list" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldir") + it[1].replace(" ", "").startsWith("debricked") + } + } + + def "uninstall"() { + def args = "tool debricked-cli uninstall -y --progress=none -v=${version}" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" UNINSTALLED") + // TODO fcli currently doesn't delete/update global bin script + Files.exists(globalBinScript); + !Files.exists(binScript); + } + } + +} diff --git a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolDefinitionsSpec.groovy b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolDefinitionsSpec.groovy new file mode 100644 index 0000000000..232dc4e273 --- /dev/null +++ b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolDefinitionsSpec.groovy @@ -0,0 +1,146 @@ +/** + * Copyright 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.ftest.tool + +import static com.fortify.cli.ftest._common.spec.FcliSessionType.SSC + +import com.fortify.cli.ftest._common.Fcli +import com.fortify.cli.ftest._common.spec.FcliBaseSpec +import com.fortify.cli.ftest._common.spec.FcliSession +import com.fortify.cli.ftest._common.spec.Prefix +import com.fortify.cli.ftest.ssc._common.SSCRoleSupplier +import com.fortify.cli.ftest.ssc._common.SSCRoleSupplier.SSCRole +import spock.lang.AutoCleanup +import spock.lang.Requires +import spock.lang.Shared +import spock.lang.Stepwise + +@Prefix("tool.definitions.update") @Stepwise +class ToolDefinitionsSpec extends FcliBaseSpec { + def "reset"() { + def args = "tool definitions reset" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameSourceLastupdateAction") + it[1].replace(" ", "").contains("INTERNAL") + it[1].contains(" RESET") + } + } + + def "ls-after-reset"() { + def args = "tool definitions list" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameSourceLastupdate") + it[1].replace(" ", "").contains("INTERNAL") + } + } + + def "update"() { + def args = "tool definitions update" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameSourceLastupdateAction") + it[1].replace(" ", "").contains("https://github.com/") + it[1].contains(" UPDATED") + } + } + + def "ls-after-update"() { + def args = "tool definitions list" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameSourceLastupdate") + it[1].replace(" ", "").contains("https://github.com/") + } + } + + def "listVersions"() { + def args = "tool debricked-cli list" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldir") + it[1].replace(" ", "").startsWith("debricked") + } + } + + /* + // TODO The tool definitions hosted on this URL are no longer valid due to yaml structure changes + def "updateWithUrl"() { + def args = "tool definitions update --source https://github.com/psmf22/tool-definitions/raw/main/v1/tool-definitions.yaml.zip" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("SourceLastupdateAction") + it[1].replace(" ", "").contains("https://github.com/psmf22/tool-definitions/raw/main/v1/tool-definitions") + it[1].contains(" UPDATED") + } + } + */ + + def "listVersions2"() { + def args = "tool debricked-cli list" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldir") + it[1].replace(" ", "").startsWith("debricked") + } + } + /* + def "updateWithLocalPath"() { + def args = "tool config update --file C:/temp/tool-definitions.yaml.zip" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("RemotepathLocalpathAction") + it[1].replace(" ", "").contains("C:/temp/tool-definitions.yaml.zip") + it[1].contains("UPDATED") + } + } + + def "updateWithUNCPath"() { + def args = "tool config update --file \\\\localhost\\C\$\\temp\\tool-definitions.yaml.zip" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("RemotepathLocalpathAction") + it[1].replace(" ", "").contains("\\\\localhost\\C\$\\temp\\tool-definitions.yaml.zip") + it[1].contains("UPDATED") + } + }*/ + +} diff --git a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolFcliSpec.groovy b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolFcliSpec.groovy new file mode 100644 index 0000000000..c739b1cbb9 --- /dev/null +++ b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolFcliSpec.groovy @@ -0,0 +1,74 @@ +/** + * Copyright 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.ftest.tool + +import java.nio.file.Files +import java.nio.file.Path + +import com.fortify.cli.ftest._common.Fcli +import com.fortify.cli.ftest._common.spec.FcliBaseSpec +import com.fortify.cli.ftest._common.spec.Prefix +import com.fortify.cli.ftest._common.spec.TempDir + +import spock.lang.Shared +import spock.lang.Stepwise + +@Prefix("tool.fcli") @Stepwise +class ToolFcliSpec extends FcliBaseSpec { + @Shared @TempDir("fortify/tools") String baseDir; + @Shared String version = "2.1.0" + @Shared Path globalBinScript = Path.of(baseDir).resolve("bin/fcli.bat"); + @Shared Path binScript = Path.of(baseDir).resolve("fcli/${version}/bin/fcli.exe"); + + def "install"() { + def args = "tool fcli install -y -v=${version} --progress=none -b ${baseDir} --platform windows/x64" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" INSTALLED") + Files.exists(binScript); + Files.exists(globalBinScript); + } + } + + def "listVersions"() { + def args = "tool fcli list" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldir") + it[1].replace(" ", "").startsWith("fcli") + } + } + + def "uninstall"() { + def args = "tool fcli uninstall -y --progress=none -v=${version}" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" UNINSTALLED") + // TODO fcli currently doesn't delete/update global bin script + Files.exists(globalBinScript); + !Files.exists(binScript); + } + } + +} diff --git a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolFoDUploaderSpec.groovy b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolFoDUploaderSpec.groovy index 287cb1422d..baa6f08bbc 100644 --- a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolFoDUploaderSpec.groovy +++ b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolFoDUploaderSpec.groovy @@ -12,32 +12,35 @@ */ package com.fortify.cli.ftest.tool -import static com.fortify.cli.ftest._common.spec.FcliSessionType.SSC +import java.nio.file.Files +import java.nio.file.Path import com.fortify.cli.ftest._common.Fcli import com.fortify.cli.ftest._common.spec.FcliBaseSpec -import com.fortify.cli.ftest._common.spec.FcliSession import com.fortify.cli.ftest._common.spec.Prefix -import com.fortify.cli.ftest.ssc._common.SSCRoleSupplier -import com.fortify.cli.ftest.ssc._common.SSCRoleSupplier.SSCRole -import spock.lang.AutoCleanup -import spock.lang.Requires +import com.fortify.cli.ftest._common.spec.TempDir + import spock.lang.Shared import spock.lang.Stepwise @Prefix("tool.fod-uploader") @Stepwise class ToolFoDUploaderSpec extends FcliBaseSpec { + @Shared @TempDir("fortify/tools") String baseDir; + @Shared String version = "5.4.0" + @Shared Path globalBinScript = Path.of(baseDir).resolve("bin/FoDUpload.bat"); + @Shared Path binScript = Path.of(baseDir).resolve("fod-uploader/${version}/bin/FoDUpload.bat"); - def "install"() { - def args = "tool fod-uploader install -y latest" + def "installLatest"() { + def args = "tool fod-uploader install -y -v=${version} --progress=none -b ${baseDir}" when: def result = Fcli.run(args) then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindirAction") - it[1].replace(" ", "").contains("YesYes") - it[1].contains("INSTALLED") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" INSTALLED") + Files.exists(binScript); + Files.exists(globalBinScript); } } @@ -48,21 +51,62 @@ class ToolFoDUploaderSpec extends FcliBaseSpec { then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindir") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldir") it[1].replace(" ", "").startsWith("fod-uploader") - it[1].replace(" ", "").contains("YesYes") } } def "uninstall"() { - def args = "tool fod-uploader uninstall -y default" + def args = "tool fod-uploader uninstall -y --progress=none -v=${version}" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" UNINSTALLED") + // TODO fcli currently doesn't delete/update global bin script + Files.exists(globalBinScript); + !Files.exists(binScript); + } + } + + def "installV5"() { + def args = "tool fod-uploader install -y -v=5 --progress=none" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains("5.4.0") + it[1].contains(" INSTALLED") + } + } + + def "installV50"() { + def args = "tool fod-uploader install -y -v=5.0 --progress=none" + when: + def result = Fcli.run(args) + then: + verifyAll(result.stdout) { + size()>0 + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains("5.0.1") + it[1].contains(" INSTALLED") + } + } + + def "installV500"() { + def args = "tool fod-uploader install -y -v=5.0.0 --progress=none" when: def result = Fcli.run(args) then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindirAction") - it[1].replace(" ", "").contains("YesNoN/AN/AUNINSTALLED") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains("5.0.0") + it[1].contains(" INSTALLED") } } diff --git a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolScClientSpec.groovy b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolScClientSpec.groovy index 6938ae059c..a3c02a6ea4 100644 --- a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolScClientSpec.groovy +++ b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolScClientSpec.groovy @@ -12,32 +12,35 @@ */ package com.fortify.cli.ftest.tool -import static com.fortify.cli.ftest._common.spec.FcliSessionType.SSC +import java.nio.file.Files +import java.nio.file.Path import com.fortify.cli.ftest._common.Fcli import com.fortify.cli.ftest._common.spec.FcliBaseSpec -import com.fortify.cli.ftest._common.spec.FcliSession import com.fortify.cli.ftest._common.spec.Prefix -import com.fortify.cli.ftest.ssc._common.SSCRoleSupplier -import com.fortify.cli.ftest.ssc._common.SSCRoleSupplier.SSCRole -import spock.lang.AutoCleanup -import spock.lang.Requires +import com.fortify.cli.ftest._common.spec.TempDir + import spock.lang.Shared import spock.lang.Stepwise @Prefix("tool.sc-client") @Stepwise class ToolScClientSpec extends FcliBaseSpec { + @Shared @TempDir("fortify/tools") String baseDir; + @Shared String version = "23.1.0" + @Shared Path globalBinScript = Path.of(baseDir).resolve("bin/scancentral.bat") + @Shared Path binScript = Path.of(baseDir).resolve("sc-client/${version}/bin/scancentral.bat") def "install"() { - def args = "tool sc-client install -y latest" + def args = "tool sc-client install -y -v=${version} --progress=none -b ${baseDir}" when: def result = Fcli.run(args) then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindirAction") - it[1].replace(" ", "").contains("YesYes") - it[1].contains("INSTALLED") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" INSTALLED") + Files.exists(binScript); + Files.exists(globalBinScript); } } @@ -48,21 +51,23 @@ class ToolScClientSpec extends FcliBaseSpec { then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindir") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldir") it[1].replace(" ", "").startsWith("sc-client") - it[1].replace(" ", "").contains("YesYes") } } def "uninstall"() { - def args = "tool sc-client uninstall -y default" + def args = "tool sc-client uninstall -y --progress=none -v=${version}" when: def result = Fcli.run(args) then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindirAction") - it[1].replace(" ", "").contains("YesNoN/AN/AUNINSTALLED") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" UNINSTALLED") + // TODO fcli currently doesn't delete/update global bin script + Files.exists(globalBinScript); + !Files.exists(binScript); } } diff --git a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolVulnExporterSpec.groovy b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolVulnExporterSpec.groovy index 37340336a8..704d03a313 100644 --- a/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolVulnExporterSpec.groovy +++ b/fcli-other/fcli-functional-test/src/ftest/groovy/com/fortify/cli/ftest/tool/ToolVulnExporterSpec.groovy @@ -12,32 +12,35 @@ */ package com.fortify.cli.ftest.tool -import static com.fortify.cli.ftest._common.spec.FcliSessionType.SSC +import java.nio.file.Files +import java.nio.file.Path import com.fortify.cli.ftest._common.Fcli import com.fortify.cli.ftest._common.spec.FcliBaseSpec -import com.fortify.cli.ftest._common.spec.FcliSession import com.fortify.cli.ftest._common.spec.Prefix -import com.fortify.cli.ftest.ssc._common.SSCRoleSupplier -import com.fortify.cli.ftest.ssc._common.SSCRoleSupplier.SSCRole -import spock.lang.AutoCleanup -import spock.lang.Requires +import com.fortify.cli.ftest._common.spec.TempDir + import spock.lang.Shared import spock.lang.Stepwise @Prefix("tool.vuln-exporter") @Stepwise class ToolVulnExporterSpec extends FcliBaseSpec { + @Shared @TempDir("fortify/tools") String baseDir; + @Shared String version = "2.0.4" + @Shared Path globalBinScript = Path.of(baseDir).resolve("bin/FortifyVulnerabilityExporter.bat"); + @Shared Path binScript = Path.of(baseDir).resolve("vuln-exporter/${version}/bin/FortifyVulnerabilityExporter.bat"); def "install"() { - def args = "tool vuln-exporter install -y latest" + def args = "tool vuln-exporter install -y -v=${version} --progress=none -b ${baseDir}" when: def result = Fcli.run(args) then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindirAction") - it[1].replace(" ", "").contains("YesYes") - it[1].contains("INSTALLED") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" INSTALLED") + Files.exists(binScript); + Files.exists(globalBinScript); } } @@ -48,21 +51,23 @@ class ToolVulnExporterSpec extends FcliBaseSpec { then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindir") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldir") it[1].replace(" ", "").startsWith("vuln-exporter") - it[1].replace(" ", "").contains("YesYes") } } def "uninstall"() { - def args = "tool vuln-exporter uninstall -y default" + def args = "tool vuln-exporter uninstall -y --progress=none -v=${version}" when: def result = Fcli.run(args) then: verifyAll(result.stdout) { size()>0 - it[0].replace(' ', '').equals("NameVersionDefaultInstalledInstalldirBindirAction") - it[1].replace(" ", "").contains("YesNoN/AN/AUNINSTALLED") + it[0].replace(' ', '').equals("NameVersionAliasesStableInstalldirAction") + it[1].contains(" UNINSTALLED") + // TODO fcli currently doesn't delete/update global bin script + Files.exists(globalBinScript); + !Files.exists(binScript); } } diff --git a/fcli-other/fcli-gradle/fcli-java.gradle b/fcli-other/fcli-gradle/fcli-java.gradle index 47ac2735c3..ee1eb57ca8 100644 --- a/fcli-other/fcli-gradle/fcli-java.gradle +++ b/fcli-other/fcli-gradle/fcli-java.gradle @@ -59,6 +59,9 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-params' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + //required for unpacking tar.gz (debricked cli) + implementation('org.apache.commons:commons-compress') } compileJava {