mirror of
https://github.com/harness/drone.git
synced 2025-05-05 15:32:56 +00:00
feat: [AH-1146]: add support for RPM artifact details (#3697)
* feat: [AH-1063]: RPM client setup details (#3726) * add credentials * feat: [AH-1063]: RPM client setup details * fix lint issues * fix lint issue * refactoring * refactoring * fix tests * fix rebase issues * feat: [AH-1146]: add support for RPM artifact details
This commit is contained in:
parent
b8653351a4
commit
c3a70ca12e
@ -135,6 +135,8 @@ import (
|
||||
"github.com/harness/gitness/pubsub"
|
||||
registryevents "github.com/harness/gitness/registry/app/events"
|
||||
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||
rpmutils "github.com/harness/gitness/registry/app/utils/rpm"
|
||||
registryindex "github.com/harness/gitness/registry/services/index"
|
||||
registrywebhooks "github.com/harness/gitness/registry/services/webhook"
|
||||
"github.com/harness/gitness/ssh"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
@ -292,6 +294,8 @@ func initSystem(ctx context.Context, config *types.Config) (*cliserver.System, e
|
||||
registrywebhooks.WireSet,
|
||||
gitspacedeleteevents.WireSet,
|
||||
gitspacedeleteeventservice.WireSet,
|
||||
registryindex.WireSet,
|
||||
rpmutils.WireSet,
|
||||
)
|
||||
return &cliserver.System{}, nil
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ import (
|
||||
npm2 "github.com/harness/gitness/registry/app/api/controller/pkg/npm"
|
||||
nuget2 "github.com/harness/gitness/registry/app/api/controller/pkg/nuget"
|
||||
python2 "github.com/harness/gitness/registry/app/api/controller/pkg/python"
|
||||
rpm2 "github.com/harness/gitness/registry/app/api/controller/pkg/rpm"
|
||||
rpm3 "github.com/harness/gitness/registry/app/api/controller/pkg/rpm"
|
||||
"github.com/harness/gitness/registry/app/api/router"
|
||||
events12 "github.com/harness/gitness/registry/app/events"
|
||||
"github.com/harness/gitness/registry/app/pkg"
|
||||
@ -141,9 +141,11 @@ import (
|
||||
"github.com/harness/gitness/registry/app/pkg/npm"
|
||||
"github.com/harness/gitness/registry/app/pkg/nuget"
|
||||
"github.com/harness/gitness/registry/app/pkg/python"
|
||||
"github.com/harness/gitness/registry/app/pkg/rpm"
|
||||
rpm2 "github.com/harness/gitness/registry/app/pkg/rpm"
|
||||
database2 "github.com/harness/gitness/registry/app/store/database"
|
||||
"github.com/harness/gitness/registry/app/utils/rpm"
|
||||
"github.com/harness/gitness/registry/gc"
|
||||
"github.com/harness/gitness/registry/services/index"
|
||||
webhook3 "github.com/harness/gitness/registry/services/webhook"
|
||||
"github.com/harness/gitness/ssh"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
@ -523,7 +525,9 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiHandler := router.APIHandlerProvider(registryRepository, upstreamProxyConfigRepository, fileManager, tagRepository, manifestRepository, cleanupPolicyRepository, imageRepository, storageDriver, spaceFinder, transactor, authenticator, provider, authorizer, auditService, artifactRepository, webhooksRepository, webhooksExecutionRepository, service2, spacePathStore, reporter10, downloadStatRepository)
|
||||
registryHelper := rpm.LocalRegistryHelperProvider(fileManager, artifactRepository)
|
||||
indexService := index.ProvideService(registryHelper)
|
||||
apiHandler := router.APIHandlerProvider(registryRepository, upstreamProxyConfigRepository, fileManager, tagRepository, manifestRepository, cleanupPolicyRepository, imageRepository, storageDriver, spaceFinder, transactor, authenticator, provider, authorizer, auditService, artifactRepository, webhooksRepository, webhooksExecutionRepository, service2, spacePathStore, reporter10, downloadStatRepository, indexService)
|
||||
mavenDBStore := maven.DBStoreProvider(registryRepository, imageRepository, artifactRepository, spaceStore, bandwidthStatRepository, downloadStatRepository, nodesRepository, upstreamProxyConfigRepository)
|
||||
mavenLocalRegistry := maven.LocalRegistryProvider(mavenDBStore, transactor, fileManager)
|
||||
mavenController := maven.ProvideProxyController(mavenLocalRegistry, secretService, spaceFinder)
|
||||
@ -551,10 +555,9 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
||||
npmProxy := npm.ProxyProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, provider, spaceFinder, secretService, npmLocalRegistryHelper)
|
||||
npmController := npm2.ControllerProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, downloadStatRepository, provider, npmLocalRegistry, npmProxy)
|
||||
npmHandler := api2.NewNPMHandlerProvider(npmController, packagesHandler)
|
||||
rpmLocalRegistryHelper := rpm.LocalRegistryHelperProvider(fileManager, artifactRepository)
|
||||
rpmLocalRegistry := rpm.LocalRegistryProvider(localBase, fileManager, upstreamProxyConfigRepository, transactor, registryRepository, imageRepository, artifactRepository, provider, rpmLocalRegistryHelper)
|
||||
rpmProxy := rpm.ProxyProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, provider)
|
||||
rpmController := rpm2.ControllerProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, provider, rpmLocalRegistry, rpmProxy)
|
||||
rpmLocalRegistry := rpm2.LocalRegistryProvider(localBase, fileManager, upstreamProxyConfigRepository, transactor, registryRepository, imageRepository, artifactRepository, provider, indexService)
|
||||
rpmProxy := rpm2.ProxyProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, provider)
|
||||
rpmController := rpm3.ControllerProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, provider, rpmLocalRegistry, rpmProxy)
|
||||
rpmHandler := api2.NewRpmHandlerProvider(rpmController, packagesHandler)
|
||||
handler4 := router.PackageHandlerProvider(packagesHandler, mavenHandler, genericHandler, pythonHandler, nugetHandler, npmHandler, rpmHandler)
|
||||
appRouter := router.AppRouterProvider(registryOCIHandler, apiHandler, handler2, handler3, handler4)
|
||||
|
@ -128,6 +128,8 @@ func toPackageType(packageTypeStr string) (artifactapi.PackageType, error) {
|
||||
return artifactapi.PackageTypePYTHON, nil
|
||||
case string(artifactapi.PackageTypeNPM):
|
||||
return artifactapi.PackageTypeNPM, nil
|
||||
case string(artifactapi.PackageTypeRPM):
|
||||
return artifactapi.PackageTypeRPM, nil
|
||||
default:
|
||||
return "", errors.New("invalid package type")
|
||||
}
|
||||
@ -234,15 +236,18 @@ func GetArtifactFilesMetadata(
|
||||
filePathPrefix := "/" + artifactName + "/" + version + "/"
|
||||
filename := strings.Replace(file.Path, filePathPrefix, "", 1)
|
||||
var downloadCommand string
|
||||
if artifactapi.PackageTypeGENERIC == packageType ||
|
||||
artifactapi.PackageTypePYTHON == packageType || artifactapi.PackageTypeNPM == packageType {
|
||||
//nolint:exhaustive
|
||||
switch packageType {
|
||||
case artifactapi.PackageTypeGENERIC, artifactapi.PackageTypePYTHON, artifactapi.PackageTypeNPM:
|
||||
downloadCommand = GetGenericArtifactFileDownloadCommand(registryURL, artifactName, version, filename)
|
||||
} else if artifactapi.PackageTypeMAVEN == packageType {
|
||||
case artifactapi.PackageTypeMAVEN:
|
||||
artifactName = strings.ReplaceAll(artifactName, ".", "/")
|
||||
artifactName = strings.ReplaceAll(artifactName, ":", "/")
|
||||
filePathPrefix = "/" + artifactName + "/" + version + "/"
|
||||
filename = strings.Replace(file.Path, filePathPrefix, "", 1)
|
||||
downloadCommand = GetMavenArtifactFileDownloadCommand(registryURL, artifactName, version, filename)
|
||||
case artifactapi.PackageTypeRPM:
|
||||
downloadCommand = GetRPMArtifactFileDownloadCommand(registryURL, filename)
|
||||
}
|
||||
files = append(files, artifactapi.FileDetail{
|
||||
Checksums: getCheckSums(file),
|
||||
@ -536,6 +541,42 @@ func GetNPMArtifactDetail(
|
||||
return *artifactDetail
|
||||
}
|
||||
|
||||
func GetRPMArtifactDetail(
|
||||
image *types.Image, artifact *types.Artifact,
|
||||
metadata map[string]interface{},
|
||||
downloadCount int64,
|
||||
) artifactapi.ArtifactDetail {
|
||||
createdAt := GetTimeInMs(artifact.CreatedAt)
|
||||
modifiedAt := GetTimeInMs(artifact.UpdatedAt)
|
||||
size, ok := metadata["size"].(float64)
|
||||
if !ok {
|
||||
log.Error().Msg("failed to get size from RPM metadata")
|
||||
}
|
||||
fileMetadata, ok := metadata["file_metadata"].(map[string]interface{})
|
||||
if ok {
|
||||
delete(fileMetadata, "files")
|
||||
delete(fileMetadata, "changelogs")
|
||||
}
|
||||
|
||||
totalSize := strconv.FormatInt(int64(size), 10)
|
||||
artifactDetail := &artifactapi.ArtifactDetail{
|
||||
CreatedAt: &createdAt,
|
||||
ModifiedAt: &modifiedAt,
|
||||
Name: &image.Name,
|
||||
Version: artifact.Version,
|
||||
DownloadCount: &downloadCount,
|
||||
Size: &totalSize,
|
||||
}
|
||||
err := artifactDetail.FromRpmArtifactDetailConfig(artifactapi.RpmArtifactDetailConfig{
|
||||
Metadata: &metadata,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error setting the artifact details for artifact: [%s/%s].", image.Name, artifact.Version)
|
||||
return artifactapi.ArtifactDetail{}
|
||||
}
|
||||
return *artifactDetail
|
||||
}
|
||||
|
||||
func GetArtifactSummary(artifact types.ImageMetadata) *artifactapi.ArtifactSummaryResponseJSONResponse {
|
||||
createdAt := GetTimeInMs(artifact.CreatedAt)
|
||||
modifiedAt := GetTimeInMs(artifact.ModifiedAt)
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
registryevents "github.com/harness/gitness/registry/app/events"
|
||||
"github.com/harness/gitness/registry/app/pkg/filemanager"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/registry/services/index"
|
||||
"github.com/harness/gitness/registry/services/webhook"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
)
|
||||
@ -51,6 +52,7 @@ type APIController struct {
|
||||
WebhookService webhook.ServiceInterface
|
||||
ArtifactEventReporter registryevents.Reporter
|
||||
DownloadStatRepository store.DownloadStatRepository
|
||||
RegistryIndexService index.Service
|
||||
}
|
||||
|
||||
func NewAPIController(
|
||||
@ -76,6 +78,7 @@ func NewAPIController(
|
||||
webhookService webhook.ServiceInterface,
|
||||
artifactEventReporter registryevents.Reporter,
|
||||
downloadStatRepository store.DownloadStatRepository,
|
||||
registryIndexService index.Service,
|
||||
) *APIController {
|
||||
return &APIController{
|
||||
fileManager: fileManager,
|
||||
@ -100,5 +103,6 @@ func NewAPIController(
|
||||
WebhookService: webhookService,
|
||||
ArtifactEventReporter: artifactEventReporter,
|
||||
DownloadStatRepository: downloadStatRepository,
|
||||
RegistryIndexService: registryIndexService,
|
||||
}
|
||||
}
|
||||
|
@ -263,6 +263,7 @@ func TestCreateRegistry(t *testing.T) {
|
||||
nil, // webhookService.
|
||||
eventReporter,
|
||||
nil, // downloadStatRepository.
|
||||
nil, // registryIndexService - not needed for this test.
|
||||
)
|
||||
},
|
||||
},
|
||||
@ -332,6 +333,7 @@ func TestCreateRegistry(t *testing.T) {
|
||||
mockRegistryMetadataHelper,
|
||||
nil, // webhookService.
|
||||
eventReporter,
|
||||
nil, //
|
||||
nil, // downloadStatRepository.
|
||||
)
|
||||
},
|
||||
|
@ -120,7 +120,12 @@ func (c *APIController) DeleteArtifactVersion(ctx context.Context, r artifact.De
|
||||
case artifact.PackageTypeNUGET:
|
||||
err = fmt.Errorf("delete version not supported for nuget")
|
||||
case artifact.PackageTypeRPM:
|
||||
err = fmt.Errorf("delete version not supported for rpm")
|
||||
err = c.deleteVersion(ctx, regInfo, artifactName, versionName)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = c.RegistryIndexService.RegenerateRpmRepoData(ctx, regInfo.RegistryID,
|
||||
regInfo.RootIdentifierID, regInfo.RootIdentifier)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported package type: %s", regInfo.PackageType)
|
||||
}
|
||||
|
@ -155,6 +155,17 @@ func (c *APIController) GetArtifactDetails(
|
||||
}, nil
|
||||
}
|
||||
artifactDetails = GetNPMArtifactDetail(img, art, result, downloadCount)
|
||||
case artifact.PackageTypeRPM:
|
||||
var result map[string]interface{}
|
||||
err := json.Unmarshal(art.Metadata, &result)
|
||||
if err != nil {
|
||||
return artifact.GetArtifactDetails500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
artifactDetails = GetRPMArtifactDetail(img, art, result, downloadCount)
|
||||
case artifact.PackageTypeDOCKER:
|
||||
case artifact.PackageTypeHELM:
|
||||
default:
|
||||
|
@ -23,6 +23,8 @@ import (
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/app/api/utils"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
@ -99,24 +101,15 @@ func (c *APIController) GetArtifactFiles(
|
||||
|
||||
registryURL := c.URLProvider.RegistryURL(ctx,
|
||||
reqInfo.RootIdentifier, strings.ToLower(string(registry.PackageType)), reqInfo.RegistryIdentifier)
|
||||
filePathPrefix := "/" + img.Name + "/" + art.Version + "%"
|
||||
|
||||
if artifact.PackageTypeMAVEN == registry.PackageType {
|
||||
artifactName := strings.ReplaceAll(img.Name, ".", "/")
|
||||
artifactName = strings.ReplaceAll(artifactName, ":", "/")
|
||||
filePathPrefix = "/" + artifactName + "/" + art.Version + "%"
|
||||
}
|
||||
fileMetadataList, err := c.fileManager.GetFilesMetadata(ctx, filePathPrefix, img.RegistryID,
|
||||
reqInfo.sortByField, reqInfo.sortByOrder, reqInfo.limit, reqInfo.offset, reqInfo.searchTerm)
|
||||
|
||||
filePathPrefix, err := utils.GetFilePath(registry.PackageType, img.Name, art.Version)
|
||||
if err != nil {
|
||||
log.Error().Msgf("Failed to fetch files for artifact, err: %v", err.Error())
|
||||
return artifact.GetArtifactFiles500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError,
|
||||
fmt.Sprintf("Failed to fetch files for artifact with name: [%s]", art.Version)),
|
||||
),
|
||||
}, nil
|
||||
return failedToFetchFilesResponse(err, art)
|
||||
}
|
||||
filePathPattern := filePathPrefix + "%"
|
||||
fileMetadataList, err := c.fileManager.GetFilesMetadata(ctx, filePathPattern, img.RegistryID,
|
||||
reqInfo.sortByField, reqInfo.sortByOrder, reqInfo.limit, reqInfo.offset, reqInfo.searchTerm)
|
||||
if err != nil {
|
||||
return failedToFetchFilesResponse(err, art)
|
||||
}
|
||||
|
||||
count, err := c.fileManager.CountFilesByPath(ctx, filePathPrefix, img.RegistryID)
|
||||
@ -133,7 +126,8 @@ func (c *APIController) GetArtifactFiles(
|
||||
|
||||
//nolint:exhaustive
|
||||
switch registry.PackageType {
|
||||
case artifact.PackageTypeGENERIC, artifact.PackageTypeMAVEN, artifact.PackageTypePYTHON, artifact.PackageTypeNPM:
|
||||
case artifact.PackageTypeGENERIC, artifact.PackageTypeMAVEN, artifact.PackageTypePYTHON,
|
||||
artifact.PackageTypeNPM, artifact.PackageTypeRPM:
|
||||
return artifact.GetArtifactFiles200JSONResponse{
|
||||
FileDetailResponseJSONResponse: *GetAllArtifactFilesResponse(
|
||||
fileMetadataList, count, reqInfo.pageNumber, reqInfo.limit, registryURL, img.Name, art.Version,
|
||||
@ -147,3 +141,13 @@ func (c *APIController) GetArtifactFiles(
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func failedToFetchFilesResponse(err error, art *types.Artifact) (artifact.GetArtifactFilesResponseObject, error) {
|
||||
log.Error().Msgf("Failed to fetch files for artifact, err: %v", err.Error())
|
||||
return artifact.GetArtifactFiles500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError,
|
||||
fmt.Sprintf("Failed to fetch files for artifact with name: [%s]", art.Version)),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
@ -109,6 +109,8 @@ func (c *APIController) GenerateClientSetupDetails(
|
||||
loginPasswordLabel := "Password: *see step 2*"
|
||||
blankString := ""
|
||||
switch packageType {
|
||||
case string(artifact.PackageTypeRPM):
|
||||
return c.generateRpmClientSetupDetail(ctx, image, tag, registryRef, username)
|
||||
case string(artifact.PackageTypeMAVEN):
|
||||
return c.generateMavenClientSetupDetail(ctx, image, tag, registryRef, username, registryType)
|
||||
case string(artifact.PackageTypeHELM):
|
||||
@ -782,6 +784,208 @@ func (c *APIController) generateMavenClientSetupDetail(
|
||||
}
|
||||
}
|
||||
|
||||
func (c *APIController) generateRpmClientSetupDetail(
|
||||
ctx context.Context,
|
||||
artifactName *artifact.ArtifactParam,
|
||||
version *artifact.VersionParam,
|
||||
registryRef string,
|
||||
username string,
|
||||
) *artifact.ClientSetupDetailsResponseJSONResponse {
|
||||
staticStepType := artifact.ClientSetupStepTypeStatic
|
||||
generateTokenStepType := artifact.ClientSetupStepTypeGenerateToken
|
||||
|
||||
// Authentication section
|
||||
section1 := artifact.ClientSetupSection{
|
||||
Header: utils.StringPtr("1. Configure Authentication"),
|
||||
}
|
||||
_ = section1.FromClientSetupStepConfig(artifact.ClientSetupStepConfig{
|
||||
Steps: &[]artifact.ClientSetupStep{
|
||||
{
|
||||
Header: utils.StringPtr("Generate an identity token for authentication"),
|
||||
Type: &generateTokenStepType,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
yumSection1 := artifact.ClientSetupSection{
|
||||
Header: utils.StringPtr("2. Install a RPM Package"),
|
||||
}
|
||||
_ = yumSection1.FromClientSetupStepConfig(artifact.ClientSetupStepConfig{
|
||||
Steps: &[]artifact.ClientSetupStep{
|
||||
{
|
||||
Header: utils.StringPtr("Create or edit the .repo file."),
|
||||
Type: &staticStepType,
|
||||
Commands: &[]artifact.ClientSetupStepCommand{
|
||||
{
|
||||
Value: utils.StringPtr("sudo vi /etc/yum.repos.d/harness-<REGISTRY_NAME>.repo"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: utils.StringPtr("Add the following content:"),
|
||||
Type: &staticStepType,
|
||||
Commands: &[]artifact.ClientSetupStepCommand{
|
||||
{
|
||||
Value: utils.StringPtr("[harness-<REGISTRY_NAME>]\n" +
|
||||
"name=harness-<REGISTRY_NAME>\n" +
|
||||
"baseurl=<REGISTRY_URL>\n" +
|
||||
"enabled=1\n" +
|
||||
"gpgcheck=0\n" +
|
||||
"username=<USERNAME>\n" +
|
||||
"password=*see step 2*\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: utils.StringPtr("Clear the YUM cache."),
|
||||
Type: &staticStepType,
|
||||
Commands: &[]artifact.ClientSetupStepCommand{
|
||||
{
|
||||
Value: utils.StringPtr("sudo yum clean all"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: utils.StringPtr("Install package."),
|
||||
Type: &staticStepType,
|
||||
Commands: &[]artifact.ClientSetupStepCommand{
|
||||
{
|
||||
Value: utils.StringPtr("sudo yum install <ARTIFACT_NAME>"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
yumSection2 := artifact.ClientSetupSection{
|
||||
Header: utils.StringPtr("3. Upload RPM Package"),
|
||||
}
|
||||
|
||||
_ = yumSection2.FromClientSetupStepConfig(artifact.ClientSetupStepConfig{
|
||||
Steps: &[]artifact.ClientSetupStep{
|
||||
{
|
||||
Header: utils.StringPtr("To upload a RPM artifact run the following cURL with your package file:"),
|
||||
Type: &staticStepType,
|
||||
Commands: &[]artifact.ClientSetupStepCommand{
|
||||
{
|
||||
//nolint:lll
|
||||
Value: utils.StringPtr("curl --location --request PUT '<REGISTRY_URL>/' \\\n--form 'file=@\"<FILE_PATH>\"' \\\n--header 'x-api-key: <API_KEY>'"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
dnfSection1 := artifact.ClientSetupSection{
|
||||
Header: utils.StringPtr("2. Install a RPM Package"),
|
||||
}
|
||||
_ = dnfSection1.FromClientSetupStepConfig(artifact.ClientSetupStepConfig{
|
||||
Steps: &[]artifact.ClientSetupStep{
|
||||
{
|
||||
Header: utils.StringPtr("Create or edit the .repo file."),
|
||||
Type: &staticStepType,
|
||||
Commands: &[]artifact.ClientSetupStepCommand{
|
||||
{
|
||||
Value: utils.StringPtr("sudo vi /etc/yum.repos.d/harness-<REGISTRY_NAME>.repo"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: utils.StringPtr("Add the following content:"),
|
||||
Type: &staticStepType,
|
||||
Commands: &[]artifact.ClientSetupStepCommand{
|
||||
{
|
||||
Value: utils.StringPtr("[harness-<REGISTRY_NAME>]\n" +
|
||||
"name=harness-<REGISTRY_NAME>\n" +
|
||||
"baseurl=<REGISTRY_URL>\n" +
|
||||
"enabled=1\n" +
|
||||
"gpgcheck=0\n"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: utils.StringPtr("Clear the DNF cache."),
|
||||
Type: &staticStepType,
|
||||
Commands: &[]artifact.ClientSetupStepCommand{
|
||||
{
|
||||
Value: utils.StringPtr("sudo dnf clean all"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: utils.StringPtr("Install package."),
|
||||
Type: &staticStepType,
|
||||
Commands: &[]artifact.ClientSetupStepCommand{
|
||||
{
|
||||
Value: utils.StringPtr("sudo dnf install <ARTIFACT_NAME>"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
dnfSection2 := artifact.ClientSetupSection{
|
||||
Header: utils.StringPtr("3. Upload RPM Package"),
|
||||
}
|
||||
|
||||
_ = dnfSection2.FromClientSetupStepConfig(artifact.ClientSetupStepConfig{
|
||||
Steps: &[]artifact.ClientSetupStep{
|
||||
{
|
||||
Header: utils.StringPtr("To upload a RPM artifact run the following cURL with your package file:"),
|
||||
Type: &staticStepType,
|
||||
Commands: &[]artifact.ClientSetupStepCommand{
|
||||
{
|
||||
//nolint:lll
|
||||
Value: utils.StringPtr("curl --location --request PUT '<REGISTRY_URL>/' \\\n--form 'file=@\"<FILE_PATH>\"' \\\n--header 'x-api-key: <API_KEY>'"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
section2 := artifact.ClientSetupSection{}
|
||||
config := artifact.TabSetupStepConfig{
|
||||
Tabs: &[]artifact.TabSetupStep{
|
||||
{
|
||||
Header: utils.StringPtr("YUM"),
|
||||
Sections: &[]artifact.ClientSetupSection{
|
||||
yumSection1,
|
||||
yumSection2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: utils.StringPtr("DNF"),
|
||||
Sections: &[]artifact.ClientSetupSection{
|
||||
dnfSection1,
|
||||
dnfSection2,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_ = section2.FromTabSetupStepConfig(config)
|
||||
|
||||
clientSetupDetails := artifact.ClientSetupDetails{
|
||||
MainHeader: "RPM Client Setup",
|
||||
SecHeader: "Follow these instructions to install/upload RPM packages.",
|
||||
Sections: []artifact.ClientSetupSection{
|
||||
section1,
|
||||
section2,
|
||||
},
|
||||
}
|
||||
|
||||
registryURL := c.URLProvider.PackageURL(ctx, registryRef, "rpm")
|
||||
|
||||
//nolint:lll
|
||||
c.replacePlaceholders(ctx, &clientSetupDetails.Sections, username, registryRef, artifactName, version, registryURL,
|
||||
"", "")
|
||||
|
||||
return &artifact.ClientSetupDetailsResponseJSONResponse{
|
||||
Data: clientSetupDetails,
|
||||
Status: artifact.StatusSUCCESS,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *APIController) generatePythonClientSetupDetail(
|
||||
ctx context.Context,
|
||||
registryRef string,
|
||||
|
@ -117,6 +117,7 @@ var validPackageTypes = []string{
|
||||
string(a.PackageTypeMAVEN),
|
||||
string(a.PackageTypePYTHON),
|
||||
string(a.PackageTypeNPM),
|
||||
string(a.PackageTypeRPM),
|
||||
}
|
||||
|
||||
var validUpstreamSources = []string{
|
||||
@ -376,6 +377,8 @@ func GetPullCommand(
|
||||
return GetPythonDownloadCommand(image, tag)
|
||||
case string(a.PackageTypeNPM):
|
||||
return GetNPMDownloadCommand(image, tag)
|
||||
case string(a.PackageTypeRPM):
|
||||
return GetRPMDownloadCommand(image, tag)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@ -392,6 +395,22 @@ func GetHelmPullCommand(image string, tag string, registryURL string) string {
|
||||
return "helm pull oci://" + GetRepoURLWithoutProtocol(registryURL) + "/" + image + " --version " + tag
|
||||
}
|
||||
|
||||
func GetRPMDownloadCommand(artifact, version string) string {
|
||||
downloadCommand := "yum install <ARTIFACT>-<VERSION>"
|
||||
|
||||
// Replace the placeholders with the actual values
|
||||
replacements := map[string]string{
|
||||
"<ARTIFACT>": artifact,
|
||||
"<VERSION>": version,
|
||||
}
|
||||
|
||||
for placeholder, value := range replacements {
|
||||
downloadCommand = strings.ReplaceAll(downloadCommand, placeholder, value)
|
||||
}
|
||||
|
||||
return downloadCommand
|
||||
}
|
||||
|
||||
func GetNPMDownloadCommand(artifact, version string) string {
|
||||
downloadCommand := "npm install <ARTIFACT>@<VERSION> "
|
||||
|
||||
@ -443,6 +462,21 @@ func GetGenericArtifactFileDownloadCommand(regURL, artifact, version, filename s
|
||||
return downloadCommand
|
||||
}
|
||||
|
||||
func GetRPMArtifactFileDownloadCommand(regURL, filename string) string {
|
||||
downloadCommand := "curl --location '<HOSTNAME>/package<FILENAME>' --header 'x-api-key: <API_KEY>'" +
|
||||
" -J -O"
|
||||
replacements := map[string]string{
|
||||
"<HOSTNAME>": regURL,
|
||||
"<FILENAME>": filename,
|
||||
}
|
||||
|
||||
for placeholder, value := range replacements {
|
||||
downloadCommand = strings.ReplaceAll(downloadCommand, placeholder, value)
|
||||
}
|
||||
|
||||
return downloadCommand
|
||||
}
|
||||
|
||||
func GetMavenArtifactFileDownloadCommand(regURL, artifact, version, filename string) string {
|
||||
downloadCommand := "curl --location '<HOSTNAME>/<ARTIFACT>/<VERSION>/<FILENAME>'" +
|
||||
" --header 'x-api-key: <IDENTITY_TOKEN>' -O"
|
||||
|
@ -2249,6 +2249,7 @@ components:
|
||||
MAVEN: "#/components/schemas/MavenArtifactDetailConfig"
|
||||
PYTHON: "#/components/schemas/PythonArtifactDetailConfig"
|
||||
NPM: "#/components/schemas/NpmArtifactDetailConfig"
|
||||
RPM: "#/components/schemas/RpmArtifactDetailConfig"
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/DockerArtifactDetailConfig"
|
||||
- $ref: "#/components/schemas/HelmArtifactDetailConfig"
|
||||
@ -2256,6 +2257,7 @@ components:
|
||||
- $ref: "#/components/schemas/MavenArtifactDetailConfig"
|
||||
- $ref: "#/components/schemas/PythonArtifactDetailConfig"
|
||||
- $ref: "#/components/schemas/NpmArtifactDetailConfig"
|
||||
- $ref: "#/components/schemas/RpmArtifactDetailConfig"
|
||||
required:
|
||||
- imageName
|
||||
- version
|
||||
@ -2296,6 +2298,13 @@ components:
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
RpmArtifactDetailConfig:
|
||||
type: object
|
||||
description: Config for RPM artifact details
|
||||
properties:
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
HelmArtifactDetailConfig:
|
||||
type: object
|
||||
description: Config for helm artifact details
|
||||
|
@ -7021,114 +7021,114 @@ func (sh *strictHandler) GetAllRegistries(w http.ResponseWriter, r *http.Request
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
"SDQQAQA=",
|
||||
"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",
|
||||
"LHss44Rke8XdkafPT/8fAAD//8d9dIJLEQEA",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
@ -697,6 +697,11 @@ type ReplicationRuleRequestDestinationType string
|
||||
// ReplicationRuleRequestSourceType defines model for ReplicationRuleRequest.SourceType.
|
||||
type ReplicationRuleRequestSourceType string
|
||||
|
||||
// RpmArtifactDetailConfig Config for RPM artifact details
|
||||
type RpmArtifactDetailConfig struct {
|
||||
Metadata *map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// SectionType refers to client setup section type
|
||||
type SectionType string
|
||||
|
||||
@ -1612,6 +1617,36 @@ func (t *ArtifactDetail) MergeNpmArtifactDetailConfig(v NpmArtifactDetailConfig)
|
||||
return err
|
||||
}
|
||||
|
||||
// AsRpmArtifactDetailConfig returns the union data inside the ArtifactDetail as a RpmArtifactDetailConfig
|
||||
func (t ArtifactDetail) AsRpmArtifactDetailConfig() (RpmArtifactDetailConfig, error) {
|
||||
var body RpmArtifactDetailConfig
|
||||
err := json.Unmarshal(t.union, &body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
// FromRpmArtifactDetailConfig overwrites any union data inside the ArtifactDetail as the provided RpmArtifactDetailConfig
|
||||
func (t *ArtifactDetail) FromRpmArtifactDetailConfig(v RpmArtifactDetailConfig) error {
|
||||
t.PackageType = "RPM"
|
||||
|
||||
b, err := json.Marshal(v)
|
||||
t.union = b
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeRpmArtifactDetailConfig performs a merge with any union data inside the ArtifactDetail, using the provided RpmArtifactDetailConfig
|
||||
func (t *ArtifactDetail) MergeRpmArtifactDetailConfig(v RpmArtifactDetailConfig) error {
|
||||
t.PackageType = "RPM"
|
||||
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merged, err := runtime.JSONMerge(t.union, b)
|
||||
t.union = merged
|
||||
return err
|
||||
}
|
||||
|
||||
func (t ArtifactDetail) Discriminator() (string, error) {
|
||||
var discriminator struct {
|
||||
Discriminator string `json:"packageType"`
|
||||
@ -1638,6 +1673,8 @@ func (t ArtifactDetail) ValueByDiscriminator() (interface{}, error) {
|
||||
return t.AsNpmArtifactDetailConfig()
|
||||
case "PYTHON":
|
||||
return t.AsPythonArtifactDetailConfig()
|
||||
case "RPM":
|
||||
return t.AsRpmArtifactDetailConfig()
|
||||
default:
|
||||
return nil, errors.New("unknown discriminator value: " + discriminator)
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
registryevents "github.com/harness/gitness/registry/app/events"
|
||||
"github.com/harness/gitness/registry/app/pkg/filemanager"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/registry/services/index"
|
||||
registrywebhook "github.com/harness/gitness/registry/services/webhook"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
|
||||
@ -77,6 +78,7 @@ func NewAPIHandler(
|
||||
spacePathStore corestore.SpacePathStore,
|
||||
artifactEventReporter registryevents.Reporter,
|
||||
downloadStatRepository store.DownloadStatRepository,
|
||||
registryIndexService index.Service,
|
||||
) APIHandler {
|
||||
r := chi.NewRouter()
|
||||
r.Use(audit.Middleware())
|
||||
@ -106,6 +108,7 @@ func NewAPIHandler(
|
||||
&webhookService,
|
||||
artifactEventReporter,
|
||||
downloadStatRepository,
|
||||
registryIndexService,
|
||||
)
|
||||
|
||||
handler := artifact.NewStrictHandler(apiController, []artifact.StrictMiddlewareFunc{})
|
||||
|
@ -29,7 +29,7 @@ import (
|
||||
hoci "github.com/harness/gitness/registry/app/api/handler/oci"
|
||||
"github.com/harness/gitness/registry/app/api/handler/packages"
|
||||
"github.com/harness/gitness/registry/app/api/handler/python"
|
||||
rpm "github.com/harness/gitness/registry/app/api/handler/rpm"
|
||||
"github.com/harness/gitness/registry/app/api/handler/rpm"
|
||||
generic2 "github.com/harness/gitness/registry/app/api/router/generic"
|
||||
"github.com/harness/gitness/registry/app/api/router/harness"
|
||||
mavenRouter "github.com/harness/gitness/registry/app/api/router/maven"
|
||||
@ -39,6 +39,7 @@ import (
|
||||
registryevents "github.com/harness/gitness/registry/app/events"
|
||||
"github.com/harness/gitness/registry/app/pkg/filemanager"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/registry/services/index"
|
||||
registrywebhook "github.com/harness/gitness/registry/services/webhook"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
|
||||
@ -77,6 +78,7 @@ func APIHandlerProvider(
|
||||
spacePathStore corestore.SpacePathStore,
|
||||
artifactEventReporter *registryevents.Reporter,
|
||||
downloadStatRepository store.DownloadStatRepository,
|
||||
registryIndexService index.Service,
|
||||
) harness.APIHandler {
|
||||
return harness.NewAPIHandler(
|
||||
repoDao,
|
||||
@ -101,6 +103,7 @@ func APIHandlerProvider(
|
||||
spacePathStore,
|
||||
*artifactEventReporter,
|
||||
downloadStatRepository,
|
||||
registryIndexService,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -39,9 +39,17 @@ func GetGenericFilePath(imageName string, version string) string {
|
||||
return filePathPrefix
|
||||
}
|
||||
|
||||
func GetRpmFilePath(imageName string, version string) string {
|
||||
lastDotIndex := strings.LastIndex(version, ".")
|
||||
rpmVersion := version[:lastDotIndex]
|
||||
rpmArch := version[lastDotIndex+1:]
|
||||
return "/" + imageName + "/" + rpmVersion + "/" + rpmArch
|
||||
}
|
||||
|
||||
func GetFilePath(
|
||||
packageType artifact.PackageType,
|
||||
imageName string, version string) (string, error) {
|
||||
imageName string, version string,
|
||||
) (string, error) {
|
||||
switch packageType {
|
||||
case artifact.PackageTypeDOCKER:
|
||||
return "", fmt.Errorf("docker package type not supported")
|
||||
@ -58,7 +66,7 @@ func GetFilePath(
|
||||
case artifact.PackageTypeNUGET:
|
||||
return "", fmt.Errorf("nuget package type not supported")
|
||||
case artifact.PackageTypeRPM:
|
||||
return "", fmt.Errorf("rpm package type not supported")
|
||||
return GetRpmFilePath(imageName, version), nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported package type: %s", packageType)
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ import (
|
||||
rpmtype "github.com/harness/gitness/registry/app/pkg/types/rpm"
|
||||
"github.com/harness/gitness/registry/app/storage"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
rpmutil "github.com/harness/gitness/registry/app/utils/rpm"
|
||||
"github.com/harness/gitness/registry/services/index"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
@ -40,15 +42,15 @@ var _ pkg.Artifact = (*localRegistry)(nil)
|
||||
var _ Registry = (*localRegistry)(nil)
|
||||
|
||||
type localRegistry struct {
|
||||
localBase base.LocalBase
|
||||
fileManager filemanager.FileManager
|
||||
proxyStore store.UpstreamProxyConfigRepository
|
||||
tx dbtx.Transactor
|
||||
registryDao store.RegistryRepository
|
||||
imageDao store.ImageRepository
|
||||
artifactDao store.ArtifactRepository
|
||||
urlProvider urlprovider.Provider
|
||||
localRegistryHelper LocalRegistryHelper
|
||||
localBase base.LocalBase
|
||||
fileManager filemanager.FileManager
|
||||
proxyStore store.UpstreamProxyConfigRepository
|
||||
tx dbtx.Transactor
|
||||
registryDao store.RegistryRepository
|
||||
imageDao store.ImageRepository
|
||||
artifactDao store.ArtifactRepository
|
||||
urlProvider urlprovider.Provider
|
||||
registryIndexService index.Service
|
||||
}
|
||||
|
||||
type LocalRegistry interface {
|
||||
@ -64,18 +66,18 @@ func NewLocalRegistry(
|
||||
imageDao store.ImageRepository,
|
||||
artifactDao store.ArtifactRepository,
|
||||
urlProvider urlprovider.Provider,
|
||||
localRegistryHelper LocalRegistryHelper,
|
||||
registryIndexService index.Service,
|
||||
) LocalRegistry {
|
||||
return &localRegistry{
|
||||
localBase: localBase,
|
||||
fileManager: fileManager,
|
||||
proxyStore: proxyStore,
|
||||
tx: tx,
|
||||
registryDao: registryDao,
|
||||
imageDao: imageDao,
|
||||
artifactDao: artifactDao,
|
||||
urlProvider: urlProvider,
|
||||
localRegistryHelper: localRegistryHelper,
|
||||
localBase: localBase,
|
||||
fileManager: fileManager,
|
||||
proxyStore: proxyStore,
|
||||
tx: tx,
|
||||
registryDao: registryDao,
|
||||
imageDao: imageDao,
|
||||
artifactDao: artifactDao,
|
||||
urlProvider: urlProvider,
|
||||
registryIndexService: registryIndexService,
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +105,7 @@ func (c *localRegistry) UploadPackageFile(
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
p, err := parsePackage(r)
|
||||
p, err := rpmutil.ParsePackage(r)
|
||||
if err != nil {
|
||||
log.Printf("failded to parse rpm package: %v", err)
|
||||
return nil, "", err
|
||||
@ -129,7 +131,7 @@ func (c *localRegistry) UploadPackageFile(
|
||||
}
|
||||
|
||||
//TODO: make it async / atomic operation, implement artifact status (sync successful, sync failed..... statuses)
|
||||
err = c.localRegistryHelper.BuildRegistryFiles(ctx, info)
|
||||
err = c.registryIndexService.RegenerateRpmRepoData(ctx, info.RegistryID, info.RootParentID, info.RootIdentifier)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -152,7 +154,7 @@ func (c *localRegistry) GetRepoData(
|
||||
}
|
||||
|
||||
fileReader, _, redirectURL, err := c.fileManager.DownloadFile(
|
||||
ctx, "/"+RepoDataPrefix+fileName, info.RegistryID, info.RegIdentifier, info.RootIdentifier,
|
||||
ctx, "/"+rpmutil.RepoDataPrefix+fileName, info.RegistryID, info.RegIdentifier, info.RootIdentifier,
|
||||
)
|
||||
if err != nil {
|
||||
return responseHeaders, nil, nil, "", err
|
||||
|
@ -1,316 +0,0 @@
|
||||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
rpmmetadata "github.com/harness/gitness/registry/app/metadata/rpm"
|
||||
"github.com/harness/gitness/registry/app/pkg/filemanager"
|
||||
rpmtype "github.com/harness/gitness/registry/app/pkg/types/rpm"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
)
|
||||
|
||||
const artifactBatchLimit = 50
|
||||
|
||||
type LocalRegistryHelper interface {
|
||||
BuildRegistryFiles(ctx context.Context, info rpmtype.ArtifactInfo) error
|
||||
}
|
||||
|
||||
type localRegistryHelper struct {
|
||||
fileManager filemanager.FileManager
|
||||
artifactDao store.ArtifactRepository
|
||||
}
|
||||
|
||||
func NewLocalRegistryHelper(
|
||||
fileManager filemanager.FileManager,
|
||||
artifactDao store.ArtifactRepository,
|
||||
) LocalRegistryHelper {
|
||||
return &localRegistryHelper{
|
||||
fileManager: fileManager,
|
||||
artifactDao: artifactDao,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *localRegistryHelper) BuildRegistryFiles(ctx context.Context, info rpmtype.ArtifactInfo) error {
|
||||
lastArtifactID := int64(0)
|
||||
var packageInfos []*packageInfo
|
||||
|
||||
for {
|
||||
artifacts, err := l.artifactDao.GetAllArtifactsByRepo(ctx, info.RegistryID, artifactBatchLimit, lastArtifactID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, a := range *artifacts {
|
||||
metadata := rpmmetadata.RpmMetadata{}
|
||||
err := json.Unmarshal(a.Metadata, &metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
packageInfos = append(packageInfos, &packageInfo{
|
||||
Name: a.Name,
|
||||
Sha256: metadata.GetFiles()[0].Sha256,
|
||||
Size: metadata.GetFiles()[0].Size,
|
||||
VersionMetadata: &metadata.VersionMetadata,
|
||||
FileMetadata: &metadata.FileMetadata,
|
||||
})
|
||||
if a.ID > lastArtifactID {
|
||||
lastArtifactID = a.ID
|
||||
}
|
||||
}
|
||||
if len(*artifacts) < artifactBatchLimit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
primary, err := l.buildPrimary(ctx, packageInfos, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileLists, err := l.buildFileLists(ctx, packageInfos, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
other, err := l.buildOther(ctx, packageInfos, info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return l.buildRepomd(ctx, []*repoData{
|
||||
primary,
|
||||
fileLists,
|
||||
other,
|
||||
}, info)
|
||||
}
|
||||
|
||||
func (l *localRegistryHelper) buildPrimary(
|
||||
ctx context.Context,
|
||||
pds []*packageInfo,
|
||||
info rpmtype.ArtifactInfo,
|
||||
) (*repoData, error) {
|
||||
packages := make([]*primaryPackage, 0, len(pds))
|
||||
for _, pd := range pds {
|
||||
files := make([]*rpmmetadata.File, 0, 3)
|
||||
for _, f := range pd.FileMetadata.Files {
|
||||
if f.IsExecutable {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
packageVersion := fmt.Sprintf("%s-%s", pd.FileMetadata.Version, pd.FileMetadata.Release)
|
||||
packages = append(packages, &primaryPackage{
|
||||
Type: "rpm",
|
||||
Name: pd.Name,
|
||||
Architecture: pd.FileMetadata.Architecture,
|
||||
Version: primaryVersion{
|
||||
Epoch: pd.FileMetadata.Epoch,
|
||||
Version: pd.FileMetadata.Version,
|
||||
Release: pd.FileMetadata.Release,
|
||||
},
|
||||
Checksum: primaryChecksum{
|
||||
Type: "sha256",
|
||||
Checksum: pd.Sha256,
|
||||
Pkgid: "YES",
|
||||
},
|
||||
Summary: pd.VersionMetadata.Summary,
|
||||
Description: pd.VersionMetadata.Description,
|
||||
Packager: pd.FileMetadata.Packager,
|
||||
URL: pd.VersionMetadata.ProjectURL,
|
||||
Time: primaryTimes{
|
||||
File: pd.FileMetadata.FileTime,
|
||||
Build: pd.FileMetadata.BuildTime,
|
||||
},
|
||||
Size: primarySizes{
|
||||
Package: pd.Size,
|
||||
Installed: pd.FileMetadata.InstalledSize,
|
||||
Archive: pd.FileMetadata.ArchiveSize,
|
||||
},
|
||||
Location: PrimaryLocation{
|
||||
Href: fmt.Sprintf("package/%s/%s/%s/%s",
|
||||
url.PathEscape(pd.Name),
|
||||
url.PathEscape(packageVersion),
|
||||
url.PathEscape(pd.FileMetadata.Architecture),
|
||||
url.PathEscape(fmt.Sprintf("%s-%s.%s.rpm", pd.Name, packageVersion, pd.FileMetadata.Architecture))),
|
||||
},
|
||||
Format: primaryFormat{
|
||||
License: pd.VersionMetadata.License,
|
||||
Vendor: pd.FileMetadata.Vendor,
|
||||
Group: pd.FileMetadata.Group,
|
||||
Buildhost: pd.FileMetadata.BuildHost,
|
||||
Sourcerpm: pd.FileMetadata.SourceRpm,
|
||||
Provides: primaryEntryList{
|
||||
Entries: pd.FileMetadata.Provides,
|
||||
},
|
||||
Requires: primaryEntryList{
|
||||
Entries: pd.FileMetadata.Requires,
|
||||
},
|
||||
Conflicts: primaryEntryList{
|
||||
Entries: pd.FileMetadata.Conflicts,
|
||||
},
|
||||
Obsoletes: primaryEntryList{
|
||||
Entries: pd.FileMetadata.Obsoletes,
|
||||
},
|
||||
Files: files,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return l.addDataAsFileToRepo(ctx, "primary", &primaryMetadata{
|
||||
Xmlns: "http://linux.duke.edu/metadata/common",
|
||||
XmlnsRpm: "http://linux.duke.edu/metadata/rpm",
|
||||
PackageCount: len(pds),
|
||||
Packages: packages,
|
||||
}, info)
|
||||
}
|
||||
|
||||
func (l *localRegistryHelper) buildOther(
|
||||
ctx context.Context,
|
||||
pds []*packageInfo,
|
||||
info rpmtype.ArtifactInfo,
|
||||
) (*repoData, error) {
|
||||
packages := make([]*otherPackage, 0, len(pds))
|
||||
for _, pd := range pds {
|
||||
packages = append(packages, &otherPackage{
|
||||
Pkgid: pd.Sha256,
|
||||
Name: pd.Name,
|
||||
Architecture: pd.FileMetadata.Architecture,
|
||||
Version: otherVersion{
|
||||
Epoch: pd.FileMetadata.Epoch,
|
||||
Version: pd.FileMetadata.Version,
|
||||
Release: pd.FileMetadata.Release,
|
||||
},
|
||||
Changelogs: pd.FileMetadata.Changelogs,
|
||||
})
|
||||
}
|
||||
|
||||
return l.addDataAsFileToRepo(ctx, "other", &otherdata{
|
||||
Xmlns: "http://linux.duke.edu/metadata/other",
|
||||
PackageCount: len(pds),
|
||||
Packages: packages,
|
||||
}, info)
|
||||
}
|
||||
|
||||
func (l *localRegistryHelper) buildFileLists(
|
||||
ctx context.Context,
|
||||
pds []*packageInfo,
|
||||
info rpmtype.ArtifactInfo,
|
||||
) (*repoData, error) { //nolint:dupl
|
||||
packages := make([]*fileListPackage, 0, len(pds))
|
||||
for _, pd := range pds {
|
||||
packages = append(packages, &fileListPackage{
|
||||
Pkgid: pd.Sha256,
|
||||
Name: pd.Name,
|
||||
Architecture: pd.FileMetadata.Architecture,
|
||||
Version: fileListVersion{
|
||||
Epoch: pd.FileMetadata.Epoch,
|
||||
Version: pd.FileMetadata.Version,
|
||||
Release: pd.FileMetadata.Release,
|
||||
},
|
||||
Files: pd.FileMetadata.Files,
|
||||
})
|
||||
}
|
||||
|
||||
return l.addDataAsFileToRepo(ctx, "filelists", &filelists{
|
||||
Xmlns: "http://linux.duke.edu/metadata/other",
|
||||
PackageCount: len(pds),
|
||||
Packages: packages,
|
||||
}, info)
|
||||
}
|
||||
|
||||
func (l *localRegistryHelper) buildRepomd(
|
||||
ctx context.Context,
|
||||
data []*repoData,
|
||||
info rpmtype.ArtifactInfo,
|
||||
) error {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(xml.Header)
|
||||
if err := xml.NewEncoder(&buf).Encode(&repomd{
|
||||
Xmlns: "http://linux.duke.edu/metadata/repo",
|
||||
XmlnsRpm: "http://linux.duke.edu/metadata/rpm",
|
||||
Data: data,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
repomdContent, _ := CreateHashedBufferFromReader(&buf)
|
||||
defer repomdContent.Close()
|
||||
|
||||
_, err := l.fileManager.UploadFile(ctx, RepoDataPrefix+RepoMdFile, info.RegistryID,
|
||||
info.RootParentID, info.RootIdentifier, repomdContent, repomdContent, RepoMdFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *localRegistryHelper) addDataAsFileToRepo(
|
||||
ctx context.Context,
|
||||
filetype string,
|
||||
obj any,
|
||||
info rpmtype.ArtifactInfo,
|
||||
) (*repoData, error) {
|
||||
content, _ := NewHashedBuffer()
|
||||
defer content.Close()
|
||||
|
||||
gzw := gzip.NewWriter(content)
|
||||
wc := &writtenCounter{}
|
||||
h := sha256.New()
|
||||
|
||||
w := io.MultiWriter(gzw, wc, h)
|
||||
_, _ = w.Write([]byte(xml.Header))
|
||||
|
||||
if err := xml.NewEncoder(w).Encode(obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := gzw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filename := filetype + ".xml.gz"
|
||||
_, err := l.fileManager.UploadFile(ctx, RepoDataPrefix+filename, info.RegistryID,
|
||||
info.RootParentID, info.RootIdentifier, content, content, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, hashSHA256, _ := content.Sums()
|
||||
|
||||
return &repoData{
|
||||
Type: filetype,
|
||||
Checksum: repoChecksum{
|
||||
Type: "sha256",
|
||||
Value: hex.EncodeToString(hashSHA256),
|
||||
},
|
||||
OpenChecksum: repoChecksum{
|
||||
Type: "sha256",
|
||||
Value: hex.EncodeToString(h.Sum(nil)),
|
||||
},
|
||||
Location: repoLocation{
|
||||
Href: "repodata/" + filename,
|
||||
},
|
||||
Timestamp: time.Now().Unix(),
|
||||
Size: content.Size(),
|
||||
OpenSize: wc.Written(),
|
||||
}, nil
|
||||
}
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/harness/gitness/registry/app/pkg/base"
|
||||
"github.com/harness/gitness/registry/app/pkg/filemanager"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/registry/services/index"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
|
||||
"github.com/google/wire"
|
||||
@ -33,10 +34,10 @@ func LocalRegistryProvider(
|
||||
imageDao store.ImageRepository,
|
||||
artifactDao store.ArtifactRepository,
|
||||
urlProvider urlprovider.Provider,
|
||||
helper LocalRegistryHelper,
|
||||
registryIndexService index.Service,
|
||||
) LocalRegistry {
|
||||
registry := NewLocalRegistry(localBase, fileManager, proxyStore, tx, registryDao, imageDao, artifactDao,
|
||||
urlProvider, helper)
|
||||
urlProvider, registryIndexService)
|
||||
base.Register(registry)
|
||||
return registry
|
||||
}
|
||||
@ -55,11 +56,4 @@ func ProxyProvider(
|
||||
return proxy
|
||||
}
|
||||
|
||||
func LocalRegistryHelperProvider(
|
||||
fileManager filemanager.FileManager,
|
||||
artifactDao store.ArtifactRepository,
|
||||
) LocalRegistryHelper {
|
||||
return NewLocalRegistryHelper(fileManager, artifactDao)
|
||||
}
|
||||
|
||||
var WireSet = wire.NewSet(LocalRegistryProvider, ProxyProvider, LocalRegistryHelperProvider)
|
||||
var WireSet = wire.NewSet(LocalRegistryProvider, ProxyProvider)
|
||||
|
@ -17,20 +17,29 @@ package rpm
|
||||
//nolint:gosec
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
rpmmetadata "github.com/harness/gitness/registry/app/metadata/rpm"
|
||||
"github.com/harness/gitness/registry/app/pkg/filemanager"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/registry/validation"
|
||||
|
||||
"github.com/sassoftware/go-rpmutils"
|
||||
@ -52,7 +61,8 @@ const (
|
||||
RepoMdFile = "repomd.xml"
|
||||
RepoDataPrefix = "repodata/"
|
||||
|
||||
DefaultMemorySize = 32 * 1024 * 1024
|
||||
DefaultMemorySize = 32 * 1024 * 1024
|
||||
artifactBatchLimit = 50
|
||||
)
|
||||
|
||||
var (
|
||||
@ -60,7 +70,306 @@ var (
|
||||
ErrWriteAfterRead = errors.New("write is unsupported after a read operation")
|
||||
)
|
||||
|
||||
func parsePackage(r io.Reader) (*rpmPackage, error) {
|
||||
type RegistryHelper interface {
|
||||
BuildRegistryFiles(ctx context.Context, registryID int64, rootParentID int64, rootIdentifier string) error
|
||||
}
|
||||
|
||||
type registryHelper struct {
|
||||
fileManager filemanager.FileManager
|
||||
artifactDao store.ArtifactRepository
|
||||
}
|
||||
|
||||
func NewRegistryHelper(
|
||||
fileManager filemanager.FileManager,
|
||||
artifactDao store.ArtifactRepository,
|
||||
) RegistryHelper {
|
||||
return ®istryHelper{
|
||||
fileManager: fileManager,
|
||||
artifactDao: artifactDao,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *registryHelper) BuildRegistryFiles(
|
||||
ctx context.Context,
|
||||
registryID int64,
|
||||
rootParentID int64,
|
||||
rootIdentifier string,
|
||||
) error {
|
||||
lastArtifactID := int64(0)
|
||||
var packageInfos []*packageInfo
|
||||
|
||||
for {
|
||||
artifacts, err := l.artifactDao.GetAllArtifactsByRepo(ctx, registryID, artifactBatchLimit, lastArtifactID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, a := range *artifacts {
|
||||
metadata := rpmmetadata.RpmMetadata{}
|
||||
err := json.Unmarshal(a.Metadata, &metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
packageInfos = append(packageInfos, &packageInfo{
|
||||
Name: a.Name,
|
||||
Sha256: metadata.GetFiles()[0].Sha256,
|
||||
Size: metadata.GetFiles()[0].Size,
|
||||
VersionMetadata: &metadata.VersionMetadata,
|
||||
FileMetadata: &metadata.FileMetadata,
|
||||
})
|
||||
if a.ID > lastArtifactID {
|
||||
lastArtifactID = a.ID
|
||||
}
|
||||
}
|
||||
if len(*artifacts) < artifactBatchLimit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
primary, err := l.buildPrimary(ctx, packageInfos, registryID, rootParentID, rootIdentifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileLists, err := l.buildFileLists(ctx, packageInfos, registryID, rootParentID, rootIdentifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
other, err := l.buildOther(ctx, packageInfos, registryID, rootParentID, rootIdentifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return l.buildRepomd(ctx, []*repoData{
|
||||
primary,
|
||||
fileLists,
|
||||
other,
|
||||
}, registryID, rootParentID, rootIdentifier)
|
||||
}
|
||||
|
||||
func (l *registryHelper) buildPrimary(
|
||||
ctx context.Context,
|
||||
pds []*packageInfo,
|
||||
registryID int64,
|
||||
rootParentID int64,
|
||||
rootIdentifier string,
|
||||
) (*repoData, error) {
|
||||
packages := make([]*primaryPackage, 0, len(pds))
|
||||
for _, pd := range pds {
|
||||
files := make([]*rpmmetadata.File, 0, 3)
|
||||
for _, f := range pd.FileMetadata.Files {
|
||||
if f.IsExecutable {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
packageVersion := fmt.Sprintf("%s-%s", pd.FileMetadata.Version, pd.FileMetadata.Release)
|
||||
packages = append(packages, &primaryPackage{
|
||||
Type: "rpm",
|
||||
Name: pd.Name,
|
||||
Architecture: pd.FileMetadata.Architecture,
|
||||
Version: primaryVersion{
|
||||
Epoch: pd.FileMetadata.Epoch,
|
||||
Version: pd.FileMetadata.Version,
|
||||
Release: pd.FileMetadata.Release,
|
||||
},
|
||||
Checksum: primaryChecksum{
|
||||
Type: "sha256",
|
||||
Checksum: pd.Sha256,
|
||||
Pkgid: "YES",
|
||||
},
|
||||
Summary: pd.VersionMetadata.Summary,
|
||||
Description: pd.VersionMetadata.Description,
|
||||
Packager: pd.FileMetadata.Packager,
|
||||
URL: pd.VersionMetadata.ProjectURL,
|
||||
Time: primaryTimes{
|
||||
File: pd.FileMetadata.FileTime,
|
||||
Build: pd.FileMetadata.BuildTime,
|
||||
},
|
||||
Size: primarySizes{
|
||||
Package: pd.Size,
|
||||
Installed: pd.FileMetadata.InstalledSize,
|
||||
Archive: pd.FileMetadata.ArchiveSize,
|
||||
},
|
||||
Location: PrimaryLocation{
|
||||
Href: fmt.Sprintf("package/%s/%s/%s/%s",
|
||||
url.PathEscape(pd.Name),
|
||||
url.PathEscape(packageVersion),
|
||||
url.PathEscape(pd.FileMetadata.Architecture),
|
||||
url.PathEscape(fmt.Sprintf("%s-%s.%s.rpm", pd.Name, packageVersion, pd.FileMetadata.Architecture))),
|
||||
},
|
||||
Format: primaryFormat{
|
||||
License: pd.VersionMetadata.License,
|
||||
Vendor: pd.FileMetadata.Vendor,
|
||||
Group: pd.FileMetadata.Group,
|
||||
Buildhost: pd.FileMetadata.BuildHost,
|
||||
Sourcerpm: pd.FileMetadata.SourceRpm,
|
||||
Provides: primaryEntryList{
|
||||
Entries: pd.FileMetadata.Provides,
|
||||
},
|
||||
Requires: primaryEntryList{
|
||||
Entries: pd.FileMetadata.Requires,
|
||||
},
|
||||
Conflicts: primaryEntryList{
|
||||
Entries: pd.FileMetadata.Conflicts,
|
||||
},
|
||||
Obsoletes: primaryEntryList{
|
||||
Entries: pd.FileMetadata.Obsoletes,
|
||||
},
|
||||
Files: files,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
primaryData := &primaryMetadata{
|
||||
Xmlns: "http://linux.duke.edu/metadata/common",
|
||||
XmlnsRpm: "http://linux.duke.edu/metadata/rpm",
|
||||
PackageCount: len(pds),
|
||||
Packages: packages,
|
||||
}
|
||||
return l.addDataAsFileToRepo(ctx, "primary", primaryData, registryID, rootParentID, rootIdentifier)
|
||||
}
|
||||
|
||||
func (l *registryHelper) buildOther(
|
||||
ctx context.Context,
|
||||
pds []*packageInfo,
|
||||
registryID int64,
|
||||
rootParentID int64,
|
||||
rootIdentifier string,
|
||||
) (*repoData, error) {
|
||||
packages := make([]*otherPackage, 0, len(pds))
|
||||
for _, pd := range pds {
|
||||
packages = append(packages, &otherPackage{
|
||||
Pkgid: pd.Sha256,
|
||||
Name: pd.Name,
|
||||
Architecture: pd.FileMetadata.Architecture,
|
||||
Version: otherVersion{
|
||||
Epoch: pd.FileMetadata.Epoch,
|
||||
Version: pd.FileMetadata.Version,
|
||||
Release: pd.FileMetadata.Release,
|
||||
},
|
||||
Changelogs: pd.FileMetadata.Changelogs,
|
||||
})
|
||||
}
|
||||
otherData := &otherdata{
|
||||
Xmlns: "http://linux.duke.edu/metadata/other",
|
||||
PackageCount: len(pds),
|
||||
Packages: packages,
|
||||
}
|
||||
|
||||
return l.addDataAsFileToRepo(ctx, "other", otherData, registryID, rootParentID, rootIdentifier)
|
||||
}
|
||||
|
||||
func (l *registryHelper) buildFileLists(
|
||||
ctx context.Context,
|
||||
pds []*packageInfo,
|
||||
registryID int64,
|
||||
rootParentID int64,
|
||||
rootIdentifier string,
|
||||
) (*repoData, error) { //nolint:dupl
|
||||
packages := make([]*fileListPackage, 0, len(pds))
|
||||
for _, pd := range pds {
|
||||
packages = append(packages, &fileListPackage{
|
||||
Pkgid: pd.Sha256,
|
||||
Name: pd.Name,
|
||||
Architecture: pd.FileMetadata.Architecture,
|
||||
Version: fileListVersion{
|
||||
Epoch: pd.FileMetadata.Epoch,
|
||||
Version: pd.FileMetadata.Version,
|
||||
Release: pd.FileMetadata.Release,
|
||||
},
|
||||
Files: pd.FileMetadata.Files,
|
||||
})
|
||||
}
|
||||
|
||||
fileLists := &filelists{
|
||||
Xmlns: "http://linux.duke.edu/metadata/other",
|
||||
PackageCount: len(pds),
|
||||
Packages: packages,
|
||||
}
|
||||
|
||||
return l.addDataAsFileToRepo(ctx, "filelists", fileLists, registryID, rootParentID, rootIdentifier)
|
||||
}
|
||||
|
||||
func (l *registryHelper) buildRepomd(
|
||||
ctx context.Context,
|
||||
data []*repoData,
|
||||
registryID int64,
|
||||
rootParentID int64,
|
||||
rootIdentifier string,
|
||||
) error {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(xml.Header)
|
||||
if err := xml.NewEncoder(&buf).Encode(&repomd{
|
||||
Xmlns: "http://linux.duke.edu/metadata/repo",
|
||||
XmlnsRpm: "http://linux.duke.edu/metadata/rpm",
|
||||
Data: data,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
repomdContent, _ := CreateHashedBufferFromReader(&buf)
|
||||
defer repomdContent.Close()
|
||||
|
||||
_, err := l.fileManager.UploadFile(ctx, RepoDataPrefix+RepoMdFile, registryID,
|
||||
rootParentID, rootIdentifier, repomdContent, repomdContent, RepoMdFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *registryHelper) addDataAsFileToRepo(
|
||||
ctx context.Context,
|
||||
filetype string,
|
||||
obj any,
|
||||
registryID int64,
|
||||
rootParentID int64,
|
||||
rootIdentifier string,
|
||||
) (*repoData, error) {
|
||||
content, _ := NewHashedBuffer()
|
||||
defer content.Close()
|
||||
|
||||
gzw := gzip.NewWriter(content)
|
||||
wc := &writtenCounter{}
|
||||
h := sha256.New()
|
||||
|
||||
w := io.MultiWriter(gzw, wc, h)
|
||||
_, _ = w.Write([]byte(xml.Header))
|
||||
|
||||
if err := xml.NewEncoder(w).Encode(obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := gzw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filename := filetype + ".xml.gz"
|
||||
_, err := l.fileManager.UploadFile(ctx, RepoDataPrefix+filename, registryID,
|
||||
rootParentID, rootIdentifier, content, content, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, hashSHA256, _ := content.Sums()
|
||||
|
||||
return &repoData{
|
||||
Type: filetype,
|
||||
Checksum: repoChecksum{
|
||||
Type: "sha256",
|
||||
Value: hex.EncodeToString(hashSHA256),
|
||||
},
|
||||
OpenChecksum: repoChecksum{
|
||||
Type: "sha256",
|
||||
Value: hex.EncodeToString(h.Sum(nil)),
|
||||
},
|
||||
Location: repoLocation{
|
||||
Href: "repodata/" + filename,
|
||||
},
|
||||
Timestamp: time.Now().Unix(),
|
||||
Size: content.Size(),
|
||||
OpenSize: wc.Written(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParsePackage(r io.Reader) (*Package, error) {
|
||||
rpm, err := rpmutils.ReadRpm(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -76,7 +385,7 @@ func parsePackage(r io.Reader) (*rpmPackage, error) {
|
||||
version = fmt.Sprintf("%s-%s", nevra.Epoch, version)
|
||||
}
|
||||
|
||||
p := &rpmPackage{
|
||||
p := &Package{
|
||||
Name: nevra.Name,
|
||||
Version: version,
|
||||
VersionMetadata: &rpmmetadata.VersionMetadata{
|
@ -165,7 +165,7 @@ type packageInfo struct {
|
||||
FileMetadata *rpmmetadata.FileMetadata
|
||||
}
|
||||
|
||||
type rpmPackage struct {
|
||||
type Package struct {
|
||||
Name string
|
||||
Version string
|
||||
VersionMetadata *rpmmetadata.VersionMetadata
|
31
registry/app/utils/rpm/wire.go
Normal file
31
registry/app/utils/rpm/wire.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpm
|
||||
|
||||
import (
|
||||
"github.com/harness/gitness/registry/app/pkg/filemanager"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
func LocalRegistryHelperProvider(
|
||||
fileManager filemanager.FileManager,
|
||||
artifactDao store.ArtifactRepository,
|
||||
) RegistryHelper {
|
||||
return NewRegistryHelper(fileManager, artifactDao)
|
||||
}
|
||||
|
||||
var WireSet = wire.NewSet(LocalRegistryHelperProvider)
|
47
registry/services/index/service.go
Normal file
47
registry/services/index/service.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package index
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/harness/gitness/registry/app/utils/rpm"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
RegenerateRpmRepoData(ctx context.Context, registryID int64, rootParentID int64, rootIdentifier string) error
|
||||
}
|
||||
|
||||
type service struct {
|
||||
rpmRegistryHelper rpm.RegistryHelper
|
||||
}
|
||||
|
||||
func (s *service) RegenerateRpmRepoData(
|
||||
ctx context.Context,
|
||||
registryID int64,
|
||||
rootParentID int64,
|
||||
rootIdentifier string,
|
||||
) error {
|
||||
// TODO: integrate with distributed lock
|
||||
return s.rpmRegistryHelper.BuildRegistryFiles(ctx, registryID, rootParentID, rootIdentifier)
|
||||
}
|
||||
|
||||
func NewService(
|
||||
rpmRegistryHelper rpm.RegistryHelper,
|
||||
) Service {
|
||||
return &service{
|
||||
rpmRegistryHelper: rpmRegistryHelper,
|
||||
}
|
||||
}
|
32
registry/services/index/wire.go
Normal file
32
registry/services/index/wire.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2023 Harness, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package index
|
||||
|
||||
import (
|
||||
"github.com/harness/gitness/registry/app/utils/rpm"
|
||||
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
// WireSet provides a wire set for this package.
|
||||
var WireSet = wire.NewSet(
|
||||
ProvideService,
|
||||
)
|
||||
|
||||
func ProvideService(
|
||||
rpmRegistryHelper rpm.RegistryHelper,
|
||||
) Service {
|
||||
return NewService(rpmRegistryHelper)
|
||||
}
|
@ -20,7 +20,7 @@ import (
|
||||
gitnesswebhook "github.com/harness/gitness/app/services/webhook"
|
||||
)
|
||||
|
||||
// Service interface for webhook operations.
|
||||
// ServiceInterface interface for webhook operations.
|
||||
type ServiceInterface interface {
|
||||
ReTriggerWebhookExecution(ctx context.Context, webhookExecutionID int64) (*gitnesswebhook.TriggerResult, error)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user