Skip to content

Commit 9defe70

Browse files
authored
Merge pull request #1537 from tonobo/reproducible-publish
Add SOURCE_DATE_EPOCH support for reproducible builds
2 parents 23943d4 + 49f3428 commit 9defe70

File tree

8 files changed

+119
-3
lines changed

8 files changed

+119
-3
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,4 @@ List of contributors, in chronological order:
8080
* Roman Lebedev (https://github.com/LebedevRI)
8181
* Brian Witt (https://github.com/bwitt)
8282
* Ales Bregar (https://github.com/abregar)
83+
* Tim Foerster (https://github.com/tonobo)

deb/publish.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"path/filepath"
1111
"sort"
12+
"strconv"
1213
"strings"
1314
"time"
1415

@@ -1136,8 +1137,14 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
11361137
release["Suite"] = p.GetSuite()
11371138
release["Codename"] = p.GetCodename()
11381139
datetime_format := "Mon, 2 Jan 2006 15:04:05 MST"
1139-
date_now := time.Now().UTC()
1140-
release["Date"] = date_now.Format(datetime_format)
1140+
1141+
publishDate := time.Now().UTC()
1142+
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
1143+
if sec, err := strconv.ParseInt(epoch, 10, 64); err == nil {
1144+
publishDate = time.Unix(sec, 0).UTC()
1145+
}
1146+
}
1147+
release["Date"] = publishDate.Format(datetime_format)
11411148
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
11421149
if p.AcquireByHash {
11431150
release["Acquire-By-Hash"] = "yes"
@@ -1149,7 +1156,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
11491156
// is not present or if it is expired."
11501157
release["Signed-By"] = p.SignedBy
11511158
// Let's use a century as a "forever" value.
1152-
release["Valid-Until"] = date_now.AddDate(100, 0, 0).Format(datetime_format)
1159+
release["Valid-Until"] = publishDate.AddDate(100, 0, 0).Format(datetime_format)
11531160
}
11541161
release["Description"] = " Generated by aptly\n"
11551162
release["MD5Sum"] = ""

deb/publish_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,47 @@ func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
433433
c.Check(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/binary-i386/Release"), PathExists)
434434
}
435435

436+
func (s *PublishedRepoSuite) TestPublishSourceDateEpoch(c *C) {
437+
// Test with SOURCE_DATE_EPOCH set
438+
_ = os.Setenv("SOURCE_DATE_EPOCH", "1234567890")
439+
defer os.Unsetenv("SOURCE_DATE_EPOCH")
440+
441+
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
442+
c.Assert(err, IsNil)
443+
444+
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"))
445+
c.Assert(err, IsNil)
446+
defer rf.Close()
447+
448+
cfr := NewControlFileReader(rf, true, false)
449+
st, err := cfr.ReadStanza()
450+
c.Assert(err, IsNil)
451+
452+
// Expected date for Unix timestamp 1234567890: Fri, 13 Feb 2009 23:31:30 UTC
453+
c.Check(st["Date"], Equals, "Fri, 13 Feb 2009 23:31:30 UTC")
454+
}
455+
456+
func (s *PublishedRepoSuite) TestPublishSourceDateEpochInvalid(c *C) {
457+
// Test with invalid SOURCE_DATE_EPOCH (should fallback to current time)
458+
_ = os.Setenv("SOURCE_DATE_EPOCH", "invalid")
459+
defer os.Unsetenv("SOURCE_DATE_EPOCH")
460+
461+
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
462+
c.Assert(err, IsNil)
463+
464+
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/maverick/Release"))
465+
c.Assert(err, IsNil)
466+
defer rf.Close()
467+
468+
cfr := NewControlFileReader(rf, true, false)
469+
st, err := cfr.ReadStanza()
470+
c.Assert(err, IsNil)
471+
472+
// Should have a valid Date field (not empty, not the fixed date from SOURCE_DATE_EPOCH)
473+
c.Check(st["Date"], Not(Equals), "")
474+
c.Check(st["Date"], Not(Equals), "Fri, 13 Feb 2009 23:31:30 UTC")
475+
}
476+
436477
func (s *PublishedRepoSuite) TestPublishLocalRepo(c *C) {
437478
err := s.repo2.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
438479
c.Assert(err, IsNil)

man/aptly.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2472,6 +2472,9 @@ show yaml config
24722472
.SH "ENVIRONMENT"
24732473
If environment variable \fBHTTP_PROXY\fR is set \fBaptly\fR would use its value to proxy all HTTP requests\.
24742474
.
2475+
.P
2476+
If environment variable \fBSOURCE_DATE_EPOCH\fR is set to a Unix timestamp, \fBaptly\fR would use that timestamp for the \fBDate\fR and \fBValid\-Until\fR fields in the \fBRelease\fR file when publishing\. This enables reproducible builds as specified by \fIhttps://reproducible\-builds\.org/specs/source\-date\-epoch/\fR\.
2477+
.
24752478
.SH "RETURN VALUES"
24762479
\fBaptly\fR exists with:
24772480
.

man/aptly.1.ronn.tmpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,11 @@ For example, default aptly display format could be presented with the following
533533
If environment variable `HTTP_PROXY` is set `aptly` would use its value
534534
to proxy all HTTP requests.
535535

536+
If environment variable `SOURCE_DATE_EPOCH` is set to a Unix timestamp,
537+
`aptly` would use that timestamp for the `Date` and `Valid-Until` fields
538+
in the `Release` file when publishing. This enables reproducible builds
539+
as specified by https://reproducible-builds.org/specs/source-date-epoch/.
540+
536541
## RETURN VALUES
537542

538543
`aptly` exists with:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Loading packages...
2+
Generating metadata files and linking package files...
3+
Finalizing metadata files...
4+
5+
Local repo local-repo has been successfully published.
6+
Please setup your webserver to serve directory '${HOME}/.aptly/public' with autoindexing.
7+
Now you can add following line to apt sources:
8+
deb http://your-server/ maverick main
9+
deb-src http://your-server/ maverick main
10+
Don't forget to add your GPG key to apt with apt-key.
11+
12+
You can also use `aptly serve` to publish your repositories over HTTP quickly.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Origin: . maverick
2+
Label: . maverick
3+
Suite: maverick
4+
Codename: maverick
5+
Date: Fri, 13 Feb 2009 23:31:30 UTC
6+
Architectures: i386
7+
Components: main
8+
Description: Generated by aptly
9+
MD5Sum:
10+
SHA1:
11+
SHA256:
12+
SHA512:

system/t06_publish/repo.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ def strip_processor(output):
99
return "\n".join([l for l in output.split("\n") if not l.startswith(' ') and not l.startswith('Date:') and not l.startswith('Valid-Until:')])
1010

1111

12+
def strip_processor_keep_date(output):
13+
return "\n".join([l for l in output.split("\n") if not l.startswith(' ')])
14+
15+
1216
class PublishRepo1Test(BaseTest):
1317
"""
1418
publish repo: default
@@ -970,3 +974,34 @@ def check(self):
970974
# verify contents except of sums
971975
self.check_file_contents(
972976
'public/dists/maverick/Release', 'release', match_prepare=strip_processor)
977+
978+
979+
class PublishRepo36Test(BaseTest):
980+
"""
981+
publish repo: SOURCE_DATE_EPOCH produces byte-identical output
982+
"""
983+
fixtureCmds = [
984+
"aptly repo create local-repo",
985+
"aptly repo add local-repo ${files}",
986+
]
987+
runCmd = "aptly publish repo -skip-signing -distribution=maverick local-repo"
988+
gold_processor = BaseTest.expand_environ
989+
environmentOverride = {"SOURCE_DATE_EPOCH": "1234567890"}
990+
991+
def check(self):
992+
super(PublishRepo36Test, self).check()
993+
994+
# verify Release file includes the expected date from SOURCE_DATE_EPOCH
995+
self.check_file_contents(
996+
'public/dists/maverick/Release', 'release', match_prepare=strip_processor_keep_date)
997+
998+
# save Release file from first publish
999+
first_release = self.read_file('public/dists/maverick/Release')
1000+
1001+
# drop and republish with same SOURCE_DATE_EPOCH
1002+
self.run_cmd("aptly publish drop maverick")
1003+
self.run_cmd("aptly publish repo -skip-signing -distribution=maverick local-repo")
1004+
1005+
# verify byte-identical output
1006+
second_release = self.read_file('public/dists/maverick/Release')
1007+
self.check_equal(first_release, second_release)

0 commit comments

Comments
 (0)