jfrog-client-go is a library which provides Go APIs to performs actions on JFrog Artifactory, Xray and Distribution from your Go application. The project is still relatively new, and its APIs may therefore change frequently between releases. The library can be used as a go-module, which should be added to your project's go.mod file. As a reference you may look at JFrog CLI' s go.mod file, which uses this library as a dependency.
Pull Requests
We welcome pull requests from the community.
Guidelines
If the existing tests do not already cover your changes, please add tests.
Pull requests should be created on the dev branch.
Please use gofmt for formatting the code before submitting the pull request.
Tests
To run the tests on the source code, you'll need a running JFrog instance. See the Prerequisites column in the Test Types section below for more information.
Use the following command with the below options to run the tests.
If you'd like to run a specific test, add the test function name using the -run flag. For example:
go test -v github.com/jfrog/jfrog-client-go/tests -timeout 0 -run TestGetArtifactoryVersionWithCustomHttpClient -test.artifactory -rt.url=http://127.0.0.1:8081/artifactory -rt.user=admin -rt.password=password
Note: The tests create an Artifactory repository named jfrog-client-tests-repo1. Once the tests are completed, the content of this repository is deleted.
Flags
Test Types
Connection Details
General APIs
Setting the Logger
Default logger:
log.SetLogger(log.NewLogger(log.INFO, nil))
You may also log to a file, and/or add log prefixes as shown below:
var file *os.File// Log flags as described in https://pkg.go.dev/log#pkg-constants.logFlags := Ldate | Ltime...log.SetLogger(log.NewLoggerWithFlags(log.DEBUG, file, logFlags))
Setting the Temp Dir
The default temp dir used is 'os.TempDir()'. Use the following API to set a new temp dir:
rtDetails := auth.NewArtifactoryDetails()rtDetails.SetUrl("http://localhost:8081/artifactory")rtDetails.SetSshKeyPath("path/to/.ssh/")rtDetails.SetApiKey("apikey")rtDetails.SetUser("user")rtDetails.SetPassword("password")rtDetails.SetAccessToken("accesstoken")// if client certificates are requiredrtDetails.SetClientCertPath("path/to/.cer")rtDetails.SetClientCertKeyPath("path/to/.key")
Creating Artifactory Details with Custom HTTP Client
serviceConfig, err := config.NewConfigBuilder().SetServiceDetails(rtDetails).SetCertificatesPath(certPath).SetThreads(threads).SetDryRun(false).// Add [Context](https://golang.org/pkg/context/)SetContext(ctx).// Optionally overwrite the default dial timeout, which is set to 30 seconds.SetDialTimeout(180* time.Second).// Optionally set the total HTTP request timeout.SetOverallRequestTimeout(10* time.Minute).// Optionally overwrite the default HTTP retries, which is set to 3.SetHttpRetries(8).Build()
Creating New Artifactory Service Manager
rtManager, err := artifactory.New(serviceConfig)
Using Artifactory Services
Uploading Files to Artifactory
Using the UploadFiles() function, we can upload files and get the general statistics of the action (The actual number of successful and failed uploads), and the error value if it occurred.
params := services.NewUploadParams()params.Pattern ="repo/*/*.zip"params.Target ="repo/path/"params.AddVcsProps =falseparams.BuildProps ="build.name=buildName;build.number=17;build.timestamp=1600856623553"params.Recursive =trueparams.Regexp =falseparams.IncludeDirs =falseparams.Flat =trueparams.ExplodeArchive =falseparams.Archive ="zip"params.Deb =""params.Symlink =falseparams.Exclusions ="(.*)a.zip"// Retries default value: 3params.Retries =5// The min file size in bytes for "checksum deploy".// "Checksum deploy" is the action of calculating the file checksum locally, before// the upload, and skipping the actual file transfer if the file already// exists in Artifactory.// MinChecksumDeploy default value: 10400params.MinChecksumDeploy =15360// Set to false to disable all checksum calculation, including "checksum deploy".// ChecksumsCalcEnabled default value: trueparams.ChecksumsCalcEnabled =false// Attach properties to the uploaded filestargetProps := utils.NewProperties()targetProps.AddProperty("key1", "val1")params.TargetProps = targetPropstotalUploaded, totalFailed, err := rtManager.UploadFiles(params)
Downloading Files from Artifactory
Using the DownloadFiles() function, we can download files and get the general statistics of the action (The actual number of files downloaded, and the number of files we expected to download). In addition, we get the error value if it occurred.
Using the DownloadFiles() function, we can download release bundles v1 and get the general statistics of the action (The actual number of files downloaded, and the number of files we expected to download). In addition, we get the error value if it occurred.
It is possible to validate the downloaded release bundle's files by providing a local path to a GPG public key file (the public GPG key should of course correspond to the private GPG key which was used to sign the release bundle).
params := services.NewDownloadParams()// Path on the local file system to which the files should be downloaded.params.Target ="target/path/"// Bundle's name and version should be separated with "/".params.Bundle ="bundleName/10"// Optional GPG validationparams.PublicGpgKey ="public/key/file/path"totalDownloaded, totalFailed, err := rtManager.DownloadFiles(params)
Read more about GPG signing release bundles v1 here.
Uploading and Downloading Files with Summary
The methods UploadFilesWithSummary() and DownloadFilesWithSummary() are similar to UploadFlies() and DownloadFlies(), but return an OperationSummary struct, which allows iterating over the details of the uploaded/downloaded files.
The OperationSummary struct contains:
TotalSucceeded - the number of successful uploads/downloads
TotalFailed - the number of failed uploads/downloads
TransferDetailsReader - a ContentReader of FileTransferDetails structs, with a struct for each successful transfer of file
ArtifactsDetailsReader - a ContentReader of ArtifactDetails structs, with a struct for each artifact in Artifactory that was uploaded/downloaded successfully
The ContentReaders can be closed separately by calling Close() on each of them, or they both can be closed at once by calling Close() on the OperationSummary struct.
Each package type has its own parameters struct, can be created using the method New<packageType>LocalRepositoryParams().
Example for creating local Generic repository:
params := services.NewGenericLocalRepositoryParams()params.Key ="generic-repo"params.Description ="This is a public description for generic-repo"params.Notes ="These are internal notes for generic-repo"params.RepoLayoutRef ="simple-default"params.ArchiveBrowsingEnabled =trueparams.XrayIndex =trueparams.IncludesPattern ="**/*"params.ExcludesPattern ="excludedDir/*"params.DownloadRedirect =trueerr = servicesManager.CreateLocalRepository().Generic(params)
You can also create a local repository with basic local params:
params := services.NewLocalRepositoryBaseParams()params.Key ="generic-repo"params.PackageType ="generic"params.Description ="This is a public description for generic-repo"err := servicesManager.CreateLocalRepository(params)
Each package type has its own parameters struct, can be created using the method New<packageType>VirtualRepositoryParams().
Example for creating virtual Go repository:
params := services.NewGoVirtualRepositoryParams()params.Description ="This is an aggregated repository for several go repositories"params.RepoLayoutRef ="go-default"params.Repositories = {"gocenter-remote", "go-local"}params.DefaultDeploymentRepo ="go-local"params.ExternalDependenciesEnabled =trueparams.ExternalDependenciesPatterns = {"**/github.com/**", "**/golang.org/**", "**/gopkg.in/**"}params.ArtifactoryRequestsCanRetrieveRemoteArtifacts =trueerr = servicesManager.CreateVirtualRepository().Go(params)
You can also create a virtual repository with basic virtual params:
params := services.NewVirtualRepositoryBaseParams()params.Key ="generic-repo"params.PackageType ="generic"params.Description ="This is a public description for generic-repo"params.Repositories =string[]{"remote-repo","local-repo"}err := servicesManager.CreateVirtualRepository(params)
You can remove a repository from Artifactory using its key:
servicesManager.DeleteRepository("generic-repo")
Getting Repository Details
You can get repository details from Artifactory using its key, and the desired params struct. The function expects to get the repo key (name) and a pointer to a param struct that will be filled up. The param struct should contain the desired params fields corresponded to the Artifactory REST API:
You can create or update a permission target in Artifactory. Permissions are set according to the following conventions: read, write, annotate, delete, manage, managedXrayMeta, distribute For repositories You can specify the name "ANY" in order to apply to all repositories, "ANY REMOTE" for all remote repositories or "ANY LOCAL" for all local repositories.
Unlocks a locked out user. This function succeeds even if the user doesn't exist or not locked.
err := serviceManager.UnlockUser("userToUnlock")
Fetching All Groups
You can get all groups from Artifactory
groups, err := serviceManager.GetAllGroups()
Fetching Group Details
params := services.NewGroupParams()
params.GroupDetails.Name = "myGroupName"
// Set this param to true to receive the usernames associated with this group
params.IncludeUsers = true
group, err := serviceManager.GetGroup(params)
If the requested group does not exist, a nil value is returned for the Group param, with a nil error value
Creating and Updating a Group
params := services.NewGroupParams()
params.GroupDetails.Name = "myGroupName"
params.GroupDetails.Description = "Description"
params.GroupDetails.AutoJoin = &falseValue
params.GroupDetails.AdminPrivileges = &trueValue
params.GroupDetails.Realm = "internal"
params.GroupDetails.UsersNames = [2]string{"UserA", "UserB"}
// Set to true in order to replace exist group with the same name
params.ReplaceIfExists = false
err := serviceManager.CreateGroup(params)
params.GroupDetails.Description = "Newer Description"
// Will add UserC to the group (in addition to existing UserA and UserB)
params.GroupDetails.UsersNames = [1]string{"UserC"}
err := serviceManager.UpdateGroup(params)
params := services.NewCreateReleaseBundleParams("bundle-name", "1")
params.Description = "Description"
params.ReleaseNotes = "Release notes"
params.ReleaseNotesSyntax = "plain_text"
targetProps := utils.NewProperties()
targetProps.AddProperty("key1", "val1")
params.SpecFiles = []*utils.CommonParams{{Pattern: "repo/*/*.zip", TargetProps: targetProps}}
// Be default, artifacts that are distributed as part of a release bundle v1, have the same path in their destination server
// (the edge node) as the path they had on the distributing Artifactory server.
// You have however the option for modifying the target path on edge node. You do this by defining the Target property as shown below.
// The Pattern property is a wildcard based pattern. Any wildcards enclosed in parentheses in the pattern (source)
// path can be matched with a corresponding placeholder in the target path, to determine the path and name
// of the artifact, once distributed to the edge node.
// In the following example, the path in the edge node is similar to the path in the source Artifactory server, except for the additional "dir" level at the root of the repository.
// Pattern: my-repo/(*)/a.zip
// Target: my-repo/dir/{1}/a.zip
pathMappingSpec := &utils.CommonParams{Pattern: "source-repo/(a)/(*.zip)", Target: "target-repo/{1}-{2}"}
params.SpecFiles = append(params.SpecFiles, pathMappingSpec)
// In case: params.SignImmediately == true, the summary contain the release bundle v1 details. Otherwise, summary is nil.
summary, err := distManager.CreateReleaseBundle(params)
Updating a Release Bundle v1
params := services.NewUpdateReleaseBundleParams("bundle-name", "1")
params.Description = "New Description"
params.ReleaseNotes = "New Release notes"
params.ReleaseNotesSyntax = "plain_text"
targetProps := utils.NewProperties()
targetProps.AddProperty("key1", "val1")
params.SpecFiles = []*utils.CommonParams{{Pattern: "repo/*/*.zip", TargetProps: targetProps}}
// The Target property defines the target path in the edge node, and can include replaceable in the form of {1}, {2}, ...
// Read more about it in the above "Creating a Release Bundle v1" section.
pathMappingSpec := &utils.CommonParams{Pattern: "source-repo/(a)/(*.zip)", Target: "target-repo/{1}-{2}"}
params.SpecFiles = append(params.SpecFiles, pathMappingSpec)
// In case: params.SignImmediately == true, the summary contain the release bundle v1 details. Otherwise, summary is nil.
summary, err := distManager.UpdateReleaseBundle(params)
params := services.NewDistributeReleaseBundleParams("bundle-name", "1")
distributionRules := utils.DistributionCommonParams{SiteName: "Swamp-1", "CityName": "Tel-Aviv", "CountryCodes": []string{"123"}}}
params.DistributionRules = []*utils.DistributionCommonParams{distributionRules}
// Auto-creating repository if it does not exist
autoCreateRepo := true
err := distManager.DistributeReleaseBundle(params, autoCreateRepo)
Sync Distributing a Release Bundle v1
params := services.NewDistributeReleaseBundleParams("bundle-name", "1")
distributionRules := utils.DistributionCommonParams{SiteName: "Swamp-1", "CityName": "Tel-Aviv", "CountryCodes": []string{"123"}}}
params.DistributionRules = []*utils.DistributionCommonParams{distributionRules}
// Auto-creating repository if it does not exist
autoCreateRepo := true
// Wait up to 120 minutes for the release bundle v1 distribution
err := distManager.DistributeReleaseBundleSync(params, 120, autoCreateRepo)
Getting Distribution Status
params := services.NewDistributionStatusParams()
// Optional parameters:
// If missing, get status for all distributions
params.Name = "bundle-name"
// If missing, get status for all versions of "bundle-name"
params.Version = "1"
// If missing, get status for all "bundle-name" with version "1"
params.TrackerId = "123456789"
status, err := distributeBundleService.GetStatus(params)
Deleting a Remote Release Bundle v1
params := services.NewDeleteReleaseBundleParams("bundle-name", "1")
params.DeleteFromDistribution = true
distributionRules := utils.DistributionCommonParams{SiteName: "Swamp-1", "CityName": "Tel-Aviv", "CountryCodes": []string{"123"}}}
params.DistributionRules = []*utils.DistributionCommonParams{distributionRules}
// Set to true to enable sync deletion (the command execution will end when the deletion process ends).
param.Sync = true
// Max minutes to wait for sync deletion.
param.MaxWaitMinutes = 10
err := distManager.DeleteReleaseBundle(params)
Some APIs return a content.ContentReader struct, which allows reading the API's output. content.ContentReader provides access to large amounts of data safely, without loading all of it into the memory. Here's an example for how content.ContentReader should be used:
reader, err := servicesManager.SearchFiles(searchParams)
if err != nil {
return err
}
// Remove the data file used by the reader.
defer func() {
if reader != nil {
err = reader.Close()
}
}()
// Iterate over the results.
for currentResult := new(utils.ResultItem); reader.NextRecord(currentResult) == nil; currentResult = new(utils.ResultItem) {
fmt.Printf("Found artifact: %s of type: %s\n", currentResult.Name, currentResult.Type)
}
if err := resultReader.GetError(); err != nil {
return err
}
// Resets the reader pointer back to the beginning of the output. Make sure not to call this method after the reader had been closed using ```reader.Close()```
reader.Reset()
reader.NextRecord(currentResult) reads the next record from the reader into currentResult of type utils.ResultItem.
reader.Close() removes the file used by the reader after it is used (preferably using defer).
reader.GetError() returns any error that might have occurred during NextRecord().
reader.Reset() resets the reader back to the beginning of the output.
Xray APIs
Creating Xray Service Manager
Creating Xray Details
xrayDetails := auth.NewXrayDetails()
xrayDetails.SetUrl("http://localhost:8081/xray")
xrayDetails.SetSshKeyPath("path/to/.ssh/")
xrayDetails.SetApiKey("apikey")
xrayDetails.SetUser("user")
xrayDetails.SetPassword("password")
xrayDetails.SetAccessToken("accesstoken")
// if client certificates are required
xrayDetails.SetClientCertPath("path/to/.cer")
xrayDetails.SetClientCertKeyPath("path/to/.key")
Creating Xray Service Config
serviceConfig, err := config.NewConfigBuilder().
SetServiceDetails(xrayDetails).
SetCertificatesPath(certPath).
// Optionally overwrite the default HTTP retries, which is set to 3.
SetHttpRetries(8).
Build()
Creating New Xray Service Manager
xrayManager, err := xray.New(serviceConfig)
Using Xray Services
Fetching Xray's Version
version, err := xrayManager.GetVersion()
Creating an Xray Watch
This uses API version 2.
You are able to configure repositories and builds on a watch. However, bundles are not supported.
graphScanParams := &XrayGraphScanParams{}
// Dependency tree. Each node must have a component identifier, see https://www.jfrog.com/confluence/display/JFROG/Xray+REST+API#XrayRESTAPI-ComponentIdentifiers.
graphScanParams.Graph = &GraphNode{
Id: "gav://org.jfrog.buildinfo:build-info-extractor-gradle:4.24.5",
Nodes: []*GraphNode{{Id: "gav://junit:junit:4.13.2"}, {Id: "gav://commons-lang:commons-lang:2.6"}}}
scanId, err := xrayManager.ScanGraph(graphScanParams)
Retrieve the Graph Scan Results
// scanId should be received from xrayManager.ScanGraph(graphScanParams) request.
scanResults, err := xrayManager.GetScanGraphResults(scanId)
Generate Vulnerabilities Report
reportRequest := services.ReportRequestParams{
Name: "example-report",
Filters: services.Filter{
HasRemediation: &trueValue,
Severity: []string{ "High" },
},
Resources: services.Resource{
IncludePathPatterns: []string{ "/example-sub-dir/**" },
Repositories: []services.Repository{
{
Name: "example-repository",
},
},
},
}
// The reportRequestResponse will contain the report ID to use in subsequent requests
reportRequestResponse, err := xrayManager.GenerateVulnerabilitiesReport(reportRequest)
Get Vulnerabilities Report Details
// The reportId argument value is returned as part of the xrayManager.GenerateVulnerabilitiesReport API response.
reportDetails, err := xrayManager.ReportDetails(reportId)
Get Vulnerabilities Report Content
// The ReportId value is returned as part of the xrayManager.GenerateVulnerabilitiesReport API response.
reportContentRequest := services.ReportContentRequestParams{
ReportId: "example-report-id",
Direction: "asc",
PageNum: 0,
NumRows: 0,
OrderBy: "severity",
}
reportContent, err := xrayManager.ReportContent(reportContentRequest)
Delete Vulnerabilities Report
// The reportId argument value is returned as part of the xrayManager.GenerateVulnerabilitiesReport API response.
err := xrayManager.DeleteReport(reportId)
// The featureId is the requested feature ID to check, for instance: "contextual_analysis"
isEntitled, err := xrayManager.IsEntitled(featureId)
Pipelines APIs
Creating Pipelines Service Manager
Creating Pipelines Details
pipelinesDetails := auth.NewPipelinesDetails()
pipelinesDetails.SetUrl("http://localhost:8081/pipelines")
pipelinesDetails.SetAccessToken("accesstoken")
// if client certificates are required
pipelinesDetails.SetClientCertPath("path/to/.cer")
pipelinesDetails.SetClientCertKeyPath("path/to/.key")
Creating Pipelines Service Config
serviceConfig, err := config.NewConfigBuilder().
SetServiceDetails(pipelinesDetails).
SetCertificatesPath(pipelinesDetails.GetClientCertPath()).
// Optionally overwrite the default HTTP retries, which is set to 3.
SetHttpRetries(8).
Build()
runID := 234 // run id of pipeline
err := pipelinesManager.CancelRun(runID)
Lifecycle APIs
Creating Lifecycle Service Manager
Creating Lifecycle Details
lcDetails := auth.NewLifecycleDetails()
lcDetails.SetUrl("http://localhost:8081/lifecycle")
lcDetails.SetAccessToken("access-token")
// if client certificates are required
lcDetails.SetClientCertPath("path/to/.cer")
lcDetails.SetClientCertKeyPath("path/to/.key")
Creating Lifecycle Service Config
serviceConfig, err := config.NewConfigBuilder().
SetServiceDetails(lcDetails).
SetCertificatesPath(lcDetails.GetClientCertPath()).
// Optionally overwrite the default HTTP retries, which is set to 3.
SetHttpRetries(8).
Build()