Release file: support Version field

https://wiki.debian.org/DebianRepository/Format#Version

The Version field, if specified, shall be the version of the release.
On the other hand, if not set or set to an empty value, the Version
field will not be included in the Release file.

Signed-off-by: Zhang Xiao <xiao.zhang@windriver.com>
This commit is contained in:
Zhang Xiao
2026-02-10 01:26:02 +00:00
parent 9defe70190
commit e2ebcbb02a
23 changed files with 189 additions and 13 deletions

View File

@@ -81,3 +81,4 @@ List of contributors, in chronological order:
* Brian Witt (https://github.com/bwitt)
* Ales Bregar (https://github.com/abregar)
* Tim Foerster (https://github.com/tonobo)
* Zhang Xiao (https://github.com/xzhang1)

View File

@@ -186,6 +186,8 @@ type publishedRepoCreateParams struct {
SignedBy *string ` json:"SignedBy" example:""`
// Enable multiple packages with the same filename in different distributions
MultiDist *bool ` json:"MultiDist" example:"false"`
// Version of the release
Version string ` json:"Version" example:""`
}
// @Summary Create Published Repository
@@ -361,6 +363,10 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
published.SignedBy = *b.SignedBy
}
if b.Version != "" {
published.Version = b.Version
}
duplicate := collection.CheckDuplicate(published)
if duplicate != nil {
_ = collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
@@ -404,6 +410,8 @@ type publishedRepoUpdateSwitchParams struct {
Label *string ` json:"Label" example:"Debian"`
// Value of Origin: field in published repository stanza
Origin *string ` json:"Origin" example:"Debian"`
// Version of the release: Optional
Version *string ` json:"Version" example:"13.3"`
}
// @Summary Update Published Repository
@@ -503,6 +511,10 @@ func apiPublishUpdateSwitch(c *gin.Context) {
published.Origin = *b.Origin
}
if b.Version != nil {
published.Version = *b.Version
}
resources := []string{string(published.Key())}
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
@@ -1000,6 +1012,8 @@ type publishedRepoUpdateParams struct {
Label *string ` json:"Label" example:"Debian"`
// Value of Origin: field in published repository stanza
Origin *string ` json:"Origin" example:"Debian"`
// Version of the release: Optional
Version *string ` json:"Version" example:"13.3"`
}
// @Summary Update Published Repository
@@ -1080,6 +1094,10 @@ func apiPublishUpdate(c *gin.Context) {
published.Origin = *b.Origin
}
if b.Version != nil {
published.Version = *b.Version
}
resources := []string{string(published.Key())}
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {

View File

@@ -53,6 +53,7 @@ Example:
cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
cmd.Flag.String("version", "", "version of the release")
return cmd
}

View File

@@ -158,6 +158,10 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
}
if context.Flags().IsSet("version") {
published.Version = context.Flags().Lookup("version").Value.String()
}
duplicate := collectionFactory.PublishedRepoCollection().CheckDuplicate(published)
if duplicate != nil {
_ = collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
@@ -252,6 +256,7 @@ Example:
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
cmd.Flag.Bool("acquire-by-hash", false, "provide index files by hash")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.String("version", "", "version of the release")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
return cmd

View File

@@ -103,6 +103,10 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
}
if context.Flags().IsSet("version") {
published.Version = context.Flags().Lookup("version").Value.String()
}
if context.Flags().IsSet("multi-dist") {
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
}
@@ -167,6 +171,7 @@ This command would switch published repository (with one component) named ppa/wh
cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)")
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
cmd.Flag.String("signed-by", "", "an optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file")
cmd.Flag.String("version", "", "version of the release")
cmd.Flag.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")

View File

@@ -76,6 +76,10 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
}
if context.Flags().IsSet("version") {
published.Version = context.Flags().Lookup("version").Value.String()
}
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
if err != nil {
return fmt.Errorf("unable to publish: %s", err)
@@ -142,6 +146,7 @@ Example:
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
cmd.Flag.String("origin", "", "overwrite origin name to publish")
cmd.Flag.String("label", "", "overwrite label to publish")
cmd.Flag.String("version", "", "version of the release")
return cmd
}

View File

@@ -55,6 +55,7 @@ type PublishedRepo struct {
Label string
Suite string
Codename string
Version string
// Architectures is a list of all architectures published
Architectures []string
// SourceKind is "local"/"repo"
@@ -526,6 +527,7 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
"Origin": p.Origin,
"Suite": p.Suite,
"Codename": p.Codename,
"Version": p.Version,
"NotAutomatic": p.NotAutomatic,
"ButAutomaticUpgrades": p.ButAutomaticUpgrades,
"Prefix": p.Prefix,
@@ -1080,6 +1082,9 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
if p.SignedBy != "" {
release["Signed-By"] = p.SignedBy
}
if p.Version != "" {
release["Version"] = p.Version
}
var bufWriter *bufio.Writer
bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter()
@@ -1158,6 +1163,9 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
// Let's use a century as a "forever" value.
release["Valid-Until"] = publishDate.AddDate(100, 0, 0).Format(datetime_format)
}
if p.Version != "" {
release["Version"] = p.Version
}
release["Description"] = " Generated by aptly\n"
release["MD5Sum"] = ""
release["SHA1"] = ""

View File

@@ -1569,6 +1569,10 @@ Options:
set value for Signed-By field
.
.TP
\-\fBversion\fR
version of the release
.
.TP
\-\fBacquire\-by\-hash\fR
provide index files by hash
.
@@ -1714,6 +1718,10 @@ Options:
set value for Signed-By field
.
.TP
\-\fBversion\fR
version of the release
.
.TP
\-\fBacquire\-by\-hash\fR
provide index files by hash
.
@@ -2187,6 +2195,10 @@ Options:
set value for Signed-By field
.
.TP
\-\fBversion\fR
version of the release
.
.TP
\-\fBbatch\fR
run GPG with detached tty
.

View File

@@ -24,7 +24,8 @@
}
],
"Storage": "",
"Suite": ""
"Suite": "",
"Version": ""
},
{
"AcquireByHash": false,
@@ -50,7 +51,8 @@
}
],
"Storage": "",
"Suite": ""
"Suite": "",
"Version": ""
},
{
"AcquireByHash": false,
@@ -77,7 +79,8 @@
}
],
"Storage": "",
"Suite": ""
"Suite": "",
"Version": ""
},
{
"AcquireByHash": false,
@@ -104,6 +107,7 @@
}
],
"Storage": "",
"Suite": ""
"Suite": "",
"Version": ""
}
]

View File

@@ -0,0 +1,14 @@
Loading packages...
Generating metadata files and linking package files...
Finalizing metadata files...
Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Local repo local-repo has been successfully published.
Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing.
Now you can add following line to apt sources:
deb http://your-server/ maverick contrib
deb-src http://your-server/ maverick contrib
Don't forget to add your GPG key to apt with apt-key.
You can also use `aptly serve` to publish your repositories over HTTP quickly.

View File

@@ -0,0 +1,12 @@
Origin: . maverick
Label: label37
Suite: maverick
Version: 13.3
Codename: maverick
Architectures: i386
Components: contrib
Description: Generated by aptly
MD5Sum:
SHA1:
SHA256:
SHA512:

View File

@@ -23,5 +23,6 @@
}
],
"Storage": "",
"Suite": ""
"Suite": "",
"Version": ""
}

View File

@@ -23,5 +23,6 @@
}
],
"Storage": "",
"Suite": ""
"Suite": "",
"Version": ""
}

View File

@@ -1,6 +1,7 @@
Origin: aptly24
Label: . squeeze
Suite: squeeze
Version: 13.3
Codename: squeeze
NotAutomatic: yes
ButAutomaticUpgrades: yes

View File

@@ -1,6 +1,7 @@
Origin: LP-PPA-gladky-anton-gnuplot
Label: . maverick
Suite: maverick
Version: 13.3
Codename: maverick
Signed-By: a,string
Architectures: amd64 i386

View File

@@ -1,6 +1,7 @@
Origin: earth
Label: fun
Suite: maverick
Version: 13.3
Codename: maverick
Architectures: i386
Components: main

View File

@@ -0,0 +1,9 @@
Loading packages...
Generating metadata files and linking package files...
Finalizing metadata files...
Signing file 'Release' with gpg, please enter your passphrase when prompted:
Clearsigning file 'Release' with gpg, please enter your passphrase when prompted:
Cleaning up published repository ./maverick...
Cleaning up component 'main'...
Published local repository ./maverick [i386, source] publishes {main: [local-repo]} has been updated successfully.

View File

@@ -0,0 +1,11 @@
Origin: . maverick
Label: . maverick
Suite: maverick
Codename: maverick
Architectures: i386
Components: main
Description: Generated by aptly
MD5Sum:
SHA1:
SHA256:
SHA512:

View File

@@ -1005,3 +1005,22 @@ class PublishRepo36Test(BaseTest):
# verify byte-identical output
second_release = self.read_file('public/dists/maverick/Release')
self.check_equal(first_release, second_release)
class PublishRepo37Test(BaseTest):
"""
publish repo: custom version
"""
fixtureCmds = [
"aptly repo create local-repo",
"aptly repo add local-repo ${files}",
]
runCmd = "aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -component=contrib -label=label37 -version=13.3 local-repo"
gold_processor = BaseTest.expand_environ
def check(self):
super(PublishRepo37Test, self).check()
# verify contents except of sums
self.check_file_contents(
'public/dists/maverick/Release', 'release', match_prepare=strip_processor)

View File

@@ -690,7 +690,7 @@ class PublishSnapshot23Test(BaseTest):
class PublishSnapshot24Test(BaseTest):
"""
publish snapshot: custom origin, notautomatic and butautomaticupgrades
publish snapshot: custom origin, version, notautomatic and butautomaticupgrades
"""
fixtureDB = True
fixturePool = True
@@ -698,7 +698,7 @@ class PublishSnapshot24Test(BaseTest):
"aptly snapshot create snap24 from mirror gnuplot-maverick",
]
sortOutput = True
runCmd = "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=squeeze -origin=aptly24 -notautomatic=yes -butautomaticupgrades=yes snap24"
runCmd = "aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=squeeze -origin=aptly24 -version=13.3 -notautomatic=yes -butautomaticupgrades=yes snap24"
gold_processor = BaseTest.expand_environ
def check(self):

View File

@@ -607,7 +607,7 @@ class PublishSwitch16Test(BaseTest):
class PublishSwitch17Test(BaseTest):
"""
publish switch: signed-by
publish switch: signed-by, version
"""
fixtureDB = True
fixturePool = True
@@ -616,7 +616,7 @@ class PublishSwitch17Test(BaseTest):
"aptly snapshot create snap2 from mirror gnuplot-maverick",
"aptly publish snapshot -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick snap1",
]
runCmd = "aptly publish switch -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -signed-by=a,string maverick snap2"
runCmd = "aptly publish switch -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -signed-by=a,string -version=13.3 maverick snap2"
gold_processor = BaseTest.expand_environ
def check(self):

View File

@@ -629,7 +629,7 @@ class PublishUpdate19Test(BaseTest):
class PublishUpdate20Test(BaseTest):
"""
publish update: update label and origin
publish update: update label, origin, version
"""
fixtureCmds = [
"aptly repo create local-repo",
@@ -637,7 +637,7 @@ class PublishUpdate20Test(BaseTest):
"aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -skip-bz2 local-repo",
"aptly repo remove local-repo pyspi"
]
runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -label=fun -origin=earth maverick"
runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -label=fun -origin=earth -version=13.3 maverick"
gold_processor = BaseTest.expand_environ
def check(self):
@@ -647,3 +647,25 @@ class PublishUpdate20Test(BaseTest):
# verify contents except of sums
self.check_file_contents('public/dists/maverick/Release', 'release', match_prepare=strip_processor)
class PublishUpdate21Test(BaseTest):
"""
publish update: update version with empty value
"""
fixtureCmds = [
"aptly repo create local-repo",
"aptly repo add local-repo ${files}/",
"aptly publish repo -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -distribution=maverick -skip-bz2 -version=13.3 local-repo",
"aptly repo remove local-repo pyspi"
]
runCmd = "aptly publish update -keyring=${files}/aptly.pub -secret-keyring=${files}/aptly.sec -version='' maverick"
gold_processor = BaseTest.expand_environ
def check(self):
super(PublishUpdate21Test, self).check()
self.check_exists('public/dists/maverick/InRelease')
# verify contents except of sums
self.check_file_contents('public/dists/maverick/Release', 'release', match_prepare=strip_processor)

View File

@@ -48,6 +48,7 @@ class PublishAPITestRepo(APITest):
'Distribution': 'wheezy',
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
@@ -94,6 +95,7 @@ class PublishAPITestRepo(APITest):
'Distribution': distribution,
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': './' + distribution,
@@ -166,6 +168,7 @@ class PublishAPITestRepoMultiDist(APITest):
'Distribution': 'bookworm',
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'bookworm',
@@ -232,6 +235,7 @@ class PublishAPITestRepoSignedBy(APITest):
'Distribution': 'wheezy',
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
@@ -283,6 +287,7 @@ class PublishSnapshotAPITest(APITest):
"ButAutomaticUpgrades": "yes",
"Origin": "earth",
"Label": "fun",
"Version": "13.3",
}
)
self.check_task(task)
@@ -299,6 +304,7 @@ class PublishSnapshotAPITest(APITest):
'Distribution': 'squeeze',
'Label': 'fun',
'Origin': 'earth',
"Version": "13.3",
'MultiDist': False,
'NotAutomatic': 'yes',
'ButAutomaticUpgrades': 'yes',
@@ -377,6 +383,7 @@ class PublishSnapshotAPITestSignedBy(APITest):
'Distribution': 'squeeze',
'Label': 'fun',
'Origin': 'earth',
'Version': '',
'MultiDist': False,
'NotAutomatic': 'yes',
'ButAutomaticUpgrades': 'yes',
@@ -441,12 +448,13 @@ class PublishUpdateAPITestRepo(APITest):
json={"PackageRefs": ['Psource pyspi 0.6.1-1.4 f8f1daa806004e89']})
self.check_task(task)
# Update and switch AcquireByHash on.
# Update and switch AcquireByHash on, Version.
task = self.put_task(
"/api/publish/" + prefix + "/wheezy",
json={
"AcquireByHash": True,
"Signing": DefaultSigningOptions,
"Version": "13.3"
}
)
self.check_task(task)
@@ -457,6 +465,7 @@ class PublishUpdateAPITestRepo(APITest):
'Distribution': 'wheezy',
'Label': '',
'Origin': '',
'Version': '13.3',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
@@ -549,6 +558,7 @@ class PublishUpdateAPITestRepoSignedBy(APITest):
'Distribution': 'wheezy',
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
@@ -629,6 +639,7 @@ class PublishUpdateAPIMultiDist(APITest):
'Distribution': 'bookworm',
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'bookworm',
@@ -731,6 +742,7 @@ class PublishConcurrentUpdateAPITestRepo(APITest):
'Distribution': 'wheezy',
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
@@ -830,6 +842,7 @@ class PublishUpdateSkipCleanupAPITestRepo(APITest):
'Distribution': 'wheezy',
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
@@ -901,6 +914,7 @@ class PublishSwitchAPITestRepo(APITest):
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Origin': '',
'Version': '',
'Path': prefix + '/' + 'wheezy',
'Prefix': prefix,
'SignedBy': '',
@@ -941,6 +955,7 @@ class PublishSwitchAPITestRepo(APITest):
"SkipContents": True,
"Label": "fun",
"Origin": "earth",
"Version": "13.3",
})
self.check_task(task)
repo_expected = {
@@ -950,6 +965,7 @@ class PublishSwitchAPITestRepo(APITest):
'Distribution': 'wheezy',
'Label': 'fun',
'Origin': 'earth',
'Version': '13.3',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
@@ -1017,6 +1033,7 @@ class PublishSwitchAPITestRepoSignedBy(APITest):
'Codename': '',
'Distribution': 'wheezy',
'Label': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Origin': '',
@@ -1053,6 +1070,7 @@ class PublishSwitchAPITestRepoSignedBy(APITest):
'Distribution': 'wheezy',
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
@@ -1113,6 +1131,7 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest):
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Origin': '',
'Version': '',
'Path': prefix + '/' + 'wheezy',
'Prefix': prefix,
'SignedBy': '',
@@ -1152,6 +1171,7 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest):
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Origin': '',
'Version': '',
'Path': prefix + '/' + 'otherdist',
'Prefix': prefix,
'SignedBy': '',
@@ -1194,6 +1214,7 @@ class PublishSwitchAPISkipCleanupTestRepo(APITest):
'Distribution': 'wheezy',
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
@@ -1258,6 +1279,7 @@ class PublishShowAPITestRepo(APITest):
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Origin': '',
'Version': '',
'Path': prefix + '/' + 'wheezy',
'Prefix': prefix,
'SignedBy': '',
@@ -1850,6 +1872,7 @@ class PublishUpdateSourcesAPITestRepo(APITest):
"SkipContents": True,
"Label": "fun",
"Origin": "earth",
"Version": "13.3",
}
).status_code, 200)
@@ -1860,6 +1883,7 @@ class PublishUpdateSourcesAPITestRepo(APITest):
'Distribution': 'wheezy',
'Label': 'fun',
'Origin': 'earth',
'Version': '13.3',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
@@ -1915,6 +1939,7 @@ class PublishAPITestDualSignature(APITest):
'Distribution': 'wheezy',
'Label': '',
'Origin': '',
'Version': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',