mirror of https://github.com/harness/drone.git
feat: [AH-1158]: Fixed around auth, download command and metadata api update (#3610)
* [AH-1158]: Fixed around auth, download command and metadata api updatemain
parent
edcad7b744
commit
cc86280a6c
|
@ -369,6 +369,8 @@ func GetPullCommand(
|
|||
return GetHelmPullCommand(image, tag, registryURL)
|
||||
case string(a.PackageTypeGENERIC):
|
||||
return GetGenericArtifactFileDownloadCommand(registryURL, image, tag, "<FILENAME>")
|
||||
case string(a.PackageTypePYTHON):
|
||||
return GetPythonDownloadCommand(image, tag)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
@ -385,6 +387,22 @@ func GetHelmPullCommand(image string, tag string, registryURL string) string {
|
|||
return "helm pull oci://" + GetRepoURLWithoutProtocol(registryURL) + "/" + image + " --version " + tag
|
||||
}
|
||||
|
||||
func GetPythonDownloadCommand(artifact, version string) string {
|
||||
downloadCommand := "pip 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 GetGenericArtifactFileDownloadCommand(regURL, artifact, version, filename string) string {
|
||||
downloadCommand := "curl --location '<HOSTNAME>/<ARTIFACT>:<VERSION>:<FILENAME>' --header 'x-api-key: <API_KEY>'" +
|
||||
" -J -O"
|
||||
|
|
|
@ -68,7 +68,7 @@ func (r *remoteRegistryHelper) init(
|
|||
spaceFinder refcache.SpaceFinder,
|
||||
service secret.Service,
|
||||
) error {
|
||||
key := string(artifact.UpstreamConfigSourcePyPi)
|
||||
key := string(artifact.PackageTypePYTHON)
|
||||
if r.registry.Source == string(artifact.UpstreamConfigSourcePyPi) {
|
||||
r.registry.RepoURL = pypi.PyPiURL
|
||||
}
|
||||
|
|
|
@ -17,9 +17,11 @@ package pypi
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
|
@ -48,14 +50,33 @@ type SimpleMetadata struct {
|
|||
}
|
||||
|
||||
type Package struct {
|
||||
Name string
|
||||
ATags map[string]string
|
||||
SimpleURL string
|
||||
Name string
|
||||
ATags map[string]string
|
||||
}
|
||||
|
||||
// URL returns the "href" attribute from the package's map.
|
||||
func (p Package) URL() string {
|
||||
return p.ATags["href"]
|
||||
href := p.ATags["href"]
|
||||
parsedURL, err := url.Parse(href)
|
||||
if err != nil {
|
||||
return href
|
||||
}
|
||||
|
||||
if parsedURL.IsAbs() {
|
||||
return href
|
||||
}
|
||||
|
||||
// If href is relative, resolve it against SimpleURL
|
||||
baseURL, err := url.Parse(p.SimpleURL)
|
||||
if err != nil {
|
||||
log.Err(err).Msgf("failed to parse url %s", p.SimpleURL)
|
||||
return href
|
||||
}
|
||||
|
||||
return baseURL.ResolveReference(parsedURL).String()
|
||||
}
|
||||
|
||||
func (p Package) Valid() bool {
|
||||
return p.URL() != "" && p.Name != ""
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/harness/gitness/app/services/refcache"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/app/common/lib"
|
||||
"github.com/harness/gitness/registry/app/common/lib/errors"
|
||||
adp "github.com/harness/gitness/registry/app/remote/adapter"
|
||||
|
@ -60,7 +61,8 @@ func NewAdapter(
|
|||
return nil
|
||||
}
|
||||
|
||||
adapter.Client = registry.NewClient(url, accessKey, secretKey, false)
|
||||
adapter.Client = registry.NewClient(url, accessKey, secretKey, false,
|
||||
reg.PackageType == artifact.PackageTypeDOCKER || reg.PackageType == artifact.PackageTypeHELM)
|
||||
return adapter
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ func (f *factory) Create(
|
|||
}
|
||||
|
||||
func init() {
|
||||
adapterType := string(artifact.UpstreamConfigSourcePyPi)
|
||||
adapterType := string(artifact.PackageTypePYTHON)
|
||||
if err := adp.RegisterFactory(adapterType, new(factory)); err != nil {
|
||||
log.Error().Stack().Err(err).Msgf("Failed to register adapter factory for %s", adapterType)
|
||||
return
|
||||
|
@ -85,12 +85,16 @@ func init() {
|
|||
}
|
||||
|
||||
func (a *adapter) GetMetadata(_ context.Context, pkg string) (*pypi.SimpleMetadata, error) {
|
||||
_, readCloser, err := a.GetFile("simple/" + pkg)
|
||||
filePath := "simple/" + pkg
|
||||
if a.registry.RepoURL != PyPiURL {
|
||||
filePath += "/index.html"
|
||||
}
|
||||
_, readCloser, err := a.GetFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer readCloser.Close()
|
||||
response, err := ParsePyPISimple(readCloser)
|
||||
response, err := ParsePyPISimple(readCloser, a.GetURL(filePath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -170,7 +174,7 @@ func ParseMetadata(ctx context.Context, body io.ReadCloser) (python.Metadata, er
|
|||
}
|
||||
|
||||
// ParsePyPISimple parses the given HTML and returns a SimpleMetadata DTO.
|
||||
func ParsePyPISimple(r io.ReadCloser) (pypi.SimpleMetadata, error) {
|
||||
func ParsePyPISimple(r io.ReadCloser, url string) (pypi.SimpleMetadata, error) {
|
||||
doc, err := html.Parse(r)
|
||||
if err != nil {
|
||||
return pypi.SimpleMetadata{}, err
|
||||
|
@ -214,8 +218,9 @@ func ParsePyPISimple(r io.ReadCloser) (pypi.SimpleMetadata, error) {
|
|||
linkText = n.FirstChild.Data
|
||||
}
|
||||
packages = append(packages, pypi.Package{
|
||||
ATags: aMap,
|
||||
Name: linkText,
|
||||
SimpleURL: url,
|
||||
ATags: aMap,
|
||||
Name: linkText,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,10 +34,11 @@ import (
|
|||
)
|
||||
|
||||
// NewAuthorizer creates an authorizer that can handle different auth schemes.
|
||||
func NewAuthorizer(username, password string, insecure bool) lib.Authorizer {
|
||||
func NewAuthorizer(username, password string, insecure, isOCI bool) lib.Authorizer {
|
||||
return &authorizer{
|
||||
username: username,
|
||||
password: password,
|
||||
isOCI: isOCI,
|
||||
client: &http.Client{
|
||||
Transport: commonhttp.GetHTTPTransport(commonhttp.WithInsecure(insecure)),
|
||||
},
|
||||
|
@ -54,6 +55,7 @@ type authorizer struct {
|
|||
client *http.Client
|
||||
url *url.URL // registry URL
|
||||
authorizer modifier.Modifier // the underlying authorizer
|
||||
isOCI bool
|
||||
}
|
||||
|
||||
func (a *authorizer) Modify(req *http.Request) error {
|
||||
|
@ -82,6 +84,12 @@ func (a *authorizer) initialize(u *url.URL) error {
|
|||
if a.authorizer != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !a.isOCI {
|
||||
a.authorizer = basic.NewAuthorizer(a.username, a.password)
|
||||
return nil
|
||||
}
|
||||
|
||||
url, err := url.Parse(u.Scheme + "://" + u.Host + "/v2/")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse URL for scheme %s and host %s: %w", u.Scheme, u.Host, err)
|
||||
|
@ -128,15 +136,17 @@ func (a *authorizer) initialize(u *url.URL) error {
|
|||
// If not, the request shouldn't be handled by the authorizer, e.g., requests sent to backend storage (S3, etc.).
|
||||
func (a *authorizer) isTarget(req *http.Request) bool {
|
||||
// Check if the path contains the versioned API endpoint (e.g., "/v2/")
|
||||
const versionedPath = "/v2/"
|
||||
if !strings.Contains(req.URL.Path, versionedPath) {
|
||||
return false
|
||||
}
|
||||
if a.isOCI {
|
||||
const versionedPath = "/v2/"
|
||||
if !strings.Contains(req.URL.Path, versionedPath) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ensure that the request's host, scheme, and versioned path match the authorizer's URL.
|
||||
if req.URL.Host != a.url.Host || req.URL.Scheme != a.url.Scheme ||
|
||||
!strings.HasPrefix(req.URL.Path, a.url.Path) {
|
||||
return false
|
||||
// Ensure that the request's host, scheme, and versioned path match the authorizer's URL.
|
||||
if req.URL.Host != a.url.Host || req.URL.Scheme != a.url.Scheme ||
|
||||
!strings.HasPrefix(req.URL.Path, a.url.Path) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
|
|
@ -152,13 +152,14 @@ type Client interface {
|
|||
|
||||
// GetFileFromURL Download the file from URL instead of provided endpoint. Authorizer still remains the same.
|
||||
GetFileFromURL(url string) (*commons.ResponseHeaders, io.ReadCloser, error)
|
||||
GetURL(filePath string) string
|
||||
}
|
||||
|
||||
// NewClient creates a registry client with the default authorizer which determines the auth scheme
|
||||
// of the registry automatically and calls the corresponding underlying authorizers(basic/bearer) to
|
||||
// do the auth work. If a customized authorizer is needed, use "NewClientWithAuthorizer" instead.
|
||||
func NewClient(url, username, password string, insecure bool, interceptors ...interceptor.Interceptor) Client {
|
||||
authorizer := auth.NewAuthorizer(username, password, insecure)
|
||||
func NewClient(url, username, password string, insecure, isOCI bool, interceptors ...interceptor.Interceptor) Client {
|
||||
authorizer := auth.NewAuthorizer(username, password, insecure, isOCI)
|
||||
return NewClientWithAuthorizer(url, authorizer, insecure, interceptors...)
|
||||
}
|
||||
|
||||
|
@ -875,3 +876,7 @@ func (c *client) HeadFile(filePath string) (*commons.ResponseHeaders, bool, erro
|
|||
func buildFileURL(endpoint, filePath string) string {
|
||||
return fmt.Sprintf("%s/%s", endpoint, filePath)
|
||||
}
|
||||
|
||||
func (c *client) GetURL(filePath string) string {
|
||||
return buildFileURL(c.url, filePath)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue