Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New XSC analytics metrics capabilities. #928

Merged
merged 68 commits into from
Apr 3, 2024
Merged
Changes from 1 commit
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
1b94940
change branches
gailazar300 Dec 29, 2021
0df64cd
test
gailazar300 Dec 29, 2021
1edfa13
all branches
gailazar300 Dec 29, 2021
a664ea4
Promote version to 1.28.1 (#733)
omerzi Apr 5, 2023
0b22cb3
Added the Frogbot badge to the README (#740)
eyalbe4 Apr 13, 2023
93d1ee0
Merge remote-tracking branch 'upstream/dev' into masterupstream
sverdlov93 Apr 19, 2023
60012f0
Merge remote-tracking branch 'origin/dev'
omerzi May 2, 2023
72340a7
Merge branch 'dev'
eyalbe4 May 16, 2023
3c0e410
Merge remote-tracking branch 'origin/dev'
talarian1 May 17, 2023
7dd161b
Merge remote-tracking branch 'origin/dev'
omerzi May 21, 2023
88f4c5c
Merge remote-tracking branch 'origin/dev'
talarian1 May 23, 2023
21b99ad
Merge branch 'master' into dev
yahavi May 23, 2023
b71c280
Promoted version to 1.29.1
yahavi May 23, 2023
8f7755c
Merge branch 'dev'
eyalbe4 Jun 10, 2023
e6ba583
Merge branch 'dev'
eyalbe4 Jun 25, 2023
b624428
Merge branch 'dev'
RobiNino Jun 29, 2023
da0c018
Merge branch 'master' into dev
omerzi Jul 12, 2023
f750025
Promoted version to 1.31.1
omerzi Jul 12, 2023
e1d57bd
Merge remote-tracking branch 'origin/dev'
omerzi Jul 18, 2023
886ff5f
Merge branch 'dev'
eyalbe4 Jul 31, 2023
b1ae836
Promoted version to 1.31.3
eyalbe4 Jul 31, 2023
df38e23
Merge branch 'dev'
eyalbe4 Jul 31, 2023
46b7fb9
Merge branch 'dev'
eyalbe4 Aug 3, 2023
5f58723
Resolve conflicts
eyalbe4 Aug 3, 2023
080ef1a
Merge branch 'dev'
eyalbe4 Aug 23, 2023
7c244c5
Merge branch 'dev'
attiasas Aug 28, 2023
6d1c9e7
Merge branch 'master' into dev
EyalDelarea Sep 11, 2023
b3d917f
Merge remote-tracking branch 'origin/dev'
EyalDelarea Sep 11, 2023
3be412b
Merge remote-tracking branch 'upstream/dev'
sverdlov93 Sep 13, 2023
fa697b5
Merge branch 'dev'
RobiNino Sep 14, 2023
7b2943f
Merge remote-tracking branch 'origin/dev'
eranturgeman Oct 3, 2023
15552e7
Merge remote-tracking branch 'jfrog-prod/dev'
eranturgeman Oct 3, 2023
9684938
Merge remote-tracking branch 'origin/dev'
yahavi Oct 4, 2023
9dd7283
Merge branch 'dev'
EyalDelarea Oct 5, 2023
21998b7
Merge remote-tracking branch 'origin/dev'
omerzi Oct 17, 2023
dc27aae
Merge remote-tracking branch 'origin/dev'
attiasas Nov 8, 2023
0abdd92
Merge remote-tracking branch 'origin/dev'
yahavi Nov 9, 2023
696df1a
Merge remote-tracking branch 'upstream/dev'
yahavi Nov 30, 2023
c09e8dd
Merge branch 'dev'
eyalbe4 Dec 15, 2023
3edc13f
Promote to v1.35.2
eyalbe4 Dec 18, 2023
ddfd377
Merge branch 'master' into dev
eyalbe4 Dec 18, 2023
36e989c
Resolved conflict
eyalbe4 Dec 21, 2023
cc27960
Merge branch 'dev'
eyalbe4 Dec 25, 2023
7f02060
Promote version to 1.35.5
omerzi Dec 27, 2023
5dd6e86
Promote version to 1.35.6
omerzi Jan 7, 2024
4f0f4b7
Merge branch 'dev'
eyalbe4 Jan 22, 2024
e249dfe
Promote version to '1.36.1' (#892)
attiasas Jan 29, 2024
8b005ae
Merge changes to master (#907)
yahavi Feb 20, 2024
18d4ebf
Merge remote-tracking branch 'origin/dev'
yahavi Feb 20, 2024
14a4407
Merge remote-tracking branch 'origin/dev'
yahavi Feb 21, 2024
1aca291
Merge remote-tracking branch 'origin/dev'
RobiNino Mar 18, 2024
ed22625
XRAY-36905 - Add Analytics Metrics capabilities in CLI.
gailazar300 Mar 20, 2024
6d38f21
Merge remote-tracking branch 'upstream/master' into feature/XRAY-36905
gailazar300 Mar 20, 2024
1579176
Add POST general event.
gailazar300 Mar 24, 2024
a972545
Add new XscAddGeneralEventRequest struct.
gailazar300 Mar 26, 2024
336b75c
Before review fixes.
gailazar300 Mar 26, 2024
2e6841d
Add UpdateGeneralEvent and remove unnecessary xsc apis from xray serv…
gailazar300 Mar 27, 2024
0064d7a
Minor fixes.
gailazar300 Mar 31, 2024
0da6a25
Review fixes.
gailazar300 Mar 31, 2024
e2e7cd4
Merge remote-tracking branch 'upstream/dev' into feature/XRAY-36905
gailazar300 Mar 31, 2024
71b5429
Add data validation to the tests using GetGeneralEvent api.
gailazar300 Mar 31, 2024
bab8c9a
Add TestSetEnvWithResetCallback.
gailazar300 Apr 1, 2024
9b8da01
Add GetAnalyticsGeneralEvent.
gailazar300 Apr 1, 2024
8df71a3
Second review fixes.
gailazar300 Apr 2, 2024
015add4
Fix README.md.
gailazar300 Apr 2, 2024
f4a07a4
Minor change.
gailazar300 Apr 2, 2024
38badc2
Eyal's review fixes.
gailazar300 Apr 2, 2024
3003542
Minor fixes.
gailazar300 Apr 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge changes to master (#907)
  • Loading branch information
yahavi authored Feb 20, 2024
commit 8b005ae4222a17c989d874dacb602b702c401131
89 changes: 57 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -169,6 +169,9 @@
- [Delete Violations Report](#delete-violations-report)
- [Get Artifact Summary](#get-artifact-summary)
- [Get Entitlement info](#get-entitlement-info)
- [Using XSC Service](#using-xsc-service)
- [Check if xsc is enabled](#check-if-xsc-is-enabled)
- [Send git info details to xsc](#send-git-info-details-to-xsc)
- [Pipelines APIs](#pipelines-apis)
- [Creating Pipelines Service Manager](#creating-pipelines-service-manager)
- [Creating Pipelines Details](#creating-pipelines-details)
@@ -185,6 +188,7 @@
- [Get Integration by Id](#get-integration-by-id)
- [Get Integration by Name](#get-integration-by-name)
- [Get All Integrations](#get-all-integrations)
- [Get All Raw Integrations](#get-all-raw-integrations)
- [Delete Integration](#delete-integration)
- [Add Pipeline Source](#add-pipeline-source)
- [Get Recent Pipeline Run Status](#get-recent-pipeline-run-status)
@@ -802,7 +806,7 @@ apiKey, err := rtManager.GetAPIKey()
You can create and update a local repository for the following package types:

Alpine, Bower, Cran, Cargo, Chef, Cocoapods, Composer, Conan, Conda, Debian, Docker, Gems, Generic, Gitlfs, Go, Gradle,
Helm, Ivy, Maven, Npm, Nuget, Opkg, Puppet, Pypi, Rpm, Sbt, Swift, Vagrant, and Yum.
Helm, Ivy, Maven, Npm, Nuget, Opkg, Puppet, Pypi, Rpm, Sbt, Swift, Terraform, Vagrant, and Yum.

Each package type has its own parameters struct, can be created using the method
`New<packageType>LocalRepositoryParams()`.
@@ -845,7 +849,7 @@ err = servicesManager.UpdateLocalRepository().Generic(params)
You can create and update a remote repository for the following package types:

Alpine, Bower, Cran, Cargo, Chef, Cocoapods, Composer, Conan, Conda, Debian, Docker, Gems, Generic, Gitlfs, Go, Gradle,
Helm, Ivy, Maven, Npm, Nuget, Opkg, P2, Puppet, Pypi, Rpm, Sbt, Swift, Vcs, and Yum.
Helm, Ivy, Maven, Npm, Nuget, Opkg, P2, Puppet, Pypi, Rpm, Sbt, Swift, Terraform, Vcs, and Yum.

Each package type has its own parameters struct, can be created using the method
`New<packageType>RemoteRepositoryParams()`.
@@ -889,7 +893,7 @@ err := servicesManager.CreateRemoteRepository(params)
You can create and update a virtual repository for the following package types:

Alpine, Bower, Cran, Chef, Conan, Conda, Debian, Docker, Gems, Generic, Gitlfs, Go, Gradle, Helm, Ivy, Maven, Npm,
Nuget, P2, Puppet, Pypi, Rpm, Sbt, Swift and Yum.
Nuget, P2, Puppet, Pypi, Rpm, Sbt, Swift, Terraform and Yum.

Each package type has its own parameters struct, can be created using the method
`New<packageType>VirtualRepositoryParams()`.
@@ -931,7 +935,7 @@ err = servicesManager.UpdateVirtualRepository().Go(params)
You can create and update a federated repository for the following package types:

Alpine, Bower, Cran, Cargo, Chef, Cocoapods, Composer, Conan, Conda, Debian, Docker, Gems, Generic, Gitlfs, Go, Gradle,
Helm, Ivy, Maven, Npm, Nuget, Opkg, Puppet, Pypi, Rpm, Sbt, Swift Vagrant and Yum
Helm, Ivy, Maven, Npm, Nuget, Opkg, Puppet, Pypi, Rpm, Sbt, Swift, Terraform, Vagrant and Yum

Each package type has its own parameters struct, can be created using the method
`New<packageType>FederatedRepositoryParams()`.
@@ -2210,6 +2214,23 @@ artifactSummary, err := xrayManager.ArtifactSummary(artifactSummaryRequest)
isEntitled, err := xrayManager.IsEntitled(featureId)
```

### Using XSC Service

#### Check if xsc is enabled

```go
// Will try to get XSC version. If route is not available, user is not entitled for XSC.
xscVersion, err := scanService.IsXscEnabled()
```

#### Send git info details to xsc

```go
// Details are the git info details (gitRepoUrl, branchName, commitHash are required fields). Returns multi scan id.
multiScanId, err := scanService.SendScanGitInfoContext(details)
```


## Pipelines APIs

### Creating Pipelines Service Manager
@@ -2305,6 +2326,12 @@ integration, err := pipelinesManager.GetIntegrationByName("integrationName")
integrations, err := pipelinesManager.GetAllIntegrations()
```

#### Get All Raw Integrations

```go
integrations, err := pipelinesManager.GetAllRawIntegrations()
```

#### Delete Integration

```go
@@ -2396,13 +2423,12 @@ lifecycleManager, err := lifecycle.New(serviceConfig)

```go
rbDetails := ReleaseBundleDetails{"rbName", "rbVersion"}
params := CreateOrPromoteReleaseBundleParams{}
// The GPG/RSA key-pair name given in Artifactory.
params.SigningKeyName = "key-pair"
// Optional:
params.ProjectKey = "project"
params.Async = true
queryParams := CommonOptionalQueryParams{}
queryParams.ProjectKey = "project"
queryParams.Async = true

// The GPG/RSA key-pair name given in Artifactory.
signingKeyName = "key-pair"

source := CreateFromBuildsSource{Builds: []BuildSource{
{
@@ -2412,19 +2438,19 @@ source := CreateFromBuildsSource{Builds: []BuildSource{
BuildRepository: "artifactory-build-info",
},
}}
serviceManager.CreateReleaseBundleFromBuilds(rbDetails, params, source)
serviceManager.CreateReleaseBundleFromBuilds(rbDetails, queryParams, signingKeyName, source)
```

#### Creating a Release Bundle From Release Bundles

```go
rbDetails := ReleaseBundleDetails{"rbName", "rbVersion"}
params := CreateOrPromoteReleaseBundleParams{}
queryParams := CommonOptionalQueryParams{}
queryParams.ProjectKey = "project"
queryParams.Async = true

// The GPG/RSA key-pair name given in Artifactory.
params.SigningKeyName = "key-pair"
// Optional:
params.ProjectKey = "project"
params.Async = true
signingKeyName = "key-pair"

source := CreateFromReleaseBundlesSource{ReleaseBundles: []ReleaseBundleSource{
{
@@ -2433,23 +2459,25 @@ source := CreateFromReleaseBundlesSource{ReleaseBundles: []ReleaseBundleSource{
ProjectKey: "default",
},
}}
serviceManager.CreateReleaseBundleFromBundles(rbDetails, params, source)
serviceManager.CreateReleaseBundleFromBundles(rbDetails, params, signingKeyName, source)
```

#### Promoting a Release Bundle

```go
rbDetails := ReleaseBundleDetails{"rbName", "rbVersion"}
params := CreateOrPromoteReleaseBundleParams{}
queryParams := CommonOptionalQueryParams{}
queryParams.ProjectKey = "project"
queryParams.Async = true

// The GPG/RSA key-pair name given in Artifactory.
params.SigningKeyName = "key-pair"
// Optional:
params.ProjectKey = "project"
params.Async = true
signingKeyName = "key-pair"

environment := "target-env"
overwrite:=true
resp, err := serviceManager.PromoteReleaseBundle(rbDetails, params, environment, overwrite)
promotionParams := RbPromotionParams{}
promotionParams.Environment := "target-env"
promotionParams.IncludedRepositoryKeys := []string{"generic-local"}

resp, err := serviceManager.PromoteReleaseBundle(rbDetails, queryParams, signingKeyName, promotionParams)
```

#### Get Release Bundle Creation Status
@@ -2498,14 +2526,11 @@ resp, err := serviceManager.DistributeReleaseBundle(params, autoCreateRepo, path

```go
rbDetails := ReleaseBundleDetails{"rbName", "rbVersion"}
params := CreateOrPromoteReleaseBundleParams{}
// The GPG/RSA key-pair name given in Artifactory.
params.SigningKeyName = "key-pair"
// Optional:
params.ProjectKey = "project"
params.Async = true
queryParams := CommonOptionalQueryParams{}
queryParams.ProjectKey = "project"
queryParams.Async = true

resp, err := serviceManager.DeleteReleaseBundle(rbDetails, params)
resp, err := serviceManager.DeleteReleaseBundle(rbDetails, queryParams)
```

#### Remote Delete Release Bundle
12 changes: 12 additions & 0 deletions artifactory/services/federatedrepository.go
Original file line number Diff line number Diff line change
@@ -135,6 +135,10 @@ func (frs *FederatedRepositoryService) Swift(params SwiftFederatedRepositoryPara
return frs.performRequest(params, params.Key)
}

func (frs *FederatedRepositoryService) Terraform(params TerraformFederatedRepositoryParams) error {
return frs.performRequest(params, params.Key)
}

func (frs *FederatedRepositoryService) Vagrant(params VagrantFederatedRepositoryParams) error {
return frs.performRequest(params, params.Key)
}
@@ -389,6 +393,14 @@ func NewSwiftFederatedRepositoryParams() SwiftFederatedRepositoryParams {
return SwiftFederatedRepositoryParams{FederatedRepositoryBaseParams: NewFederatedRepositoryPackageParams("swift")}
}

type TerraformFederatedRepositoryParams struct {
FederatedRepositoryBaseParams
}

func NewTerraformFederatedRepositoryParams() TerraformFederatedRepositoryParams {
return TerraformFederatedRepositoryParams{FederatedRepositoryBaseParams: NewFederatedRepositoryPackageParams("terraform")}
}

type VagrantFederatedRepositoryParams struct {
FederatedRepositoryBaseParams
}
3 changes: 3 additions & 0 deletions artifactory/services/fspatterns/utils.go
Original file line number Diff line number Diff line change
@@ -125,6 +125,9 @@ func isPathExcluded(path string, excludePathPattern string) (excludedPath bool,

// If filePath is path to a symlink we should return the link content e.g where the link points
func GetFileSymlinkPath(filePath string) (string, error) {
if filePath == "" {
return "", nil
}
fileInfo, e := os.Lstat(filePath)
if errorutils.CheckError(e) != nil {
return "", e
3 changes: 1 addition & 2 deletions artifactory/services/remoterepository.go
Original file line number Diff line number Diff line change
@@ -481,10 +481,9 @@ func NewSwiftRemoteRepositoryParams() SwiftRemoteRepositoryParams {

type TerraformRemoteRepositoryParams struct {
RemoteRepositoryBaseParams
VcsGitRemoteRepositoryParams
TerraformRegistryUrl string `json:"terraformRegistryUrl,omitempty"`
TerraformProvidersUrl string `json:"terraformProvidersUrl,omitempty"`
VcsType string `json:"vcsType,omitempty"`
VcsGitProvider string `json:"vcsGitProvider,omitempty"`
}

func NewTerraformRemoteRepositoryParams() TerraformRemoteRepositoryParams {
27 changes: 22 additions & 5 deletions artifactory/services/upload.go
Original file line number Diff line number Diff line change
@@ -40,6 +40,8 @@ type UploadService struct {
resultsManager *resultsManager
}

const JfrogCliUploadEmptyArchiveEnv = "JFROG_CLI_UPLOAD_EMPTY_ARCHIVE"

func NewUploadService(client *jfroghttpclient.JfrogHttpClient) *UploadService {
return &UploadService{client: client}
}
@@ -317,10 +319,17 @@ func scanFilesByPattern(uploadParams UploadParams, rootPath string, progressMgr
}
// Longest files path first
sort.Sort(sort.Reverse(sort.StringSlice(paths)))
var uploadedTargets []string

// 'uploadedDirs' is in use only when we need to upload folders with flat=true.
// 'uploadedDirs' will contain only local directories paths that have been uploaded to Artifactory.
var uploadedDirs []string
var uploadedTargets, uploadedDirs []string

if shouldUploadAnEmptyArchive(uploadParams.Archive, paths) {
log.Info("All files were filtered out by the exclusion pattern, but the archive flag is set together with JFROG_CLI_UPLOAD_EMPTY_ARCHIVE environment variable. " +
"Proceeding with an empty archive.")
paths = []string{""}
}

for _, path := range paths {
matches, isDir, err := fspatterns.SearchPatterns(path, uploadParams.IsSymlink(), uploadParams.IsIncludeDirs(), patternRegex)
if err != nil {
@@ -350,6 +359,12 @@ func scanFilesByPattern(uploadParams UploadParams, rootPath string, progressMgr
return nil
}

func shouldUploadAnEmptyArchive(archive string, paths []string) bool {
return len(paths) == 0 &&
archive != "" &&
strings.ToLower(os.Getenv(JfrogCliUploadEmptyArchiveEnv)) == "true"
}

// targetFiles - Paths in Artifactory of the files that were uploaded.
// sourceDirs - Paths of the local dirs that have already been uploaded to Artifactory. (Longest files path first).
// targetDir - The directory target path to be uploaded.
@@ -834,9 +849,11 @@ func (us *UploadService) readFilesAsZip(archiveDataReader *content.ContentReader
}
}()
for uploadData := new(UploadData); archiveDataReader.NextRecord(uploadData) == nil; uploadData = new(UploadData) {
e = us.addFileToZip(&uploadData.Artifact, progressPrefix, flat, symlink, zipWriter)
if e != nil {
errorsQueue.AddError(e)
if uploadData.Artifact.LocalPath != "" {
e = us.addFileToZip(&uploadData.Artifact, progressPrefix, flat, symlink, zipWriter)
if e != nil {
errorsQueue.AddError(e)
}
}
if saveFilesPathsFunc != nil {
e = saveFilesPathsFunc(uploadData.Artifact.LocalPath)
21 changes: 21 additions & 0 deletions artifactory/services/utils/tests/xray/consts.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package xray

import xrayServices "github.com/jfrog/jfrog-client-go/xray/services"

const FatalErrorXrayScanResponse = `
{
"errors": [{"status":-1}, {"status":500}]
@@ -1414,6 +1416,25 @@ const BuildScanResultsResponse = `
]
}
`
const xscVersionResponse = `{"xsc_version": "1.0.0"}`

const XscGitInfoResponse = `{"multi_scan_id": "3472b4e2-bddc-11ee-a9c9-acde48001122"}`

const XscGitInfoBadResponse = `"failed create git info request: git_repo_url field must contain value"`

var GitInfoContextWithMinimalRequiredFields = xrayServices.XscGitInfoContext{
GitRepoUrl: "https://git.jfrog.info/projects/XSC/repos/xsc-service",
BranchName: "feature/XRAY-123-cool-feature",
CommitHash: "acc5e24e69a-d3c1-4022-62eb-69e4a1e5",
}

var GitInfoContextWithMissingFields = xrayServices.XscGitInfoContext{
GitRepoUrl: "https://git.jfrog.info/projects/XSC/repos/xsc-service",
BranchName: "feature/XRAY-123-cool-feature",
}

const TestMultiScanId = "3472b4e2-bddc-11ee-a9c9-acde48001122"
const TestXscVersion = "1.0.0"

var MapReportIdEndpoint = map[int]string{
777: VulnerabilitiesEndpoint,
41 changes: 40 additions & 1 deletion artifactory/services/utils/tests/xray/server.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package xray

import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"testing"

"github.com/buger/jsonparser"
"github.com/jfrog/jfrog-client-go/utils/log"
clienttests "github.com/jfrog/jfrog-client-go/utils/tests"
"github.com/jfrog/jfrog-client-go/xray/services"
"github.com/stretchr/testify/assert"
)

const (
@@ -183,11 +186,47 @@ func buildScanHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Invalid reports request", http.StatusBadRequest)
}

func StartXrayMockServer() int {
func xscGetVersionHandlerFunc(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
_, err := fmt.Fprint(w, xscVersionResponse)
assert.NoError(t, err)
return
}
http.Error(w, "Invalid xsc request", http.StatusBadRequest)
}
}

func xscGitInfoHandlerFunc(t *testing.T) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
req, err := io.ReadAll(r.Body)
assert.NoError(t, err)
if r.Method == http.MethodPost {
var reqBody services.XscGitInfoContext
err = json.Unmarshal(req, &reqBody)
assert.NoError(t, err)
if reqBody.GitRepoUrl == "" || reqBody.BranchName == "" || reqBody.CommitHash == "" {
w.WriteHeader(http.StatusBadRequest)
_, err := fmt.Fprint(w, XscGitInfoBadResponse)
assert.NoError(t, err)
return
}
w.WriteHeader(http.StatusCreated)
_, err = fmt.Fprint(w, XscGitInfoResponse)
assert.NoError(t, err)
return
}
http.Error(w, "Invalid xsc request", http.StatusBadRequest)
}
}

func StartXrayMockServer(t *testing.T) int {
handlers := clienttests.HttpServerHandlers{}
handlers["/api/xray/scanBuild"] = scanBuildHandler
handlers["/api/v2/summary/artifact"] = artifactSummaryHandler
handlers["/api/v1/entitlements/feature/"] = entitlementsHandler
handlers["/xsc/api/v1/system/version"] = xscGetVersionHandlerFunc(t)
handlers["/xsc/api/v1/gitinfo"] = xscGitInfoHandlerFunc(t)
handlers[fmt.Sprintf("/%s/", services.ReportsAPI)] = reportHandler
handlers[fmt.Sprintf("/%s/", services.BuildScanAPI)] = buildScanHandler
handlers["/"] = http.NotFound
Loading