Command Summaries
The Command Summaries feature records JFrog CLI command outputs into the local file system. This functionality can be used to generate a summary in the context of an entire workflow (a sequence of JFrog CLI commands) and not only in the scope of a specific command.
For example, the setup-jfrog-cli GitHub Action uses the compiled markdown to generate a comprehensive summary of the entire workflow.
Currently Supported Commands
Command summaries are organized by operation categories. Commands that perform operations in these categories will automatically generate summaries when the JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR environment variable is set.
Security Operations
Commands that perform security scanning operations generate summaries in the Security section:
jf scan
Scans files and directories for security vulnerabilities with JFrog Xray.
Example: jf scan path/to/files --watches=watch-name
jf build-scan
Scans build-info for security vulnerabilities.
Example: jf build-scan build-name build-number
jf docker scan
Scans Docker images for security vulnerabilities with JFrog Xray.
Example: jf docker scan my-image:tag --watches=watch-name
Build-Info Operations
Commands that publish build-info generate summaries in the BuildInfo section:
jf rt build-publish
Publishes build-info to Artifactory.
Example: jf rt build-publish build-name build-number
Upload Operations
Commands that upload files or artifacts generate summaries in the Upload section:
jf rt upload
Uploads files to Artifactory repositories.
Example: jf rt upload local-path repo-name/remote-path
jf docker push
Pushes Docker images to Artifactory.
Example: jf docker push my-image:tag
jf docker pull
Pulls Docker images from Artifactory. May generate summaries when configured.
Example: jf docker pull my-image:tag
Evidence Operations
Commands that collect evidence generate summaries in the Evidence section:
Evidence collection commands (specific commands depend on your JFrog Platform configuration)
Commands That Do Not Generate Summaries
Configuration and management commands do not generate command summaries, as they don't perform operational tasks that produce artifacts or scan results:
jf c(config) commands - Server configuration management (add, edit, show, remove, import, export, use)
Note: The command summary feature aggregates results from multiple commands executed in a workflow. Each command that supports summaries will contribute to its respective section in the final markdown output generated by jf generate-summary-markdown (or jf gsm).
Configuration
To use Command Summaries, you will need to set the JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR environment variable. This variable designates the directory where the data files and markdown files will be stored.
export JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR="/path/to/output/directory"Prerequisites
Before running jf generate-summary-markdown, ensure:
- The
JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIRenvironment variable is set - A JFrog Platform server is configured using
jf c add(required for generating URLs in the summary)
Warning: Files Remain After CLI Execution
The CLI does not automatically remove the files as they are designed to remain beyond a single execution. It is your responsibility to manage your pipelines and delete files as necessary. You can clear the entire directory of
JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIRthat you have configured to activate this feature.
Generating the Summary
After running commands that support summaries, generate the final markdown using:
jf generate-summary-markdownOr use the alias:
jf gsmTroubleshooting
| Error Message | Cause | Solution |
|---|---|---|
| "output directory is not specified" | JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR not set | Set the environment variable before running commands |
| "no JFrog Platform URL specified" | No server configured | Run jf c add to configure a server |
| "failed to get server URL or major version" | Invalid server configuration | Verify server URL and credentials with jf c show |
Notes for Developers
Each command execution that incorporates this feature can save data files to the file system. These files are then used to create an aggregated summary in Markdown format.
Saving data to the file system is essential because each CLI command executes in a separate context. Consequently, each command that records new data should also incorporate any existing data into the aggregated markdown. This is required because the CLI cannot determine when a command will be the last one executed in a sequence.
Warning: Files Remain After CLI Execution
The CLI does not automatically remove the files, as they are designed to remain beyond a single execution. As a result, it is your responsibility to manage your pipelines and delete files as necessary. You can clear the entire directory of
JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIRthat you have configured to activate this feature.
How to Implement
To contribute a new CLI command summary, follow these implementation guidelines and then submit a pull request.
Implement the CommandSummaryInterface
type CommandSummaryInterface interface {
GenerateMarkdownFromFiles(dataFilePaths []string) (finalMarkdown string, err error)
}Record Data During Runtime
// Check if summaries should be recorded
if !commandsummary.ShouldRecordSummary() {
return nil
}
// Initialize your implementation
commandSummary, err := commandsummary.New(&MyCommandStruct{}, "CommandName")
if err != nil {
return err
}
// Record the data
return commandSummary.Record(data)The GenerateMarkdownFromFiles function needs to process multiple data files, which are the results of previous command executions, and generate a single markdown string content. As each CLI command has its own context, we need to regenerate the entire markdown with the newly added results each time.
Data File Format
Data files are stored in JSON format. Use the helper function to unmarshal data:
commandsummary.UnmarshalFromFilePath(path, &yourDataStruct)Example Implementation
package mycommand
import (
"fmt"
"strings"
"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/commandsummary"
)
// Step 1. Implement the CommandSummaryInterface
type CommandStruct struct{}
type singleRecordedObject struct {
Name string `json:"name"`
Status string `json:"status"`
}
func (cs *CommandStruct) GenerateMarkdownFromFiles(dataFilePaths []string) (markdown string, err error) {
// Aggregate all the results into a slice
var recordedObjects []*singleRecordedObject
for _, path := range dataFilePaths {
var singleObject singleRecordedObject
if err = commandsummary.UnmarshalFromFilePath(path, &singleObject); err != nil {
return "", err
}
recordedObjects = append(recordedObjects, &singleObject)
}
// Create markdown
var results strings.Builder
results.WriteString("# Command Summary\n\n")
results.WriteString("| Name | Status |\n")
results.WriteString("|------|--------|\n")
for _, obj := range recordedObjects {
results.WriteString(fmt.Sprintf("| %s | %s |\n", obj.Name, obj.Status))
}
markdown = results.String()
return
}
// Step 2. Record data during runtime
func recordCommandSummary(data any) (err error) {
if !commandsummary.ShouldRecordSummary() {
return nil
}
commandSummaryImplementation, err := commandsummary.New(&CommandStruct{}, "CommandName")
if err != nil {
return err
}
return commandSummaryImplementation.Record(data)
}How Does It Work?
Each command that implements the CommandSummaryInterface will have its own subdirectory inside the JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR/jfrog-command-summary directory.
Every subdirectory will house data files (in JSON format), each one corresponding to a command recording, along with a markdown file that has been created from all the data files. The function you implement is responsible for processing all the data files within its subdirectory and generating a markdown string.
The directory structure looks like this:
$JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR/
└── jfrog-command-summary/
│
├── security/
│ ├── docker-scan/
│ │ └── scan-result-001.json
│ ├── build-scan/
│ │ └── scan-result-001.json
│ ├── binaries-scans/
│ │ └── scan-result-001.json
│ ├── sarif-reports/
│ │ ├── report-001.sarif
│ │ └── final.sarif
│ └── markdown.md
│
├── build-info/
│ ├── build-001.json
│ ├── build-002.json
│ └── markdown.md
│
├── upload/
│ ├── upload-001.json
│ ├── upload-002.json
│ └── markdown.md
│
├── evidence/
│ ├── evidence-001.json
│ └── markdown.md
│
└── markdown.md ← Final merged summary from all sections
Section Index Types
For security-related summaries, the following index types are available:
| Index Constant | Directory Name | Used By |
|---|---|---|
BinariesScan | binaries-scans | jf scan |
BuildScan | build-scan | jf build-scan |
DockerScan | docker-scan | jf docker scan |
SarifReport | sarif-reports | All security scans |
Complete Example Workflow
#!/bin/bash
# 1. Set the output directory
export JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR="/tmp/jfrog-summaries"
mkdir -p "$JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR"
# 2. Configure server (if not already done)
jf c add my-server \
--url="https://my-instance.jfrog.io" \
--access-token="$JFROG_ACCESS_TOKEN" \
--interactive=false
jf c use my-server
# 3. Run commands that generate summaries
jf rt upload "build/*.jar" my-repo/libs/
jf rt build-publish my-build 1
# 4. Run security scans
jf docker scan my-image:tag
jf build-scan my-build 1
# 5. Generate the summary markdown
jf gsm
# 6. View the results
echo "=== Summary Generated ==="
cat "$JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR/jfrog-command-summary/markdown.md"
# 7. Cleanup (optional)
# rm -rf "$JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR"GitHub Actions Integration
name: Build and Scan with Summary
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JFrog CLI
uses: jfrog/setup-jfrog-cli@v4
with:
version: latest
env:
JF_URL: ${{ secrets.JF_URL }}
JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }}
- name: Enable Command Summaries
run: |
echo "JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR=${{ runner.temp }}/jfrog-summary" >> $GITHUB_ENV
mkdir -p "${{ runner.temp }}/jfrog-summary"
- name: Build and Upload
run: |
jf rt upload "build/*.jar" my-repo/
jf rt build-publish ${{ github.repository }} ${{ github.run_number }}
- name: Security Scan
run: jf build-scan ${{ github.repository }} ${{ github.run_number }}
- name: Generate Summary
run: jf gsm
- name: Display Summary
run: |
echo "## JFrog CLI Summary" >> $GITHUB_STEP_SUMMARY
cat "$JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR/jfrog-command-summary/markdown.md" >> $GITHUB_STEP_SUMMARY
- name: Upload Summary as Artifact
uses: actions/upload-artifact@v4
with:
name: jfrog-summary
path: ${{ runner.temp }}/jfrog-summary/Updated 7 days ago
