Built and signed with sigstore using GitHub Actions.
\n")
- reportBuilder.WriteString("\n")
- reportBuilder.WriteString("Attribute | Details |
\n")
- reportBuilder.WriteString(fmt.Sprintf("Source repo | %s |
\n", result.Provenance.Description.Provenance.SourceRepo, result.Provenance.Description.Provenance.SourceRepo))
- reportBuilder.WriteString(fmt.Sprintf("Github Action Workflow | %s |
\n", result.Provenance.Description.Provenance.Workflow))
- reportBuilder.WriteString(fmt.Sprintf("Issuer | %s |
\n", result.Provenance.Description.Provenance.Issuer))
- reportBuilder.WriteString(fmt.Sprintf("Rekor Public Ledger | %s |
\n", result.Provenance.Description.Provenance.Transparency, result.Provenance.Description.Provenance.Transparency))
+ reportBuilder.WriteString("Built and signed with sigstore using GitHub Actions.
\n\n")
+ reportBuilder.WriteString("| | |\n")
+ reportBuilder.WriteString("| --- | --- |\n")
+ reportBuilder.WriteString(fmt.Sprintf("| Source repo | %s |\n", result.Provenance.Description.Provenance.SourceRepo))
+ reportBuilder.WriteString(fmt.Sprintf("| Github Action Workflow | %s |\n", result.Provenance.Description.Provenance.Workflow))
+ reportBuilder.WriteString(fmt.Sprintf("| Issuer | %s |\n", result.Provenance.Description.Provenance.Issuer))
+ reportBuilder.WriteString(fmt.Sprintf("| Rekor Public Ledger | %s |\n", result.Provenance.Description.Provenance.Transparency))
} else {
// need to write regular provenance info
- if result.Provenance.Description.Hp.Common > 2 {
- reportBuilder.WriteString("Proof of origin (Git Tags) ✅\n\n\n")
- reportBuilder.WriteString("This package can be mapped to the source code repository, based on the density of Git tags/releases.
\n")
- } else {
- reportBuilder.WriteString("Proof of origin (Git Tags) ❌ (failed)\n\n")
- reportBuilder.WriteString("This package could not be mapped to the source code repository based on the density of Git tags/releases.
\n")
- }
-
- reportBuilder.WriteString("\n")
- reportBuilder.WriteString("Attribute | Count |
\n")
- reportBuilder.WriteString(fmt.Sprintf("Number of versions | %.0f |
\n", result.Provenance.Description.Hp.Versions))
- reportBuilder.WriteString(fmt.Sprintf("Number of Git Tags/Releases | %.0f |
\n", result.Provenance.Description.Hp.Tags))
- reportBuilder.WriteString(fmt.Sprintf("Number of versions matched to Git Tags/Releases | %.0f |
\n", result.Provenance.Description.Hp.Common))
+ reportBuilder.WriteString("
\n\n")
+ reportBuilder.WriteString("| | |\n")
+ reportBuilder.WriteString("| --- | --- |\n")
+ reportBuilder.WriteString(fmt.Sprintf("| Number of versions | %.0f |\n", result.Provenance.Description.Hp.Versions))
+ reportBuilder.WriteString(fmt.Sprintf("| Number of Git Tags/Releases | %.0f |\n", result.Provenance.Description.Hp.Tags))
+ reportBuilder.WriteString(fmt.Sprintf("| Number of versions matched to Git Tags/Releases | %.0f |\n", result.Provenance.Description.Hp.Common))
}
- reportBuilder.WriteString("
\n")
- reportBuilder.WriteString("\nLearn more about source of origin provenance
\n")
+ reportBuilder.WriteString("\n[Learn more about source of origin provenance](https://docs.stacklok.com/trusty/understand/provenance)\n") // Ensure newlines around this link
reportBuilder.WriteString("\n")
// Include alternative packages in a Markdown table if available and if the package is deprecated, archived or malicious
if result.Alternatives.Packages != nil && len(result.Alternatives.Packages) > 0 {
reportBuilder.WriteString("\n")
- reportBuilder.WriteString("Alternative Package Recommendations 💡
\n\n")
+ reportBuilder.WriteString("Alternative Packages 💡
\n\n")
reportBuilder.WriteString("| Package | Score | Trusty Link |\n")
reportBuilder.WriteString("| ------- | ----- | ---------- |\n")
for _, alt := range result.Alternatives.Packages {
@@ -281,7 +241,7 @@ func ProcessDependency(dep string, ecosystem string, globalThreshold float64, re
shouldFail = true
}
- return reportBuilder.String(), shouldFail
+ return reportBuilder.String(), shouldFail, details
}
// fetchPackageData fetches package data from the specified request URL for a given dependency and ecosystem.
@@ -343,3 +303,51 @@ func fetchPackageData(requestURL, dep, ecosystem string, resultChan chan<- Packa
}
}()
}
+
+// BuildReport analyzes the dependencies of a PR and generates a report based on their Trusty scores.
+// It takes the following parameters:
+// - ctx: The context.Context for the function.
+// - ghClient: A pointer to a github.Client for interacting with the GitHub API.
+// - owner: The owner of the repository.
+// - repo: The name of the repository.
+// - prNumber: The number of the pull request.
+// - dependencies: A slice of strings representing the dependencies to be analyzed.
+// - ecosystem: The ecosystem of the dependencies (e.g., "npm", "pip", "maven").
+// - scoreThreshold: The threshold for Trusty scores below which a warning will be generated.
+//
+// The function generates a report and posts it as a comment on the pull request.
+func BuildReport(ctx context.Context,
+ ghClient *github.Client,
+ owner,
+ repo string,
+ prNumber int,
+ dependencies []string,
+ ecosystem string,
+ globalThreshold float64,
+ repoActivityThreshold float64,
+ authorActivityThreshold float64,
+ provenanceThreshold float64,
+ typosquattingThreshold float64,
+ failOnMalicious bool,
+ failOnDeprecated bool,
+ failOnArchived bool) {
+
+ reportContent, failAction := GenerateReportContent(dependencies, ecosystem, globalThreshold, repoActivityThreshold, authorActivityThreshold, provenanceThreshold, typosquattingThreshold,
+ failOnMalicious, failOnDeprecated, failOnArchived)
+
+ if strings.TrimSpace(reportContent) != "## 🐻 Trusty Dependency Analysis Action Report \n\n" {
+ _, _, err := ghClient.Issues.CreateComment(ctx, owner, repo, prNumber, &github.IssueComment{Body: &reportContent})
+ if err != nil {
+ log.Printf("error posting comment to PR: %v\n", err)
+ } else {
+ log.Printf("posted comment to PR: %s/%s#%d\n", owner, repo, prNumber)
+ }
+ } else {
+ log.Println("No report content to post, skipping comment.")
+ }
+
+ if failAction {
+ log.Println("Failing the GitHub Action due to dependencies not meeting the required criteria.")
+ os.Exit(1)
+ }
+}
diff --git a/pkg/trustyapi/trustyapi_test.go b/pkg/trustyapi/trustyapi_test.go
index 359e5c1..a576964 100644
--- a/pkg/trustyapi/trustyapi_test.go
+++ b/pkg/trustyapi/trustyapi_test.go
@@ -35,10 +35,13 @@ func TestProcessGoDependencies(t *testing.T) {
for i, dep := range dependencies {
log.Printf("Analyzing dependency: %s\n", dep)
- report, shouldFail := ProcessDependency(dep, ecosystem, repoActivityThreshold, authorActivityThreshold, provenanceThreshold, typosquattingThreshold, scoreThreshold, true, true, true)
+ report, shouldFail, dependencyDetails := ProcessDependency(dep, ecosystem, repoActivityThreshold, authorActivityThreshold, provenanceThreshold, typosquattingThreshold, scoreThreshold, true, true, true)
if shouldFail != expectedFail[i] {
t.Errorf("Dependency %s failed check unexpectedly, expected %v, got %v", dep, expectedFail[i], shouldFail)
}
+ if dependencyDetails.Name != dep {
+ t.Errorf("Dependency name mismatch, expected %s, got %s", dep, dependencyDetails.Name)
+ }
if dep == "github.com/Tinkoff/libvirt-exporter" {
if !strings.Contains(report, "Archived") {
t.Errorf("Expected report to contain 'Archived' for %s", dep)
@@ -57,7 +60,7 @@ func TestProcessDeprecatedDependencies(t *testing.T) {
for _, dep := range dependencies {
log.Printf("Analyzing dependency: %s\n", dep)
- report, _ := ProcessDependency(dep, ecosystem, scoreThreshold, 0.0, 0.0, 0.0, 0.0, true, true, true)
+ report, _, _ := ProcessDependency(dep, ecosystem, scoreThreshold, 0.0, 0.0, 0.0, 0.0, true, true, true)
if !strings.Contains(report, "Deprecated") {
t.Errorf("Expected report to contain 'Deprecated' for %s", dep)
}
@@ -73,7 +76,7 @@ func TestProcessMaliciousDependencies(t *testing.T) {
for _, dep := range dependencies {
log.Printf("Analyzing dependency: %s\n", dep)
- report, _ := ProcessDependency(dep, ecosystem, scoreThreshold, 0.0, 0.0, 0.0, 0.0, true, true, true)
+ report, _, _ := ProcessDependency(dep, ecosystem, scoreThreshold, 0.0, 0.0, 0.0, 0.0, true, true, true)
if !strings.Contains(report, "Malicious") {
t.Errorf("Expected report to contain 'Malicious' for %s", dep)
}
@@ -85,7 +88,7 @@ func TestProcessSigstoreProvenance(t *testing.T) {
ecosystem := "npm"
scoreThreshold := 5.0
- report, _ := ProcessDependency("sigstore", ecosystem, scoreThreshold, 0.0, 0.0, 0.0, 0.0, true, true, true)
+ report, _, _ := ProcessDependency("sigstore", ecosystem, scoreThreshold, 0.0, 0.0, 0.0, 0.0, true, true, true)
if !strings.Contains(report, "sigstore") {
t.Errorf("Expected report to contain 'sigstore'")
}
@@ -104,7 +107,7 @@ func TestProcessHistoricalProvenance(t *testing.T) {
ecosystem := "npm"
scoreThreshold := 5.0
- report, _ := ProcessDependency("openpgp", ecosystem, scoreThreshold, 0.0, 0.0, 0.0, 0.0, true, true, true)
+ report, _, _ := ProcessDependency("openpgp", ecosystem, scoreThreshold, 0.0, 0.0, 0.0, 0.0, true, true, true)
if !strings.Contains(report, "Number of versions") {
t.Errorf("Versions for historical provenance not populated")
}