From 4ade11ee691e4d8839acb4101b4c26ab8955791e Mon Sep 17 00:00:00 2001 From: Michael Sverdlov Date: Tue, 24 Dec 2024 15:56:20 +0200 Subject: [PATCH 1/2] Improve upload archive progress bar (#1318) Signed-off-by: Michael Sverdlov --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 940b45df5..01ee183f8 100644 --- a/go.mod +++ b/go.mod @@ -11,11 +11,11 @@ require ( github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 github.com/google/uuid v1.6.0 github.com/gookit/color v1.5.4 - github.com/jedib0t/go-pretty/v6 v6.6.3 + github.com/jedib0t/go-pretty/v6 v6.6.5 github.com/jfrog/build-info-go v1.10.7 github.com/jfrog/gofrog v1.7.6 github.com/jfrog/jfrog-client-go v1.48.5 - github.com/magiconair/properties v1.8.7 + github.com/magiconair/properties v1.8.9 github.com/manifoldco/promptui v0.9.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/spf13/viper v1.19.0 @@ -89,14 +89,14 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/tools v0.27.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-client-go => github.com/eyalbe4/jfrog-client-go v1.28.1-0.20241223165051-2717483395bc +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20241223175448-88f1089d0694 // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go v1.8.9-0.20241121100855-e7a75ceee2bd diff --git a/go.sum b/go.sum index 4ad9a0961..be2367cd0 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,6 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/eyalbe4/jfrog-client-go v1.28.1-0.20241223165051-2717483395bc h1:VLvI2P75Fc8iJVrydKEMcmeBCcKeUHmB7lLEm5P8aYU= -github.com/eyalbe4/jfrog-client-go v1.28.1-0.20241223165051-2717483395bc/go.mod h1:2ySOMva54L3EYYIlCBYBTcTgqfrrQ19gtpA/MWfA/ec= github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= github.com/forPelevin/gomoji v1.2.0/go.mod h1:8+Z3KNGkdslmeGZBC3tCrwMrcPy5GRzAD+gL9NAwMXg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -87,14 +85,16 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jedib0t/go-pretty/v6 v6.6.3 h1:nGqgS0tgIO1Hto47HSaaK4ac/I/Bu7usmdD3qvs0WvM= -github.com/jedib0t/go-pretty/v6 v6.6.3/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= +github.com/jedib0t/go-pretty/v6 v6.6.5 h1:9PgMJOVBedpgYLI56jQRJYqngxYAAzfEUua+3NgSqAo= +github.com/jedib0t/go-pretty/v6 v6.6.5/go.mod h1:Uq/HrbhuFty5WSVNfjpQQe47x16RwVGXIveNGEyGtHs= github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI= github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw= github.com/jfrog/build-info-go v1.10.7 h1:10NVHYg0193gJpQft+S4WQfvYMtj5jlwwhJRvkFJtBE= github.com/jfrog/build-info-go v1.10.7/go.mod h1:JcISnovFXKx3wWf3p1fcMmlPdt6adxScXvoJN4WXqIE= github.com/jfrog/gofrog v1.7.6 h1:QmfAiRzVyaI7JYGsB7cxfAJePAZTzFz0gRWZSE27c6s= github.com/jfrog/gofrog v1.7.6/go.mod h1:ntr1txqNOZtHplmaNd7rS4f8jpA5Apx8em70oYEe7+4= +github.com/jfrog/jfrog-client-go v1.28.1-0.20241223175448-88f1089d0694 h1:1EP8yAhB+SGTXhfGa/w4S748S12qJKXU4u/yiAJxnX0= +github.com/jfrog/jfrog-client-go v1.28.1-0.20241223175448-88f1089d0694/go.mod h1:2ySOMva54L3EYYIlCBYBTcTgqfrrQ19gtpA/MWfA/ec= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -112,8 +112,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -229,8 +229,8 @@ golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1 golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= From 8cd365a2093c16b23f27e3dcd39e0677c9155c61 Mon Sep 17 00:00:00 2001 From: Eyal Ben Moshe Date: Wed, 25 Dec 2024 16:29:42 +0200 Subject: [PATCH 2/2] Internal - Modify the Markdown Summary Artifactory links (#1319) --- .../utils/commandsummary/buildinfosummary.go | 133 ++++++++++++------ .../commandsummary/buildinfosummary_test.go | 63 ++++++--- .../utils/commandsummary/uploadsummary.go | 25 ++-- artifactory/utils/commandsummary/utils.go | 47 ++++++- .../utils/commandsummary/utils_test.go | 84 ++++++++++- .../extended/build-info-table.md | 2 +- .../extended/docker-image-module.md | 2 +- .../extended/generic-module.md | 2 +- .../extended/maven-module.md | 2 +- .../extended/maven-nested-module.md | 4 +- .../extended/multiarch-docker-image.md | 2 +- 11 files changed, 279 insertions(+), 87 deletions(-) diff --git a/artifactory/utils/commandsummary/buildinfosummary.go b/artifactory/utils/commandsummary/buildinfosummary.go index 806cf59a6..8071b7694 100644 --- a/artifactory/utils/commandsummary/buildinfosummary.go +++ b/artifactory/utils/commandsummary/buildinfosummary.go @@ -2,13 +2,14 @@ package commandsummary import ( "fmt" + "net/url" + "path" + "strings" + buildInfo "github.com/jfrog/build-info-go/entities" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/container" "github.com/jfrog/jfrog-client-go/utils/log" - "net/url" - "path" - "strings" ) const ( @@ -62,8 +63,14 @@ func (bis *BuildInfoSummary) GenerateMarkdownFromFiles(dataFilePaths []string) ( return } - buildInfoTableMarkdown := bis.buildInfoTable(builds) - publishedModulesMarkdown := bis.buildInfoModules(builds) + buildInfoTableMarkdown, err := bis.buildInfoTable(builds) + if err != nil { + return "", err + } + publishedModulesMarkdown, err := bis.buildInfoModules(builds) + if err != nil { + return "", err + } if publishedModulesMarkdown != "" { publishedModulesMarkdown = WrapCollapsableMarkdown(modulesTitle, publishedModulesMarkdown, 2) } @@ -73,61 +80,75 @@ func (bis *BuildInfoSummary) GenerateMarkdownFromFiles(dataFilePaths []string) ( } // Create a table with published builds and possible scan results. -func (bis *BuildInfoSummary) buildInfoTable(builds []*buildInfo.BuildInfo) string { +func (bis *BuildInfoSummary) buildInfoTable(builds []*buildInfo.BuildInfo) (string, error) { var tableBuilder strings.Builder tableBuilder.WriteString(getBuildInfoTableHeader()) for _, build := range builds { - appendBuildInfoRow(&tableBuilder, build) + if err := appendBuildInfoRow(&tableBuilder, build); err != nil { + return "", err + } } tableBuilder.WriteString("\n\n") - return tableBuilder.String() + return tableBuilder.String(), nil } // Generates a view for published modules within the build. // Modules are displayed as tables if they are scannable via CLI command, // otherwise, they are shown as an artifact tree. -func (bis *BuildInfoSummary) buildInfoModules(builds []*buildInfo.BuildInfo) string { +func (bis *BuildInfoSummary) buildInfoModules(builds []*buildInfo.BuildInfo) (string, error) { var markdownBuilder strings.Builder markdownBuilder.WriteString("\n\n

Published Modules

\n\n") var shouldGenerate bool for _, build := range builds { supportedModules := filterModules(build.Modules...) - if modulesMarkdown := bis.generateModulesMarkdown(supportedModules...); modulesMarkdown != "" { + modulesMarkdown, err := bis.generateModulesMarkdown(supportedModules...) + if err != nil { + return "", err + } + if modulesMarkdown != "" { markdownBuilder.WriteString(modulesMarkdown) shouldGenerate = true } } if !shouldGenerate { - return "" + return "", nil } - return markdownBuilder.String() + return markdownBuilder.String(), nil } -func (bis *BuildInfoSummary) generateModulesMarkdown(modules ...buildInfo.Module) string { +func (bis *BuildInfoSummary) generateModulesMarkdown(modules ...buildInfo.Module) (string, error) { var modulesMarkdown strings.Builder // Modules could include nested modules inside of them // Group the modules by their root module ID // If a module has no root, it is considered as a root module itself. groupedModuleMap := groupModules(modules) if len(groupedModuleMap) == 0 { - return "" + return "", nil } for rootModuleID, subModules := range groupedModuleMap { if len(subModules) == 0 { continue } if !scannableModuleType[subModules[0].Type] { - modulesMarkdown.WriteString(bis.generateModuleArtifactTree(rootModuleID, subModules)) + tree, err := bis.generateModuleArtifactTree(rootModuleID, subModules) + if err != nil { + return "", err + } + modulesMarkdown.WriteString(tree) } else { - modulesMarkdown.WriteString(bis.generateModuleTableView(rootModuleID, subModules)) + view, err := bis.generateModuleTableView(rootModuleID, subModules) + if err != nil { + return "", err + } + modulesMarkdown.WriteString(view) } } - return modulesMarkdown.String() + return modulesMarkdown.String(), nil } -func (bis *BuildInfoSummary) generateModuleArtifactTree(rootModuleID string, nestedModules []buildInfo.Module) string { +func (bis *BuildInfoSummary) generateModuleArtifactTree(rootModuleID string, nestedModules []buildInfo.Module) (string, error) { if len(nestedModules) == 0 { - return "" + return "", nil } var markdownBuilder strings.Builder isMultiModule := len(nestedModules) > 1 @@ -140,26 +161,33 @@ func (bis *BuildInfoSummary) generateModuleArtifactTree(rootModuleID string, nes if isMultiModule && rootModuleID == module.Id { continue } - markdownBuilder.WriteString(fmt.Sprintf("\n\n
%s
\n\n", bis.generateModuleArtifactsTree(&module, isMultiModule))) + tree, err := bis.generateModuleArtifactsTree(&module, isMultiModule) + if err != nil { + return "", err + } + markdownBuilder.WriteString(fmt.Sprintf("\n\n
%s
\n\n", tree)) } - return markdownBuilder.String() + return markdownBuilder.String(), nil } -func (bis *BuildInfoSummary) generateModuleTableView(rootModuleID string, subModules []buildInfo.Module) string { +func (bis *BuildInfoSummary) generateModuleTableView(rootModuleID string, subModules []buildInfo.Module) (string, error) { var markdownBuilder strings.Builder markdownBuilder.WriteString(generateModuleHeader(rootModuleID)) markdownBuilder.WriteString(generateModuleTableHeader()) isMultiModule := len(subModules) > 1 - nestedModuleMarkdownTree := bis.generateTableModuleMarkdown(subModules, rootModuleID, isMultiModule) + nestedModuleMarkdownTree, err := bis.generateTableModuleMarkdown(subModules, rootModuleID, isMultiModule) + if err != nil { + return "", err + } scanResult := getScanResults(extractDockerImageTag(subModules)) markdownBuilder.WriteString(generateTableRow(nestedModuleMarkdownTree, scanResult)) - return markdownBuilder.String() + return markdownBuilder.String(), nil } -func (bis *BuildInfoSummary) generateTableModuleMarkdown(nestedModules []buildInfo.Module, parentModuleID string, isMultiModule bool) string { +func (bis *BuildInfoSummary) generateTableModuleMarkdown(nestedModules []buildInfo.Module, parentModuleID string, isMultiModule bool) (string, error) { var nestedModuleMarkdownTree strings.Builder if len(nestedModules) == 0 { - return "" + return "", nil } if !StaticMarkdownConfig.IsExtendedSummary() { @@ -174,19 +202,26 @@ func (bis *BuildInfoSummary) generateTableModuleMarkdown(nestedModules []buildIn if isMultiModule && parentModuleID == module.Id { continue } - nestedModuleMarkdownTree.WriteString(bis.generateModuleArtifactsTree(&module, isMultiModule)) + tree, err := bis.generateModuleArtifactsTree(&module, isMultiModule) + if err != nil { + return "", err + } + nestedModuleMarkdownTree.WriteString(tree) } nestedModuleMarkdownTree.WriteString(appendSpacesToTableColumn("")) nestedModuleMarkdownTree.WriteString("") - return nestedModuleMarkdownTree.String() + return nestedModuleMarkdownTree.String(), nil } -func (bis *BuildInfoSummary) generateModuleArtifactsTree(module *buildInfo.Module, shouldCollapseArtifactsTree bool) string { - artifactsTree := bis.createArtifactsTree(module) +func (bis *BuildInfoSummary) generateModuleArtifactsTree(module *buildInfo.Module, shouldCollapseArtifactsTree bool) (string, error) { + artifactsTree, err := bis.createArtifactsTree(module) + if err != nil { + return "", err + } if shouldCollapseArtifactsTree { - return bis.generateModuleCollapsibleSection(module, artifactsTree) + return bis.generateModuleCollapsibleSection(module, artifactsTree), nil } - return artifactsTree + return artifactsTree, nil } func (bis *BuildInfoSummary) generateModuleCollapsibleSection(module *buildInfo.Module, sectionContent string) string { @@ -198,12 +233,16 @@ func (bis *BuildInfoSummary) generateModuleCollapsibleSection(module *buildInfo. } } -func (bis *BuildInfoSummary) createArtifactsTree(module *buildInfo.Module) string { +func (bis *BuildInfoSummary) createArtifactsTree(module *buildInfo.Module) (string, error) { artifactsTree := utils.NewFileTree() for _, artifact := range module.Artifacts { var artifactUrlInArtifactory string + var err error if StaticMarkdownConfig.IsExtendedSummary() { - artifactUrlInArtifactory = generateArtifactUrl(artifact) + artifactUrlInArtifactory, err = generateArtifactUrl(artifact, *module) + if err != nil { + return "", err + } } if artifact.OriginalDeploymentRepo == "" { artifact.OriginalDeploymentRepo = " " @@ -211,17 +250,24 @@ func (bis *BuildInfoSummary) createArtifactsTree(module *buildInfo.Module) strin artifactTreePath := path.Join(artifact.OriginalDeploymentRepo, artifact.Path) artifactsTree.AddFile(artifactTreePath, artifactUrlInArtifactory) if artifactsTree.IsTreeExceedsMax() { - return "" + return "", nil } } - return artifactsTree.String() + return artifactsTree.String(), nil } -func generateArtifactUrl(artifact buildInfo.Artifact) string { +func generateArtifactUrl(artifact buildInfo.Artifact, module buildInfo.Module) (string, error) { if strings.TrimSpace(artifact.OriginalDeploymentRepo) == "" { - return "" + return "", nil + } + var section summarySection + + if module.Type == buildInfo.Generic { + section = artifactsSection + } else { + section = packagesSection } - return GenerateArtifactUrl(path.Join(artifact.OriginalDeploymentRepo, artifact.Path)) + return GenerateArtifactUrl(path.Join(artifact.OriginalDeploymentRepo, artifact.Path), section) } func groupModules(modules []buildInfo.Module) map[string][]buildInfo.Module { @@ -280,16 +326,21 @@ func appendSpacesToTableColumn(str string) string { return str } -func appendBuildInfoRow(tableBuilder *strings.Builder, build *buildInfo.BuildInfo) { +func appendBuildInfoRow(tableBuilder *strings.Builder, build *buildInfo.BuildInfo) error { buildName := build.Name + " " + build.Number buildScanResult := getScanResults(buildName) if StaticMarkdownConfig.IsExtendedSummary() { - tableBuilder.WriteString(fmt.Sprintf("| [%s](%s) %s | %s | %s | \n", buildName, build.BuildUrl, appendSpacesToTableColumn(""), appendSpacesToTableColumn(buildScanResult.GetViolations()), appendSpacesToTableColumn(buildScanResult.GetVulnerabilities()))) + buildInfoUrl, err := addGitHubTrackingToUrl(build.BuildUrl, buildInfoSection) + if err != nil { + return err + } + tableBuilder.WriteString(fmt.Sprintf("| [%s](%s) %s | %s | %s | \n", buildName, buildInfoUrl, appendSpacesToTableColumn(""), appendSpacesToTableColumn(buildScanResult.GetViolations()), appendSpacesToTableColumn(buildScanResult.GetVulnerabilities()))) } else { upgradeMessage := fmt.Sprintf(basicSummaryUpgradeNotice, StaticMarkdownConfig.GetExtendedSummaryLangPage()) buildName = fmt.Sprintf(" %s %s", upgradeMessage, buildName) tableBuilder.WriteString(fmt.Sprintf("| %s %s | %s | %s |\n", fitInsideMarkdownTable(buildName), appendSpacesToTableColumn(""), appendSpacesToTableColumn(buildScanResult.GetViolations()), appendSpacesToTableColumn(buildScanResult.GetVulnerabilities()))) } + return nil } func getBuildInfoTableHeader() string { diff --git a/artifactory/utils/commandsummary/buildinfosummary_test.go b/artifactory/utils/commandsummary/buildinfosummary_test.go index 7b20f072e..a533cde2a 100644 --- a/artifactory/utils/commandsummary/buildinfosummary_test.go +++ b/artifactory/utils/commandsummary/buildinfosummary_test.go @@ -1,15 +1,16 @@ package commandsummary import ( - buildInfo "github.com/jfrog/build-info-go/entities" - buildinfo "github.com/jfrog/build-info-go/entities" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/stretchr/testify/assert" "os" "path/filepath" "regexp" "strings" "testing" + + buildInfo "github.com/jfrog/build-info-go/entities" + buildinfo "github.com/jfrog/build-info-go/entities" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/stretchr/testify/assert" ) const ( @@ -58,6 +59,8 @@ func prepareBuildInfoTest() (*BuildInfoSummary, func()) { return buildInfoSummary, cleanup } +const buildUrl = "http://myJFrogPlatform/builds/buildName/123?gh_job_id=JFrog+CLI+Core+Tests&gh_section=buildInfo" + func TestBuildInfoTable(t *testing.T) { buildInfoSummary, cleanUp := prepareBuildInfoTest() defer func() { @@ -68,17 +71,19 @@ func TestBuildInfoTable(t *testing.T) { Name: "buildName", Number: "123", Started: "2024-05-05T12:47:20.803+0300", - BuildUrl: "http://myJFrogPlatform/builds/buildName/123", + BuildUrl: buildUrl, }, } t.Run("Extended Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(true) - res := buildInfoSummary.buildInfoTable(builds) + res, err := buildInfoSummary.buildInfoTable(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, buildInfoTable), res) }) t.Run("Basic Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(false) - res := buildInfoSummary.buildInfoTable(builds) + res, err := buildInfoSummary.buildInfoTable(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, buildInfoTable), res) }) } @@ -93,7 +98,7 @@ func TestBuildInfoModulesMaven(t *testing.T) { Name: "buildName", Number: "123", Started: "2024-05-05T12:47:20.803+0300", - BuildUrl: "http://myJFrogPlatform/builds/buildName/123", + BuildUrl: buildUrl, Modules: []buildinfo.Module{ { Id: "maven", @@ -113,12 +118,14 @@ func TestBuildInfoModulesMaven(t *testing.T) { t.Run("Extended Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(true) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, mavenModule), res) }) t.Run("Basic Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(false) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, mavenModule), res) }) } @@ -133,7 +140,7 @@ func TestBuildInfoModulesMavenWithSubModules(t *testing.T) { Name: "buildName", Number: "123", Started: "2024-05-05T12:47:20.803+0300", - BuildUrl: "http://myJFrogPlatform/builds/buildName/123", + BuildUrl: buildUrl, Modules: []buildinfo.Module{ { Id: "maven", @@ -179,12 +186,14 @@ func TestBuildInfoModulesMavenWithSubModules(t *testing.T) { t.Run("Extended Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(true) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, mavenNestedModule), res) }) t.Run("Basic Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(false) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, mavenNestedModule), res) }) } @@ -199,7 +208,7 @@ func TestBuildInfoModulesGradle(t *testing.T) { Name: "buildName", Number: "123", Started: "2024-05-05T12:47:20.803+0300", - BuildUrl: "http://myJFrogPlatform/builds/buildName/123", + BuildUrl: buildUrl, Modules: []buildinfo.Module{ { Id: "gradle", @@ -218,12 +227,14 @@ func TestBuildInfoModulesGradle(t *testing.T) { t.Run("Extended Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(true) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) assert.Empty(t, res) }) t.Run("Basic Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(false) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) assert.Empty(t, res) }) } @@ -238,7 +249,7 @@ func TestBuildInfoModulesGeneric(t *testing.T) { Name: "buildName", Number: "123", Started: "2024-05-05T12:47:20.803+0300", - BuildUrl: "http://myJFrogPlatform/builds/buildName/123", + BuildUrl: buildUrl, Modules: []buildinfo.Module{ { Id: "generic", @@ -255,12 +266,14 @@ func TestBuildInfoModulesGeneric(t *testing.T) { t.Run("Extended Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(true) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, genericModule), res) }) t.Run("Basic Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(false) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, genericModule), res) }) } @@ -305,12 +318,14 @@ func TestDockerModule(t *testing.T) { t.Run("Extended Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(true) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, dockerImageModule), res) }) t.Run("Basic Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(false) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, dockerImageModule), res) }) @@ -380,12 +395,14 @@ func TestDockerMultiArchModule(t *testing.T) { t.Run("Extended Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(true) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, dockerMultiArchModule), res) }) t.Run("Basic Summary", func(t *testing.T) { StaticMarkdownConfig.setExtendedSummary(false) - res := buildInfoSummary.buildInfoModules(builds) + res, err := buildInfoSummary.buildInfoModules(builds) + assert.NoError(t, err) testMarkdownOutput(t, getTestDataFile(t, dockerMultiArchModule), res) }) diff --git a/artifactory/utils/commandsummary/uploadsummary.go b/artifactory/utils/commandsummary/uploadsummary.go index cccaca2d8..542e96b48 100644 --- a/artifactory/utils/commandsummary/uploadsummary.go +++ b/artifactory/utils/commandsummary/uploadsummary.go @@ -2,6 +2,7 @@ package commandsummary import ( "fmt" + "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" ) @@ -33,8 +34,12 @@ func (us *UploadSummary) GenerateMarkdownFromFiles(dataFilePaths []string) (mark if err = us.loadResults(dataFilePaths); err != nil { return } + md, err := us.generateFileTreeMarkdown() + if err != nil { + return + } // Wrap the Markdown in a
 tags to preserve spaces
-	markdown = fmt.Sprintf("\n
\n\n\n%s
\n\n", us.generateFileTreeMarkdown()) + markdown = fmt.Sprintf("\n
\n\n\n%s
\n\n", md) return } @@ -51,21 +56,25 @@ func (us *UploadSummary) loadResults(filePaths []string) error { return nil } -func (us *UploadSummary) generateFileTreeMarkdown() string { +func (us *UploadSummary) generateFileTreeMarkdown() (string, error) { us.uploadTree = utils.NewFileTree() for _, uploadResult := range us.uploadedArtifacts.Results { - us.uploadTree.AddFile(uploadResult.TargetPath, us.buildUiUrl(uploadResult.TargetPath)) + buildUiUrl, err := us.buildUiUrl(uploadResult.TargetPath) + if err != nil { + return "", err + } + us.uploadTree.AddFile(uploadResult.TargetPath, buildUiUrl) if us.uploadTree.IsTreeExceedsMax() { - return "" + return "", nil } } - return us.uploadTree.String() + return us.uploadTree.String(), nil } -func (us *UploadSummary) buildUiUrl(targetPath string) string { +func (us *UploadSummary) buildUiUrl(targetPath string) (string, error) { // Only build URL if extended summary is enabled if StaticMarkdownConfig.IsExtendedSummary() { - return GenerateArtifactUrl(targetPath) + return GenerateArtifactUrl(targetPath, artifactsSection) } - return "" + return "", nil } diff --git a/artifactory/utils/commandsummary/utils.go b/artifactory/utils/commandsummary/utils.go index fa92f98af..f9e51b3ec 100644 --- a/artifactory/utils/commandsummary/utils.go +++ b/artifactory/utils/commandsummary/utils.go @@ -4,6 +4,10 @@ import ( "crypto/sha1" // #nosec G505 - This is only used for encoding, not security. "encoding/hex" "fmt" + "net/url" + "os" + + "github.com/jfrog/jfrog-client-go/utils/errorutils" ) const ( @@ -12,11 +16,14 @@ const ( artifactoryDockerPackagesUiFormat = "%s/ui/packages/docker:%s/sha256__%s" ) -func GenerateArtifactUrl(pathInRt string) string { +func GenerateArtifactUrl(pathInRt string, section summarySection) (url string, err error) { if StaticMarkdownConfig.GetPlatformMajorVersion() == 6 { - return fmt.Sprintf(artifactory6UiFormat, StaticMarkdownConfig.GetPlatformUrl(), pathInRt) + url = fmt.Sprintf(artifactory6UiFormat, StaticMarkdownConfig.GetPlatformUrl(), pathInRt) + } else { + url = fmt.Sprintf(artifactory7UiFormat, StaticMarkdownConfig.GetPlatformUrl(), pathInRt) } - return fmt.Sprintf(artifactory7UiFormat, StaticMarkdownConfig.GetPlatformUrl(), pathInRt) + url, err = addGitHubTrackingToUrl(url, section) + return } func WrapCollapsableMarkdown(title, markdown string, headerSize int) string { @@ -33,3 +40,37 @@ func fileNameToSha1(fileName string) string { hashBytes := hash.Sum(nil) return hex.EncodeToString(hashBytes) } + +type summarySection string + +const ( + artifactsSection summarySection = "artifacts" + packagesSection summarySection = "packages" + buildInfoSection summarySection = "buildInfo" +) + +// addGitHubTrackingToUrl adds GitHub-related query parameters to a given URL if the GITHUB_WORKFLOW environment variable is set. +func addGitHubTrackingToUrl(urlStr string, section summarySection) (string, error) { + // Check if GITHUB_WORKFLOW environment variable is set + githubWorkflow := os.Getenv("GITHUB_WORKFLOW") + if githubWorkflow == "" { + // Return the original URL if the variable is not set + return urlStr, nil + } + + // Parse the input URL + parsedUrl, err := url.Parse(urlStr) + if errorutils.CheckError(err) != nil { + // Return an error if the URL is invalid + return "", err + } + + // Get the query parameters and add the GitHub tracking parameters + queryParams := parsedUrl.Query() + queryParams.Set("gh_job_id", githubWorkflow) + queryParams.Set("gh_section", string(section)) + parsedUrl.RawQuery = queryParams.Encode() + + // Return the modified URL + return parsedUrl.String(), nil +} diff --git a/artifactory/utils/commandsummary/utils_test.go b/artifactory/utils/commandsummary/utils_test.go index 364239afc..5473df4d0 100644 --- a/artifactory/utils/commandsummary/utils_test.go +++ b/artifactory/utils/commandsummary/utils_test.go @@ -1,8 +1,10 @@ package commandsummary import ( - "github.com/stretchr/testify/assert" "testing" + + testsutils "github.com/jfrog/jfrog-client-go/utils/tests" + "github.com/stretchr/testify/assert" ) const ( @@ -17,15 +19,16 @@ func TestGenerateArtifactUrl(t *testing.T) { majorVersion int expected string }{ - {"artifactory 7 without project", "", 7, "https://myplatform.com/ui/repos/tree/General/repo/path/file?clearFilter=true"}, - {"artifactory 7 with project", "proj", 7, "https://myplatform.com/ui/repos/tree/General/repo/path/file?clearFilter=true"}, - {"artifactory 6 without project", "", 6, "https://myplatform.com/artifactory/webapp/#/artifacts/browse/tree/General/repo/path/file"}, + {"artifactory 7 without project", "", 7, "https://myplatform.com/ui/repos/tree/General/repo/path/file?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=test-section"}, + {"artifactory 7 with project", "proj", 7, "https://myplatform.com/ui/repos/tree/General/repo/path/file?clearFilter=true&gh_job_id=JFrog+CLI+Core+Tests&gh_section=test-section"}, + {"artifactory 6 without project", "", 6, "https://myplatform.com/artifactory/webapp/?gh_job_id=JFrog+CLI+Core+Tests&gh_section=test-section#/artifacts/browse/tree/General/repo/path/file"}, } StaticMarkdownConfig.setPlatformUrl(testPlatformUrl) for _, testCase := range cases { t.Run(testCase.testName, func(t *testing.T) { StaticMarkdownConfig.setPlatformMajorVersion(testCase.majorVersion) - artifactUrl := GenerateArtifactUrl(fullPath) + artifactUrl, err := GenerateArtifactUrl(fullPath, "test-section") + assert.NoError(t, err) assert.Equal(t, testCase.expected, artifactUrl) }) } @@ -45,3 +48,74 @@ func TestFileNameToSha1(t *testing.T) { assert.Equal(t, test.expected, hash) } } + +func TestAddGitHubTrackingToUrl(t *testing.T) { + tests := []struct { + name string + url string + section summarySection + envValue string + expectedResult string + expectsError bool + }{ + { + "No GITHUB_WORKFLOW set", + "https://example.com/path", + buildInfoSection, + "", + "https://example.com/path", + false, + }, + { + "GITHUB_WORKFLOW set", + "https://example.com/path", + buildInfoSection, + "workflow123", + "https://example.com/path?gh_job_id=workflow123&gh_section=buildInfo", + false, + }, + { + "Invalid URL", + ":invalid-url", + buildInfoSection, + "workflow123", + "", + true, + }, + { + "URL with existing query parameters", + "https://example.com/path?existing_param=value", + packagesSection, + "workflow123", + "https://example.com/path?existing_param=value&gh_job_id=workflow123&gh_section=packages", + false, + }, + { + "GITHUB_WORKFLOW with special characters", + "https://example.com/path", + artifactsSection, + "workflow with spaces & special?characters", + "https://example.com/path?gh_job_id=workflow+with+spaces+%26+special%3Fcharacters&gh_section=artifacts", + false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Set up the environment variable + + cleanup := testsutils.SetEnvWithCallbackAndAssert(t, "GITHUB_WORKFLOW", test.envValue) + defer cleanup() + + // Call the function + result, err := addGitHubTrackingToUrl(test.url, test.section) + + if test.expectsError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, test.expectedResult, result) + } + }) + } +} diff --git a/artifactory/utils/testdata/command_summaries/extended/build-info-table.md b/artifactory/utils/testdata/command_summaries/extended/build-info-table.md index c8b411b49..5d0bb212c 100644 --- a/artifactory/utils/testdata/command_summaries/extended/build-info-table.md +++ b/artifactory/utils/testdata/command_summaries/extended/build-info-table.md @@ -2,6 +2,6 @@ | Build Info| Security Violations| Security Issues| |:---------|:------------|:------------| -| [buildName 123](http://myJFrogPlatform/builds/buildName/123) | Not scanned| Not scanned| +| [buildName 123](http://myJFrogPlatform/builds/buildName/123?gh_job_id=JFrog+CLI+Core+Tests&gh_section=buildInfo) | Not scanned| Not scanned| diff --git a/artifactory/utils/testdata/command_summaries/extended/docker-image-module.md b/artifactory/utils/testdata/command_summaries/extended/docker-image-module.md index 1227569a1..2300c23c0 100644 --- a/artifactory/utils/testdata/command_summaries/extended/docker-image-module.md +++ b/artifactory/utils/testdata/command_summaries/extended/docker-image-module.md @@ -10,4 +10,4 @@ | Artifacts | Security Violations | Security Issues | | :------------ | :--------------------- | :------------------ | -|
📦 docker-local
└── 📁 image2
└── 📁 sha256:552c
└── sha256__aae9

| Not scanned | Not scanned | +|
📦 docker-local
└── 📁 image2
└── 📁 sha256:552c
└── sha256__aae9

| Not scanned | Not scanned | diff --git a/artifactory/utils/testdata/command_summaries/extended/generic-module.md b/artifactory/utils/testdata/command_summaries/extended/generic-module.md index af0bee52e..33e1fe0f1 100644 --- a/artifactory/utils/testdata/command_summaries/extended/generic-module.md +++ b/artifactory/utils/testdata/command_summaries/extended/generic-module.md @@ -11,7 +11,7 @@
📦 generic-local
 └── 📁 path
     └── 📁 to
-        └── artifact2
+        └── artifact2
 
 
diff --git a/artifactory/utils/testdata/command_summaries/extended/maven-module.md b/artifactory/utils/testdata/command_summaries/extended/maven-module.md index 738da70cb..c1454b39c 100644 --- a/artifactory/utils/testdata/command_summaries/extended/maven-module.md +++ b/artifactory/utils/testdata/command_summaries/extended/maven-module.md @@ -11,7 +11,7 @@
📦 libs-release
 └── 📁 path
     └── 📁 to
-        └── artifact1
+        └── artifact1
 
 
diff --git a/artifactory/utils/testdata/command_summaries/extended/maven-nested-module.md b/artifactory/utils/testdata/command_summaries/extended/maven-nested-module.md index 59a105406..5e6d5b90c 100644 --- a/artifactory/utils/testdata/command_summaries/extended/maven-nested-module.md +++ b/artifactory/utils/testdata/command_summaries/extended/maven-nested-module.md @@ -12,7 +12,7 @@ 📦 libs-release └── 📁 path └── 📁 to - └── artifact2 + └── artifact2
@@ -22,7 +22,7 @@ 📦 libs-release └── 📁 path └── 📁 to - └── artifact3 + └── artifact3 diff --git a/artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image.md b/artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image.md index c460a99b2..5dfc53802 100644 --- a/artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image.md +++ b/artifactory/utils/testdata/command_summaries/extended/multiarch-docker-image.md @@ -10,4 +10,4 @@ | Artifacts | Security Violations | Security Issues | | :------------ | :--------------------- | :------------------ | -|
linux/amd64/multiarch-image:1 (🐸 View)
📦 docker-local
└── 📁 multiarch-image
├── 📁 sha256:552c
│ └── sha256
└── sha256

| Not scanned | Not scanned | +|
linux/amd64/multiarch-image:1 (🐸 View)
📦 docker-local
└── 📁 multiarch-image
├── 📁 sha256:552c
│ └── sha256
└── sha256

| Not scanned | Not scanned |