Files
aptly/jfrog/public.go
2026-06-18 21:34:55 +02:00

293 lines
7.8 KiB
Go

package jfrog
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/aptly-dev/aptly/aptly"
aptly_utils "github.com/aptly-dev/aptly/utils"
"github.com/jfrog/jfrog-client-go/artifactory"
"github.com/jfrog/jfrog-client-go/artifactory/auth"
"github.com/jfrog/jfrog-client-go/artifactory/services"
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
"github.com/jfrog/jfrog-client-go/config"
"github.com/pkg/errors"
)
// PublishedStorage represents published repository on JFrog Artifactory
type PublishedStorage struct {
manager artifactory.ArtifactoryServicesManager
repository string
prefix string
plusWorkaround bool
}
// Check interface
var (
_ aptly.PublishedStorage = (*PublishedStorage)(nil)
)
func createPublishedStorageConfig(url, user, password, apiKey, accessToken string) (config.Config, error) {
artDetails := auth.NewArtifactoryDetails()
artDetails.SetUrl(url)
if user == "" {
user = os.Getenv("JFROG_USERNAME");
}
if password == "" {
password = os.Getenv("JFROG_PASSWORD");
}
if apiKey == "" {
apiKey = os.Getenv("JFROG_APIKEY");
}
if accessToken == "" {
accessToken = os.Getenv("JFROG_ACCESSTOKEN");
}
if user != "" && password != "" {
artDetails.SetUser(user)
artDetails.SetPassword(password)
} else if apiKey != "" {
artDetails.SetApiKey(apiKey)
} else if accessToken != "" {
artDetails.SetAccessToken(accessToken)
}
return config.NewConfigBuilder().
SetServiceDetails(artDetails).
SetDryRun(false).
Build()
}
// NewPublishedStorageRaw creates jfrog PublishedStorage from raw connection specs
func NewPublishedStorageRaw(
repository, url, user, password, apiKey, accessToken, prefix string,
plusWorkaround, debug bool,
) (*PublishedStorage, error) {
serviceConfig, err := createPublishedStorageConfig(url, user, password, apiKey, accessToken)
if err != nil {
return nil, errors.Wrap(err, "error building jfrog client config")
}
manager, err := artifactory.New(serviceConfig)
if err != nil {
return nil, errors.Wrap(err, "error creating jfrog manager")
}
return &PublishedStorage{
manager: manager,
repository: repository,
prefix: prefix,
plusWorkaround: plusWorkaround,
}, nil
}
// NewPublishedStorage creates published storage from aptly configuration struct
func NewPublishedStorage(
account string, root aptly_utils.JFrogPublishRoot,
) (*PublishedStorage, error) {
return NewPublishedStorageRaw(
root.Repository, root.URL, root.User, root.Password, root.APIKey, root.AccessToken,
root.Prefix, root.PlusWorkaround, root.Debug)
}
func (storage *PublishedStorage) String() string {
return fmt.Sprintf("jfrog:%s:%s", storage.repository, storage.prefix)
}
func (storage *PublishedStorage) MkDir(path string) error {
return nil
}
func (storage *PublishedStorage) PutFile(path string, sourceFilename string) error {
targetPath := filepath.Join(storage.repository, storage.prefix, path)
if storage.plusWorkaround {
targetPath = strings.Replace(targetPath, "+", "%2B", -1)
}
params := services.NewUploadParams()
params.Pattern = sourceFilename
params.Target = targetPath
params.Flat = true
_, _, err := storage.manager.UploadFiles(artifactory.UploadServiceOptions{}, params)
return err
}
func (storage *PublishedStorage) Remove(path string) error {
targetPath := filepath.Join(storage.repository, storage.prefix, path)
if storage.plusWorkaround {
targetPath = strings.Replace(targetPath, "+", "%2B", -1)
}
deleteParams := services.NewDeleteParams()
deleteParams.SetPattern(targetPath)
res, err := storage.manager.GetPathsToDelete(deleteParams)
if err != nil {
return err
}
defer func() { _ = res.Close() }()
_, err = storage.manager.DeleteFiles(res)
return err
}
func (storage *PublishedStorage) RemoveDirs(path string, progress aptly.Progress) error {
return storage.Remove(path)
}
func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, fileName string, sourcePool aptly.PackagePool, sourcePath string, sourceMD5 aptly_utils.ChecksumInfo, force bool) error {
sourceFilename := sourcePath
cleanup := func() {}
if sourcePool != nil {
if localPool, ok := sourcePool.(aptly.LocalPackagePool); ok {
sourceFilename = localPool.FullPath(sourcePath)
} else {
src, err := sourcePool.Open(sourcePath)
if err != nil {
return err
}
defer func() { _ = src.Close() }()
tmpFile, err := os.CreateTemp("", "aptly-jfrog-pool-*")
if err != nil {
return err
}
if _, err := io.Copy(tmpFile, src); err != nil {
_ = tmpFile.Close()
_ = os.Remove(tmpFile.Name())
return err
}
if err := tmpFile.Close(); err != nil {
_ = os.Remove(tmpFile.Name())
return err
}
sourceFilename = tmpFile.Name()
cleanup = func() {
_ = os.Remove(sourceFilename)
}
}
}
defer cleanup()
return storage.PutFile(filepath.Join(publishedPrefix, publishedRelPath, fileName), sourceFilename)
}
func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
searchPattern := filepath.Join(storage.repository, storage.prefix, prefix, "*")
params := services.NewSearchParams()
params.Pattern = searchPattern
reader, err := storage.manager.SearchFiles(params)
if err != nil {
return nil, err
}
defer func() { _ = reader.Close() }()
var paths []string
for element := new(utils.ResultItem); reader.NextRecord(element) == nil; element = new(utils.ResultItem) {
path := element.Path + "/" + element.Name
relPath := strings.TrimPrefix(path, storage.repository+"/"+storage.prefix+"/")
if storage.plusWorkaround {
relPath = strings.Replace(relPath, "%2B", "+", -1)
}
paths = append(paths, relPath)
}
return paths, nil
}
func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
oldTarget := filepath.Join(storage.repository, storage.prefix, oldName)
newTarget := filepath.Join(storage.repository, storage.prefix, newName)
if storage.plusWorkaround {
oldTarget = strings.Replace(oldTarget, "+", "%2B", -1)
newTarget = strings.Replace(newTarget, "+", "%2B", -1)
}
params := services.NewMoveCopyParams()
params.Pattern = oldTarget
params.Target = newTarget
params.Flat = true
_, _, err := storage.manager.Move(params)
return err
}
func (storage *PublishedStorage) SymLink(src string, dst string) error {
oldTarget := filepath.Join(storage.repository, storage.prefix, src)
newTarget := filepath.Join(storage.repository, storage.prefix, dst)
if storage.plusWorkaround {
oldTarget = strings.Replace(oldTarget, "+", "%2B", -1)
newTarget = strings.Replace(newTarget, "+", "%2B", -1)
}
params := services.NewMoveCopyParams()
params.Pattern = oldTarget
params.Target = newTarget
params.Flat = true
props := utils.NewProperties()
props.AddProperty("SymLink", src)
params.SetTargetProps(props)
_, _, err := storage.manager.Copy(params)
return err
}
func (storage *PublishedStorage) HardLink(src string, dst string) error {
return storage.SymLink(src, dst)
}
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
targetPath := filepath.Join(storage.repository, storage.prefix, path)
if storage.plusWorkaround {
targetPath = strings.Replace(targetPath, "+", "%2B", -1)
}
params := services.NewSearchParams()
params.Pattern = targetPath
reader, err := storage.manager.SearchFiles(params)
if err != nil {
return false, err
}
defer func() { _ = reader.Close() }()
length, err := reader.Length()
isEmpty := length == 0
return !isEmpty, err
}
func (storage *PublishedStorage) ReadLink(path string) (string, error) {
targetPath := filepath.Join(storage.repository, storage.prefix, path)
if storage.plusWorkaround {
targetPath = strings.Replace(targetPath, "+", "%2B", -1)
}
props, err := storage.manager.GetItemProps(targetPath)
if err != nil {
return "", nil
}
for k, v := range props.Properties {
if k == "SymLink" && len(v) > 0 {
return v[0], nil
}
}
return "", nil
}