Skip to content

Commit fbd4eac

Browse files
authored
Display image size for multiarch container images (#23821)
Fixes #23771 Changes the display of different architectures for multiarch images to show the image size: ![grafik](https://user-images.githubusercontent.com/1666336/228781477-cc76c4d1-4728-434f-8a27-fc008790d924.png)
1 parent f5593d0 commit fbd4eac

File tree

8 files changed

+198
-22
lines changed

8 files changed

+198
-22
lines changed

models/migrations/migrations.go

+2
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,8 @@ var migrations = []Migration{
477477
NewMigration("Add version column to action_runner table", v1_20.AddVersionToActionRunner),
478478
// v249 -> v250
479479
NewMigration("Improve Action table indices v3", v1_20.ImproveActionTableIndices),
480+
// v250 -> v251
481+
NewMigration("Change Container Metadata", v1_20.ChangeContainerMetadataMultiArch),
480482
}
481483

482484
// GetCurrentDBVersion returns the current db version

models/migrations/v1_20/v250.go

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_20 //nolint
5+
6+
import (
7+
"strings"
8+
9+
"code.gitea.io/gitea/modules/json"
10+
11+
"xorm.io/xorm"
12+
)
13+
14+
func ChangeContainerMetadataMultiArch(x *xorm.Engine) error {
15+
sess := x.NewSession()
16+
defer sess.Close()
17+
18+
if err := sess.Begin(); err != nil {
19+
return err
20+
}
21+
22+
type PackageVersion struct {
23+
ID int64 `xorm:"pk"`
24+
MetadataJSON string `xorm:"metadata_json"`
25+
}
26+
27+
type PackageBlob struct{}
28+
29+
// Get all relevant packages (manifest list images have a container.manifest.reference property)
30+
31+
var pvs []*PackageVersion
32+
err := sess.
33+
Table("package_version").
34+
Select("id, metadata_json").
35+
Where("id IN (SELECT DISTINCT ref_id FROM package_property WHERE ref_type = 0 AND name = 'container.manifest.reference')").
36+
Find(&pvs)
37+
if err != nil {
38+
return err
39+
}
40+
41+
type MetadataOld struct {
42+
Type string `json:"type"`
43+
IsTagged bool `json:"is_tagged"`
44+
Platform string `json:"platform,omitempty"`
45+
Description string `json:"description,omitempty"`
46+
Authors []string `json:"authors,omitempty"`
47+
Licenses string `json:"license,omitempty"`
48+
ProjectURL string `json:"project_url,omitempty"`
49+
RepositoryURL string `json:"repository_url,omitempty"`
50+
DocumentationURL string `json:"documentation_url,omitempty"`
51+
Labels map[string]string `json:"labels,omitempty"`
52+
ImageLayers []string `json:"layer_creation,omitempty"`
53+
MultiArch map[string]string `json:"multiarch,omitempty"`
54+
}
55+
56+
type Manifest struct {
57+
Platform string `json:"platform"`
58+
Digest string `json:"digest"`
59+
Size int64 `json:"size"`
60+
}
61+
62+
type MetadataNew struct {
63+
Type string `json:"type"`
64+
IsTagged bool `json:"is_tagged"`
65+
Platform string `json:"platform,omitempty"`
66+
Description string `json:"description,omitempty"`
67+
Authors []string `json:"authors,omitempty"`
68+
Licenses string `json:"license,omitempty"`
69+
ProjectURL string `json:"project_url,omitempty"`
70+
RepositoryURL string `json:"repository_url,omitempty"`
71+
DocumentationURL string `json:"documentation_url,omitempty"`
72+
Labels map[string]string `json:"labels,omitempty"`
73+
ImageLayers []string `json:"layer_creation,omitempty"`
74+
Manifests []*Manifest `json:"manifests,omitempty"`
75+
}
76+
77+
for _, pv := range pvs {
78+
var old *MetadataOld
79+
if err := json.Unmarshal([]byte(pv.MetadataJSON), &old); err != nil {
80+
return err
81+
}
82+
83+
// Calculate the size of every contained manifest
84+
85+
manifests := make([]*Manifest, 0, len(old.MultiArch))
86+
for platform, digest := range old.MultiArch {
87+
size, err := sess.
88+
Table("package_blob").
89+
Join("INNER", "package_file", "package_blob.id = package_file.blob_id").
90+
Join("INNER", "package_version pv", "pv.id = package_file.version_id").
91+
Join("INNER", "package_version pv2", "pv2.package_id = pv.package_id").
92+
Where("pv.lower_version = ? AND pv2.id = ?", strings.ToLower(digest), pv.ID).
93+
SumInt(new(PackageBlob), "size")
94+
if err != nil {
95+
return err
96+
}
97+
98+
manifests = append(manifests, &Manifest{
99+
Platform: platform,
100+
Digest: digest,
101+
Size: size,
102+
})
103+
}
104+
105+
// Convert to new metadata format
106+
107+
new := &MetadataNew{
108+
Type: old.Type,
109+
IsTagged: old.IsTagged,
110+
Platform: old.Platform,
111+
Description: old.Description,
112+
Authors: old.Authors,
113+
Licenses: old.Licenses,
114+
ProjectURL: old.ProjectURL,
115+
RepositoryURL: old.RepositoryURL,
116+
DocumentationURL: old.DocumentationURL,
117+
Labels: old.Labels,
118+
ImageLayers: old.ImageLayers,
119+
Manifests: manifests,
120+
}
121+
122+
metadataJSON, err := json.Marshal(new)
123+
if err != nil {
124+
return err
125+
}
126+
127+
pv.MetadataJSON = string(metadataJSON)
128+
129+
if _, err := sess.ID(pv.ID).Update(pv); err != nil {
130+
return err
131+
}
132+
}
133+
134+
return sess.Commit()
135+
}

modules/packages/container/metadata.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ type Metadata struct {
6262
DocumentationURL string `json:"documentation_url,omitempty"`
6363
Labels map[string]string `json:"labels,omitempty"`
6464
ImageLayers []string `json:"layer_creation,omitempty"`
65-
MultiArch map[string]string `json:"multiarch,omitempty"`
65+
Manifests []*Manifest `json:"manifests,omitempty"`
66+
}
67+
68+
type Manifest struct {
69+
Platform string `json:"platform"`
70+
Digest string `json:"digest"`
71+
Size int64 `json:"size"`
6672
}
6773

6874
// ParseImageConfig parses the metadata of an image config

modules/packages/container/metadata_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestParseImageConfig(t *testing.T) {
4646
},
4747
metadata.Labels,
4848
)
49-
assert.Empty(t, metadata.MultiArch)
49+
assert.Empty(t, metadata.Manifests)
5050

5151
configHelm := `{"description":"` + description + `", "home": "` + projectURL + `", "sources": ["` + repositoryURL + `"], "maintainers":[{"name":"` + author + `"}]}`
5252

routers/api/packages/container/manifest.go

+16-5
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ func processImageManifestIndex(mci *manifestCreationInfo, buf *packages_module.H
217217

218218
metadata := &container_module.Metadata{
219219
Type: container_module.TypeOCI,
220-
MultiArch: make(map[string]string),
220+
Manifests: make([]*container_module.Manifest, 0, len(index.Manifests)),
221221
}
222222

223223
for _, manifest := range index.Manifests {
@@ -233,7 +233,7 @@ func processImageManifestIndex(mci *manifestCreationInfo, buf *packages_module.H
233233
}
234234
}
235235

236-
_, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
236+
pfd, err := container_model.GetContainerBlob(ctx, &container_model.BlobSearchOptions{
237237
OwnerID: mci.Owner.ID,
238238
Image: mci.Image,
239239
Digest: string(manifest.Digest),
@@ -246,7 +246,18 @@ func processImageManifestIndex(mci *manifestCreationInfo, buf *packages_module.H
246246
return err
247247
}
248248

249-
metadata.MultiArch[platform] = string(manifest.Digest)
249+
size, err := packages_model.CalculateFileSize(ctx, &packages_model.PackageFileSearchOptions{
250+
VersionID: pfd.File.VersionID,
251+
})
252+
if err != nil {
253+
return err
254+
}
255+
256+
metadata.Manifests = append(metadata.Manifests, &container_module.Manifest{
257+
Platform: platform,
258+
Digest: string(manifest.Digest),
259+
Size: size,
260+
})
250261
}
251262

252263
pv, err := createPackageAndVersion(ctx, mci, metadata)
@@ -369,8 +380,8 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met
369380
return nil, err
370381
}
371382
}
372-
for _, digest := range metadata.MultiArch {
373-
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, digest); err != nil {
383+
for _, manifest := range metadata.Manifests {
384+
if _, err := packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, container_module.PropertyManifestReference, manifest.Digest); err != nil {
374385
log.Error("Error setting package version property: %v", err)
375386
return nil, err
376387
}

templates/package/content/container.tmpl

+18-10
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,27 @@
2323
</div>
2424
</div>
2525
</div>
26-
{{if .PackageDescriptor.Metadata.MultiArch}}
26+
{{if .PackageDescriptor.Metadata.Manifests}}
2727
<h4 class="ui top attached header">{{.locale.Tr "packages.container.multi_arch"}}</h4>
2828
<div class="ui attached segment">
29-
<div class="ui form">
30-
{{range $arch, $digest := .PackageDescriptor.Metadata.MultiArch}}
31-
<div class="field">
32-
<label>{{svg "octicon-terminal"}} {{$arch}}</label>
33-
{{if eq $.PackageDescriptor.Metadata.Type "oci"}}
34-
<div class="markup"><pre class="code-block"><code>docker pull {{$.RegistryHost}}/{{$.PackageDescriptor.Owner.LowerName}}/{{$.PackageDescriptor.Package.LowerName}}@{{$digest}}</code></pre></div>
29+
<table class="ui very basic compact table">
30+
<thead>
31+
<tr>
32+
<th>{{.locale.Tr "packages.container.digest"}}</th>
33+
<th>{{.locale.Tr "packages.container.multi_arch"}}</th>
34+
<th>{{.locale.Tr "admin.packages.size"}}</th>
35+
</tr>
36+
</thead>
37+
<tbody>
38+
{{range .PackageDescriptor.Metadata.Manifests}}
39+
<tr>
40+
<td><a href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .Digest}}">{{.Digest}}</a></td>
41+
<td>{{.Platform}}</td>
42+
<td>{{FileSize .Size}}</td>
43+
</tr>
3544
{{end}}
36-
</div>
37-
{{end}}
38-
</div>
45+
</tbody>
46+
</table>
3947
</div>
4048
{{end}}
4149
{{if .PackageDescriptor.Metadata.Description}}

templates/package/view.tmpl

+2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@
6262
{{template "package/metadata/rubygems" .}}
6363
{{template "package/metadata/swift" .}}
6464
{{template "package/metadata/vagrant" .}}
65+
{{if not (and (eq .PackageDescriptor.Package.Type "container") .PackageDescriptor.Metadata.Manifests)}}
6566
<div class="item">{{svg "octicon-database" 16 "gt-mr-3"}} {{FileSize .PackageDescriptor.CalculateBlobSize}}</div>
67+
{{end}}
6668
</div>
6769
{{if not (eq .PackageDescriptor.Package.Type "container")}}
6870
<div class="ui divider"></div>

tests/integration/api_packages_container_test.go

+17-5
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func TestPackageContainer(t *testing.T) {
321321
metadata := pd.Metadata.(*container_module.Metadata)
322322
assert.Equal(t, container_module.TypeOCI, metadata.Type)
323323
assert.Len(t, metadata.ImageLayers, 2)
324-
assert.Empty(t, metadata.MultiArch)
324+
assert.Empty(t, metadata.Manifests)
325325

326326
assert.Len(t, pd.Files, 3)
327327
for _, pfd := range pd.Files {
@@ -462,10 +462,22 @@ func TestPackageContainer(t *testing.T) {
462462
assert.IsType(t, &container_module.Metadata{}, pd.Metadata)
463463
metadata := pd.Metadata.(*container_module.Metadata)
464464
assert.Equal(t, container_module.TypeOCI, metadata.Type)
465-
assert.Contains(t, metadata.MultiArch, "linux/arm/v7")
466-
assert.Equal(t, manifestDigest, metadata.MultiArch["linux/arm/v7"])
467-
assert.Contains(t, metadata.MultiArch, "linux/arm64/v8")
468-
assert.Equal(t, untaggedManifestDigest, metadata.MultiArch["linux/arm64/v8"])
465+
assert.Len(t, metadata.Manifests, 2)
466+
assert.Condition(t, func() bool {
467+
for _, m := range metadata.Manifests {
468+
switch m.Platform {
469+
case "linux/arm/v7":
470+
assert.Equal(t, manifestDigest, m.Digest)
471+
assert.EqualValues(t, 1524, m.Size)
472+
case "linux/arm64/v8":
473+
assert.Equal(t, untaggedManifestDigest, m.Digest)
474+
assert.EqualValues(t, 1514, m.Size)
475+
default:
476+
return false
477+
}
478+
}
479+
return true
480+
})
469481

470482
assert.Len(t, pd.Files, 1)
471483
assert.True(t, pd.Files[0].File.IsLead)

0 commit comments

Comments
 (0)