diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b9d2d3917..fd7900e799 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,15 @@ name: Build and release on: + workflow_dispatch: + push: branches: - '**' env: native_image_opts: --verbose -H:Log=registerResource:verbose -H:+PrintClassInitialization - graal_version: 22.3.1 - graal_java_version: 11 + graal_distribution: graalvm-community + graal_java_version: 17 jobs: build: @@ -16,6 +18,10 @@ jobs: steps: - name: Check-out source code uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' - name: PROD - Prepare GitHub release id: create_prod_release @@ -24,8 +30,8 @@ jobs: with: command: github-release release-type: simple - package-name: ${{ github.event.repository.name }} - default-branch: main + package-name: ${{ github.event.repository.name }} + default-branch: main - name: PROD - Define release info if: steps.create_prod_release.outputs.release_created @@ -62,7 +68,7 @@ jobs: - name: Build release ${{env.RELEASE_VERSION}} if: env.DO_BUILD - run: ./gradlew clean build dist distThirdParty -Pversion=${{env.RELEASE_VERSION}} + run: ./gradlew clean build dist distThirdPartyReleaseAsset distFtest -Pversion=${{env.RELEASE_VERSION}} - name: Check fcli version if: env.DO_BUILD @@ -71,7 +77,7 @@ jobs: - name: Publish build artifacts uses: actions/upload-artifact@v3 with: - path: build/dist/* + path: build/dist/**/* outputs: do_release: ${{ env.DO_RELEASE }} @@ -92,7 +98,7 @@ jobs: - uses: graalvm/setup-graalvm@v1 with: - version: ${{ env.graal_version }} + distribution: ${{ env.graal_distribution }} java-version: ${{ env.graal_java_version }} components: 'native-image' native-image-musl: true @@ -110,7 +116,7 @@ jobs: # at build time (see https://www.graalvm.org/22.1/reference-manual/native-image/Properties/). # We also exclude the native Jansi library resources, as these are now no longer needed. - name: Create native fcli - run: native-image ${{ env.native_image_opts }} --static --libc=musl -Djansi.disable=true --initialize-at-build-time=com.fortify.cli.app.FortifyCLI -H:ExcludeResources="org/fusesource/jansi/internal/native/.*" -jar ./artifact/fcli.jar fcli + run: native-image ${{ env.native_image_opts }} --static --libc=musl -Djansi.disable=true --initialize-at-build-time=com.fortify.cli.app.FortifyCLI -H:ExcludeResources="org/fusesource/jansi/internal/native/.*" -jar ./artifact/release-assets/fcli.jar fcli - name: Compress native fcli uses: svenstaro/upx-action@v2 @@ -124,23 +130,23 @@ jobs: run: ./fcli --version | tee /dev/stderr | grep -E '[0-9]+\.[0-9]+\.[0-9]+' >/dev/null || (echo "fcli --version doesn't output proper version number"; exit 1) - name: Package native fcli - run: tar -zcvf fcli-linux.tgz fcli -C ./artifact fcli_completion + run: tar -zcvf artifact/release-assets/fcli-linux.tgz fcli -C ./artifact fcli_completion - uses: actions/upload-artifact@v3 with: - path: ./fcli-linux.tgz + path: ./artifact/**/fcli-linux.tgz native_mac: name: native-image-mac needs: build - runs-on: macos-12 + runs-on: macos-latest steps: - name: Check-out source code uses: actions/checkout@v3 - uses: graalvm/setup-graalvm@v1 with: - version: ${{ env.graal_version }} + distribution: ${{ env.graal_distribution }} java-version: ${{ env.graal_java_version }} components: 'native-image' github-token: ${{ secrets.GITHUB_TOKEN }} @@ -153,7 +159,7 @@ jobs: # file to include native libraries for all platforms; we override this to include only the MacOS # libraries - name: Create native fcli - run: native-image ${{ env.native_image_opts }} -H:ExcludeResources="org/fusesource/jansi/internal/native/Windows/.*" -H:ExcludeResources="org/fusesource/jansi/internal/native/Linux/.*" -H:ExcludeResources="org/fusesource/jansi/internal/native/FreeBSD/.*" -jar ./artifact/fcli.jar fcli + run: native-image ${{ env.native_image_opts }} -march=compatibility -H:ExcludeResources="org/fusesource/jansi/internal/native/Windows/.*" -H:ExcludeResources="org/fusesource/jansi/internal/native/Linux/.*" -H:ExcludeResources="org/fusesource/jansi/internal/native/FreeBSD/.*" -jar ./artifact/release-assets/fcli.jar fcli - name: Compress native fcli uses: svenstaro/upx-action@v2 @@ -164,11 +170,11 @@ jobs: run: ./fcli --help && ./fcli get --help - name: Package native fcli - run: tar -zcvf fcli-mac.tgz fcli -C ./artifact fcli_completion + run: tar -zcvf ./artifact/release-assets/fcli-mac.tgz fcli -C ./artifact fcli_completion - uses: actions/upload-artifact@v3 with: - path: ./fcli-mac.tgz + path: ./artifact/**/fcli-mac.tgz native_win: name: native-image-win @@ -177,7 +183,7 @@ jobs: steps: - uses: graalvm/setup-graalvm@v1 with: - version: ${{ env.graal_version }} + distribution: ${{ env.graal_distribution }} java-version: ${{ env.graal_java_version }} components: 'native-image' github-token: ${{ secrets.GITHUB_TOKEN }} @@ -192,7 +198,7 @@ jobs: - name: Create native fcli run: >- "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && - ${{ env.JAVA_HOME }}\bin\native-image.cmd ${{ env.native_image_opts }} -H:ExcludeResources="org/fusesource/jansi/internal/native/Mac/.*" -H:ExcludeResources="org/fusesource/jansi/internal/native/Linux/.*" -H:ExcludeResources="org/fusesource/jansi/internal/native/FreeBSD/.*" -jar .\artifact\fcli.jar fcli + ${{ env.JAVA_HOME }}\bin\native-image.cmd ${{ env.native_image_opts }} -H:ExcludeResources="org/fusesource/jansi/internal/native/Mac/.*" -H:ExcludeResources="org/fusesource/jansi/internal/native/Linux/.*" -H:ExcludeResources="org/fusesource/jansi/internal/native/FreeBSD/.*" -jar .\artifact\release-assets\fcli.jar fcli shell: cmd # We don't compress the Windows binary for now as this is incompatible with current Graal version. @@ -208,11 +214,11 @@ jobs: .\fcli.exe get --help - name: Package native fcli - run: 7z a fcli-windows.zip fcli*.exe + run: 7z a artifact\release-assets\fcli-windows.zip fcli*.exe - uses: actions/upload-artifact@v3 with: - path: ./fcli-windows.zip + path: ./artifact/**/fcli-windows.zip release: name: release @@ -228,9 +234,6 @@ jobs: with: path: ./ - - name: Remove fcli_completion script - run: rm -f artifact/fcli_completion - - name: PROD - Prepare release PR if: github.ref == 'refs/heads/main' uses: GoogleCloudPlatform/release-please-action@v3 @@ -259,7 +262,7 @@ jobs: - name: Upload assets to release if: needs.build.outputs.do_release run: | - files=$(find "./artifact" -type f -printf "%p ") + files=$(find "./artifact/release-assets" -type f -printf "%p ") gh release upload "${{ needs.build.outputs.release_tag }}" $files --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -315,15 +318,3 @@ jobs: publish_dir: ./docs enable_jekyll: true - - cleanup: - name: cleanup - if: needs.build.outputs.do_release - needs: [release, publishPages] - runs-on: ubuntu-latest - steps: - - name: Delete artifacts if uploaded to release - uses: geekyeggo/delete-artifact@v1 - with: - name: artifact - failOnError: false diff --git a/.github/workflows/fortify-analysis.yml b/.github/workflows/fortify-analysis.yml index ef129a1d99..b90ebf76fe 100644 --- a/.github/workflows/fortify-analysis.yml +++ b/.github/workflows/fortify-analysis.yml @@ -22,7 +22,7 @@ name: Fortify on Demand Scan on: workflow_dispatch: push: - branches: [ main ] + branches: [ develop ] schedule: - cron: '16 0 * * 5' @@ -39,14 +39,17 @@ jobs: steps: # Check out source code - name: Check Out Source Code - uses: actions/checkout@v2 + uses: actions/checkout@v4 + with: + ref: develop # Java is required to run the various Fortify utilities. # When scanning a Java application, please use the appropriate Java version for building your application. - name: Setup Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: 11 + distribution: 'temurin' + java-version: '17' # Prepare source+dependencies for upload. The default example is for a Maven project that uses pom.xml. # TODO: Update PACKAGE_OPTS based on the ScanCentral Client documentation for your project's included tech stack(s). Helpful hints: @@ -56,20 +59,18 @@ jobs: # For other build tools, add your build commands to download necessary dependencies and prepare according to Fortify on Demand Packaging documentation. # ScanCentral Client documentation is located at https://www.microfocus.com/documentation/fortify-software-security-center/ - name: Download Fortify ScanCentral Client - uses: fortify/gha-setup-scancentral-client@5b7382f8234fb9840958c49d5f32ae854115f9f3 - with: - version: 21.2.0-prerelease # Required as Gradle 7.2 not supported by earlier versions + uses: fortify/gha-setup-scancentral-client@v2 - name: Package Code + Dependencies run: scancentral package $PACKAGE_OPTS -o package.zip env: - PACKAGE_OPTS: "-bt gradle" + PACKAGE_OPTS: "-bt gradle -oss" # Start Fortify on Demand SAST scan and wait until results complete. For more information on FoDUploader commands, see https://github.com/fod-dev/fod-uploader-java # TODO: Update ENV variables for your application and create the necessary GitHub Secrets. Helpful hints: # Credentials and release ID should be obtained from your FoD tenant (either Personal Access Token or API Key can be used). # Automated Audit preference should be configured for the release's Static Scan Settings in the Fortify on Demand portal. - name: Download Fortify on Demand Universal CI Tool - uses: fortify/gha-setup-fod-uploader@6e6bb8a33cb476e240929fa8ebc739ff110e7433 + uses: fortify/gha-setup-fod-uploader@v1 - name: Perform SAST Scan run: java -jar $FOD_UPLOAD_JAR -z package.zip -aurl $FOD_API_URL -purl $FOD_URL -rid "$FOD_RELEASE_ID" -tc "$FOD_TENANT" -uc "$FOD_USER" "$FOD_PAT" $FOD_UPLOADER_OPTS -n "$FOD_UPLOADER_NOTES" env: @@ -84,7 +85,7 @@ jobs: # Once scan completes, pull SAST issues from Fortify on Demand and generate SARIF output. - name: Export results to GitHub-optimized SARIF - uses: fortify/gha-export-vulnerabilities@fcb374411cff9809028c911dabb8b57dbdae623b + uses: fortify/gha-export-vulnerabilities@v1 with: fod_base_url: "https://ams.fortify.com/" fod_tenant: ${{ secrets.OSS_FOD_TENANT }} @@ -94,6 +95,6 @@ jobs: # Import Fortify on Demand results to GitHub Security Code Scanning - name: Import Results - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v2 with: - sarif_file: ./gh-fortify-sast.sarif \ No newline at end of file + sarif_file: ./gh-fortify-sast.sarif diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml new file mode 100644 index 0000000000..a95203c228 --- /dev/null +++ b/.github/workflows/functional-tests.yml @@ -0,0 +1,156 @@ +name: Functional Tests + +on: + workflow_dispatch: + inputs: + runNumber: # Accessible through ${{ inputs.runNumber }} + description: 'Required "Build and release"" workflow run number from which to get artifacts to be tested' + required: true + type: number + workflow_run: + workflows: [Build and release] + types: [completed] + + +jobs: + ft-core: + if: github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + type: [java, jar, native] + + runs-on: ${{ matrix.os }} + + steps: + # Java is required for running the functional tests + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Get artifacts from triggering workflow + if: github.event_name == 'workflow_run' + uses: dawidd6/action-download-artifact@v2 + with: + run_id: ${{ github.event.workflow_run.id }} + - name: Get artifacts from specified workflow + if: github.event_name == 'workflow_dispatch' + uses: dawidd6/action-download-artifact@v2 + with: + run_number: ${{ inputs.runNumber }} + workflow: ci.yml + + - name: List artifact contents + shell: bash + run: ls -lR + + - name: Run Tests + shell: bash + run: | + mv artifact/release-assets/* . + mv artifact/fcli-ftest.jar . + case "${{ matrix.type }}" in + "java" ) + java -jar fcli-ftest.jar -Dft.fcli=build -Dft.run=core,config ;; + "jar" ) + java -jar fcli-ftest.jar -Dft.fcli=fcli.jar -Dft.run=core,config ;; + "native" ) + case "${{ matrix.os }}" in + "ubuntu-latest" ) + tar -zxvf fcli-linux.tgz + java -jar fcli-ftest.jar -Dft.fcli=./fcli -Dft.run=core,config ;; + "windows-latest" ) + 7z e fcli-windows.zip + java -jar fcli-ftest.jar -Dft.fcli=fcli.exe -Dft.run=core,config ;; + "macos-latest" ) + tar -zxvf fcli-mac.tgz + java -jar fcli-ftest.jar -Dft.fcli=./fcli -Dft.run=core,config ;; + esac ;; + esac + + - name: Rename test log + if: always() + shell: bash + run: mv test.log "test-${{ matrix.os }}-${{ matrix.type }}.log" + + - name: Publish test logs + if: failure() + uses: actions/upload-artifact@v3 + with: + name: test-log + path: test-*.log + + ft-product: + if: github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' + strategy: + fail-fast: false + matrix: + type: [fod, ssc, sc-sast, sc-dast, report] + + runs-on: ubuntu-latest + + steps: + # Java is required for running the functional tests + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + + - name: Get artifacts from triggering workflow + if: github.event_name == 'workflow_run' + uses: dawidd6/action-download-artifact@v2 + with: + run_id: ${{ github.event.workflow_run.id }} + - name: Get artifacts from specified workflow + if: github.event_name == 'workflow_dispatch' + uses: dawidd6/action-download-artifact@v2 + with: + run_number: ${{ inputs.runNumber }} + workflow: ci.yml + + - name: Run Tests + shell: bash + run: | + mv artifact/release-assets/* . + mv artifact/fcli-ftest.jar . + tar -zxvf fcli-linux.tgz + # Although we only run the current matrix entry type, we pass connection + # options for all session types, as some SC-SAST/SC-DAST tests may also + # require an SSC session. + java -jar fcli-ftest.jar -Dft.fcli=./fcli -Dft.run=$type \ + -Dft.fod.url=$fod_url -Dft.fod.tenant=$fod_tenant -Dft.fod.user=$fod_user -Dft.fod.password=$fod_pwd \ + -Dft.ssc.url=$ssc_url -Dft.ssc.user=$ssc_user -Dft.ssc.password=$ssc_pwd \ + -Dft.sc-sast.ssc-url=$ssc_url -Dft.sc-sast.ssc-user=$ssc_user -Dft.sc-sast.ssc-password=$ssc_pwd \ + -Dft.sc-dast.ssc-url=$ssc_url -Dft.sc-dast.ssc-user=$ssc_user -Dft.sc-dast.ssc-password=$ssc_pwd \ + -Dft.sc-sast.client-auth-token=$scsast_token + env: + type: ${{ matrix.type }} + fod_url: ${{ secrets.FCLI_FT_FOD_URL }} + fod_tenant: ${{ secrets.FCLI_FT_FOD_TENANT }} + fod_user: ${{ secrets.FCLI_FT_FOD_USER }} + fod_pwd: ${{ secrets.FCLI_FT_FOD_PWD }} + ssc_url: ${{ secrets.FCLI_FT_SSC_URL }} + ssc_user: ${{ secrets.FCLI_FT_SSC_USER }} + ssc_pwd: ${{ secrets.FCLI_FT_SSC_PWD }} + scsast_token: ${{ secrets.FCLI_FT_SCSAST_TOKEN }} + # Pass GitHub and GitLab tokens for use by NCD license report tests + FCLI_FT_GITHUB_TOKEN: ${{ secrets.FCLI_FT_GITHUB_TOKEN }} + FCLI_FT_GITLAB_TOKEN: ${{ secrets.FCLI_FT_GITLAB_TOKEN }} + + - name: Rename test log + if: always() + shell: bash + run: mv test.log "test-${{ matrix.os }}-${{ matrix.type }}.log" + + - name: Publish test logs + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-log + path: test-*.log + + \ No newline at end of file diff --git a/.github/workflows/update-repo-docs.yml b/.github/workflows/update-repo-docs.yml index 3706cb96a1..c3c74f9e1f 100644 --- a/.github/workflows/update-repo-docs.yml +++ b/.github/workflows/update-repo-docs.yml @@ -6,7 +6,7 @@ on: - cron: '5 4 * * *' push: branches: - - main + - develop jobs: update-repo-docs: diff --git a/.gitignore b/.gitignore index b8b326213e..e6d472f6e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ *.csv *.json *.sarif +*.zip +NcdReportConfig.yml +MspReportConfig.yml # Files generated by native-image when run locally /fcli @@ -14,6 +17,9 @@ build/ !**/src/test/** !**/resource-config.json +# We want to include everything in our functional test resources +!**/ftest/resources/runtime/**/* + ### STS ### .apt_generated .classpath @@ -45,7 +51,7 @@ bin/ samples/**/scans/** samples/**/logs/** -*.fpr +#*.fpr FortifyImportExportUtility-common/lombok.config FortifyImportExportUtility-common-from/lombok.config FortifyImportExportUtility-common-to/lombok.config FortifyImportExportUtility-from-fod-plugin/lombok.config FortifyImportExportUtility-from-mock-plugin/lombok.config FortifyImportExportUtility-from-ssc-plugin/lombok.config FortifyImportExportUtility-to-file-plugin/lombok.config FortifyImportExportUtility-to-mock-plugin/lombok.config lombok.config fcli.log @@ -53,3 +59,4 @@ fcli.log /ReportTemplateDefAnswerTemplate.json* /ReportTemplateDefAnswerTemplate.y*ml* /*.rptdesign +*.log diff --git a/LICENSE.txt b/LICENSE.txt index 4637524dd4..0282e86da3 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright 2023 Open Text or one of its affiliates +Copyright 2021 - 2023 Open Text or one of its affiliates Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/build.gradle b/build.gradle index 60cd8fc18b..f5e8d93602 100644 --- a/build.gradle +++ b/build.gradle @@ -1,368 +1,63 @@ -// the buildscript section is temporary until the missing dependency issue is resolved. -buildscript { - repositories { - jcenter() - mavenCentral() - gradlePluginPortal() - } - - dependencies { - classpath ("org.ysb33r.gradle:grolifant:0.16.2") { - force = true - } - } -} - plugins { - id('com.github.jk1.dependency-license-report') version '2.1' - id "org.kordamp.gradle.markdown" version "2.2.0" - id("com.github.johnrengelman.shadow") version "7.1.2" - id("io.micronaut.application") version "3.6.4" apply false - id("io.micronaut.library") version "3.6.4" apply false - id 'eclipse' - id "com.github.ben-manes.versions" version "0.44.0" - id "org.asciidoctor.jvm.convert" version "3.3.2" + id('com.github.jk1.dependency-license-report') version '2.5' apply false + id("com.github.johnrengelman.shadow") version "8.1.1" apply false + id "org.asciidoctor.jvm.convert" version "3.3.2" apply false + id "io.freefair.lombok" version "8.1.0" apply false } group = "com.fortify.cli" -ext.buildTime = LocalDateTime.now() -ext.getVersion = { - def result = project.findProperty('version'); - return !result || result=='unspecified' ? buildTime.format('0.yyyyMMdd.HHmmss') : result; -} -version = ext.getVersion(); - -apply plugin: "io.micronaut.application" -subprojects { - apply plugin: "io.micronaut.library" - apply plugin: 'eclipse' -} - -application { - mainClass.set("com.fortify.cli.app.FortifyCLI") -} -shadowJar { - archiveBaseName.set('fcli') - archiveClassifier.set('') - archiveVersion.set('') +ext { + buildTime = LocalDateTime.now() + getVersion = { + def result = project.findProperty('version'); + return !result || result=='unspecified' ? buildTime.format('0.yyyyMMdd.HHmmss') : result; + } } allprojects { - java { - sourceCompatibility = JavaVersion.toVersion("11") - targetCompatibility = JavaVersion.toVersion("11") - } - micronaut { - testRuntime("junit5") - processing { - incremental(true) - annotations("com.fortify.cli.*") - } - } - - repositories { - mavenCentral() - } - - dependencies { - // Lombok dependency & annotation processor - compileOnly 'org.projectlombok:lombok:1.18.24' - annotationProcessor 'org.projectlombok:lombok:1.18.24' - - // TODO Re-enable this line (with appropriate version) once new picocli is released - //implementation("info.picocli:picocli:4.6.3") - - implementation("org.fusesource.jansi:jansi:2.4.0"); - - annotationProcessor("io.micronaut:micronaut-inject-java:3.4.2") - annotationProcessor("io.micronaut:micronaut-graal:3.4.2") - - // Micronaut dependencies - implementation("io.micronaut:micronaut-runtime:3.4.2") - implementation("io.micronaut.picocli:micronaut-picocli:4.1.0") { - exclude group: 'info.picocli' - } - implementation("javax.annotation:javax.annotation-api") - - // Logback dependency to allow us to configure logging programmatically - implementation("ch.qos.logback:logback-classic:1.2.11") - - implementation('com.konghq:unirest-java:3.13.11') { - exclude group: 'com.google.code.gson', module: 'gson' // We use Jackson, so no need for Gson - exclude group: 'commons-logging', module: 'commons-logging' // We use jcl-over-slf4j - } - implementation('com.konghq:unirest-objectmapper-jackson:3.13.11') - - implementation('com.jayway.jsonpath:json-path:2.7.0') { - //exclude group: 'net.minidev', module: 'json-smart' - } - - // for yaml support and generating trees/tables - implementation('com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.2') - implementation('com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.13.2') - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.13.2") - implementation('hu.webarticum:tree-printer:2.0.0') - implementation('com.github.freva:ascii-table:1.2.0') - - // Encryption library - implementation('org.jasypt:jasypt:1.9.3:lite') - } -} - -// TODO Remove this block once new picocli is released -configure(allprojects.findAll {it.name == 'fcli-picocli-codegen-patches'}) { - dependencies { - implementation project(':fcli-picocli-patches') - } -} - -// TODO Remove this block once new picocli is released -configure(allprojects.findAll {it.name != 'fcli-picocli-patches' && it.name != 'fcli-picocli-codegen-patches'}) { - dependencies { - implementation project(':fcli-picocli-patches') - // Picocli dependency and annotation processor. Note that for now, we cannot use 4.7.0 due to https://github.com/remkop/picocli/issues/1876 - //annotationProcessor("info.picocli:picocli-codegen:4.6.3") - annotationProcessor(project(":fcli-picocli-codegen-patches")) - } -} - -// TODO Move to allprojects block once we no longer require picocli patches -configure(allprojects.findAll {it.name != 'fcli-picocli-patches' && it.name != 'fcli-picocli-codegen-patches'}) { - // Set picocli annotation processor options - compileJava { - options.compilerArgs += ["-Averbose=true"] - } + apply plugin: 'eclipse' + version = rootProject.ext.getVersion(); + ext { + distDir = "${rootProject.buildDir}/dist" + releaseAssetsDir = "${distDir}/release-assets" + sharedGradleScriptsDir = "${rootDir}/$sharedGradleScriptsRelativeDir" + gradleHelpersLocation = "https://raw.githubusercontent.com/fortify/shared-gradle-helpers/1.8" + } + // Define *RefDir properties for each *Ref property defined in gradle.properties, + // based on the refPatterns property defined in the same gradle.properties + properties.each { p-> + if ( "$refPatterns".split(',').any { p.key.matches(it) } ) { + ext[p.key+"Dir"] = "$rootDir" + p.value.replaceAll(':', '/') + } + } + + // Define Maven Central repository + repositories { + mavenCentral() + maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + } - // Micronaut is supposed to generate a resource-config.json file that includes all resources, - // but for some reason this doesn't work, so we generate our own resource-config.json file for - // each module - ext.generatedResourceConfigDir = "${buildDir}/generated-resource-config" - tasks.register('generateResourceConfig') { - doLast { - def outputDir = "${generatedResourceConfigDir}/META-INF/native-image/fcli-generated/${project.name}"; - mkdir "${outputDir}" - def entries = []; - fileTree(dir: 'src/main/resources', excludes: ['**/i18n/**', 'META-INF/**']) - .visit {e -> if ( !e.isDirectory() ) {entries << '\n {"pattern":"'+e.relativePath+'"}'}}; - if ( entries.size>0 ) { - def contents = '{"resources":[' + entries.join(",") + '\n]}'; - file("${outputDir}/resource-config.json").text = contents; - println contents - } - } - } - sourceSets.main.output.dir generatedResourceConfigDir, builtBy: generateResourceConfig -} - -// TODO Remove fcli-picocli-patches condition once new picocli is released -configure(allprojects.findAll {it.name != 'fcli-common' && it.name != 'fcli-picocli-patches' && it.name != 'fcli-picocli-codegen-patches'}) { - dependencies { - implementation project(':fcli-common') - } -} - -dependencies { - implementation project(':fcli-config') - implementation project(':fcli-ssc') - implementation project(':fcli-fod') - implementation project(':fcli-sc-sast') - implementation project(':fcli-sc-dast') - implementation project(':fcli-tool') - implementation project(':fcli-util') - - // Logging dependencies - runtimeOnly('ch.qos.logback:logback-classic:1.2.11') - runtimeOnly('org.slf4j:jcl-over-slf4j:1.7.36') - - // GraalVM dependency - compileOnly("org.graalvm.nativeimage:svm") -} - -ext { - gradleHelpersLocation = "https://raw.githubusercontent.com/fortify/shared-gradle-helpers/1.8" - thirdPartyBaseName = "${rootProject.name}" - autoCompleteDir = "${project.buildDir}/autocomplete" -} -apply from: "${gradleHelpersLocation}/thirdparty-helper.gradle" -apply from: "${gradleHelpersLocation}/readme2html.gradle" - -mainClassName = "com.fortify.cli.app.FCLIRootCommands" - -ext.buildPropertiesDir = "${buildDir}/generated-build-properties" -task generateFcliBuildProperties { - doLast { - def outputDir = "${buildPropertiesDir}/com/fortify/cli/app" - mkdir "${outputDir}" - ant.propertyfile(file: "${outputDir}/fcli-build.properties") { - entry(key: "projectName", value: project.name) - entry(key: "projectVersion", value: project.version) - entry(key: "buildDate", value: buildTime.format('yyyy-MM-dd HH:mm:ss')) + task createDistDir { + doFirst { + mkdir "${distDir}" + mkdir "${releaseAssetsDir}" } - def resourceConfigOutputDir = "${buildPropertiesDir}/META-INF/native-image/fcli-build-properties" - mkdir "${resourceConfigOutputDir}" - def contents = - '{"resources":[\n' + - ' {"pattern":"com/fortify/cli/app/fcli-build.properties"}\n' + - ']}\n' - file("${resourceConfigOutputDir}/resource-config.json").text = contents; - println contents - } -} -sourceSets.main.output.dir buildPropertiesDir, builtBy: generateFcliBuildProperties - -tasks.register('generateAutoCompleteDir') { - doLast { - mkdir "${autoCompleteDir}" } } -task generateAutoComplete(type: JavaExec) { - dependsOn(classes, generateAutoCompleteDir) - group = "Documentation" - description = "Generate autocomplete" - classpath(configurations.runtimeClasspath, configurations.annotationProcessor, sourceSets.main.runtimeClasspath) - main 'picocli.AutoComplete' - args mainClassName, "-f", "--completionScript=${autoCompleteDir}/fcli_completion" -} - -task generateManpageAsciiDoc(type: JavaExec) { - dependsOn(classes) - group = "Documentation" - description = "Generate AsciiDoc manpage" - classpath(configurations.runtimeClasspath, configurations.annotationProcessor, sourceSets.main.runtimeClasspath) - main 'picocli.codegen.docgen.manpage.ManPageGenerator' - args mainClassName, "--outdir=${project.buildDir}/generated-docs/src/manpage", "-v" +task clean(type: Delete) { + delete "build" } - -task copyStaticAsciiDoc(type: Copy) { - into "${project.buildDir}/generated-docs/src" - from("${projectDir}/doc-resources/asciidoc/versioned") { - include "*.adoc" - } +task build(type: Copy) { + dependsOn("${fcliAppRef}:build") + from "${fcliAppRefDir}/build/libs/fcli.jar" + into "build/libs" } - -task prepareAsciiDoc { - dependsOn 'generateManpageAsciiDoc', 'copyStaticAsciiDoc' -} - -task asciiDoctorManPage(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) { - dependsOn(prepareAsciiDoc) - sourceDir = file("${project.buildDir}/generated-docs/src/manpage") - outputDir = file("${project.buildDir}/generated-docs/manpage") - logDocuments = true - outputOptions { - backends = ['manpage'] - } -} - -task asciiDoctorHtml(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) { - dependsOn(prepareAsciiDoc) - sourceDir = file("${project.buildDir}/generated-docs/src") - outputDir = file("${project.buildDir}/generated-docs/html5") - logDocuments = true - outputOptions { - backends = ['html5'] - } - attributes = [ - 'toc' : 'left', - 'sectanchors' : 'true', - 'docinfo' : 'shared', - 'jekyll' : false, - 'bannertitle' : 'FCLI: The Universal Fortify CLI', - 'docversion' : "${project.version}" - ] - options = [ - 'template_dirs': [new File("${project.rootDir}/doc-resources/asciidoc/templates").absolutePath] - ] -} - -task asciidoctorJekyll(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) { - dependsOn(prepareAsciiDoc) - sourceDir = file("${project.buildDir}/generated-docs/src") - outputDir = file("${project.buildDir}/generated-docs/jekyll") - logDocuments = true - outputOptions { - backends = ['html5'] - } - attributes = [ - 'toc' : 'left', - 'sectanchors' : 'true', - 'docinfo' : 'shared', - 'jekyll' : true, - 'stylesheet' : false, - 'bannertitle' : 'FCLI: The Universal Fortify CLI', - 'docversion' : "${project.version}" - ] - options = [ - 'template_dirs': [new File("${project.rootDir}/doc-resources/asciidoc/templates").absolutePath] - ] -} - -task asciidoctorGHPages(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) { - sourceDir = file("${project.rootDir}/doc-resources/asciidoc/gh-pages") - outputDir = file("${project.buildDir}/generated-docs/gh-pages") - logDocuments = true - outputOptions { - backends = ['html5'] - } - attributes = [ - 'toc' : 'left', - 'sectanchors' : 'true', - 'docinfo' : 'shared', - 'jekyll' : true, - 'stylesheet' : false, - 'bannertitle' : 'FCLI: The Universal Fortify CLI', - 'docversion' : "[select]", - 'revnumber' : null - ] - options = [ - 'template_dirs': [new File("${project.rootDir}/doc-resources/asciidoc/templates").absolutePath] - ] -} - -tasks.register('distDocsHtml5', Zip) { - dependsOn 'asciiDoctorHtml' - archiveFileName = "docs-html.zip" - destinationDirectory = layout.buildDirectory.dir('dist') - from layout.buildDirectory.dir("generated-docs/html5") -} - -tasks.register('distDocsManPage', Zip) { - dependsOn 'asciiDoctorManPage' - archiveFileName = "docs-manpage.zip" - destinationDirectory = layout.buildDirectory.dir('dist') - from layout.buildDirectory.dir("generated-docs/manpage") -} - -tasks.register('distDocsJekyll', Zip) { - dependsOn 'asciidoctorJekyll' - archiveFileName = "docs-jekyll.zip" - destinationDirectory = layout.buildDirectory.dir('dist') - from layout.buildDirectory.dir("generated-docs/jekyll") -} - -tasks.register('distDocsGHPages', Zip) { - dependsOn 'asciidoctorGHPages' - archiveFileName = "docs-gh-pages.zip" - destinationDirectory = layout.buildDirectory.dir('dist') - from layout.buildDirectory.dir("generated-docs/gh-pages") -} - -task distDocs { - dependsOn 'distDocsHtml5', 'distDocsManPage', 'distDocsJekyll', 'distDocsGHPages' -} - task dist(type: Copy) { - dependsOn 'build', 'readme2html', 'distDocs', 'generateAutoComplete' - from "${buildDir}/html" - from "${autoCompleteDir}" + dependsOn(createDistDir) from("${projectDir}") { - include "config/**/*" - include "LICENSE.TXT" + include "LICENSE.txt" } - from("${buildDir}/${libsDirName}") { - include "${rootProject.name}.jar" - } - into "$buildDir/dist" -} + into "${releaseAssetsDir}" +} \ No newline at end of file diff --git a/doc-resources/asciidoc/gh-pages/dev-info.adoc b/doc-resources/asciidoc/gh-pages/dev-info.adoc deleted file mode 100644 index ca68201719..0000000000 --- a/doc-resources/asciidoc/gh-pages/dev-info.adoc +++ /dev/null @@ -1,176 +0,0 @@ -= Fortify CLI (fcli) Developer Information - -The following sections provide information that may be useful for developers of this utility. - -== Conventional Commits & Versioning - -Versioning is handled automatically by https://github.com/google-github-actions/release-please-action[release-please-action] based on https://www.conventionalcommits.org/[Conventional Commits]. Every commit to the `+main+` branch should follow the Conventional Commits convention. Following are some examples; these can be combined in a single commit message (separated by empty lines), or you can have commit messages describing just a single fix or feature. - -.... -chore: Won't show up in changelog - -ci: Change to GitHub Actions workflow; won't show up in changelog - -docs: Change to documentation; won't show up in changelog - -fix: Some fix (#2) - -feat: New feature (#3) - -feat!: Some feature that breaks backward compatibility - -feat: Some feature - BREAKING-CHANGE: No longer supports xyz -.... - -See the output of `+git log+` to view some sample commit messages. - -`+release-please-action+` invoked from the GitHub CI workflow generates pull requests containing updated `+CHANGELOG.md+` and `+version.txt+` files based on these commit messages. Merging the pull request will -result in a new release version being published; this includes publishing the image to Docker Hub, and creating a GitHub release describing the changes. - -== Technologies & Frameworks - -Following is a list of the main frameworks and technologies used by fcli: - -* https://picocli.info/[picocli]: Process command line options, generate usage information, … -* https://micronaut.io/[Micronaut]: Dependency injection, features for GraalVM native image generation -* https://github.com/FasterXML/jackson[Jackson]: Parse and generate data in JSON and other formats -* https://www.graalvm.org/[GraalVM]: Generate native images (native executables) - -== Prerequisites & Considerations - -As can be seen in the link:#_technologies_frameworks[Technologies & frameworks] section, this is no ordinary Java project. Some of these technologies and frameworks require special prerequisites, precautions and other considerations to be taken into account to prevent compilation issues and runtime errors, as described below. - -=== IDE Setup - -This project uses the following frameworks that may require some special setup in order to have your IDE compile this project without errors: - -* Lombok: Please see https://projectlombok.org/setup/overview for more information on how to add Lombok support to your IDE -* Micronaut & picocli: These frameworks require annotation processors to be run during builds; please see your IDE documentation on how to enable annotation processing - -=== Incremental Compilation - -Incremental compilation (for example in IDE or when doing a `+gradle build+` without `+clean+`) may leave behind old AOT artifacts causing exceptions when trying to run `+fcli+`. This is especially the case when renaming or moving classes, which may result in exceptions like the below: - -.... -Message: Error loading bean [com.fortify.cli.ssc.rest.unirest.SSCUnirestRunner]: com/fortify/cli/rest/unirest/AbstractUnirestRunner -... -Caused by: java.lang.NoClassDefFoundError: com/fortify/cli/rest/unirest/AbstractUnirestRunner -... -.... - -In this example, the AbstractUnirestRunner class was moved to a different package, but the now obsolete AOT-generated classes were still present. So, if at any time you see unexpected exceptions, please try to do a full clean/rebuild and run `+fcli+` again. - -=== Reflection - -GraalVM Native Image needs to know ahead-of-time the reflectively accessed program elements; see https://www.graalvm.org/reference-manual/native-image/Reflection/[GraalVM Native Image & Reflection] for details. Micronaut allows for generating the necessary GraalVM reflection configuration using the `+@ReflectiveAccess+` annotation if necessary. - -If a command runs fine on a regular JVM but not when running as a native image, then quite likely this is caused by missing reflection configuration which may be fixed by adding the `+@ReflectiveAccess+` annotation to the appropriate classes. Also see below for some reflection-related considerations specific to picocli and Jackson. - -=== Picocli - -Picocli and it’s annotation processor should in theory generate the necessary reflection configuration as described above. However, the annotation processor doesn’t seem to take class hierarchies into account; see https://github.com/remkop/picocli/issues/1444[GraalVM: Options from superclass not visible]. As a work-around, we can complement the picocli-generated reflection configuration by using Micronaut’s `+@ReflectiveAccess+` annotation. In general the following classes should probably be annotated with `+@ReflectiveAccess+`: - -* All classes annotated with `+@Command+` (this likely duplicates the reflection configuration generated by picocli, but doesn’t hurt) -* All super-classes of all classes annotated with `+@Command+`, in particular if they contain any picocli annotations like `+@Mixin+`, `+@ArgGroup+`, or `+@Option+` -* All classes used as an `+@Mixin+` or `+@ArgGroup+`, and their super-classes - -The following native image runtime behavior may indicate a missing `+@ReflectiveAccess+` annotation: - -* Options and sub-commands not listed in help output, and not recognized when entered on the command line -* Exceptions related to picocli trying to access classes, methods and fields using reflection - -=== Jackson - -Jackson is heavily based on reflection to perform object serialization and deserialization. All classes that need to be (de-)serialized with Jackson therefore need to be annotated with `+@ReflectiveAccess+` to allow reflection-based (de-)serialization. - -Note that Micronaut provides the `+@Introspected+` annotation that should allow for reflection-free (de-)serialization. However experience learns that there are just too many differences between reflection-based -and introspection-based (de-)serialization. When running in a normal JVM these differences are often not visible as Jackson can easily fall back to reflection-based (de-)serialization. In native images, Jackson cannot fall back to reflection-based (de-)serialization unless properly configured, and even when properly configured there are still some differences in behavior. - -As such, it has been decided to disable Jackson introspection-based (de-)serialization using the `+jackson.bean-introspection-module+` configuration property in `+application.yml+`. This also means that we will not be using the `+@Introspected+` annotation on classes that need to be (de-)serialized with Jackson (unless of course introspection-based access is used for non Jackson-related purposes). - -== Gradle Wrapper - -It is strongly recommended to build this project using the included Gradle Wrapper scripts; using other Gradle versions may result in build errors and other issues. - -The Gradle build uses various helper scripts from https://github.com/fortify/shared-gradle-helpers; please refer to the documentation and comments in included scripts for more information. - -== Common Commands - -All commands listed below use Linux/bash notation; adjust accordingly if you are running on a different platform. All commands are to be executed from the main project directory. - -* `+./gradlew tasks --all+`: List all available tasks -* Build: (plugin binary will be stored in `+build/libs+`) -** `+./gradlew clean build+`: Clean and build the project -** `+./gradlew build+`: Build the project without cleaning -** `+./gradlew dist distThirdParty+`: Build distribution zip and third-party information bundle - -== Documentation - -Two types of documentation are automatically being generated; the standard repository documentation like `+README.md+` and `+CONTRIBUTING.md+`, and fcli user documentation (including manual pages). The following two sections describe the generation process in more detail. - -=== Repository Documentation - -Most or all of the `+*.md+` and `LICENSE.txt` files located in the repository root are generated automatically. Generation of `+CHANGELOG.md+` is done by `+release-please-action+` as described in the link:#_conventional_commits_versioning[Conventional Commits & Versioning] section. Generation of the other files is done by the `+doc-resources/update-repo-docs.sh+` scripts, based on the templates provided in https://github.com/fortify/shared-doc-resources, combined with the repo-specific MarkDown files in the repository `+doc-resources+` directory. For more information about this generation process, please see https://github.com/fortify/shared-doc-resources/blob/main/USAGE.md. - -=== User Documentation - -User documentation is generated automatically from the following three locations: - -* AsciiDoc located in the repository `+doc-resources/asciidoc/gh-pages+` directory -** Published to the root directory of the GitHub Pages site -* AsciiDoc located in the repository `+doc-resources/asciidoc/versioned+` directory -** Published to a version-specific directory on the GitHub Pages site -** Published to docs-html.zip in release assets -* Manual pages generated from the fcli code -** Published to a version-specific directory on the GitHub Pages site -** Published to docs-html.zip in release assets -** Published to docs-manpage.zip in release assets - -The Gradle build includes various tasks for generating this documentation, following are the main tasks: - -* `+generateManpageAsciiDoc+`: Generate man-page style AsciiDoc documentation from fcli code -* `+asciiDoctorManPage+`: Convert man-page style AsciiDoc to Linux man-page format -* `+asciiDoctorHtml+`: Convert both man-page style AsciiDoc and versioned user documentation to offline HTML format -* `+asciidoctorJekyll+`: Convert both man-page style AsciiDoc and versioned user documentation to Jekyll HTML format for publishing on the GitHub Pages site -* `+asciidoctorGHPages+`: Convert AsciiDoc files from `+doc-resources/asciidoc/gh-pages+` to Jekyll HTML format for publishing on the GitHub Pages site -* `+distDocs+`: Calls of the tasks above and packages the output from these tasks into separate `+docs-*.zip+` files in the `+build/dist+` directory - -The GitHub Actions workflow defined in `+.github/workflows/ci.yml+` is responsible for publishing the documentation: - -* The `+build+` job builds the documentation artifacts and archives them as artifacts -* The `+release+` job publishes `+docs-html.zip+` and `+docs-manpage.zip+` to the release artifacts (when building a release or development version) -* The `+publishPages+` job published the output of the `+asciidoctorJekyll+` and `+asciidoctorGHPages+` to the appropriate directories on the GitHub Pages site, and updates the version index in the Jekyll `+_data+` directory (when building a release or development version) - -All HTML-formatted documentation described above is generated using the `+doc-resources/templates/html5/document.html.erb+` template. This template is based on the link:https://github.com/asciidoctor/asciidoctor-backends/blob/master/erb/html5/document.html.erb[official AsciiDoctor template] with various modifications. Based on the attributes provided in the relevant Gradle tasks: - -* For Jekyll output: -** Add Jekyll front matter -** Add a Jekyll include to include additional content in the HTML `++` section; mostly used for applying stylesheets -** Add a Jekyll include to include the site-wide banner and (version) navigation bar -* For offline HTML output: -** Add hardcoded custom styling -** Add hardcoded banner and version bar - -The offline HTML documentation is supposed to be self-contained, i.e., users can open any HTML file from `+docs-html.zip+` without extracting the full contents, and the page will render correctly. Of course, links to other documentation files will not work unless the full zip-file is extracted. - -For now, the hardcoded banner and navigation bar in the offline documentation is similar to the banner included by Jekyll. However: - -* Stylesheets and images are linked rather than being included in the HTML page, allowing for better browser cache utilization -* The navigation bar in the offline documentation contains just a static version number, whereas the navigation bar in the online documentation allows for navigating to different versions -* We can potentially add more advanced (navigation) functionalities in the online documentation -* We can easily update the banner for the online documentation to have a new layout/styling, for example to apply OpenText styling; this will be automatically applied to all existing online documentation pages - -Usually it shouldn't be necessary to update the documentation contents for existing release versions. However, if necessary, and assuming the build.gradle file is compatible with older versions, potentially a command like the following can be used to regenerate the documentation for the given versions: - -.... -for v in 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.2.0 1.2.1 1.2.2; do (git restore . && git clean -fd && git checkout v$v && cp -r ../fcli-fork/doc-resources ../fcli-fork/build.gradle . && ./gradlew clean distDocs -Pversion=$v && mkdir -p ~/Downloads/fcli-docs/$v && cp build/dist/docs-html.zip ~/Downloads/fcli-docs/$v && cd ../fcli-pages/v$v && echo $pwd && rm -rf * && unzip ../../fcli/build/dist/docs-jekyll.zip && cd - && git restore . && git clean -fd); done -.... - -This command iterates over the given version numbers, regenerates the documentation for each version (using latest `+build.gradle+` and `+doc-resources+`), copies the `docs-html.zip` to a separate directory for later upload to the corresponding release assets, and updates the GitHub Pages site, based on the following assumptions: - -* Current directory is a clone of the fcli repository -* `+../fcli-fork+` would contain the latest version of `+doc-resources+` and `+build.gradle+` -* `+../fcli-pages+` would be a clone of the fcli repository with the gh-pages branch checked out - - diff --git a/doc-resources/asciidoc/gh-pages/index.adoc b/doc-resources/asciidoc/gh-pages/index.adoc deleted file mode 100644 index b4070f3784..0000000000 --- a/doc-resources/asciidoc/gh-pages/index.adoc +++ /dev/null @@ -1,7 +0,0 @@ -= Fortify CLI (fcli) Documentation - -The fcli utility can be used to interact with various Fortify products, like Fortify on Demand (FoD), Software Security Center (SSC), ScanCentral SAST and ScanCentral DAST. Please select a version from the menu on the top-right to view detailed usage documentation and manual pages for that version. - -== Developer Documentation - -Information useful for developers can be found in the link:./dev-info.html[developer documentation]. \ No newline at end of file diff --git a/doc-resources/asciidoc/versioned/index.adoc b/doc-resources/asciidoc/versioned/index.adoc deleted file mode 100644 index 3c78dbc427..0000000000 --- a/doc-resources/asciidoc/versioned/index.adoc +++ /dev/null @@ -1,294 +0,0 @@ -= Fortify CLI (fcli) Installation & Usage - -The fcli utility can be used to interact with various Fortify products, like Fortify on Demand (FoD), Software Security Center (SSC), ScanCentral SAST and ScanCentral DAST. This document describes installation and general usage of fcli. For a full listing of fcli commands and corresponding command line options, please see the -man-pages as listed in the link:#_manual_pages[Manual Pages] section. - -Some of the fcli highlights: - -* Interact with many different Fortify products with just a single command-line utility -* link:#_installation[Both plain Java and native platform binaries for Windows, Linux and Mac available] -* link:#_command_structure[Modular command structure], making it easy to focus on particular tasks -* link:#_o_output[Rich output formats]; save command output in JSON, CSV, XML or plain-text formats -* link:#_session_management[Session-based]; no need to pass URL’s and credentials on every individual fcli invocation -* Support for configuring option values through link:#_environment_variables[environment variables] -* Support for link:#_fcli_variables[fcli variables]; pass data between fcli commands - -The following Fortify products are currently supported by fcli: - -* Software Security Center (SSC) -** Includes virtually all functionality provided by the legacy FortifyClient utility -** Includes virtually all functionality provided by the ssc-client sample shipped with SSC -** Adds a wide range of other functionalities not previously included in any Fortify client-side utilities -* ScanCentral SAST -** Support for starting and managing ScanCentral SAST scans -* ScanCentral DAST -** Support for starting and managing ScanCentral DAST scans -** Support for listing scan policies and settings -** Support for listing and managing sensors -* Fortify on Demand (FoD) -** Currently in preview mode -** No link:#_manual_pages[manual pages] available -** `+fod+` command is hidden from `+fcli -h+` output -** View available FoD commands by running `+fcli fod -h+` - -== Installation - -Download bundles for fcli are available on the https://github.com/fortify/fcli/releases[Releases] page, containing both development releases (named `+Development Release - branch+`) and final releases. In general, the use of a final release is recommended, unless you want to use any functionality that hasn’t made it into a final release yet. - -Each release comes with a list of assets: - -* `+docs-html.zip+` & `+docs-manpage.zip+`: Manual pages in either HTML or manpage format -* `+fcli-linux.tgz+`, `+fcli-mac.tgz+` & `+fcli-windows.zip+`: Native binaries for each of the mentioned platforms -** Note that some browsers by default will disallow downloading of `+fcli-windows.zip+`; please bypass the warning -** Linux and Mac downloads include an `+auto-completion+` script that makes interactive fcli usage easier -* `+fcli.jar+`: Java version of fcli, which should be runnable on any platform that has Java 11+ installed -** Note that in general, the native binaries are easier to invoke, offer better performance, and have the benefit of auto-completion capabilities on Linux & Mac -** If you experience any unexpected behavior with native binaries, like commands or command line options not being listed or recognized, or technical error messages about methods, constructors or serializers not being -found, please try with the Java version a s it may be an issue specific to the native binaries. See the link:#_troubleshooting[Troubleshooting] section for details. -* `+fcli-thirdparty.zip+`: Third-party licenses and sources for license purposes; usually no need to download -* `+LICENSE.TXT+` & `+README.md+`: Some generic information and license for fcli - -Please note that when publishing a new release, it may take up to 30-60 minutes before release assets are posted. If the latest release doesn’t show any of the assets listed above, please check again in 30-60 minutes. If you encounter a release without these assets after waiting for 60 minutes, please consider submitting an issue on the https://github.com/fortify/fcli/issues[fcli issue tracker]. - -To install one of the binary distributions of fcli: - -* Download the appropriate binary archive for your platform -* Extract the archive contents to a directory of your choosing -* For ease of use, add this directory to your operating system or shell PATH environment variable, or move the `+fcli+`/`+fcli.exe+` binary to a directory that is already on the PATH -* Linux/Mac only: Run the following command to install fcli auto command completion, allowing for use of the `++` to get suggestions for fcli command and option names. You may want to add this to your shell startup script, such that fcli auto-completion is readily available in every shell. + -`+source /fcli_completion+` - -To install the `+.jar+` version of fcli, simply download `+fcli.jar+` and put in in a directory of your choosing, after which it can be executed using `+java -jar path/to/fcli.jar+`. You may want to set up a -simple wrapper script/batch file or shell alias to make it slightly easier to invoke `+fcli.jar+`. - -== Command Structure - -Fcli provides a product-oriented command structure, with each product represented by a separate tree of subcommands. For example, the `+fcli fod+` command tree can be used to interact with Fortify on Demand -(FoD), and the `+fcli ssc+` command tree can be used to interact with Fortify Software Security Center (SSC). There are also some non product-related command trees, like the `+fcli config+` command tree to manage fcli configuration. - -To see what top-level fcli commands are available, you can use the `+fcli --help+` command. You can drill down into the command tree to see what sub-commands are available within a particular parent command, for example by running `+fcli ssc --help+` to see all `+fcli ssc+` sub-commands, or `+fcli ssc session --help+` to see all SSC session management commands. - -If you don’t have fcli up and running yet, you can also refer to the downloadable or online manual pages; refer to the link:#_manual_pages[Manual Pages] section for more information. - -== Common Options - -The following sections describe common options that are available on (most) fcli commands. - -=== -h | –help - -This option can be used on every fcli (sub-)command to view usage information for that command. Usage information usually shows the command synopsis, a description of the functionality provided by the command, and a description of each command line option or parameter accepted by the command. - -=== –env-prefix - -As described in the link:#_environment_variables[Environment Variables] section, default option and parameter values can be retrieved from environment variables starting with `+FCLI_DEFAULT+`. This option allows for configuring a different environment variable prefix. This may be useful if, for example, you want to login to multiple instances of the same system using environment variables. For example, when running `+fcli ssc session login --env-prefix PROD+`, fcli will look for environment variables like `+PROD_SSC_URL+` instead of `+FCLI_DEFAULT_SSC_URL+`. - -Note that a default value for the `+--env-prefix+` option itself can be specified through an `+FCLI_DEFAULT_ENV_PREFIX+` environment variable, for example if you want to globally override the `+FCLI_DEFAULT+` prefix. - -=== –log-level - -This option can be used on every fcli (sub-)command to specify the fcli log level; see the help output for a list of allowed levels. Note that this option also requires the `+--log-file+` option to be specified, -otherwise no log will be written. - -=== –log-file - -This option can be used on every fcli (sub-)command to specify the file to which to output log data. If not specified, currently no log data will be written, although future versions may specify a default log file -location in the fcli home folder. - -=== -o | –output - -Available on virtually all (leaf) commands that output data, this option can be used to specify the output format. Fcli supports a wide variety of output formats, like `+table+`, `+csv+`, `+json+`, `+xml+`, and `+tree+` formats, allowing for both human-readable output or output suitable for automations. The `+csv-plain+` and `+table-plain+` output formats produce CSV or table output without headers. The `+*-flat+` -output formats produce a flattened view of the output data, potentially making it easier to process that data without having to navigate through an object tree. For a full list of output formats supported by your fcli -version, please refer to the help output or link:#_manual_pages[Manual Pages]. - -Most output formats allow for specifying the JSON properties to be included in the output, for example `+-o csv=id,name+`. If no JSON properties are specified, most output formats will output all available -JSON properties, except for table output, which usually outputs a predefined set of JSON properties. - -There are two output formats that are somewhat special: - -* `+-o 'expr=Text with {property1} or {property2}\n'+` Formats the output data based on the given expression, which is a combination of (optional) plain text and JSON property placeholders. This can be used for a variety of purposes, for example generating output in a human-readable format, or for generating a list of commands to be run at a later stage. Note that by default, no newline character will be inserted after -evaluating the given expression. If necessary, the expression should explicitly include `+\n+` to output a newline character. To demonstrate the power of this output format, following are two examples of how `+-o expr+` can be used to generate a script that purges all application versions matching certain criteria: -** `+fcli ssc appversion list -q createdBy=admin -o 'expr=fcli ssc appversion-artifact purge --older-than 30d --appversion {id}\n'+` -** for id in $(fcli ssc appversion list -q createdBy=admin -o '`expr=\{id}`'); do echo "`fcli ssc appversion-artifact purge –older-than 30d –appversion $\{id}`"; done -* `+-o json-properties+` List all JSON properties returned by the current command, which can be used -on options that take JSON propert ies as input, like output expressions (`+-o expr={prop}+`), properties to include in the output (`+-o table=prop1,prop2+`), queries (`+-q prop1=value1+`), and fcli variables (`+--store var:prop1,prop2+` & `+{?var:prop1}+`). Two important notes about this output format: -** The command will be executed as specified, so be careful when using this output option on any command -that changes state (delete/update/create/…) * On some commands, the list of available JSON properties may vary depending on command line options. For example, when a query returns no records, then `+-o json-properties+` will not output any properties. Likewise, a command may provide options for including additional data for each record; the corresponding JSON properties will only be shown if `+-o json-properties+` is used in combination with these options that load additional data. - -=== –output-to-file - -Available on virtually all (leaf) commands that output data, this option can be used to write the command output data to a file, in the format specified by the `+--output+` option listed above. In some cases, this may be more convenient than redirecting the output to a file. For example, although currently not implemented, fcli could potentially skip creating the output file if there is no output data or if an error occurs. Also, for commands that output status updates, like `+wait-for+` commands, the `+--output-to-file+` option allows for status updates to be written to standard output while the final output of the command will be written to the file specified. - -=== –store - -Available on virtually all (leaf) commands that output data, this option can be used to store command output data in an fcli variable. For more details, see the link:#_fcli_variables[Fcli Variables] section. - -=== -q | –query - -Available on most `+list+` commands and some other commands, this option allows for querying the output data, outputting only records that match the given query or queries. For now, only equals-based matching is supported; future fcli versions may provide additional matching options. General format is `+-q =+`; the list of JSON properties available for matching can be found by executing the same command with the `+-o json-properties+` option; see link:++#-o--output++[-o | –output] for details. - -=== –session - -Available on virtually all commands that interact with a target system, this option allows for specifying a session name. For more details, see the link:#_session_management[Session Management] section. - -== Session Management - -Most fcli product modules are session-based, meaning that you need to run a `+session login+` command before you can use most of the other commands provided by a product module, and run a `+session logout+` command when finished, for example: - -[source,bash] ----- -fcli ssc session login --url https://my.ssc.org/ssc --user --password -fcli ssc appversion list -fcli ssc session logout --user --password ----- - -For interactive use, you can choose to keep the session open until it expires (expiration period depends on target system and login method). For pipeline use or other automation scenarios, it is highly recommended to issue a `+session logout+` command when no further interaction with the target system is required, to allow for any client-side and server-side cleanup to be performed. For example, upon logging in to SSC with user credentials, fcli will generate a `+UnifiedLoginToken+`, which will be invalidated when the `+ssc session logout+` is being run. If you have many (frequently executed) pipelines that interact with SSC, and you don’t run the `+ssc session logout+` command when the pipeline finishes, you risk exhausting SSC’s limit on active tokens. In addition, the `+logout+` commands will perform client-side cleanup, like removing session details like URL and authentication tokens from the client system. - -For product modules that support it, like SSC or ScanCentral DAST, it is also highly recommended to use token-based authentication rather than username/password-based authentication when incorporating fcli into pipelines or other automation tasks. This will avoid creation of a temporary token as described above, but also allows for better access control based on token permissions. Similarly, for systems that support Personal Access tokens, like FoD, it is highly recommended to utilize a Personal Access Token rather than user password. Note however that depending on (personal access) token permissions, not all fcli functionality may be available. In particular, even the least restrictive SSC `+CIToken+` may not provide access to all endpoints covered by fcli. If you need access to functionality not covered by `+CIToken+`, you may need to define a custom token definition, but this can only be done on self-hosted SSC environments, not on Fortify Hosted. If all else fails, you may need to revert to username/password-based authentication to utilize the short-lived `+UnifiedLoginToken+`. - -=== Named Sessions - -Fcli supports named sessions, allowing you to have multiple open sessions for a single product. When issuing a `+session login+` command, you can optionally provide a session name as in `+fcli ssc session login mySession ...+`, and then use that session in other commands using the `+--session mySession+` command line option. If no session name is specified, a session named `+default+` will be created/used. Named sessions allow for a variety of use cases, for example: - -* Run fcli commands against multiple instances of the same product, like DEV and PROD instances or an on-premise instance and a Fortify Hosted instance, without having to continuously login and logout from one instance to switch to another instance -* Run fcli commands against a single instance of a product, but with alternating credentials, for example with one session providing admin rights and another session providing limited user rights -* Run one session with username/password credentials to allow access to all fcli functionality (based on user permissions), and another session with token-based authentication with access to only a subset of fcli functionality -* Run multiple pipelines or automation scripts simultaneously, each with their own session name, to reduce chances of these pipelines and scripts affecting each other (see link:#_fcli_home_folder[Fcli Home Folder] though for a potentially better solution for this scenario) - -=== Session Storage - -To keep session state between fcli invocations, fcli stores session data like URL and authentication tokens in the link:#_fcli_home_folder[Fcli Home Folder]. To reduce the risk of unauthorized access to this sensitive data, fcli encrypts the session data files. However, this is not bullet-proof, as the default encryption key and algorithm can be easily viewed in fcli source code. As such, it is recommended to ensure file permissions on the FCLI Home folder are properly configured to disallow access by other users. Being stored in the user’s home directory by default, the correct file permissions should usually already be in place. For enhanced security, you may also consider setting the `+FCLI_ENCRYPT_KEY+` environment variable; see the link:#_fcli_home_folder[Fcli Home Folder] section for details. - -== Fcli Home Folder - -Fcli stores various files in its home directory, like session files (see link:#_session_management[Session Management]) and fcli variable contents (see link:#_fcli_variables[Fcli Variables]). Future versions of fcli may -also automatically generated log files in this home directory, if no `+--log-file+` option is provided. - -By default, the fcli home directory is located at `+/.fortify/fcli+`, but this can be overridden through the `+FORTIFY_HOME+` or `+FCLI_HOME+` environment variables. If the `+FCLI_HOME+` environment variable is set, then this will be used as the fcli home directory. If the `+FORTIFY_HOME+` environment variable is set (and `+FCLI_HOME+` is not set), then fcli will use `+/fcli+` as its home directory. - -When utilizing fcli in pipelines or automation scripts, especially when multiple pipelines or scripts may be running simultaneously on a single, non-containerized system, it is highly recommended to set the `+FCLI_HOME+` or `+FORTIFY_HOME+` environment variables to a dedicated directory for each individual pipeline/script run. Failure to do so may cause these runs to share session data, variables and other persistent fcli data, which will likely cause issues. For example, both pipelines may try to login with the same session name but with different URL’s or credentials, or even when using the same session configuration, one pipeline may log out of the session while the other pipeline is still using that session. Or, both pipelines may be updating the same fcli variable but with different contents, causing unexpected results when accessing those fcli variables. On containerized systems, like pipelines running in GitLab or GitHub, the fcli home folder will usually be stored inside the individual pipeline containers, so in this case it shouldn’t be necessary to set the `+FCLI_HOME+` or `+FORTIFY_HOME+` variables. - -Note that some files stored in the fcli home directory may contain sensitive data, like authentication tokens generated by login commands, or proxy credentials configured through the `+fcli config proxy+` commands. Fcli encrypts any sensitive files, but since the encryption key and algorithm are hardcoded, these files can be decrypted fairly easily. You should ensure proper file access permissions on the fcli home folder. In addition, you can consider setting the `+FCLI_ENCRYPT_KEY+` environment variable to configure an alternative encryption key. That way, the sensitive files can only be decrypted if someone has access to this customer encryption key. - -== Environment Variables - -Apart from the special-purpose environment variables described in other sections, like the link:#_fcli_home_folder[Fcli Home Folder] section, fcli allows for specifying default option and parameter values through environment variables. This is particularly useful for specifying product URL’s and credentials through pipeline secrets, but also allows for preventing having to manually supply command line options if you frequently invoke a particular command with the same option value(s). For example, you could define a default value for the `+fcli ssc appversion create --issue-template+` option, to avoid having to remember the issue template name every time you invoke this command. - -Fcli walks the command tree to find an environment variable that matches a particular option, starting with the most detailed command prefix first. For the issue-template example above, fcli would look for the following environment variable names, in this order: - -* `+FCLI_DEFAULT_SSC_APPVERSION_CREATE_ISSUE_TEMPLATE+` -* `+FCLI_DEFAULT_SSC_APPVERSION_ISSUE_TEMPLATE+` -* `+FCLI_DEFAULT_SSC_ISSUE_TEMPLATE+` -* `+FCLI_DEFAULT_ISSUE_TEMPLATE+` - -Environment variable lookups are based on the following rules: - -* Command aliases are not taken into account when looking for environment variables; suppose we have a `+delete+` command with alias `+rm+`, you will need to use `+FCLI_DEFAULT_..._DELETE_...+` and not `+FCLI_DEFAULT_..._RM_...+` -* For options, fcli will use the longest option name when looking for environment variables; suppose we have an option with names `+-a+`, `+--ab+` and `+--abc+`, you will need to use `+FCLI_DEFAULT_..._ABC+` and not `+FCLI_DEFAULT_..._AB+` or `+FCLI_DEFAULT_..._A+` -* Currently, not all positional parameters support default values from environment variables; this will be improved over time. Please refer to the positional parameter description in help output or manual pages to identify what environment variable suffix should be used for a particular positional parameter. - -Although powerful, these environment variables for providing default option and parameter values should be used with some care to avoid unexpected results: - -1. Obviously command option requirements should be respected; supplying default values for exclusive options may result in errors or unexpected behavior -2. Preferably, you should use the most specific environment variable name, like `+FCLI_DEFAULT_SSC_APPVERSION_CREATE_ISSUE_TEMPLATE+` from the example above, to avoid accidentally supplying default values to a similarly named option on other commands - -Despite #2 above, in some cases it may be useful to use less specific environment names, in particular if the same default values should be applied to multiple commands. As an example, consider an environment variable named `+FCLI_DEFAULT_SSC_URL+`: - -* This variable value will be used as a default value for all `+--url+` options in the SSC module -* This variable value will be used as a default value for all `+--ssc-url+` options in other product modules - -This means that defining a single `+FCLI_DEFAULT_SSC_URL+` environment variable, together with for example `+FCLI_DEFAULT_SSC_USER+` and `+FCLI_DEFAULT_SSC_PASSWORD+` environment variables, allows for applying these default values to all of the `+fcli ssc session login+`, `+fcli sc-sast session login+`, `+fcli sc-dast session login+`, and corresponding `+logout+` commands. - -Note that as described in the link:#_env_prefix[–env-prefix] section, you can override the `+FCLI_DEFAULT+` prefix. For example, with `+--env-prefix MYPREFIX+`, fcli will look for `+MYPREFIX_*+` environment variables instead of `+FCLI_DEFAULT_*+` environment variables. - -== Fcli Variables - -Fcli allows for storing fcli output data in fcli variables for use by subsequent fcli commands. This is a powerful feature that prevents users from having to use shell features to parse fcli output when needing to provide output from one command as input to another command. For example, this feature allows for starting a scan, and then passing the scan id to a corresponding `+wait-for+` command, or for creating an SSC application version, and passing the SSC application version id to the `+appversion-artifact upload+` command. - -Fcli supports two types of variables: - -* Named variables -** Stored using the `+--store myVarName[:prop1,prop2]+` option on data output commands -** If no properties are provided with the `+--store+` option, all JSON properties will be stored -** Referenced using the `+'{?myVarName:prop1}'+` syntax anywhere on the command line of subsequent fcli commands -** As a best practice, variable references should be quoted to avoid the shell interpreting the curly braces -** On most shells, you should be able to put the variable reference in single quotes, or use `+\{+` -** If you have any suggestions for a better syntax, please comment here: https://github.com/fortify/fcli/issues/160 -** Variable names are global and can be referenced across products and sessions -* Predefined variables: -** Stored using the `+--store '?'+` option on a subset of data output commands -** Stored under a command-specific predefined variable name -** Referenced using the `+'?'+` syntax on specific options -** As a best practice, question marks should be quoted to avoid the shell interpreting `+?+` as a file name wildcard (for example, if you have a file named `+x+` in the current directory, then `+--store ?+` would store a variable named `+x+` instead of the predefined variable name) -** On most shells, you should be able to put the `+?+` in single or double quotes, or use `+\?+` -** If you have any suggestions for a better syntax, please comment here: https://github.com/fortify/fcli/issues/160 -** Help output and manual pages may currently lack information about which commands and options support the `+'?'+` syntax, so for now you’ll need to look at examples or just try. - -Predefined variables are easier to use; they are more concise and you do not need to remember JSON property names. However, as indicated, they are more limited in use; they only store a predefined JSON property, and -are only available on a subset of commands and options. - -Following are some examples, assuming the necessary login sessions are available: - -[source,bash] ----- -fcli ssc appversion create myApp:1.0 --auto-required --skip-if-exists --store '?' -fcli ssc appversion-artifact upload myScan.fpr --appversion '?' - -fcli ssc appversion create myApp:1.0 --auto-required --skip-if-exists --store myVersion:id,name -fcli ssc appversion-artifact upload myScan.fpr --appversion {?myVersion:id} - -fcli sc-dast scan start MyScan -S 011daf6b-f2d0-4127-89ab-1d3cebab8784 --store '?' -fcli sc-dast scan wait-for '?' - -fcli sc-dast scan start MyScan -S 011daf6b-f2d0-4127-89ab-1d3cebab8784 --store myScan -fcli sc-dast scan wait-for {?myScan:id} ----- - -Fcli provides dedicated commands for managing variables and variable contents through the following commands; please see help output or manual pages for available sub-commands: - -* `+fcli config variable definition+` * `+fcli config variable contents+` - -Note that the `+fcli config variable contents+` `+get+` and `+list+` commands support the regular fcli output options, and the `+list+` command also provides query capabilities. This allows for advanced use -cases, like retrieving server data once and then outputting it in multiple formats, potentially even applying separate filters. As an example: - -[source,bash] ----- -fcli ssc appversion list --store myVersions -fcli config variable contents list myVersions -o csv --output-to-file myVersions.csv -fcli config variable contents list myVersions -o json -q createdBy=admin --output-to-file myAdminVersions.json -fcli config variable contents list myVersions -o 'expr={id}\n' --output-to-file myVersionIds.txt ----- - -== Manual Pages - -The manual pages for this fcli release can be found here: link:manpage/fcli.html[fcli (1)]. If you are viewing this documentation offline, please make sure that you have fully extracted the docs-html.zip file to access the manual pages. - -Manual pages for each fcli release are automatically generated as new fcli releases are being built, and are available in HTML and Linux man-page formats. The manual pages in man-page format can be downloaded from the fcli releases page at https://github.com/fortify/fcli/releases. The HTML-formatted manual pages, including this documentation page, can also be downloaded from the fcli release page or viewed online at https://fortify.github.io/fcli. - -== Troubleshooting - -=== Native Binaries - -Native binaries require some special source code annotations for proper operation, which are not required for the plain Java `+.jar+` version of fcli. If fcli developers forgot to include any of these annotations, you -may experience any of the following behavior: - -* Commands and/or option listed in manual pages are not listed by the help output of a native binary -* Trying to use commands and/or options listed in the manual pages result in errors stating that the command or option is not recognized -* Some commands and/or options result in technical error messages about classes, constructors or methods not being found or not being accessible - -If you encounter any of these issues, please submit a bug report as described in link:#_submitting_a_bug_report[Submitting a Bug Report]. As described in that section, please include information on whether the `+.jar+` version of fcli exhibits the same erroneous behavior. While fcli developers are working on fixing the issue, you can temporarily use the `+.jar+` version of fcli until the issue is resolved. - -=== Submitting a Bug Report - -After confirming that an issue cannot be resolved based on the information above, and is not caused by user error, please consider submitting a bug report on the https://github.com/fortify/fcli/issues[fcli issue tracker]. Before doing so, please verify that there is not already a bug report open for the issue that you are experiencing; in that case, feel free to leave a comment on the existing bug report to confirm the issue and/or provide additional details. - -When opening a bug report, please include the following information: - -* Fcli version, as shown by the `+fcli --version+` command -* Which fcli variant you are using; one of the native binaries or the `+.jar+` variant invoked using `+java -jar fcli.jar+` -* If you are experiencing an issue with the native binaries, please confirm whether the `+.jar+` version of fcli exhibits the same behavior -* Operating system and any other relevant environment details, for example: -** Interactive or pipeline/automation use -** If pipeline use, what CI/CD system are you running fcli on (Jenkins, GitHub, GitLab, …) -** What FCLI environment variables have been set -* Steps to reproduce -* Any other information that may be relevant diff --git a/doc-resources/template-values.md b/doc-resources/template-values.md index 7937fa4cae..3970133b8f 100644 --- a/doc-resources/template-values.md +++ b/doc-resources/template-values.md @@ -6,3 +6,7 @@ https://github.com/fortify/fcli # gh-pages-url https://fortify.github.io/fcli + +# copyright-years +2021 - {{var:current-year}} + diff --git a/fcli-common/src/main/java/com/fortify/cli/common/cli/cmd/AbstractFortifyCLICommand.java b/fcli-common/src/main/java/com/fortify/cli/common/cli/cmd/AbstractFortifyCLICommand.java deleted file mode 100644 index 058ab73a04..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/cli/cmd/AbstractFortifyCLICommand.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.fortify.cli.common.cli.cmd; - -import ch.qos.logback.classic.Level; -import io.micronaut.core.annotation.ReflectiveAccess; -import lombok.Getter; -import picocli.CommandLine.ArgGroup; -import picocli.CommandLine.Option; - -@ReflectiveAccess -public class AbstractFortifyCLICommand { - @ArgGroup(exclusive = false, headingKey = "fcli.genericOptions.heading", order = 50) - @Getter private GenericOptionsArgGroup genericOptions = new GenericOptionsArgGroup(); - - public static enum LogLevel { - TRACE(Level.TRACE), - DEBUG(Level.DEBUG), - INFO(Level.INFO), - WARN(Level.WARN), - ERROR(Level.ERROR); - - @Getter private final Level logbackLevel; - LogLevel(Level logbackLevel) { - this.logbackLevel = logbackLevel; - } - } - - @ReflectiveAccess - public static final class GenericOptionsArgGroup { - @Option(names = {"-h", "--help"}, usageHelp = true, description = "display this help message") - private boolean usageHelpRequested; - - @Option(names = "--env-prefix", defaultValue = "FCLI_DEFAULT") - @Getter private String envPrefix; - - @Option(names = "--log-file") - @Getter private String logFile; - - @Option(names = "--log-level") - @Getter private LogLevel logLevel; - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/cli/mixin/CommonOptionMixins.java b/fcli-common/src/main/java/com/fortify/cli/common/cli/mixin/CommonOptionMixins.java deleted file mode 100644 index c3b6d23a19..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/cli/mixin/CommonOptionMixins.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.fortify.cli.common.cli.mixin; - -import io.micronaut.core.annotation.ReflectiveAccess; -import lombok.Getter; -import picocli.CommandLine.Option; - -public class CommonOptionMixins { - private CommonOptionMixins() {} - - @ReflectiveAccess - public static class OptionalDestinationFile { - @Option(names = {"-f", "--dest"}, descriptionKey = "fcli.destination-file") - @Getter private String destination; - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/cli/util/EnvPrefixInitializer.java b/fcli-common/src/main/java/com/fortify/cli/common/cli/util/EnvPrefixInitializer.java deleted file mode 100644 index 52d88c3afb..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/cli/util/EnvPrefixInitializer.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.fortify.cli.common.cli.util; - -import com.fortify.cli.common.cli.util.FortifyCLIInitializerRunner.FortifyCLIInitializerCommand; - -import jakarta.inject.Singleton; - -@Singleton -public class EnvPrefixInitializer implements IFortifyCLIInitializer { - @Override - public void initializeFortifyCLI(FortifyCLIInitializerCommand cmd) { - String envPrefix = cmd.getGenericOptions().getEnvPrefix(); - System.setProperty("fcli.env.default.prefix", envPrefix); - FortifyCLIDefaultValueProvider.setEnvPrefix(envPrefix); - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/cli/util/EnvSuffix.java b/fcli-common/src/main/java/com/fortify/cli/common/cli/util/EnvSuffix.java deleted file mode 100644 index 9c6a1bbc26..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/cli/util/EnvSuffix.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fortify.cli.common.cli.util; - -import static java.lang.annotation.RetentionPolicy.*; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * This annotation allows for defining an environment variable suffix - * on options and positional parameters for resolving default values - * from environment variables. - * - * @author rsenden - * - */ -@Retention(RUNTIME) -@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) -public @interface EnvSuffix { - String value(); -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/cli/util/FortifyCLIDefaultValueProvider.java b/fcli-common/src/main/java/com/fortify/cli/common/cli/util/FortifyCLIDefaultValueProvider.java deleted file mode 100644 index bb053f452a..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/cli/util/FortifyCLIDefaultValueProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.fortify.cli.common.cli.util; - -import java.lang.reflect.AnnotatedElement; - -import com.fortify.cli.common.util.StringUtils; - -import lombok.Setter; -import picocli.CommandLine; -import picocli.CommandLine.Model.ArgSpec; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Model.OptionSpec; - -public class FortifyCLIDefaultValueProvider implements CommandLine.IDefaultValueProvider { - @Setter private static String envPrefix = "FCLI_DEFAULT"; - - @Override - public String defaultValue(ArgSpec argSpec) { - String envVarSuffix; - if (argSpec instanceof OptionSpec) { - final var optionSpec = (OptionSpec) argSpec; - envVarSuffix = getEnvVarSuffix(optionSpec.userObject(), optionSpec.longestName().replaceAll("^-+", "")); - } else { - envVarSuffix = getEnvVarSuffix(argSpec.userObject(), null); - } - return resolve(argSpec.command(), envVarSuffix); - } - - private String resolve(CommandSpec command, String suffix) { - if ( command!=null && suffix!=null ) { - var envVarName = getEnvVarName(command, suffix); - String value = System.getenv(envVarName); - if ( StringUtils.isNotBlank(value) ) { return value; } - return resolve(command.parent(), suffix); - } - return null; - } - - private final String getEnvVarSuffix(Object userObject, String defaultValue) { - if ( userObject!=null && userObject instanceof AnnotatedElement ) { - EnvSuffix envSuffixAnnotation = ((AnnotatedElement)userObject).getAnnotation(EnvSuffix.class); - if ( envSuffixAnnotation!=null ) { return envSuffixAnnotation.value(); } - } - return defaultValue; - } - - private final String getEnvVarName(CommandSpec command, String suffix) { - String qualifiedCommandName = command.qualifiedName("_"); - String combinedName = String.format("%s_%s", qualifiedCommandName, suffix); - return combinedName.replace('-', '_').toUpperCase().replaceFirst("FCLI", envPrefix); - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/cli/util/FortifyCLIInitializerRunner.java b/fcli-common/src/main/java/com/fortify/cli/common/cli/util/FortifyCLIInitializerRunner.java deleted file mode 100644 index 96997ef519..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/cli/util/FortifyCLIInitializerRunner.java +++ /dev/null @@ -1,73 +0,0 @@ -/******************************************************************************* - * (c) Copyright 2021 Micro Focus or one of its affiliates - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including without - * limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to - * whom the Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY - * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR - * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - ******************************************************************************/ -package com.fortify.cli.common.cli.util; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.stream.Stream; - -import com.fortify.cli.common.cli.cmd.AbstractFortifyCLICommand; -import com.fortify.cli.common.util.FixInjection; - -import io.micronaut.configuration.picocli.MicronautFactory; -import io.micronaut.core.annotation.ReflectiveAccess; -import jakarta.inject.Inject; -import lombok.Getter; -import picocli.CommandLine; -import picocli.CommandLine.Command; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Spec; - -@Command(defaultValueProvider = FortifyCLIDefaultValueProvider.class) -@FixInjection -public class FortifyCLIInitializerRunner { - private static final PrintWriter DUMMY_WRITER = new PrintWriter(new StringWriter()); - - public static final void initialize(String[] args, MicronautFactory micronautFactory) { - // Remove help options, as we want initialization always to occur - String[] argsWithoutHelp = Stream.of(args).filter(a->!a.matches("-h|--help")).toArray(String[]::new); - new CommandLine(FortifyCLIInitializerCommand.class, micronautFactory) - .setOut(DUMMY_WRITER) - .setErr(DUMMY_WRITER) - .setUnmatchedArgumentsAllowed(true) - .setUnmatchedOptionsArePositionalParams(true) - .setExpandAtFiles(true) - .execute(argsWithoutHelp); - } - - @Command(name = "fcli", defaultValueProvider = FortifyCLIDefaultValueProvider.class) - @FixInjection @ReflectiveAccess - public static final class FortifyCLIInitializerCommand extends AbstractFortifyCLICommand implements Runnable { - @Inject private IFortifyCLIInitializer[] initializers; - @Getter @Spec private CommandSpec commandSpec; - - @Override - public void run() { - for ( var initializer : initializers ) { - initializer.initializeFortifyCLI(this); - } - } - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/cli/util/IFortifyCLIInitializer.java b/fcli-common/src/main/java/com/fortify/cli/common/cli/util/IFortifyCLIInitializer.java deleted file mode 100644 index 6bb4779b20..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/cli/util/IFortifyCLIInitializer.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.fortify.cli.common.cli.util; - -import com.fortify.cli.common.cli.util.FortifyCLIInitializerRunner.FortifyCLIInitializerCommand; - -public interface IFortifyCLIInitializer { - void initializeFortifyCLI(FortifyCLIInitializerCommand initializerCommand); -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/http/proxy/helper/ProxyHelper.java b/fcli-common/src/main/java/com/fortify/cli/common/http/proxy/helper/ProxyHelper.java deleted file mode 100644 index 9e67510254..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/http/proxy/helper/ProxyHelper.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.fortify.cli.common.http.proxy.helper; - -import java.nio.file.Path; -import java.util.Comparator; -import java.util.stream.Stream; - -import com.fortify.cli.common.util.FcliHomeHelper; - -import kong.unirest.UnirestInstance; - -public final class ProxyHelper { - private ProxyHelper() {} - - public static final void configureProxy(UnirestInstance unirest, String module, String url) { - getProxiesStream() - .sorted(Comparator.comparingInt(ProxyDescriptor::getPriority).reversed()) - .filter(d->d.matches(module, url)) - .findFirst() - .ifPresent(d-> - unirest.config().proxy(d.getProxyHost(), d.getProxyPort(), d.getProxyUser(), d.getProxyPasswordAsString()) - ); - } - - public static final ProxyDescriptor getProxy(String name) { - Path proxyConfigPath = getProxyConfigPath(name); - if ( !FcliHomeHelper.exists(proxyConfigPath) ) { - throw new IllegalArgumentException("No proxy configuration found with name: "+name); - } - return getProxy(proxyConfigPath); - } - - public static final ProxyDescriptor addProxy(ProxyDescriptor descriptor) { - Path proxyConfigPath = getProxyConfigPath(descriptor); - if ( FcliHomeHelper.exists(proxyConfigPath) ) { - throw new IllegalArgumentException("proxy configuration with name "+descriptor.getName()+" already exists"); - } - FcliHomeHelper.saveSecuredFile(proxyConfigPath, descriptor, true); - return descriptor; - } - - public static final ProxyDescriptor updateProxy(ProxyDescriptor descriptor) { - FcliHomeHelper.saveSecuredFile(getProxyConfigPath(descriptor), descriptor, true); - return descriptor; - } - - private static final ProxyDescriptor getProxy(Path proxyDescriptorPath) { - return FcliHomeHelper.readSecuredFile(proxyDescriptorPath, ProxyDescriptor.class, true); - } - - public static final ProxyDescriptor deleteProxy(ProxyDescriptor descriptor) { - FcliHomeHelper.deleteFile(getProxyConfigPath(descriptor), true); - return descriptor; - } - - public static final Stream deleteAllProxies() { - return getProxiesStream() - .peek(ProxyHelper::deleteProxy); - } - - public static final Stream getProxiesStream() { - return FcliHomeHelper.exists(getProxiesConfigPath()) - ? FcliHomeHelper.listFilesInDir(getProxiesConfigPath(), true).map(ProxyHelper::getProxy) - : Stream.empty(); - } - - private static final Path getProxiesConfigPath() { - return Path.of("proxies"); - } - - private static final Path getProxyConfigPath(ProxyDescriptor descriptor) { - return getProxyConfigPath(descriptor.getName()); - } - - private static final Path getProxyConfigPath(String name) { - return getProxiesConfigPath().resolve(getProxyFileName(name)); - } - - private static final String getProxyFileName(String name) { - return name.replaceAll("[^a-zA-Z0-9]", "_"); - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/http/ssl/truststore/helper/TrustStoreConfigDescriptor.java b/fcli-common/src/main/java/com/fortify/cli/common/http/ssl/truststore/helper/TrustStoreConfigDescriptor.java deleted file mode 100644 index 1d58465960..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/http/ssl/truststore/helper/TrustStoreConfigDescriptor.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.fortify.cli.common.http.ssl.truststore.helper; - -import com.fortify.cli.common.json.JsonNodeHolder; - -import io.micronaut.core.annotation.ReflectiveAccess; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - -@Data @EqualsAndHashCode(callSuper = false) -@Builder @NoArgsConstructor @AllArgsConstructor @ReflectiveAccess -public class TrustStoreConfigDescriptor extends JsonNodeHolder { - private String path; - private String type; - private String password; -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/http/ssl/truststore/helper/TrustStoreConfigHelper.java b/fcli-common/src/main/java/com/fortify/cli/common/http/ssl/truststore/helper/TrustStoreConfigHelper.java deleted file mode 100644 index 9bd1ea7bbd..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/http/ssl/truststore/helper/TrustStoreConfigHelper.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.fortify.cli.common.http.ssl.truststore.helper; - -import java.nio.file.Path; - -import com.fortify.cli.common.util.FcliHomeHelper; - -public final class TrustStoreConfigHelper { - private TrustStoreConfigHelper() {} - - public static final TrustStoreConfigDescriptor getTrustStoreConfig() { - Path trustStoreConfigPath = getTrustStoreConfigPath(); - return !FcliHomeHelper.exists(trustStoreConfigPath) - ? new TrustStoreConfigDescriptor() - : FcliHomeHelper.readSecuredFile(trustStoreConfigPath, TrustStoreConfigDescriptor.class, true); - } - - public static final TrustStoreConfigDescriptor setTrustStoreConfig(TrustStoreConfigDescriptor descriptor) { - Path trustStoreConfigPath = getTrustStoreConfigPath(); - FcliHomeHelper.saveSecuredFile(trustStoreConfigPath, descriptor, true); - return descriptor; - } - - public static final void clearTrustStoreConfig() { - FcliHomeHelper.deleteFile(getTrustStoreConfigPath(), true); - } - - private static final Path getTrustStoreConfigPath() { - return FcliHomeHelper.getFcliConfigPath().resolve("ssl/truststore.json"); - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/json/IJsonNodeHolder.java b/fcli-common/src/main/java/com/fortify/cli/common/json/IJsonNodeHolder.java deleted file mode 100644 index 1502a12581..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/json/IJsonNodeHolder.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.fortify.cli.common.json; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; - -public interface IJsonNodeHolder { - void setJsonNode(JsonNode jsonNode); - JsonNode asJsonNode(); - ObjectNode asObjectNode(); - ArrayNode asArrayNode(); -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/json/JsonHelper.java b/fcli-common/src/main/java/com/fortify/cli/common/json/JsonHelper.java deleted file mode 100644 index 1bb48cf0b0..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/json/JsonHelper.java +++ /dev/null @@ -1,185 +0,0 @@ -/******************************************************************************* - * (c) Copyright 2021 Micro Focus or one of its affiliates - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including without - * limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to - * whom the Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY - * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR - * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - ******************************************************************************/ -package com.fortify.cli.common.json; - -import java.util.EnumSet; -import java.util.Iterator; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collector; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; -import com.fortify.cli.common.util.StringUtils; -import com.jayway.jsonpath.Configuration; -import com.jayway.jsonpath.DocumentContext; -import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.Option; -import com.jayway.jsonpath.ParseContext; -import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider; -import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; - -import lombok.Getter; - -/** - * This bean provides utility methods for working with Jackson JsonNode trees. - * - * @author Ruud Senden - * - */ -public class JsonHelper { - @Getter private static final ObjectMapper objectMapper = _createObjectMapper(); - private final ParseContext parseContext; - private static final JsonHelper INSTANCE = new JsonHelper(); - - public JsonHelper() { - this.parseContext = JsonPath.using(Configuration.builder() - .jsonProvider(new JacksonJsonNodeJsonProvider(objectMapper)) - .mappingProvider(new JacksonMappingProvider(objectMapper)) - .options(EnumSet.noneOf(Option.class)) - .build()); - } - - private static final ObjectMapper _createObjectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); - objectMapper.configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true); - return objectMapper; - } - - @SuppressWarnings("unchecked") - public static final R evaluateJsonPath(Object input, String path, Class returnClass) { - DocumentContext context = INSTANCE.parseContext.parse(input); - if ( !ObjectNode.class.isAssignableFrom(returnClass) ) { - return context.read(path, returnClass); - } else { - JsonNode jsonNode = context.read(path, JsonNode.class); - if ( jsonNode instanceof ObjectNode ) { - return (R)jsonNode; - } else if ( jsonNode==null || !jsonNode.isArray() || jsonNode.size()>1 ) { - throw new IllegalStateException("Unable to get ObjectNode for JSONPath "+path+", json node: "+jsonNode); - } else if ( jsonNode.size()==0 ) { - // What to return here; null, empty ObjectNode, NullNode? - return null; - } else { - return (R)jsonNode.get(0); - } - } - } - - public static final ObjectNode getFirstObjectNode(JsonNode input) { - if ( input instanceof ObjectNode ) { - return (ObjectNode)input; - } else if ( input instanceof ArrayNode ) { - ArrayNode array = (ArrayNode)input; - if ( array.size()==0 ) { return null; } - JsonNode node = array.get(0); - if ( node instanceof ObjectNode ) { - return (ObjectNode)node; - } - } - throw new IllegalArgumentException("Input must be an ObjectNode or array of ObjectNodes"); - } - - public static final Iterable iterable(ArrayNode arrayNode) { - Iterator iterator = arrayNode.iterator(); - return () -> iterator; - } - - public static final Stream stream(ArrayNode arrayNode) { - return StreamSupport.stream(iterable(arrayNode).spliterator(), false); - } - - public static final ArrayNodeCollector arrayNodeCollector() { - return new ArrayNodeCollector(); - } - - public static final ArrayNode toArrayNode(String... objects) { - return Stream.of(objects).map(TextNode::new).collect(arrayNodeCollector()); - } - - public static T treeToValue(JsonNode node, Class returnType) { - if ( node==null ) { return null; } - try { - T result = objectMapper.treeToValue(node, returnType); - if ( result instanceof IJsonNodeHolder ) { - ((IJsonNodeHolder)result).setJsonNode(node); - } - return result; - } catch (JsonProcessingException jpe ) { - throw new RuntimeException("Error processing JSON data", jpe); - } - } - - public static T jsonStringToValue(String jsonString, Class returnType) { - if ( StringUtils.isBlank(jsonString) ) { return null; } - try { - return treeToValue(objectMapper.readTree(jsonString), returnType); - } catch (JsonProcessingException jpe) { - throw new RuntimeException("Error processing JSON data", jpe); - } - } - - private static final class ArrayNodeCollector implements Collector { - @Override - public Supplier supplier() { - return objectMapper::createArrayNode; - } - - @Override - public BiConsumer accumulator() { - return ArrayNode::add; - } - - @Override - public BinaryOperator combiner() { - return (x, y) -> { - x.addAll(y); - return x; - }; - } - - @Override - public Function finisher() { - return accumulator -> accumulator; - } - - @Override - public Set characteristics() { - return EnumSet.of(Characteristics.UNORDERED); - } - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/json/JsonNodeHolder.java b/fcli-common/src/main/java/com/fortify/cli/common/json/JsonNodeHolder.java deleted file mode 100644 index abcc99be0f..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/json/JsonNodeHolder.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.fortify.cli.common.json; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; - -import lombok.EqualsAndHashCode; -import lombok.ToString; - -@ToString @EqualsAndHashCode -public class JsonNodeHolder implements IJsonNodeHolder { - private JsonNode jsonNode; - @Override - public void setJsonNode(JsonNode jsonNode) { - this.jsonNode = jsonNode; - } - - @Override @JsonIgnore - public JsonNode asJsonNode() { - if ( jsonNode==null ) { - jsonNode = JsonHelper.getObjectMapper().valueToTree(this); - } - return jsonNode; - } - - @Override @JsonIgnore - public ObjectNode asObjectNode() { - if ( !(jsonNode instanceof ObjectNode) ) { throw new IllegalStateException("JsonNode is not an instance of ObjectNode"); } - return (ObjectNode)jsonNode; - } - - @Override @JsonIgnore - public ArrayNode asArrayNode() { - if ( !(jsonNode instanceof ArrayNode) ) { throw new IllegalStateException("JsonNode is not an instance of ArrayNode"); } - return (ArrayNode)jsonNode; - } - - -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/OutputFormat.java b/fcli-common/src/main/java/com/fortify/cli/common/output/OutputFormat.java deleted file mode 100644 index d79987c5d7..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/OutputFormat.java +++ /dev/null @@ -1,80 +0,0 @@ -/******************************************************************************* - * (c) Copyright 2021 Micro Focus or one of its affiliates - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including without - * limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to - * whom the Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY - * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR - * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - ******************************************************************************/ -package com.fortify.cli.common.output; - -import com.fortify.cli.common.output.writer.record.IRecordWriterFactory; -import com.fortify.cli.common.output.writer.record.csv.CsvRecordWriterFactory; -import com.fortify.cli.common.output.writer.record.csv.CsvRecordWriter.CsvType; -import com.fortify.cli.common.output.writer.record.expr.ExprRecordWriterFactory; -import com.fortify.cli.common.output.writer.record.json.JsonRecordWriterFactory; -import com.fortify.cli.common.output.writer.record.json_properties.JsonPropertiesRecordWriterFactory; -import com.fortify.cli.common.output.writer.record.table.TableRecordWriterFactory; -import com.fortify.cli.common.output.writer.record.table.TableRecordWriter.TableType; -import com.fortify.cli.common.output.writer.record.tree.TreeRecordWriterFactory; -import com.fortify.cli.common.output.writer.record.xml.XmlRecordWriterFactory; -import com.fortify.cli.common.output.writer.record.yaml.YamlRecordWriterFactory; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -/** - * @author rsenden - */ -@RequiredArgsConstructor -public enum OutputFormat { - // These entries should be in alphabetical order, except for expr & json_properties as - // these are 'special' formats. - csv (OutputStructure.FLAT, "csv", new CsvRecordWriterFactory(CsvType.HEADERS)), - csv_plain (OutputStructure.FLAT, "csv", new CsvRecordWriterFactory(CsvType.NO_HEADERS)), - json (OutputStructure.TREE, "json", new JsonRecordWriterFactory()), - json_flat (OutputStructure.FLAT, "json", new JsonRecordWriterFactory()), - table (OutputStructure.FLAT, "table", new TableRecordWriterFactory(TableType.HEADERS)), - table_plain (OutputStructure.FLAT, "table", new TableRecordWriterFactory(TableType.NO_HEADERS)), - tree (OutputStructure.TREE, "tree", new TreeRecordWriterFactory()), - tree_flat (OutputStructure.FLAT, "tree", new TreeRecordWriterFactory()), - xml (OutputStructure.TREE, "xml", new XmlRecordWriterFactory()), - xml_flat (OutputStructure.FLAT, "xml", new XmlRecordWriterFactory()), - yaml (OutputStructure.TREE, "yaml", new YamlRecordWriterFactory()), - yaml_flat (OutputStructure.FLAT, "yaml", new YamlRecordWriterFactory()), - expr (OutputStructure.TREE, "expr", new ExprRecordWriterFactory()), - json_properties (OutputStructure.TREE, "paths", new JsonPropertiesRecordWriterFactory()); - - @Getter private final OutputStructure outputStructure; - @Getter private final String messageKey; - @Getter private final IRecordWriterFactory recordWriterFactory; - - public enum OutputStructure { TREE, FLAT } - - public final boolean isFlat() { - return isFlat(this); - } - - public static final boolean isFlat(OutputFormat outputFormat) { - switch (outputFormat.getOutputStructure()) { - case FLAT: return true; - default: return false; - } - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/basic/AbstractBasicOutputCommand.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/basic/AbstractBasicOutputCommand.java deleted file mode 100644 index 86ae1a6e4f..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/basic/AbstractBasicOutputCommand.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************* - * (c) Copyright 2021 Micro Focus or one of its affiliates - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including without - * limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to - * whom the Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY - * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR - * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - ******************************************************************************/ -package com.fortify.cli.common.output.cli.cmd.basic; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fortify.cli.common.cli.cmd.AbstractFortifyCLICommand; -import com.fortify.cli.common.output.cli.mixin.spi.basic.IBasicOutputHelper; -import com.fortify.cli.common.output.spi.ISingularSupplier; - -import io.micronaut.core.annotation.ReflectiveAccess; - -@ReflectiveAccess -public abstract class AbstractBasicOutputCommand extends AbstractFortifyCLICommand implements Runnable, ISingularSupplier { - @Override - public final void run() { - IBasicOutputHelper outputHelper = getOutputHelper(); - outputHelper.write(getJsonNode()); - } - - protected abstract JsonNode getJsonNode(); - protected abstract IBasicOutputHelper getOutputHelper(); -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/AbstractUnirestOutputCommand.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/AbstractUnirestOutputCommand.java deleted file mode 100644 index 478e6aedc9..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/AbstractUnirestOutputCommand.java +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************* - * (c) Copyright 2021 Micro Focus or one of its affiliates - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including without - * limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to - * whom the Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY - * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR - * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - ******************************************************************************/ -package com.fortify.cli.common.output.cli.cmd.unirest; - -import java.util.Arrays; -import java.util.List; - -import com.fortify.cli.common.output.cli.mixin.spi.unirest.IUnirestOutputHelper; -import com.fortify.cli.common.output.spi.ISingularSupplier; -import com.fortify.cli.common.rest.cli.cmd.AbstractUnirestRunnerCommand; -import com.fortify.cli.common.session.manager.api.ISessionData; - -import io.micronaut.core.annotation.ReflectiveAccess; -import kong.unirest.UnirestInstance; - -@ReflectiveAccess -public abstract class AbstractUnirestOutputCommand extends AbstractUnirestRunnerCommand implements ISingularSupplier { - private static final List> supportedInterfaces = Arrays.asList( - IUnirestBaseRequestSupplier.class, - IUnirestWithSessionDataBaseRequestSupplier.class, - IUnirestJsonNodeSupplier.class, - IUnirestWithSessionDataJsonNodeSupplier.class); - @SuppressWarnings("unchecked") - @Override - protected final Void run(UnirestInstance unirest, D sessionData) { - IUnirestOutputHelper outputHelper = getOutputHelper(); - if ( isInstance(IUnirestBaseRequestSupplier.class) ) { - outputHelper.write(unirest, ((IUnirestBaseRequestSupplier)this).getBaseRequest(unirest)); - } else if ( isInstance(IUnirestWithSessionDataBaseRequestSupplier.class) ) { - outputHelper.write(unirest, ((IUnirestWithSessionDataBaseRequestSupplier)this).getBaseRequest(unirest, sessionData)); - } else if ( isInstance(IUnirestJsonNodeSupplier.class) ) { - outputHelper.write(unirest, ((IUnirestJsonNodeSupplier)this).getJsonNode(unirest)); - } else if ( isInstance(IUnirestWithSessionDataJsonNodeSupplier.class) ) { - outputHelper.write(unirest, ((IUnirestWithSessionDataJsonNodeSupplier)this).getJsonNode(unirest, sessionData)); - } else { - throw new IllegalStateException(this.getClass().getName()+" must implement exactly one of "+supportedInterfaces); - } - return null; - } - - private boolean isInstance(Class clazz) { - return clazz.isAssignableFrom(this.getClass()) && - supportedInterfaces.stream() - .filter(c->!c.equals(clazz)) - .noneMatch(c->c.isAssignableFrom(this.getClass())); - } - - protected abstract IUnirestOutputHelper getOutputHelper(); -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestBaseRequestSupplier.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestBaseRequestSupplier.java deleted file mode 100644 index 448b687644..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestBaseRequestSupplier.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.fortify.cli.common.output.cli.cmd.unirest; - -import kong.unirest.HttpRequest; -import kong.unirest.UnirestInstance; - -public interface IUnirestBaseRequestSupplier { - HttpRequest getBaseRequest(UnirestInstance unirest); -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestJsonNodeSupplier.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestJsonNodeSupplier.java deleted file mode 100644 index d35fc2c08c..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestJsonNodeSupplier.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fortify.cli.common.output.cli.cmd.unirest; - -import com.fasterxml.jackson.databind.JsonNode; - -import kong.unirest.UnirestInstance; - -public interface IUnirestJsonNodeSupplier { - JsonNode getJsonNode(UnirestInstance unirest); -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestWithSessionDataBaseRequestSupplier.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestWithSessionDataBaseRequestSupplier.java deleted file mode 100644 index b39037b6e4..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestWithSessionDataBaseRequestSupplier.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.fortify.cli.common.output.cli.cmd.unirest; - -import com.fortify.cli.common.session.manager.api.ISessionData; - -import kong.unirest.HttpRequest; -import kong.unirest.UnirestInstance; - -public interface IUnirestWithSessionDataBaseRequestSupplier { - HttpRequest getBaseRequest(UnirestInstance unirest, D sessionData); -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestWithSessionDataJsonNodeSupplier.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestWithSessionDataJsonNodeSupplier.java deleted file mode 100644 index 177bbaa1ec..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/cmd/unirest/IUnirestWithSessionDataJsonNodeSupplier.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.fortify.cli.common.output.cli.cmd.unirest; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fortify.cli.common.session.manager.api.ISessionData; - -import kong.unirest.UnirestInstance; - -public interface IUnirestWithSessionDataJsonNodeSupplier { - JsonNode getJsonNode(UnirestInstance unirest, D sessionData); -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/BasicOutputHelperMixins.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/BasicOutputHelperMixins.java deleted file mode 100644 index c296c91e27..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/BasicOutputHelperMixins.java +++ /dev/null @@ -1,209 +0,0 @@ -package com.fortify.cli.common.output.cli.mixin; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fortify.cli.common.output.cli.mixin.spi.basic.AbstractBasicOutputHelper; -import com.fortify.cli.common.output.cli.mixin.spi.basic.IBasicOutputHelper; -import com.fortify.cli.common.output.cli.mixin.writer.OutputWriterWithQueryFactoryMixin; -import com.fortify.cli.common.output.cli.mixin.writer.StandardOutputWriterFactoryMixin; -import com.fortify.cli.common.output.spi.product.IProductHelper; -import com.fortify.cli.common.output.writer.output.standard.StandardOutputConfig; - -import io.micronaut.core.annotation.ReflectiveAccess; -import kong.unirest.UnirestInstance; -import lombok.Getter; -import lombok.Setter; -import picocli.CommandLine.Command; -import picocli.CommandLine.Mixin; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Spec; -import picocli.CommandLine.Spec.Target; - -/** - *

This class provides standard, product-agnostic {@link IBasicOutputHelper} implementations. - * The mixins provided in this class are equivalent to the mixins in {@link UnirestOutputHelperMixins}, - * but allows for writing {@link JsonNode} instances to be written when no {@link UnirestInstance} - * and/or {@link IProductHelper} is available or required.

- */ -public class BasicOutputHelperMixins { - - @ReflectiveAccess - public static class Login extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "login"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Logout extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "logout"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Add extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "add"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Create extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "create"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess @Command(aliases = {"rm"}) - public static class Delete extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "delete"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Revoke extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "revoke"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Clear extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "clear"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess @Command(name = "list", aliases = {"ls"}) - public static class List extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "list"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private OutputWriterWithQueryFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Get extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "get"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.details(); - } - - @ReflectiveAccess - public static class Set extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "set"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Update extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "update"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Enable extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "enable"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Disable extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "disable"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Start extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "start"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Pause extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "pause"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Resume extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "resume"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Cancel extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "cancel"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class WaitFor extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "wait-for"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Upload extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "upload"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Download extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "download"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Install extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "install"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Uninstall extends AbstractBasicOutputHelper { - public static final String CMD_NAME = "uninstall"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess @Command - public static class Other extends AbstractBasicOutputHelper { - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/UnirestOutputHelperMixins.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/UnirestOutputHelperMixins.java deleted file mode 100644 index d7f4fba4f3..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/UnirestOutputHelperMixins.java +++ /dev/null @@ -1,253 +0,0 @@ -package com.fortify.cli.common.output.cli.mixin; - -import com.fortify.cli.common.output.cli.cmd.unirest.AbstractUnirestOutputCommand; -import com.fortify.cli.common.output.cli.mixin.spi.unirest.AbstractUnirestOutputHelper; -import com.fortify.cli.common.output.cli.mixin.spi.unirest.IUnirestOutputHelper; -import com.fortify.cli.common.output.cli.mixin.writer.OutputWriterWithQueryFactoryMixin; -import com.fortify.cli.common.output.cli.mixin.writer.StandardOutputWriterFactoryMixin; -import com.fortify.cli.common.output.spi.product.IProductHelper; -import com.fortify.cli.common.output.writer.output.standard.StandardOutputConfig; - -import io.micronaut.core.annotation.ReflectiveAccess; -import lombok.Getter; -import lombok.Setter; -import picocli.CommandLine.Command; -import picocli.CommandLine.Mixin; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Spec; -import picocli.CommandLine.Spec.Target; - -// TODO Rename this class to UnirestOutputHelperMixins -/** - *

This class provides standard, product-agnostic {@link IUnirestOutputHelper} implementations. - * Each product module should provide a {@code OutputHelperMixins} class that - * provides similarly named inner classes that extend from the corresponding - * {@link UnirestOutputHelperMixins} inner class, with a no-argument constructor that injects - * the product-specific {@link IProductHelper} by calling the - * {@link AbstractUnirestOutputHelper#setProductHelper(IProductHelper)} method. For example:

- * - *
- * public class MyProductOutputHelperMixins {
- *     ...
- *     
- *     @ReflectiveAccess
- *     public static class List extends OutputHelperMixins.List {
- *         public List() { setProductHelper(new MyProductProductHelper()); }
- *     }
- *     
- *     ...
- * }
- * 
- * - *

Note that the {@link IUnirestOutputHelper} instance is injected back into the provided - * {@link IProductHelper}, so you should always create a new {@link IProductHelper} - * instance, rather than reusing the same {@link IProductHelper} instance for all - * {@link IUnirestOutputHelper} implementations.

- * - *

Each {@link Command} implementation can then use the appropriate product-specific - * {@link IUnirestOutputHelper} implementation through the {@link Mixin} annotation, i.e.:

- * - *
- * @ReflectiveAccess
- * @Command(name = MyProductOutputHelperMixins.List.CMD_NAME)
- * public class SomeListCommand extends AbstractMyProductOutputCommand implements IBaseHttpRequestSupplier {
- *     @Getter @Mixin private MyProductOutputHelperMixins.List outputHelper;
- *     ...
- * }
- * 
- * - *

Here, {@code AbstractMyProductOutputCommand} would extend from {@link AbstractUnirestOutputCommand}, - * which takes care of displaying the output generated by the command, based on the configured - * {@link IUnirestOutputHelper} {@link Mixin}.

- * - *

For consistency, command implementations should use the {@link IUnirestOutputHelper} implementation - * that exactly matches the command. For example, you should only be using the {@link List} - * implementation if you are actually implementing a 'list' command; {@link List} shouldn't be used - * by commands that just generate some list of output but are not actually called 'list'. In other - * words, the command name should match the {@code CMD_NAME} provided by the {@link IUnirestOutputHelper} - * implementation (and you should always use that constant to define the {@link Command} name.

- * - * If there is no matching standard {@link IUnirestOutputHelper} implementation, there are two options: - *
    - *
  • The {@code MyProductOutputHelperMixins} class can define additional, product-specific - * {@link IUnirestOutputHelper} implementations, that extend from {@link Other} and provide - * appropriate {@code CMD_NAME} constants and other output attributes
  • - *
  • The {@link Command} implementation can use the product-specific {@link Other} implementation
  • - *
- * - * @author rsenden - */ -public class UnirestOutputHelperMixins { - @ReflectiveAccess - public static class Add extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "add"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Create extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "create"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess @Command(aliases = {"rm"}) - public static class Delete extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "delete"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess @Command(aliases = {"clear"}) - public static class DeleteAll extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "delete-all"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess @Command(name = "list", aliases = {"ls"}) - public static class List extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "list"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private OutputWriterWithQueryFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Get extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "get"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.details(); - } - - @ReflectiveAccess - public static class Set extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "set"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Update extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "update"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Enable extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "enable"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Disable extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "disable"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Start extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "start"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Pause extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "pause"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Resume extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "resume"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Cancel extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "cancel"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Upload extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "upload"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Download extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "download"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Install extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "install"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Uninstall extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "uninstall"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Import extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "import"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Export extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "export"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess - public static class Setup extends AbstractUnirestOutputHelper { - public static final String CMD_NAME = "setup"; - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - @Getter @Mixin private StandardOutputWriterFactoryMixin outputWriterFactory; - @Getter private StandardOutputConfig basicOutputConfig = StandardOutputConfig.table(); - } - - @ReflectiveAccess @Command - public static class Other extends AbstractUnirestOutputHelper { - @Getter @Setter(onMethod=@__({@Spec(Target.MIXEE)})) private CommandSpec mixee; - } -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/impl/OutputOptionsArgGroup.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/impl/OutputOptionsArgGroup.java deleted file mode 100644 index 03d22c4ae0..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/impl/OutputOptionsArgGroup.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.fortify.cli.common.output.cli.mixin.impl; - -import com.fortify.cli.common.output.writer.output.standard.IOutputOptions; -import com.fortify.cli.common.output.writer.output.standard.OutputFormatConfig; -import com.fortify.cli.common.output.writer.output.standard.OutputFormatConfigConverter; -import com.fortify.cli.common.output.writer.output.standard.VariableStoreConfig; -import com.fortify.cli.common.output.writer.output.standard.VariableStoreConfigConverter; -import com.fortify.cli.common.output.writer.output.standard.OutputFormatConfigConverter.OutputFormatIterable; - -import lombok.Getter; -import picocli.CommandLine.Option; - -public final class OutputOptionsArgGroup implements IOutputOptions { - @Option(names = {"-o", "--output"}, order=1, converter = OutputFormatConfigConverter.class, completionCandidates = OutputFormatIterable.class, paramLabel = "format[=]") - @Getter private OutputFormatConfig outputFormatConfig; - - @Option(names = {"--store"}, order=1, converter = VariableStoreConfigConverter.class, paramLabel = "variableName[=]") - @Getter private VariableStoreConfig variableStoreConfig; - - @Option(names = {"--output-to-file"}, order=7) - - @Getter private String outputFile; -} \ No newline at end of file diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/impl/QueryOptionsArgGroup.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/impl/QueryOptionsArgGroup.java deleted file mode 100644 index deb266007f..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/impl/QueryOptionsArgGroup.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.fortify.cli.common.output.cli.mixin.impl; - -import java.util.Collections; -import java.util.List; - -import com.fortify.cli.common.output.query.IOutputQueriesSupplier; -import com.fortify.cli.common.output.query.OutputQuery; -import com.fortify.cli.common.output.query.OutputQueryConverter; - -import lombok.Getter; -import picocli.CommandLine; - -public final class QueryOptionsArgGroup implements IOutputQueriesSupplier { - @CommandLine.Option(names = {"-q", "--query"}, order=1, converter = OutputQueryConverter.class, paramLabel = "") - @Getter private List outputQueries = Collections.emptyList(); -} \ No newline at end of file diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/package-info.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/package-info.java deleted file mode 100644 index 59902bba4c..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains classes used to configure command output options and associated functionality. - */ -package com.fortify.cli.common.output.cli.mixin; - diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/spi/AbstractOutputHelper.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/spi/AbstractOutputHelper.java deleted file mode 100644 index 5bfe8b3f48..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/spi/AbstractOutputHelper.java +++ /dev/null @@ -1,265 +0,0 @@ -package com.fortify.cli.common.output.cli.mixin.spi; - -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.function.UnaryOperator; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fortify.cli.common.output.cli.mixin.UnirestOutputHelperMixins; -import com.fortify.cli.common.output.cli.mixin.spi.unirest.IUnirestOutputHelper; -import com.fortify.cli.common.output.spi.IBasicOutputConfigSupplier; -import com.fortify.cli.common.output.spi.IOutputWriterFactorySupplier; -import com.fortify.cli.common.output.spi.transform.IActionCommandResultSupplier; -import com.fortify.cli.common.output.spi.transform.IInputTransformer; -import com.fortify.cli.common.output.spi.transform.IInputTransformerSupplier; -import com.fortify.cli.common.output.spi.transform.IRecordTransformer; -import com.fortify.cli.common.output.spi.transform.IRecordTransformerSupplier; -import com.fortify.cli.common.output.transform.fields.AddFieldsTransformer; -import com.fortify.cli.common.output.writer.output.IOutputWriter; -import com.fortify.cli.common.output.writer.output.IOutputWriterFactory; -import com.fortify.cli.common.output.writer.output.standard.StandardOutputConfig; - -import io.micronaut.core.annotation.ReflectiveAccess; -import picocli.CommandLine.Model.CommandSpec; - -@ReflectiveAccess -public abstract class AbstractOutputHelper implements IOutputHelperBase { - - /** - * Utility method for retrieving the command being invoked as the given - * type, returning null if the command is not an instance of the given - * type. - */ - @Override - public final T getCommandAs(Class asType) { - return getAs(getCommand(), asType); - } - - /** - * Get the {@link CommandSpec} for the command being invoked. - */ - @Override - public final CommandSpec getCommandSpec() { - CommandSpec mixee = getMixee(); - // mixee may represent an intermediate mixin rather than representing the actual command - // so we use mixee.commandLine() if available to retrieve the CommandSpec for the actual - // command. - return mixee.commandLine()==null ? mixee : mixee.commandLine().getCommandSpec(); - } - - /** - * This default implementation of {@link IUnirestOutputHelper#getBasicOutputConfig()} - * tries to retrieve a basic output configuration from the command being invoked, - * if it implements the {@link IBasicOutputConfigSupplier} interface. If the - * command doesn't implement this interface, or if the - * {@link IBasicOutputConfigSupplier#getBasicOutputConfig()} method returns null, - * this method throws an exception. Note that most concrete {@link IUnirestOutputHelper} - * implementations will override this method; this default implementation is - * mostly used for {@link UnirestOutputHelperMixins.Other}. - */ - @Override - public StandardOutputConfig getBasicOutputConfig() { - Object cmd = getCommand(); - return applyWithDefaultSupplier(cmd, - IBasicOutputConfigSupplier.class, IBasicOutputConfigSupplier::getBasicOutputConfig, - ()->{throw new IllegalStateException(cmd.getClass().getName()+" must implement IBasicOutputConfigSupplier, or use an IOutputHelper implementation that provides a basic output configuration");}); - } - - /** - * This default implementation of {@link IUnirestOutputHelper#getOutputWriterFactory()} - * tries to retrieve an {@link IOutputWriterFactory} instance from the command - * being invoked, if it implements the {@link IOutputWriterFactorySupplier} interface. - * If the command doesn't implement this interface, or if the - * {@link IOutputWriterFactorySupplier#getOutputWriterFactory()} method returns null, - * this method throws an exception. Note that most concrete {@link IUnirestOutputHelper} - * implementations will override this method; this default implementation is - * mostly used for {@link UnirestOutputHelperMixins.Other}. - */ - @Override - public IOutputWriterFactory getOutputWriterFactory() { - Object cmd = getCommand(); - return applyWithDefaultSupplier(cmd, - IOutputWriterFactorySupplier.class, IOutputWriterFactorySupplier::getOutputWriterFactory, - ()->{throw new IllegalStateException(cmd.getClass().getName()+" must implement IOutputWriterFactorySupplier, or use an IOutputHelper implementation that provides an output factory");}); - } - - /** - * This method retrieves an {@link IOutputWriterFactory} by calling the - * {@link #getOutputWriterFactory()} method, then returns the {@link IOutputWriter} - * returned by the {@link IOutputWriterFactory#createOutputWriter(StandardOutputConfig)} - * method. - * @return {@link IOutputWriter} instance retrieved from an {@link IOutputWriterFactory} instance - */ - protected final IOutputWriter createOutputWriter() { - return getOutputWriterFactory().createOutputWriter(getOutputConfig()); - } - - /** - * Utility method for retrieving the command currently being invoked. - * @return - */ - protected final Object getCommand() { - return getCommandSpec().userObject(); - } - - /** - * This method returns an {@link StandardOutputConfig} instance that is - * based on the basic output configuration returned by the - * {@link #getBasicOutputConfig()} method, with input and record - * transformers added by the {@link #addInputTransformersForCommand(StandardOutputConfig, Object)} - * and {@link #addRecordTransformersForCommand(StandardOutputConfig, Object)} methods. - * @return - */ - protected final StandardOutputConfig getOutputConfig() { - Object cmd = getCommand(); - StandardOutputConfig standardOutputConfig = getBasicOutputConfig(cmd); - addInputTransformersForCommand(standardOutputConfig, cmd); - addRecordTransformersForCommand(standardOutputConfig, cmd); - addCommandActionResultRecordTransformer(standardOutputConfig, cmd); - return standardOutputConfig; - } - - protected abstract void addRecordTransformersForCommand(StandardOutputConfig standardOutputConfig, Object cmd); - - protected abstract void addInputTransformersForCommand(StandardOutputConfig standardOutputConfig, Object cmd); - - protected abstract CommandSpec getMixee(); - - /** - * If the command being invoked implements {@link IBasicOutputConfigSupplier}, the - * {@link IBasicOutputConfigSupplier#getBasicOutputConfig()} method is called on the - * command to retrieve the command-specific basic output configuration. If the - * command doesn't implement {@link IBasicOutputConfigSupplier}, or if the - * {@link IBasicOutputConfigSupplier#getBasicOutputConfig()} method provided by the - * command returns null, the {@link #getBasicOutputConfig()} method in this class - * will be called to retrieve the basic output configuration. - * @param cmd - * @return - */ - private final StandardOutputConfig getBasicOutputConfig(Object cmd) { - return applyWithDefaultSupplier(cmd, IBasicOutputConfigSupplier.class, IBasicOutputConfigSupplier::getBasicOutputConfig, this::getBasicOutputConfig); - } - - /** - * Utility method for getting the given object as the given type, - * returning null if the given object is not an instance of the - * given type. - * @param - * @param obj - * @param asType - * @return - */ - @SuppressWarnings("unchecked") - protected static final T getAs(Object obj, Class asType) { - if ( obj!=null && asType.isAssignableFrom(obj.getClass()) ) { - return (T)obj; - } - return null; - } - - /** - * Utility method for calling the given consumer if the given object is - * an instance of the given type. - * @param - * @param obj - * @param type - * @param consumer - */ - protected static final void accept(Object obj, Class type, Consumer consumer) { - T target = getAs(obj, type); - if ( target!=null ) { consumer.accept(target); } - } - - /** - * Utility method for applying the given function on the given object and - * returning the result, if the given object is an instance of the given - * type. If the given object is not of the given type, or if the provided - * function returns null, this method returns the value returned by the - * given defaultValueSupplier if it is not null. Otherwise, this method - * returns null. - * @param - * @param - * @param obj - * @param type - * @param function - * @param defaultValueSupplier - * @return - */ - protected static final R applyWithDefaultSupplier(Object obj, Class type, Function function, Supplier defaultValueSupplier) { - T target = getAs(obj, type); - R result = target==null ? null : function.apply(target); - if ( result==null && defaultValueSupplier!=null ) { - result = defaultValueSupplier.get(); - } - return result; - } - - /** - * Utility method for applying the given function on the given object and - * returning the result, if the given object is an instance of the given - * type. If the given object is not of the given type, or if the provided - * function returns null, this method returns the provided default value. - * @param - * @param - * @param obj - * @param type - * @param function - * @param defaultValue - * @return - */ - protected static final R applyWithDefault(Object obj, Class type, Function function, R defaultValue) { - return applyWithDefaultSupplier(obj, type, function, ()->defaultValue); - } - - /** - * Utility method for applying the given function on the given object and - * returning the result, if the given object is an instance of the given - * type. If the given object is not of the given type, or if the provided - * function returns null, this method returns null. - * @param - * @param - * @param obj - * @param type - * @param function - * @return - */ - protected static final R apply(Object obj, Class type, Function function) { - return applyWithDefaultSupplier(obj, type, function, null); - } - - /** - * This method adds record transformers from the given object if it implements {@link IRecordTransformerSupplier} - * or {@link IRecordTransformer}. Usually an object would implement only one of those interfaces, - * but if both interfaces are implemented, {@link IRecordTransformerSupplier}-based transformations - * will run before {@link IRecordTransformer}-based transformations. - * @param standardOutputConfig - * @param obj - */ - protected static final void addRecordTransformersFromObject(StandardOutputConfig standardOutputConfig, Object obj) { - apply(obj, IRecordTransformerSupplier.class, s->standardOutputConfig.recordTransformer(s.getRecordTransformer())); - apply(obj, IRecordTransformer.class, s->standardOutputConfig.recordTransformer(s::transformRecord)); - } - - /** - * This method adds input transformers from the given object if it implements {@link IInputTransformerSupplier} - * or {@link IInputTransformer}. Usually an object would implement only one of those interfaces, - * but if both interfaces are implemented, {@link IInputTransformerSupplier}-based transformations - * will run before {@link IInputTransformer}-based transformations. - * @param standardOutputConfig - * @param obj - */ - protected static final void addInputTransformersFromObject(StandardOutputConfig standardOutputConfig, Object obj) { - apply(obj, IInputTransformerSupplier.class, s->standardOutputConfig.inputTransformer(s.getInputTransformer())); - apply(obj, IInputTransformer.class, s->standardOutputConfig.inputTransformer(s::transformInput)); - } - - protected static final void addCommandActionResultRecordTransformer(StandardOutputConfig standardOutputConfig, Object cmd) { - apply(cmd, IActionCommandResultSupplier.class, s->standardOutputConfig.recordTransformer(createCommandActionResultRecordTransformer(s))); - } - - private static final UnaryOperator createCommandActionResultRecordTransformer(IActionCommandResultSupplier supplier) { - return new AddFieldsTransformer(IActionCommandResultSupplier.actionFieldName, supplier.getActionCommandResult()).overwiteExisting(false)::transform; - } - -} diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/spi/IOutputHelperBase.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/spi/IOutputHelperBase.java deleted file mode 100644 index 8742d1d57d..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/spi/IOutputHelperBase.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.fortify.cli.common.output.cli.mixin.spi; - -import com.fortify.cli.common.output.spi.IBasicOutputConfigSupplier; -import com.fortify.cli.common.output.spi.IOutputWriterFactorySupplier; - -import picocli.CommandLine.Model.CommandSpec; - -public interface IOutputHelperBase extends IBasicOutputConfigSupplier, IOutputWriterFactorySupplier { - CommandSpec getCommandSpec(); - T getCommandAs(Class asType); -} \ No newline at end of file diff --git a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/spi/basic/AbstractBasicOutputHelper.java b/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/spi/basic/AbstractBasicOutputHelper.java deleted file mode 100644 index b3280af241..0000000000 --- a/fcli-common/src/main/java/com/fortify/cli/common/output/cli/mixin/spi/basic/AbstractBasicOutputHelper.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.fortify.cli.common.output.cli.mixin.spi.basic; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fortify.cli.common.output.cli.mixin.spi.AbstractOutputHelper; -import com.fortify.cli.common.output.spi.IBasicOutputConfigSupplier; -import com.fortify.cli.common.output.spi.product.IProductHelper; -import com.fortify.cli.common.output.writer.output.standard.StandardOutputConfig; - -import io.micronaut.core.annotation.ReflectiveAccess; - -@ReflectiveAccess -public abstract class AbstractBasicOutputHelper extends AbstractOutputHelper implements IBasicOutputHelper { - /** - * Write the given {@link JsonNode} using the output writer created by the - * {@link #createOutputWriter()} method. - */ - @Override - public final void write(JsonNode jsonNode) { - createOutputWriter().write(jsonNode); - } - - /** - * This method adds record transformers to the given {@link StandardOutputConfig} by - * calling the following methods, in this order: - *