mirror of https://github.com/go-gitea/gitea.git
Merge branch 'main' into lunny/catfile_batch_refactor
This commit is contained in:
commit
2454ab6569
|
|
@ -39,12 +39,10 @@ func smtpCLIFlags() []cli.Flag {
|
|||
&cli.BoolFlag{
|
||||
Name: "force-smtps",
|
||||
Usage: "SMTPS is always used on port 465. Set this to force SMTPS on other ports.",
|
||||
Value: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "skip-verify",
|
||||
Usage: "Skip TLS verify.",
|
||||
Value: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "helo-hostname",
|
||||
|
|
@ -54,7 +52,6 @@ func smtpCLIFlags() []cli.Flag {
|
|||
&cli.BoolFlag{
|
||||
Name: "disable-helo",
|
||||
Usage: "Disable SMTP helo.",
|
||||
Value: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "allowed-domains",
|
||||
|
|
@ -64,7 +61,6 @@ func smtpCLIFlags() []cli.Flag {
|
|||
&cli.BoolFlag{
|
||||
Name: "skip-local-2fa",
|
||||
Usage: "Skip 2FA to log on.",
|
||||
Value: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "active",
|
||||
|
|
|
|||
|
|
@ -60,10 +60,8 @@ func TestAddSMTP(t *testing.T) {
|
|||
Auth: "PLAIN",
|
||||
Host: "localhost",
|
||||
Port: 25,
|
||||
// ForceSMTPS: true,
|
||||
// SkipVerify: true,
|
||||
},
|
||||
TwoFactorPolicy: "skip",
|
||||
TwoFactorPolicy: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -73,12 +71,12 @@ func TestAddSMTP(t *testing.T) {
|
|||
"--host", "localhost",
|
||||
"--port", "25",
|
||||
"--auth-type", "LOGIN",
|
||||
"--force-smtps=false",
|
||||
"--skip-verify=false",
|
||||
"--force-smtps",
|
||||
"--skip-verify",
|
||||
"--helo-hostname", "example.com",
|
||||
"--disable-helo=false",
|
||||
"--disable-helo=true",
|
||||
"--allowed-domains", "example.com,example.org",
|
||||
"--skip-local-2fa=false",
|
||||
"--skip-local-2fa",
|
||||
"--active=false",
|
||||
},
|
||||
source: &auth_model.Source{
|
||||
|
|
@ -89,13 +87,13 @@ func TestAddSMTP(t *testing.T) {
|
|||
Auth: "LOGIN",
|
||||
Host: "localhost",
|
||||
Port: 25,
|
||||
ForceSMTPS: false,
|
||||
SkipVerify: false,
|
||||
ForceSMTPS: true,
|
||||
SkipVerify: true,
|
||||
HeloHostname: "example.com",
|
||||
DisableHelo: false,
|
||||
DisableHelo: true,
|
||||
AllowedDomains: "example.com,example.org",
|
||||
},
|
||||
TwoFactorPolicy: "",
|
||||
TwoFactorPolicy: "skip",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -157,13 +155,10 @@ func TestUpdateSMTP(t *testing.T) {
|
|||
Name: "old name",
|
||||
IsActive: true,
|
||||
Cfg: &smtp.Source{
|
||||
Auth: "PLAIN",
|
||||
Host: "old host",
|
||||
Port: 26,
|
||||
ForceSMTPS: true,
|
||||
SkipVerify: true,
|
||||
Auth: "PLAIN",
|
||||
Host: "old host",
|
||||
Port: 26,
|
||||
},
|
||||
TwoFactorPolicy: "",
|
||||
},
|
||||
args: []string{
|
||||
"--id", "1",
|
||||
|
|
@ -177,13 +172,10 @@ func TestUpdateSMTP(t *testing.T) {
|
|||
Name: "test",
|
||||
IsActive: true,
|
||||
Cfg: &smtp.Source{
|
||||
Auth: "PLAIN",
|
||||
Host: "localhost",
|
||||
Port: 25,
|
||||
ForceSMTPS: true,
|
||||
SkipVerify: true,
|
||||
Auth: "PLAIN",
|
||||
Host: "localhost",
|
||||
Port: 25,
|
||||
},
|
||||
TwoFactorPolicy: "skip",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -197,10 +189,7 @@ func TestUpdateSMTP(t *testing.T) {
|
|||
Auth: "PLAIN",
|
||||
Host: "old host",
|
||||
Port: 26,
|
||||
ForceSMTPS: true,
|
||||
SkipVerify: true,
|
||||
HeloHostname: "old.example.com",
|
||||
DisableHelo: false,
|
||||
AllowedDomains: "old.example.com",
|
||||
},
|
||||
TwoFactorPolicy: "",
|
||||
|
|
@ -211,12 +200,12 @@ func TestUpdateSMTP(t *testing.T) {
|
|||
"--host", "localhost",
|
||||
"--port", "25",
|
||||
"--auth-type", "LOGIN",
|
||||
"--force-smtps=false",
|
||||
"--skip-verify=false",
|
||||
"--force-smtps",
|
||||
"--skip-verify",
|
||||
"--helo-hostname", "example.com",
|
||||
"--disable-helo=true",
|
||||
"--disable-helo",
|
||||
"--allowed-domains", "example.com,example.org",
|
||||
"--skip-local-2fa=true",
|
||||
"--skip-local-2fa",
|
||||
"--active=false",
|
||||
},
|
||||
authSource: &auth_model.Source{
|
||||
|
|
@ -228,8 +217,8 @@ func TestUpdateSMTP(t *testing.T) {
|
|||
Auth: "LOGIN",
|
||||
Host: "localhost",
|
||||
Port: 25,
|
||||
ForceSMTPS: false,
|
||||
SkipVerify: false,
|
||||
ForceSMTPS: true,
|
||||
SkipVerify: true,
|
||||
HeloHostname: "example.com",
|
||||
DisableHelo: true,
|
||||
AllowedDomains: "example.com,example.org",
|
||||
|
|
@ -252,11 +241,8 @@ func TestUpdateSMTP(t *testing.T) {
|
|||
Name: "test",
|
||||
IsActive: true,
|
||||
Cfg: &smtp.Source{
|
||||
Auth: "PLAIN",
|
||||
SkipVerify: true,
|
||||
ForceSMTPS: true,
|
||||
Auth: "PLAIN",
|
||||
},
|
||||
TwoFactorPolicy: "skip",
|
||||
}, nil
|
||||
},
|
||||
|
||||
|
|
|
|||
10
cmd/cmd.go
10
cmd/cmd.go
|
|
@ -132,3 +132,13 @@ func PrepareConsoleLoggerLevel(defaultLevel log.Level) func(context.Context, *cl
|
|||
return ctx, nil
|
||||
}
|
||||
}
|
||||
|
||||
func isValidDefaultSubCommand(cmd *cli.Command) (string, bool) {
|
||||
// Dirty patch for urfave/cli's strange design.
|
||||
// "./gitea bad-cmd" should not start the web server.
|
||||
rootArgs := cmd.Root().Args().Slice()
|
||||
if len(rootArgs) != 0 && rootArgs[0] != cmd.Name {
|
||||
return rootArgs[0], false
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func TestDefaultCommand(t *testing.T) {
|
||||
test := func(t *testing.T, args []string, expectedRetName string, expectedRetValid bool) {
|
||||
called := false
|
||||
cmd := &cli.Command{
|
||||
DefaultCommand: "test",
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "test",
|
||||
Action: func(ctx context.Context, command *cli.Command) error {
|
||||
retName, retValid := isValidDefaultSubCommand(command)
|
||||
assert.Equal(t, expectedRetName, retName)
|
||||
assert.Equal(t, expectedRetValid, retValid)
|
||||
called = true
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.NoError(t, cmd.Run(t.Context(), args))
|
||||
assert.True(t, called)
|
||||
}
|
||||
test(t, []string{"./gitea"}, "", true)
|
||||
test(t, []string{"./gitea", "test"}, "", true)
|
||||
test(t, []string{"./gitea", "other"}, "other", false)
|
||||
}
|
||||
|
|
@ -152,6 +152,8 @@ func NewMainApp(appVer AppVersion) *cli.Command {
|
|||
CmdDocs,
|
||||
}
|
||||
|
||||
// TODO: we should eventually drop the default command,
|
||||
// but not sure whether it would break Windows users who used to double-click the EXE to run.
|
||||
app.DefaultCommand = CmdWeb.Name
|
||||
|
||||
app.Flags = append(app.Flags, cli.VersionFlag)
|
||||
|
|
|
|||
|
|
@ -119,7 +119,6 @@ var (
|
|||
Name: "rotate",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "Rotate logs",
|
||||
Value: true,
|
||||
},
|
||||
&cli.Int64Flag{
|
||||
Name: "max-size",
|
||||
|
|
@ -130,7 +129,6 @@ var (
|
|||
Name: "daily",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Rotate logs daily",
|
||||
Value: true,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "max-days",
|
||||
|
|
@ -141,7 +139,6 @@ var (
|
|||
Name: "compress",
|
||||
Aliases: []string{"z"},
|
||||
Usage: "Compress rotated logs",
|
||||
Value: true,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "compression-level",
|
||||
|
|
|
|||
|
|
@ -251,6 +251,10 @@ func runWeb(_ context.Context, cmd *cli.Command) error {
|
|||
}
|
||||
}()
|
||||
|
||||
if subCmdName, valid := isValidDefaultSubCommand(cmd); !valid {
|
||||
return fmt.Errorf("unknown command: %s", subCmdName)
|
||||
}
|
||||
|
||||
managerCtx, cancel := context.WithCancel(context.Background())
|
||||
graceful.InitManager(managerCtx)
|
||||
defer cancel()
|
||||
|
|
|
|||
|
|
@ -92,8 +92,8 @@ func DeletePropertyByID(ctx context.Context, propertyID int64) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// DeletePropertyByName deletes properties by name
|
||||
func DeletePropertyByName(ctx context.Context, refType PropertyType, refID int64, name string) error {
|
||||
// DeletePropertiesByName deletes properties by name
|
||||
func DeletePropertiesByName(ctx context.Context, refType PropertyType, refID int64, name string) error {
|
||||
_, err := db.GetEngine(ctx).Where("ref_type = ? AND ref_id = ? AND name = ?", refType, refID, name).Delete(&PackageProperty{})
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ func NewContentStore() *ContentStore {
|
|||
return contentStore
|
||||
}
|
||||
|
||||
// Get gets a package blob
|
||||
func (s *ContentStore) Get(key BlobHash256Key) (storage.Object, error) {
|
||||
func (s *ContentStore) OpenBlob(key BlobHash256Key) (storage.Object, error) {
|
||||
return s.store.Open(KeyToRelativePath(key))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
container_service "code.gitea.io/gitea/services/packages/container"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
oci "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
|
@ -84,12 +85,11 @@ func processOciImageManifest(ctx context.Context, mci *manifestCreationInfo, buf
|
|||
manifestDigest := ""
|
||||
|
||||
err := func() error {
|
||||
var manifest oci.Manifest
|
||||
if err := json.NewDecoder(buf).Decode(&manifest); err != nil {
|
||||
manifest, configDescriptor, metadata, err := container_service.ParseManifestMetadata(ctx, buf, mci.Owner.ID, mci.Image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := buf.Seek(0, io.SeekStart); err != nil {
|
||||
if _, err = buf.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -99,28 +99,7 @@ func processOciImageManifest(ctx context.Context, mci *manifestCreationInfo, buf
|
|||
}
|
||||
defer committer.Close()
|
||||
|
||||
configDescriptor, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||
OwnerID: mci.Owner.ID,
|
||||
Image: mci.Image,
|
||||
Digest: string(manifest.Config.Digest),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configReader, err := packages_module.NewContentStore().Get(packages_module.BlobHash256Key(configDescriptor.Blob.HashSHA256))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer configReader.Close()
|
||||
|
||||
metadata, err := container_module.ParseImageConfig(manifest.Config.MediaType, configReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blobReferences := make([]*blobReference, 0, 1+len(manifest.Layers))
|
||||
|
||||
blobReferences = append(blobReferences, &blobReference{
|
||||
Digest: manifest.Config.Digest,
|
||||
MediaType: manifest.Config.MediaType,
|
||||
|
|
@ -388,19 +367,16 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
|
|||
return nil, err
|
||||
}
|
||||
} else {
|
||||
props, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged)
|
||||
if err != nil {
|
||||
if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestTagged); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, prop := range props {
|
||||
if err = packages_model.DeletePropertyByID(ctx, prop.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, manifest := range metadata.Manifests {
|
||||
if err = packages_model.InsertOrUpdateProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil {
|
||||
if _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
|
|
@ -20,6 +22,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/optional"
|
||||
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
|
||||
arch_module "code.gitea.io/gitea/modules/packages/arch"
|
||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||
debian_module "code.gitea.io/gitea/modules/packages/debian"
|
||||
rpm_module "code.gitea.io/gitea/modules/packages/rpm"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
|
@ -31,6 +34,7 @@ import (
|
|||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
packages_service "code.gitea.io/gitea/services/packages"
|
||||
container_service "code.gitea.io/gitea/services/packages/container"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -162,6 +166,24 @@ func RedirectToLastVersion(ctx *context.Context) {
|
|||
ctx.Redirect(pd.VersionWebLink())
|
||||
}
|
||||
|
||||
func viewPackageContainerImage(ctx gocontext.Context, pd *packages_model.PackageDescriptor, digest string) (*container_module.Metadata, error) {
|
||||
manifestBlob, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
|
||||
OwnerID: pd.Owner.ID,
|
||||
Image: pd.Package.LowerName,
|
||||
Digest: digest,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifestReader, err := packages_service.OpenBlobStream(manifestBlob.Blob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer manifestReader.Close()
|
||||
_, _, metadata, err := container_service.ParseManifestMetadata(ctx, manifestReader, pd.Owner.ID, pd.Package.LowerName)
|
||||
return metadata, err
|
||||
}
|
||||
|
||||
// ViewPackageVersion displays a single package version
|
||||
func ViewPackageVersion(ctx *context.Context) {
|
||||
if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil {
|
||||
|
|
@ -169,6 +191,7 @@ func ViewPackageVersion(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
versionSub := ctx.PathParam("version_sub")
|
||||
pd := ctx.Package.Descriptor
|
||||
ctx.Data["Title"] = pd.Package.Name
|
||||
ctx.Data["IsPackagesPage"] = true
|
||||
|
|
@ -180,6 +203,9 @@ func ViewPackageVersion(ctx *context.Context) {
|
|||
}
|
||||
ctx.Data["PackageRegistryHost"] = registryHostURL.Host
|
||||
|
||||
var pvs []*packages_model.PackageVersion
|
||||
pvsTotal := int64(0)
|
||||
|
||||
switch pd.Package.Type {
|
||||
case packages_model.TypeAlpine:
|
||||
branches := make(container.Set[string])
|
||||
|
|
@ -257,21 +283,26 @@ func ViewPackageVersion(ctx *context.Context) {
|
|||
|
||||
ctx.Data["Groups"] = util.Sorted(groups.Values())
|
||||
ctx.Data["Architectures"] = util.Sorted(architectures.Values())
|
||||
}
|
||||
|
||||
var (
|
||||
total int64
|
||||
pvs []*packages_model.PackageVersion
|
||||
)
|
||||
switch pd.Package.Type {
|
||||
case packages_model.TypeContainer:
|
||||
pvs, total, err = container_model.SearchImageTags(ctx, &container_model.ImageTagsSearchOptions{
|
||||
imageMetadata := pd.Metadata
|
||||
if versionSub != "" {
|
||||
imageMetadata, err = viewPackageContainerImage(ctx, pd, versionSub)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
} else if err != nil {
|
||||
ctx.ServerError("viewPackageContainerImage", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Data["ContainerImageMetadata"] = imageMetadata
|
||||
pvs, pvsTotal, err = container_model.SearchImageTags(ctx, &container_model.ImageTagsSearchOptions{
|
||||
Paginator: db.NewAbsoluteListOptions(0, 5),
|
||||
PackageID: pd.Package.ID,
|
||||
IsTagged: true,
|
||||
})
|
||||
default:
|
||||
pvs, total, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
pvs, pvsTotal, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
Paginator: db.NewAbsoluteListOptions(0, 5),
|
||||
PackageID: pd.Package.ID,
|
||||
IsInternal: optional.Some(false),
|
||||
|
|
@ -283,7 +314,7 @@ func ViewPackageVersion(ctx *context.Context) {
|
|||
}
|
||||
|
||||
ctx.Data["LatestVersions"] = pvs
|
||||
ctx.Data["TotalVersionCount"] = total
|
||||
ctx.Data["TotalVersionCount"] = pvsTotal
|
||||
|
||||
ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin()
|
||||
|
||||
|
|
|
|||
|
|
@ -1012,6 +1012,7 @@ func registerWebRoutes(m *web.Router) {
|
|||
m.Get("/versions", user.ListPackageVersions)
|
||||
m.Group("/{version}", func() {
|
||||
m.Get("", user.ViewPackageVersion)
|
||||
m.Get("/{version_sub}", user.ViewPackageVersion)
|
||||
m.Get("/files/{fileid}", user.DownloadPackageFile)
|
||||
m.Group("/settings", func() {
|
||||
m.Get("", user.PackageSettings)
|
||||
|
|
|
|||
|
|
@ -5,11 +5,17 @@ package container
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
container_service "code.gitea.io/gitea/models/packages/container"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/packages"
|
||||
container_module "code.gitea.io/gitea/modules/packages/container"
|
||||
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// UpdateRepositoryNames updates the repository name property for all packages of the specific owner
|
||||
|
|
@ -22,7 +28,7 @@ func UpdateRepositoryNames(ctx context.Context, owner *user_model.User, newOwner
|
|||
newOwnerName = strings.ToLower(newOwnerName)
|
||||
|
||||
for _, p := range ps {
|
||||
if err := packages_model.DeletePropertyByName(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository); err != nil {
|
||||
if err := packages_model.DeletePropertiesByName(ctx, packages_model.PropertyTypePackage, p.ID, container_module.PropertyRepository); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -33,3 +39,26 @@ func UpdateRepositoryNames(ctx context.Context, owner *user_model.User, newOwner
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseManifestMetadata(ctx context.Context, rd io.Reader, ownerID int64, imageName string) (*v1.Manifest, *packages_model.PackageFileDescriptor, *container_module.Metadata, error) {
|
||||
var manifest v1.Manifest
|
||||
if err := json.NewDecoder(rd).Decode(&manifest); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
configDescriptor, err := container_service.GetContainerBlob(ctx, &container_service.BlobSearchOptions{
|
||||
OwnerID: ownerID,
|
||||
Image: imageName,
|
||||
Digest: string(manifest.Config.Digest),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
configReader, err := packages.NewContentStore().OpenBlob(packages.BlobHash256Key(configDescriptor.Blob.HashSHA256))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
defer configReader.Close()
|
||||
metadata, err := container_module.ParseImageConfig(manifest.Config.MediaType, configReader)
|
||||
return &manifest, configDescriptor, metadata, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -599,6 +599,12 @@ func GetPackageFileStream(ctx context.Context, pf *packages_model.PackageFile) (
|
|||
return GetPackageBlobStream(ctx, pf, pb, nil)
|
||||
}
|
||||
|
||||
func OpenBlobStream(pb *packages_model.PackageBlob) (io.ReadSeekCloser, error) {
|
||||
cs := packages_module.NewContentStore()
|
||||
key := packages_module.BlobHash256Key(pb.HashSHA256)
|
||||
return cs.OpenBlob(key)
|
||||
}
|
||||
|
||||
// GetPackageBlobStream returns the content of the specific package blob
|
||||
// If the storage supports direct serving and it's enabled, only the direct serving url is returned.
|
||||
func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, pb *packages_model.PackageBlob, serveDirectReqParams url.Values) (io.ReadSeekCloser, *url.URL, *packages_model.PackageFile, error) {
|
||||
|
|
@ -617,7 +623,7 @@ func GetPackageBlobStream(ctx context.Context, pf *packages_model.PackageFile, p
|
|||
}
|
||||
}
|
||||
if u == nil {
|
||||
s, err = cs.Get(key)
|
||||
s, err = cs.OpenBlob(key)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,11 @@
|
|||
{{/* "unknown/unknown" is attestation-manifest, so we should skip it */}}
|
||||
{{if ne .Platform "unknown/unknown"}}
|
||||
<tr>
|
||||
<td><a class="tw-font-mono" href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .Digest}}">{{StringUtils.TrimPrefix .Digest "sha256:" | ShortSha}}</a></td>
|
||||
<td>
|
||||
<a class="tw-font-mono" href="{{$.PackageDescriptor.PackageWebLink}}/{{$.PackageDescriptor.Version.LowerVersion}}/{{PathEscape .Digest}}">
|
||||
{{StringUtils.TrimPrefix .Digest "sha256:" | ShortSha}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{.Platform}}</td>
|
||||
<td>{{FileSize .Size}}</td>
|
||||
</tr>
|
||||
|
|
@ -65,12 +69,24 @@
|
|||
{{.PackageDescriptor.Metadata.Description}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ImageLayers}}
|
||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.container.layers"}}</h4>
|
||||
|
||||
{{/* a container manifest may contain sub manifests, so here we try to display some information of the sub manifest,
|
||||
not perfect, just better than before */}}
|
||||
{{$imageMetadata := .ContainerImageMetadata}}
|
||||
{{if $imageMetadata.ImageLayers}}
|
||||
<h4 class="ui top attached header flex-text-block">
|
||||
{{ctx.Locale.Tr "packages.container.layers"}}
|
||||
{{/* only show the platform if the image metadata is not the package's, which means that it is a sub manifest */}}
|
||||
{{if ne .ContainerImageMetadata .PackageDescriptor.Metadata}}
|
||||
<span class="tw-text-sm flex-text-inline" title="{{ctx.Locale.Tr "packages.container.details.platform"}}">
|
||||
({{svg "octicon-cpu" 12}} {{.ContainerImageMetadata.Platform}})
|
||||
</span>
|
||||
{{end}}
|
||||
</h4>
|
||||
<div class="ui attached segment tw-break-anywhere">
|
||||
<table class="ui very basic compact table">
|
||||
<tbody>
|
||||
{{range .PackageDescriptor.Metadata.ImageLayers}}
|
||||
{{range $imageMetadata.ImageLayers}}
|
||||
<tr>
|
||||
<td>{{.}}</td>
|
||||
</tr>
|
||||
|
|
@ -79,7 +95,7 @@
|
|||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .PackageDescriptor.Metadata.Labels}}
|
||||
{{if $imageMetadata.Labels}}
|
||||
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.container.labels"}}</h4>
|
||||
<div class="ui attached segment">
|
||||
<table class="ui very basic compact table">
|
||||
|
|
@ -90,7 +106,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $key, $value := .PackageDescriptor.Metadata.Labels}}
|
||||
{{range $key, $value := $imageMetadata.Labels}}
|
||||
<tr>
|
||||
<td class="tw-align-top">{{$key}}</td>
|
||||
<td class="tw-break-anywhere">{{$value}}</td>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.pypi.install"}}</label>
|
||||
<div class="markup"><pre class="code-block"><code>pip install --index-url <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/pypi/simple/"></origin-url> {{.PackageDescriptor.Package.Name}}</code></pre></div>
|
||||
<div class="markup"><pre class="code-block"><code>pip install --index-url <origin-url data-url="{{AppSubUrl}}/api/packages/{{.PackageDescriptor.Owner.Name}}/pypi/simple/"></origin-url> --extra-index-url https://pypi.org/ {{.PackageDescriptor.Package.Name}}</code></pre></div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>{{ctx.Locale.Tr "packages.registry.documentation" "PyPI" "https://docs.gitea.com/usage/packages/pypi/"}}</label>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<div class="issue-title-header">
|
||||
{{$packageVersionLink := print $.PackageDescriptor.PackageWebLink "/" (PathEscape .PackageDescriptor.Version.LowerVersion)}}
|
||||
<h1>{{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}})</h1>
|
||||
<div>
|
||||
{{$timeStr := DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}}
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
<div class="ui relaxed list">
|
||||
{{range .PackageDescriptor.Files}}
|
||||
<div class="item">
|
||||
<a href="{{$.Link}}/files/{{.File.ID}}">{{.File.Name}}</a>
|
||||
<a href="{{$packageVersionLink}}/files/{{.File.ID}}">{{.File.Name}}</a>
|
||||
<span class="text small file-size">{{FileSize .Blob.Size}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -98,7 +99,7 @@
|
|||
<div class="item">{{svg "octicon-issue-opened"}} <a href="{{.PackageDescriptor.Repository.Link}}/issues">{{ctx.Locale.Tr "repo.issues"}}</a></div>
|
||||
{{end}}
|
||||
{{if .CanWritePackages}}
|
||||
<div class="item">{{svg "octicon-tools"}} <a href="{{.Link}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div>
|
||||
<div class="item">{{svg "octicon-tools"}} <a href="{{$packageVersionLink}}/settings">{{ctx.Locale.Tr "repo.settings"}}</a></div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -562,8 +562,7 @@ func TestPackageContainer(t *testing.T) {
|
|||
assert.ElementsMatch(t, []string{strings.ToLower(user.LowerName + "/" + image)}, getAllByName(pd.PackageProperties, container_module.PropertyRepository))
|
||||
assert.True(t, has(pd.VersionProperties, container_module.PropertyManifestTagged))
|
||||
|
||||
// only the last manifest digest is associated with the version (OCI builders will push the index manifest digest as the final step)
|
||||
assert.ElementsMatch(t, []string{untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference))
|
||||
assert.ElementsMatch(t, []string{manifestDigest, untaggedManifestDigest}, getAllByName(pd.VersionProperties, container_module.PropertyManifestReference))
|
||||
|
||||
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
|
||||
metadata := pd.Metadata.(*container_module.Metadata)
|
||||
|
|
|
|||
Loading…
Reference in New Issue