Merge pull request #1479 from abregar/issues-309-691

Add config key for 'gpgKeys' and allow multiple keyRefs when signing with gpg, fixing Issues #309 and #691
This commit is contained in:
André Roth
2026-01-26 11:58:03 +01:00
committed by GitHub
18 changed files with 357 additions and 159 deletions
+1
View File
@@ -79,3 +79,4 @@ List of contributors, in chronological order:
* Ato Araki (https://github.com/atotto)
* Roman Lebedev (https://github.com/LebedevRI)
* Brian Witt (https://github.com/bwitt)
* Ales Bregar (https://github.com/abregar)
+17 -3
View File
@@ -16,8 +16,8 @@ import (
type signingParams struct {
// Don't sign published repository
Skip bool ` json:"Skip" example:"false"`
// GPG key ID to use when signing the release, if not specified default key is used
GpgKey string ` json:"GpgKey" example:"A0546A43624A8331"`
// GPG key ID(s) to use when signing the release, separated by comma, and if not specified, default configured key(s) are used
GpgKey string ` json:"GpgKey" example:"KEY_ID_a, KEY_ID_b"`
// GPG keyring to use (instead of default)
Keyring string ` json:"Keyring" example:"trustedkeys.gpg"`
// GPG secret keyring to use (instead of default) Note: depreciated with gpg2
@@ -41,7 +41,21 @@ func getSigner(options *signingParams) (pgp.Signer, error) {
}
signer := context.GetSigner()
signer.SetKey(options.GpgKey)
var multiGpgKeys []string
// REST params have priority over config
if options.GpgKey != "" {
for _, p := range strings.Split(options.GpgKey, ",") {
if t := strings.TrimSpace(p); t != "" {
multiGpgKeys = append(multiGpgKeys, t)
}
}
} else if len(context.Config().GpgKeys) > 0 {
multiGpgKeys = context.Config().GpgKeys
}
for _, gpgKey := range multiGpgKeys {
signer.SetKey(gpgKey)
}
signer.SetKeyRing(options.Keyring, options.SecretKeyring)
signer.SetPassphrase(options.Passphrase, options.PassphraseFile)
+33 -1
View File
@@ -1,6 +1,8 @@
package cmd
import (
"strings"
"github.com/aptly-dev/aptly/pgp"
"github.com/smira/commander"
"github.com/smira/flag"
@@ -12,7 +14,20 @@ func getSigner(flags *flag.FlagSet) (pgp.Signer, error) {
}
signer := context.GetSigner()
signer.SetKey(flags.Lookup("gpg-key").Value.String())
var gpgKeys []string
// CLI args have priority over config
cliKeys := flags.Lookup("gpg-key").Value.Get().([]string)
if len(cliKeys) > 0 {
gpgKeys = cliKeys
} else if len(context.Config().GpgKeys) > 0 {
gpgKeys = context.Config().GpgKeys
}
for _, gpgKey := range gpgKeys {
signer.SetKey(gpgKey)
}
signer.SetKeyRing(flags.Lookup("keyring").Value.String(), flags.Lookup("secret-keyring").Value.String())
signer.SetPassphrase(flags.Lookup("passphrase").Value.String(), flags.Lookup("passphrase-file").Value.String())
signer.SetBatch(flags.Lookup("batch").Value.Get().(bool))
@@ -26,6 +41,23 @@ func getSigner(flags *flag.FlagSet) (pgp.Signer, error) {
}
type gpgKeyFlag struct {
gpgKeys []string
}
func (k *gpgKeyFlag) Set(value string) error {
k.gpgKeys = append(k.gpgKeys, value)
return nil
}
func (k *gpgKeyFlag) Get() interface{} {
return k.gpgKeys
}
func (k *gpgKeyFlag) String() string {
return strings.Join(k.gpgKeys, ",")
}
func makeCmdPublish() *commander.Command {
return &commander.Command{
UsageLine: "publish",
+1 -1
View File
@@ -34,7 +34,7 @@ Example:
}
cmd.Flag.String("distribution", "", "distribution name to publish")
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
+1 -1
View File
@@ -234,7 +234,7 @@ Example:
}
cmd.Flag.String("distribution", "", "distribution name to publish")
cmd.Flag.String("component", "", "component name to publish (for multi-component publishing, separate components with commas)")
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
+1 -1
View File
@@ -155,7 +155,7 @@ This command would switch published repository (with one component) named ppa/wh
`,
Flag: *flag.NewFlagSet("aptly-publish-switch", flag.ExitOnError),
}
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
+1 -1
View File
@@ -127,7 +127,7 @@ Example:
`,
Flag: *flag.NewFlagSet("aptly-publish-update", flag.ExitOnError),
}
cmd.Flag.String("gpg-key", "", "GPG key ID to use when signing the release")
cmd.Flag.Var(&gpgKeyFlag{}, "gpg-key", "GPG key ID to use when signing the release (flag is repeatable, can be specified multiple times)")
cmd.Flag.Var(&keyRingsFlag{}, "keyring", "GPG keyring to use (instead of default)")
cmd.Flag.String("secret-keyring", "", "GPG secret keyring to use (instead of default)")
cmd.Flag.String("passphrase", "", "GPG passphrase for the key (warning: could be insecure)")
+20 -1
View File
@@ -11,7 +11,26 @@ Repositories can be published to local directories, Amazon S3 buckets, Azure or
GPG key is required to sign any published repository. The key pari should be generated before publishing.
Publiс part of the key should be exported from your keyring using `gpg --export --armor` and imported on the system which uses a published repository.
Public part of the key should be exported from your keyring using `gpg --export --armor` and imported on the system which uses a published repository.
* Multiple signing keys can be defined in aptly.conf using the gpgKeys array:
```
"gpgKeys": [
"KEY_ID_x",
"KEY_ID_y"
]
```
* It is also possible to pass multiple keys via the CLI using the repeatable `--gpg-key` flag:
```
aptly publish repo my-repo --gpg-key=KEY_ID_a --gpg-key=KEY_ID_b
```
* When using the REST API, the `gpgKey` parameter supports a comma-separated list of key IDs:
```
"gpgKey": "KEY_ID_a,KEY_ID_b"
```
* If `--gpg-key` is specified on the command line, or `gpgKey` is provided via the REST API, it takes precedence over any gpgKeys configuration in aptly.conf.
* With multi-key support, aptly will sign all Release files (both clearsigned and detached signatures) with each provided key, ensuring a smooth key rotation process while maintaining compatibility for existing clients.
#### Parameters
+11 -4
View File
@@ -22,7 +22,7 @@ var (
type GpgSigner struct {
gpg string
version GPGVersion
keyRef string
keyRefs []string
keyring, secretKeyring string
passphrase, passphraseFile string
batch bool
@@ -35,7 +35,14 @@ func (g *GpgSigner) SetBatch(batch bool) {
// SetKey sets key ID to use when signing files
func (g *GpgSigner) SetKey(keyRef string) {
g.keyRef = keyRef
keyRef = strings.TrimSpace(keyRef)
if keyRef != "" {
if g.keyRefs == nil {
g.keyRefs = []string{keyRef}
} else {
g.keyRefs = append(g.keyRefs, keyRef)
}
}
}
// SetKeyRing allows to set custom keyring and secretkeyring
@@ -57,8 +64,8 @@ func (g *GpgSigner) gpgArgs() []string {
args = append(args, "--secret-keyring", g.secretKeyring)
}
if g.keyRef != "" {
args = append(args, "-u", g.keyRef)
for _, k := range g.keyRefs {
args = append(args, "-u", k)
}
if g.passphrase != "" || g.passphraseFile != "" {
+29
View File
@@ -0,0 +1,29 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGiBFL7pY8RBAC5uHg/9AuGJ7EF7RYty89IDLeqvlPe710eDQpJ+itsOaA/5rr3
IV1LMlqHpM2rkZkAPpARwjrga2ByJ1ww77Zq2uPqJIO2LZYWTLXic9Zity2OVu3Z
XwtdsqagIMfT5dAgNmhe5lL7qgGUwYcFFa52s7U4qO0z2FfwHW1IQrnMpwCg5RQh
Uqs5iUKdDtoeQjX5mWgQhjEEAI1zfXUvvcOrRsDlGNKYZigZiWC6J46jeR8Nnf9C
WwhXS2fzQaJyDq9DorkvPZgWUAaLLCdfGETqLzDKajynhS1+OnfFQNzvkvEPRBSb
C5k+GOF2E1E9rGXb31+1XZTcdIprp4/F3RNLLWNUwfgPLWJx9NzHTYqgBStecHkC
ySZRA/9PNFAbeJZ27HNuzoGnAa0piZDLeAAHsM1V6cosMh7U1IZqjZcrMC9YXNxH
2D90PvoBvpufCMRzL/fOVPT1JzQGYoKIX17Nmzvdq/a4YyLWRODjvWXd94bae2Xd
Vy03DYhfp8VOVJW6HuAX9JN6MKXSNxaibgOPjU822Hxd1iCIQ7QtQXB0bHkgVGVz
dGVyIChkb24ndCB1c2UgaXQpIDx0ZXN0QGFwdGx5LmluZm8+iGIEExECACIFAlL7
pY8CGyMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJECHbuJwW2z5t2sQAoNn+
0cADZa66HZNY2qJi44Oq4hjaAJsHzj9JKAHEpdix5N7b6QvaZQZYhrkBDQRS+6WP
EAQA9BX+kbIM6VJYoyY9vUHXfAF4E2y2M7vl9knZ+jMPfMbI7dE3gRJQb3mngST5
7eZWawo1DNE6h3LbHsB4mpro9XLUXUMBgXRsOq4D5E0ygvDZ/tJhy0AwFiTOXKEs
/erzmbF7j/TWh4LVHXFI9DrnN0+EeF/mQC/wzX7WGCKe70cAAwUEAMr7959zUYNp
E3v4IquIJpD22bT/FiyQjFG8yGy36c+7mOP3VWi0lz5yFqqeR9NDFuLDSwOEi0nB
zXNmimLy+hIwMaHjbQLjLODmy/T9wKCgeAmK1ygT6YBGJJflThZ05M80T5hBtRA9
z2eoTn0wbi6MLmD/rbEt+lUPfSA4V0t2iEkEGBECAAkFAlL7pY8CGwwACgkQIdu4
nBbbPm05hgCgvYatZXRbEdZ91jJCQi1KI7lJ5Y8AnjvrHU0g84mE45QZFegZzzQo
9relmDMEZ3YCRhYJKwYBBAHaRw8BAQdAYDU0VSBcurX+uqAeR/w/XOLSZcghvOqz
Y8yWdcj3HUy0L0FwdGx5IFNlY29uZGFyeSBTaWduaW5nIEtleSA8YXB0bHlAZXhh
bXBsZS5jb20+iJYEExYKAD4WIQSu4W3wGDVPZ/5fXHK79OGUNOkeTgUCZ3YCRgIb
AwUJA8JnAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC79OGUNOkeTid/AP9A
kIMn2qI5TqZgzrnPt7SN16VvpMppPb2H0m0P6knQKQD8DHcLcrqAl2cjcEuntv75
gOnEvmPDAO6S1rc8UgcWdQQ=
=XPoo
-----END PGP PUBLIC KEY BLOCK-----
+11
View File
@@ -0,0 +1,11 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
lFgEZ3YCRhYJKwYBBAHaRw8BAQdAYDU0VSBcurX+uqAeR/w/XOLSZcghvOqzY8yW
dcj3HUwAAP9lsZgE1YQfaS9xfVOSi3f91lbq13+U9FPdwxfiET0+bBFrtC9BcHRs
eSBTZWNvbmRhcnkgU2lnbmluZyBLZXkgPGFwdGx5QGV4YW1wbGUuY29tPoiWBBMW
CgA+FiEEruFt8Bg1T2f+X1xyu/ThlDTpHk4FAmd2AkYCGwMFCQPCZwAFCwkIBwIG
FQoJCAsCBBYCAwECHgECF4AACgkQu/ThlDTpHk4nfwD/QJCDJ9qiOU6mYM65z7e0
jdelb6TKaT29h9JtD+pJ0CkA/Ax3C3K6gJdnI3BLp7b++YDpxL5jwwDukta3PFIH
FnUE
=IXTY
-----END PGP PRIVATE KEY BLOCK-----
+3
View File
@@ -272,6 +272,9 @@ class BaseTest(object):
self.run_cmd([
self.gpgFinder.gpg2, "--import",
os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files") + "/aptly.sec"], expected_code=None)
self.run_cmd([
self.gpgFinder.gpg2, "--import",
os.path.join(os.path.dirname(inspect.getsourcefile(BaseTest)), "files") + "/aptly3.sec"], expected_code=None)
if self.fixtureGpg:
self.run_cmd([self.gpgFinder.gpg, "--no-default-keyring", "--trust-model", "always", "--batch", "--keyring", "aptlytest.gpg", "--import"] +
+1
View File
@@ -29,6 +29,7 @@
"gpgProvider": "gpg",
"gpgDisableSign": false,
"gpgDisableVerify": false,
"gpgKeys": [],
"skipContentsPublishing": false,
"skipBz2Publishing": false,
"FileSystemPublishEndpoints": {},
@@ -27,6 +27,7 @@ download_sourcepackages: false
gpg_provider: gpg
gpg_disable_sign: false
gpg_disable_verify: false
gpg_keys: []
skip_contents_publishing: false
skip_bz2_publishing: false
filesystem_publish_endpoints: {}
@@ -0,0 +1,14 @@
gpg: Signature made Mon Jan 26 10:18:32 2026 UTC
gpg: using DSA key C5ACD2179B5231DFE842EE6121DBB89C16DB3E6D
gpg: checking the trustdb
gpg: no ultimately trusted keys found
gpg: Good signature from "Aptly Tester (don't use it) <test@aptly.info>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: C5AC D217 9B52 31DF E842 EE61 21DB B89C 16DB 3E6D
gpg: Signature made Mon Jan 26 10:18:32 2026 UTC
gpg: using EDDSA key AEE16DF018354F67FE5F5C72BBF4E19434E91E4E
gpg: Good signature from "Aptly Secondary Signing Key <aptly@example.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: AEE1 6DF0 1835 4F67 FE5F 5C72 BBF4 E194 34E9 1E4E
+61
View File
@@ -1,6 +1,7 @@
import inspect
import os
import threading
import re
from api_lib import TASK_SUCCEEDED, APITest
@@ -1874,3 +1875,63 @@ class PublishUpdateSourcesAPITestRepo(APITest):
all_repos = self.get("/api/publish")
self.check_equal(all_repos.status_code, 200)
self.check_in(repo_expected, all_repos.json())
class PublishAPITestDualSignature(APITest):
"""
POST /publish/:prefix (local repos), GET /publish
"""
fixtureGpg = True
def check(self):
repo_name = self.random_name()
self.check_equal(self.post(
"/api/repos", json={"Name": repo_name, "DefaultDistribution": "wheezy"}).status_code, 201)
d = self.random_name()
self.check_equal(self.upload("/api/files/" + d,
"libboost-program-options-dev_1.49.0.1_i386.deb", "pyspi_0.6.1-1.3.dsc",
"pyspi_0.6.1-1.3.diff.gz", "pyspi_0.6.1.orig.tar.gz",
"pyspi-0.6.1-1.3.stripped.dsc").status_code, 200)
task = self.post_task("/api/repos/" + repo_name + "/file/" + d)
self.check_task(task)
# publishing under prefix, default distribution
prefix = self.random_name()
task = self.post_task(
"/api/publish/" + prefix,
json={
"SourceKind": "local",
"Sources": [{"Name": repo_name}],
"Signing": {"GPGKey": "C5ACD2179B5231DFE842EE6121DBB89C16DB3E6D,AEE16DF018354F67FE5F5C72BBF4E19434E91E4E"},
}
)
self.check_task(task)
repo_expected = {
'AcquireByHash': False,
'Architectures': ['i386', 'source'],
'Codename': '',
'Distribution': 'wheezy',
'Label': '',
'Origin': '',
'NotAutomatic': '',
'ButAutomaticUpgrades': '',
'Path': prefix + '/' + 'wheezy',
'Prefix': prefix,
'SignedBy': '',
'SkipContents': False,
'MultiDist': False,
'SourceKind': 'local',
'Sources': [{'Component': 'main', 'Name': repo_name}],
'Storage': '',
'Suite': ''}
all_repos = self.get("/api/publish")
self.check_equal(all_repos.status_code, 200)
self.check_in(repo_expected, all_repos.json())
self.check_exists("public/" + prefix + "/dists/wheezy/Release")
path = os.path.join(os.environ["HOME"], self.aptlyDir, "public", prefix, "dists/wheezy")
self.check_cmd_output(f"gpg --verify {path}/Release.gpg {path}/Release", "Release.gpg",
match_prepare=lambda s: re.sub(r'Signature made .*', '', s))
+5 -3
View File
@@ -49,9 +49,10 @@ type ConfigStructure struct { // nolint: maligned
DownloadSourcePackages bool `json:"downloadSourcePackages" yaml:"download_sourcepackages"`
// Signing
GpgProvider string `json:"gpgProvider" yaml:"gpg_provider"`
GpgDisableSign bool `json:"gpgDisableSign" yaml:"gpg_disable_sign"`
GpgDisableVerify bool `json:"gpgDisableVerify" yaml:"gpg_disable_verify"`
GpgProvider string `json:"gpgProvider" yaml:"gpg_provider"`
GpgDisableSign bool `json:"gpgDisableSign" yaml:"gpg_disable_sign"`
GpgDisableVerify bool `json:"gpgDisableVerify" yaml:"gpg_disable_verify"`
GpgKeys []string `json:"gpgKeys" yaml:"gpg_keys"`
// Publishing
SkipContentsPublishing bool `json:"skipContentsPublishing" yaml:"skip_contents_publishing"`
@@ -226,6 +227,7 @@ var Config = ConfigStructure{
GpgProvider: "gpg",
GpgDisableSign: false,
GpgDisableVerify: false,
GpgKeys: []string{},
DownloadSourcePackages: false,
PackagePoolStorage: PackagePoolStorage{
Local: &LocalPoolStorage{Path: ""},
+146 -143
View File
@@ -19,8 +19,8 @@ func (s *ConfigSuite) TestLoadConfig(c *C) {
_, _ = f.WriteString(configFile)
_ = f.Close()
// start with empty config
s.config = ConfigStructure{}
// start with empty config
s.config = ConfigStructure{}
err := LoadConfig(configname, &s.config)
c.Assert(err, IsNil)
@@ -32,8 +32,8 @@ func (s *ConfigSuite) TestLoadConfig(c *C) {
func (s *ConfigSuite) TestSaveConfig(c *C) {
configname := filepath.Join(c.MkDir(), "aptly.json2")
// start with empty config
s.config = ConfigStructure{}
// start with empty config
s.config = ConfigStructure{}
s.config.RootDir = "/tmp/aptly"
s.config.DownloadConcurrency = 5
@@ -71,93 +71,94 @@ func (s *ConfigSuite) TestSaveConfig(c *C) {
_, _ = f.Read(buf)
c.Check(string(buf), Equals, ""+
"{\n"+
" \"rootDir\": \"/tmp/aptly\",\n"+
" \"logLevel\": \"info\",\n"+
" \"logFormat\": \"json\",\n"+
" \"databaseOpenAttempts\": 5,\n"+
" \"architectures\": null,\n"+
" \"skipLegacyPool\": false,\n"+
" \"dependencyFollowSuggests\": false,\n"+
" \"dependencyFollowRecommends\": false,\n"+
" \"dependencyFollowAllVariants\": false,\n"+
" \"dependencyFollowSource\": false,\n"+
" \"dependencyVerboseResolve\": false,\n"+
" \"ppaDistributorID\": \"\",\n"+
" \"ppaCodename\": \"\",\n"+
" \"serveInAPIMode\": false,\n"+
" \"enableMetricsEndpoint\": false,\n"+
" \"enableSwaggerEndpoint\": false,\n"+
" \"AsyncAPI\": false,\n"+
" \"databaseBackend\": {\n"+
" \"type\": \"\",\n"+
" \"dbPath\": \"\",\n"+
" \"url\": \"\"\n"+
" },\n"+
" \"downloader\": \"\",\n"+
" \"downloadConcurrency\": 5,\n"+
" \"downloadSpeedLimit\": 0,\n"+
" \"downloadRetries\": 0,\n"+
" \"downloadSourcePackages\": false,\n"+
" \"gpgProvider\": \"gpg\",\n"+
" \"gpgDisableSign\": false,\n"+
" \"gpgDisableVerify\": false,\n"+
" \"skipContentsPublishing\": false,\n"+
" \"skipBz2Publishing\": false,\n"+
" \"FileSystemPublishEndpoints\": {\n"+
" \"test\": {\n"+
" \"rootDir\": \"/opt/aptly-publish\",\n"+
" \"linkMethod\": \"\",\n"+
" \"verifyMethod\": \"\"\n"+
" }\n"+
" },\n"+
" \"S3PublishEndpoints\": {\n"+
" \"test\": {\n"+
" \"region\": \"us-east-1\",\n"+
" \"bucket\": \"repo\",\n"+
" \"prefix\": \"\",\n"+
" \"acl\": \"\",\n"+
" \"awsAccessKeyID\": \"\",\n"+
" \"awsSecretAccessKey\": \"\",\n"+
" \"awsSessionToken\": \"\",\n"+
" \"endpoint\": \"\",\n"+
" \"storageClass\": \"\",\n"+
" \"encryptionMethod\": \"\",\n"+
" \"plusWorkaround\": false,\n"+
" \"disableMultiDel\": false,\n"+
" \"forceSigV2\": false,\n"+
" \"forceVirtualHostedStyle\": false,\n"+
" \"debug\": false\n"+
" }\n"+
" },\n"+
" \"SwiftPublishEndpoints\": {\n"+
" \"test\": {\n"+
" \"container\": \"repo\",\n"+
" \"prefix\": \"\",\n"+
" \"osname\": \"\",\n"+
" \"password\": \"\",\n"+
" \"tenant\": \"\",\n"+
" \"tenantid\": \"\",\n"+
" \"domain\": \"\",\n"+
" \"domainid\": \"\",\n"+
" \"tenantdomain\": \"\",\n"+
" \"tenantdomainid\": \"\",\n"+
" \"authurl\": \"\"\n"+
" }\n"+
" },\n"+
" \"AzurePublishEndpoints\": {\n"+
" \"test\": {\n"+
" \"container\": \"repo\",\n"+
" \"prefix\": \"\",\n"+
" \"accountName\": \"\",\n"+
" \"accountKey\": \"\",\n"+
" \"endpoint\": \"\"\n"+
" }\n"+
" },\n"+
" \"packagePoolStorage\": {\n"+
" \"type\": \"local\",\n"+
" \"path\": \"/tmp/aptly-pool\"\n"+
" }\n"+
"{\n" +
" \"rootDir\": \"/tmp/aptly\",\n" +
" \"logLevel\": \"info\",\n" +
" \"logFormat\": \"json\",\n" +
" \"databaseOpenAttempts\": 5,\n" +
" \"architectures\": null,\n" +
" \"skipLegacyPool\": false,\n" +
" \"dependencyFollowSuggests\": false,\n" +
" \"dependencyFollowRecommends\": false,\n" +
" \"dependencyFollowAllVariants\": false,\n" +
" \"dependencyFollowSource\": false,\n" +
" \"dependencyVerboseResolve\": false,\n" +
" \"ppaDistributorID\": \"\",\n" +
" \"ppaCodename\": \"\",\n" +
" \"serveInAPIMode\": false,\n" +
" \"enableMetricsEndpoint\": false,\n" +
" \"enableSwaggerEndpoint\": false,\n" +
" \"AsyncAPI\": false,\n" +
" \"databaseBackend\": {\n" +
" \"type\": \"\",\n" +
" \"dbPath\": \"\",\n" +
" \"url\": \"\"\n" +
" },\n" +
" \"downloader\": \"\",\n" +
" \"downloadConcurrency\": 5,\n" +
" \"downloadSpeedLimit\": 0,\n" +
" \"downloadRetries\": 0,\n" +
" \"downloadSourcePackages\": false,\n" +
" \"gpgProvider\": \"gpg\",\n" +
" \"gpgDisableSign\": false,\n" +
" \"gpgDisableVerify\": false,\n" +
" \"gpgKeys\": null,\n" +
" \"skipContentsPublishing\": false,\n" +
" \"skipBz2Publishing\": false,\n" +
" \"FileSystemPublishEndpoints\": {\n" +
" \"test\": {\n" +
" \"rootDir\": \"/opt/aptly-publish\",\n" +
" \"linkMethod\": \"\",\n" +
" \"verifyMethod\": \"\"\n" +
" }\n" +
" },\n" +
" \"S3PublishEndpoints\": {\n" +
" \"test\": {\n" +
" \"region\": \"us-east-1\",\n" +
" \"bucket\": \"repo\",\n" +
" \"prefix\": \"\",\n" +
" \"acl\": \"\",\n" +
" \"awsAccessKeyID\": \"\",\n" +
" \"awsSecretAccessKey\": \"\",\n" +
" \"awsSessionToken\": \"\",\n" +
" \"endpoint\": \"\",\n" +
" \"storageClass\": \"\",\n" +
" \"encryptionMethod\": \"\",\n" +
" \"plusWorkaround\": false,\n" +
" \"disableMultiDel\": false,\n" +
" \"forceSigV2\": false,\n" +
" \"forceVirtualHostedStyle\": false,\n" +
" \"debug\": false\n" +
" }\n" +
" },\n" +
" \"SwiftPublishEndpoints\": {\n" +
" \"test\": {\n" +
" \"container\": \"repo\",\n" +
" \"prefix\": \"\",\n" +
" \"osname\": \"\",\n" +
" \"password\": \"\",\n" +
" \"tenant\": \"\",\n" +
" \"tenantid\": \"\",\n" +
" \"domain\": \"\",\n" +
" \"domainid\": \"\",\n" +
" \"tenantdomain\": \"\",\n" +
" \"tenantdomainid\": \"\",\n" +
" \"authurl\": \"\"\n" +
" }\n" +
" },\n" +
" \"AzurePublishEndpoints\": {\n" +
" \"test\": {\n" +
" \"container\": \"repo\",\n" +
" \"prefix\": \"\",\n" +
" \"accountName\": \"\",\n" +
" \"accountKey\": \"\",\n" +
" \"endpoint\": \"\"\n" +
" }\n" +
" },\n" +
" \"packagePoolStorage\": {\n" +
" \"type\": \"local\",\n" +
" \"path\": \"/tmp/aptly-pool\"\n" +
" }\n" +
"}")
}
@@ -167,8 +168,8 @@ func (s *ConfigSuite) TestLoadYAMLConfig(c *C) {
_, _ = f.WriteString(configFileYAML)
_ = f.Close()
// start with empty config
s.config = ConfigStructure{}
// start with empty config
s.config = ConfigStructure{}
err := LoadConfig(configname, &s.config)
c.Assert(err, IsNil)
@@ -183,8 +184,8 @@ func (s *ConfigSuite) TestLoadYAMLErrorConfig(c *C) {
_, _ = f.WriteString(configFileYAMLError)
_ = f.Close()
// start with empty config
s.config = ConfigStructure{}
// start with empty config
s.config = ConfigStructure{}
err := LoadConfig(configname, &s.config)
c.Assert(err.Error(), Equals, "invalid yaml (unknown pool storage type: invalid) or json (invalid character 'p' looking for beginning of value)")
@@ -196,13 +197,13 @@ func (s *ConfigSuite) TestSaveYAMLConfig(c *C) {
_, _ = f.WriteString(configFileYAML)
_ = f.Close()
// start with empty config
s.config = ConfigStructure{}
// start with empty config
s.config = ConfigStructure{}
err := LoadConfig(configname, &s.config)
c.Assert(err, IsNil)
err = SaveConfigYAML(configname, &s.config)
err = SaveConfigYAML(configname, &s.config)
c.Assert(err, IsNil)
f, _ = os.Open(configname)
@@ -218,17 +219,17 @@ func (s *ConfigSuite) TestSaveYAMLConfig(c *C) {
}
func (s *ConfigSuite) TestSaveYAML2Config(c *C) {
// start with empty config
s.config = ConfigStructure{}
// start with empty config
s.config = ConfigStructure{}
s.config.PackagePoolStorage.Local = &LocalPoolStorage{"/tmp/aptly-pool"}
s.config.PackagePoolStorage.Azure = nil
s.config.PackagePoolStorage.Azure = nil
configname := filepath.Join(c.MkDir(), "aptly.yaml4")
err := SaveConfigYAML(configname, &s.config)
err := SaveConfigYAML(configname, &s.config)
c.Assert(err, IsNil)
f, _ := os.Open(configname)
f, _ := os.Open(configname)
defer func() {
_ = f.Close()
}()
@@ -237,44 +238,45 @@ func (s *ConfigSuite) TestSaveYAML2Config(c *C) {
buf := make([]byte, st.Size())
_, _ = f.Read(buf)
c.Check(string(buf), Equals, ""+
"root_dir: \"\"\n"+
"log_level: \"\"\n"+
"log_format: \"\"\n"+
"database_open_attempts: 0\n"+
"architectures: []\n"+
"skip_legacy_pool: false\n"+
"dep_follow_suggests: false\n"+
"dep_follow_recommends: false\n"+
"dep_follow_all_variants: false\n"+
"dep_follow_source: false\n"+
"dep_verboseresolve: false\n"+
"ppa_distributor_id: \"\"\n"+
"ppa_codename: \"\"\n"+
"serve_in_api_mode: false\n"+
"enable_metrics_endpoint: false\n"+
"enable_swagger_endpoint: false\n"+
"async_api: false\n"+
"database_backend:\n"+
" type: \"\"\n"+
" db_path: \"\"\n"+
" url: \"\"\n"+
"downloader: \"\"\n"+
"download_concurrency: 0\n"+
"download_limit: 0\n"+
"download_retries: 0\n"+
"download_sourcepackages: false\n"+
"gpg_provider: \"\"\n"+
"gpg_disable_sign: false\n"+
"gpg_disable_verify: false\n"+
"skip_contents_publishing: false\n"+
"skip_bz2_publishing: false\n"+
"filesystem_publish_endpoints: {}\n"+
"s3_publish_endpoints: {}\n"+
"swift_publish_endpoints: {}\n"+
"azure_publish_endpoints: {}\n"+
"packagepool_storage:\n"+
" type: local\n"+
c.Check(string(buf), Equals, "" +
"root_dir: \"\"\n" +
"log_level: \"\"\n" +
"log_format: \"\"\n" +
"database_open_attempts: 0\n" +
"architectures: []\n" +
"skip_legacy_pool: false\n" +
"dep_follow_suggests: false\n" +
"dep_follow_recommends: false\n" +
"dep_follow_all_variants: false\n" +
"dep_follow_source: false\n" +
"dep_verboseresolve: false\n" +
"ppa_distributor_id: \"\"\n" +
"ppa_codename: \"\"\n" +
"serve_in_api_mode: false\n" +
"enable_metrics_endpoint: false\n" +
"enable_swagger_endpoint: false\n" +
"async_api: false\n" +
"database_backend:\n" +
" type: \"\"\n" +
" db_path: \"\"\n" +
" url: \"\"\n" +
"downloader: \"\"\n" +
"download_concurrency: 0\n" +
"download_limit: 0\n" +
"download_retries: 0\n" +
"download_sourcepackages: false\n" +
"gpg_provider: \"\"\n" +
"gpg_disable_sign: false\n" +
"gpg_disable_verify: false\n" +
"gpg_keys: []\n" +
"skip_contents_publishing: false\n" +
"skip_bz2_publishing: false\n" +
"filesystem_publish_endpoints: {}\n" +
"s3_publish_endpoints: {}\n" +
"swift_publish_endpoints: {}\n" +
"azure_publish_endpoints: {}\n" +
"packagepool_storage:\n" +
" type: local\n" +
" path: /tmp/aptly-pool\n")
}
@@ -283,8 +285,8 @@ func (s *ConfigSuite) TestLoadEmptyConfig(c *C) {
f, _ := os.Create(configname)
_ = f.Close()
// start with empty config
s.config = ConfigStructure{}
// start with empty config
s.config = ConfigStructure{}
err := LoadConfig(configname, &s.config)
c.Assert(err.Error(), Equals, "invalid yaml (EOF) or json (EOF)")
@@ -322,6 +324,7 @@ download_sourcepackages: true
gpg_provider: gpg
gpg_disable_sign: true
gpg_disable_verify: true
gpg_keys: []
skip_contents_publishing: true
skip_bz2_publishing: true
filesystem_publish_endpoints: