Skip to content

Commit a794e87

Browse files
authored
Merge pull request #1456 from aptly-dev/doc/gpg-api
doc: add swagger doc for /api/gpg/key tests: use faketime for expired keys/signatures
2 parents a11e004 + 5b04d4f commit a794e87

9 files changed

Lines changed: 69 additions & 31 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ jobs:
3030
GOPROXY: "https://proxy.golang.org"
3131

3232
steps:
33-
- name: "Install Packages"
33+
- name: "Install Test Packages"
3434
run: |
3535
sudo apt-get update
36-
sudo apt-get install -y --no-install-recommends graphviz gnupg2 gpgv2 git gcc make devscripts python3 python3-requests-unixsocket python3-termcolor python3-swiftclient python3-boto python3-azure-storage python3-etcd3 python3-plyvel flake8
36+
sudo apt-get install -y --no-install-recommends graphviz gnupg2 gpgv2 git gcc make devscripts python3 python3-requests-unixsocket python3-termcolor python3-swiftclient python3-boto python3-azure-storage python3-etcd3 python3-plyvel flake8 faketime
3737
3838
- name: "Checkout Repository"
3939
uses: actions/checkout@v4
@@ -139,7 +139,7 @@ jobs:
139139
APT_LISTCHANGES_FRONTEND: none
140140
DEBIAN_FRONTEND: noninteractive
141141
steps:
142-
- name: "Install packages"
142+
- name: "Install Build Packages"
143143
run: |
144144
apt-get update
145145
apt-get install -y --no-install-recommends make ca-certificates git curl build-essential devscripts dh-golang jq bash-completion lintian \

Makefile

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ COVERAGE_DIR?=$(shell mktemp -d)
77
GOOS=$(shell go env GOHOSTOS)
88
GOARCH=$(shell go env GOHOSTARCH)
99

10+
# Unit Tests and some sysmte tests rely on expired certificates, turn back the time
11+
export TEST_FAKETIME := 2025-01-02 03:04:05
12+
1013
# export CAPUTRE=1 for regenrating test gold files
1114
ifeq ($(CAPTURE),1)
1215
CAPTURE_ARG := --capture
@@ -86,11 +89,11 @@ install:
8689
# go install -v
8790
@out=`mktemp`; if ! go install -v > $$out 2>&1; then cat $$out; rm -f $$out; echo "\nBuild failed\n"; exit 1; else rm -f $$out; fi
8891

89-
test: prepare swagger etcd-install ## Run unit tests
92+
test: prepare swagger etcd-install ## Run unit tests (add TEST=regex to specify which tests to run)
9093
@echo "\e[33m\e[1mStarting etcd ...\e[0m"
9194
@mkdir -p /tmp/aptly-etcd-data; system/t13_etcd/start-etcd.sh > /tmp/aptly-etcd-data/etcd.log 2>&1 &
9295
@echo "\e[33m\e[1mRunning go test ...\e[0m"
93-
go test -v ./... -gocheck.v=true -coverprofile=unit.out; echo $$? > .unit-test.ret
96+
faketime "$(TEST_FAKETIME)" go test -v ./... -gocheck.v=true -check.f "$(TEST)" -coverprofile=unit.out; echo $$? > .unit-test.ret
9497
@echo "\e[33m\e[1mStopping etcd ...\e[0m"
9598
@pid=`cat /tmp/etcd.pid`; kill $$pid
9699
@rm -f /tmp/aptly-etcd-data/etcd.log
@@ -104,7 +107,7 @@ system-test: prepare swagger etcd-install ## Run system tests
104107
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
105108
test -f ~/etcd.db || (curl -o ~/etcd.db.xz http://repo.aptly.info/system-tests/etcd.db.xz && xz -d ~/etcd.db.xz)
106109
# Run system tests
107-
PATH=$(BINPATH)/:$(PATH) && FORCE_COLOR=1 $(PYTHON) system/run.py --long --coverage-dir $(COVERAGE_DIR) $(CAPTURE_ARG) $(TEST)
110+
PATH=$(BINPATH)/:$(PATH) FORCE_COLOR=1 $(PYTHON) system/run.py --long --coverage-dir $(COVERAGE_DIR) $(CAPTURE_ARG) $(TEST)
108111

109112
bench:
110113
@echo "\e[33m\e[1mRunning benchmark ...\e[0m"
@@ -176,13 +179,13 @@ docker-shell: ## Run aptly and other commands in docker container
176179
docker-deb: ## Build debian packages in docker container
177180
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper dpkg DEBARCH=amd64
178181

179-
docker-unit-test: ## Run unit tests in docker container
182+
docker-unit-test: ## Run unit tests in docker container (add TEST=regex to specify which tests to run)
180183
@docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper \
181184
azurite-start \
182185
AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \
183186
AZURE_STORAGE_ACCOUNT=devstoreaccount1 \
184187
AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \
185-
test \
188+
test TEST=$(TEST) \
186189
azurite-stop
187190

188191
docker-system-test: ## Run system tests in docker container (add TEST=t04_mirror or TEST=UpdateMirror26Test to run only specific tests)

api/gpg.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,34 @@ import (
1313
)
1414

1515
type gpgAddKeyParams struct {
16-
// Keyserver, when downloading GpgKeyIDs
17-
Keyserver string `json:"Keyserver" example:"hkp://keyserver.ubuntu.com:80"`
18-
// GpgKeyIDs to download from Keyserver, comma separated list
19-
GpgKeyID string `json:"GpgKeyID" example:"EF0F382A1A7B6500,8B48AD6246925553"`
20-
// Armored gpg public ket, instead of downloading from keyserver
21-
GpgKeyArmor string `json:"GpgKeyArmor" example:""`
2216
// Keyring for adding the keys (default: trustedkeys.gpg)
2317
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
18+
19+
// Add ASCII armored gpg public key, do not download from keyserver
20+
GpgKeyArmor string `json:"GpgKeyArmor" example:""`
21+
22+
// Keyserver to download keys provided in `GpgKeyID`
23+
Keyserver string `json:"Keyserver" example:"hkp://keyserver.ubuntu.com:80"`
24+
// Keys do download from `Keyserver`, separated by space
25+
GpgKeyID string `json:"GpgKeyID" example:"EF0F382A1A7B6500 8B48AD6246925553"`
2426
}
2527

2628
// @Summary Add GPG Keys
2729
// @Description **Adds GPG keys to aptly keyring**
2830
// @Description
2931
// @Description Add GPG public keys for veryfing remote repositories for mirroring.
32+
// @Description
33+
// @Description Keys can be added in two ways:
34+
// @Description * By providing the ASCII armord key in `GpgKeyArmor` (leave Keyserver and GpgKeyID empty)
35+
// @Description * By providing a `Keyserver` and one or more key IDs in `GpgKeyID`, separated by space (leave GpgKeyArmor empty)
36+
// @Description
3037
// @Tags Mirrors
38+
// @Consume json
39+
// @Param request body gpgAddKeyParams true "Parameters"
3140
// @Produce json
3241
// @Success 200 {object} string "OK"
3342
// @Failure 400 {object} Error "Bad Request"
34-
// @Failure 404 {object} Error "Not Found"
35-
// @Router /api/gpg [post]
43+
// @Router /api/gpg/key [post]
3644
func apiGPGAddKey(c *gin.Context) {
3745
b := gpgAddKeyParams{}
3846
if c.Bind(&b) != nil {

api/repos.go

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ type repoCreateParams struct {
107107
// @Description {"Name":"aptly-repo","Comment":"","DefaultDistribution":"","DefaultComponent":""}
108108
// @Description ```
109109
// @Tags Repos
110-
// @Produce json
111110
// @Consume json
112111
// @Param request body repoCreateParams true "Parameters"
112+
// @Produce json
113113
// @Success 201 {object} deb.LocalRepo
114114
// @Failure 404 {object} Error "Source snapshot not found"
115115
// @Failure 409 {object} Error "Local repo already exists"
@@ -178,8 +178,10 @@ type reposEditParams struct {
178178
// @Summary Update Repository
179179
// @Description **Update local repository meta information**
180180
// @Tags Repos
181-
// @Produce json
181+
// @Param name path string true "Repository name"
182+
// @Consume json
182183
// @Param request body reposEditParams true "Parameters"
184+
// @Produce json
183185
// @Success 200 {object} deb.LocalRepo "msg"
184186
// @Failure 404 {object} Error "Not Found"
185187
// @Failure 500 {object} Error "Internal Server Error"
@@ -231,8 +233,8 @@ func apiReposEdit(c *gin.Context) {
231233
// @Summary Get Repository Info
232234
// @Description Returns basic information about local repository.
233235
// @Tags Repos
234-
// @Produce json
235236
// @Param name path string true "Repository name"
237+
// @Produce json
236238
// @Success 200 {object} deb.LocalRepo
237239
// @Failure 404 {object} Error "Repository not found"
238240
// @Router /api/repos/{name} [get]
@@ -254,9 +256,10 @@ func apiReposShow(c *gin.Context) {
254256
// @Description Cannot drop repos that are published.
255257
// @Description Needs force=1 to drop repos used as source by other repos.
256258
// @Tags Repos
257-
// @Produce json
259+
// @Param name path string true "Repository name"
258260
// @Param _async query bool false "Run in background and return task object"
259261
// @Param force query int false "force: 1 to enable"
262+
// @Produce json
260263
// @Success 200 {object} task.ProcessReturnValue "Repo object"
261264
// @Failure 404 {object} Error "Not Found"
262265
// @Failure 404 {object} Error "Repo Conflict"
@@ -306,12 +309,12 @@ func apiReposDrop(c *gin.Context) {
306309
// @Description ["Pi386 aptly 0.8 966561016b44ed80"]
307310
// @Description ```
308311
// @Tags Repos
309-
// @Produce json
310-
// @Param name path string true "Snapshot to search"
312+
// @Param name path string true "Repository name"
311313
// @Param q query string true "Package query (e.g Name%20(~%20matlab))"
312314
// @Param withDeps query string true "Set to 1 to include dependencies when evaluating package query"
313315
// @Param format query string true "Set to 'details' to return extra info about each package"
314316
// @Param maximumVersion query string true "Set to 1 to only return the highest version for each package name"
317+
// @Produce json
315318
// @Success 200 {object} string "msg"
316319
// @Failure 404 {object} Error "Not Found"
317320
// @Failure 404 {object} Error "Internal Server Error"
@@ -406,9 +409,10 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
406409
// @Description
407410
// @Description API verifies that packages actually exist in aptly database and checks constraint that conflicting packages can’t be part of the same local repository.
408411
// @Tags Repos
409-
// @Produce json
412+
// @Param name path string true "Repository name"
410413
// @Param request body reposPackagesAddDeleteParams true "Parameters"
411414
// @Param _async query bool false "Run in background and return task object"
415+
// @Produce json
412416
// @Success 200 {object} string "msg"
413417
// @Failure 400 {object} Error "Bad Request"
414418
// @Failure 404 {object} Error "Not Found"
@@ -426,9 +430,11 @@ func apiReposPackagesAdd(c *gin.Context) {
426430
// @Description
427431
// @Description Any package(s) can be removed from a local repository. Package references from a local repository can be retrieved with GET /api/repos/:name/packages.
428432
// @Tags Repos
429-
// @Produce json
430-
// @Param request body reposPackagesAddDeleteParams true "Parameters"
433+
// @Param name path string true "Repository name"
431434
// @Param _async query bool false "Run in background and return task object"
435+
// @Consume json
436+
// @Param request body reposPackagesAddDeleteParams true "Parameters"
437+
// @Produce json
432438
// @Success 200 {object} string "msg"
433439
// @Failure 400 {object} Error "Bad Request"
434440
// @Failure 404 {object} Error "Not Found"
@@ -608,8 +614,8 @@ type reposCopyPackageParams struct {
608614
// @Description Copies a package from a source to destination repository
609615
// @Tags Repos
610616
// @Produce json
611-
// @Param name path string true "Source repo"
612-
// @Param src path string true "Destination repo"
617+
// @Param name path string true "Destination repo"
618+
// @Param src path string true "Source repo"
613619
// @Param file path string true "File/packages to copy"
614620
// @Param _async query bool false "Run in background and return task object"
615621
// @Success 200 {object} task.ProcessReturnValue "msg"
@@ -762,12 +768,15 @@ func apiReposCopyPackage(c *gin.Context) {
762768
// @Summary Include File from Directory
763769
// @Description Allows automatic processing of .changes file controlling package upload (uploaded using File Upload API) to the local repository. i.e. Exposes repo include command in api.
764770
// @Tags Repos
765-
// @Produce json
771+
// @Param name path string true "Repository name"
772+
// @Param dir path string true "Directory of packages"
773+
// @Param file path string true "File/packages to include"
766774
// @Param forceReplace query int false "when value is set to 1, when adding package that conflicts with existing package, remove existing package"
767775
// @Param noRemoveFiles query int false "when value is set to 1, don’t remove files that have been imported successfully into repository"
768776
// @Param acceptUnsigned query int false "when value is set to 1, accept unsigned .changes files"
769777
// @Param ignoreSignature query int false "when value is set to 1 disable verification of .changes file signature"
770778
// @Param _async query bool false "Run in background and return task object"
779+
// @Produce json
771780
// @Success 200 {object} string "msg"
772781
// @Failure 404 {object} Error "Not Found"
773782
// @Router /api/repos/{name}/include/{dir}/{file} [post]
@@ -784,12 +793,14 @@ type reposIncludePackageFromDirResponse struct {
784793
// @Summary Include Directory
785794
// @Description Allows automatic processing of .changes file controlling package upload (uploaded using File Upload API) to the local repository. i.e. Exposes repo include command in api.
786795
// @Tags Repos
787-
// @Produce json
796+
// @Param name path string true "Repository name"
797+
// @Param dir path string true "Directory of packages"
788798
// @Param forceReplace query int false "when value is set to 1, when adding package that conflicts with existing package, remove existing package"
789799
// @Param noRemoveFiles query int false "when value is set to 1, don’t remove files that have been imported successfully into repository"
790800
// @Param acceptUnsigned query int false "when value is set to 1, accept unsigned .changes files"
791801
// @Param ignoreSignature query int false "when value is set to 1 disable verification of .changes file signature"
792802
// @Param _async query bool false "Run in background and return task object"
803+
// @Produce json
793804
// @Success 200 {object} reposIncludePackageFromDirResponse "Response"
794805
// @Failure 404 {object} Error "Not Found"
795806
// @Router /api/repos/{name}/include/{dir} [post]

system/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ RUN apt-get update -y && apt-get install -y --no-install-recommends curl gnupg b
66
g++ python3-etcd3 python3-plyvel graphviz devscripts sudo dh-golang binutils-i686-linux-gnu binutils-aarch64-linux-gnu \
77
binutils-arm-linux-gnueabihf bash-completion zip ruby-dev lintian npm \
88
libc6-dev-i386-cross libc6-dev-armhf-cross libc6-dev-arm64-cross \
9-
gcc-i686-linux-gnu gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu && \
9+
gcc-i686-linux-gnu gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu \
10+
faketime && \
1011
apt-get clean && rm -rf /var/lib/apt/lists/*
1112

1213
RUN useradd -m --shell /bin/bash --home-dir /var/lib/aptly aptly

system/lib.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class BaseTest(object):
130130
sortOutput = False
131131
debugOutput = False
132132
EtcdServer = None
133+
faketime = False
133134

134135
aptlyDir = ".aptly"
135136
aptlyConfigFile = ".aptly.conf"
@@ -311,6 +312,9 @@ def _start_process(self, command, stderr=subprocess.STDOUT, stdout=None):
311312
aptly_testing_bin = Path(__file__).parent / ".." / "aptly.test"
312313
command = [str(aptly_testing_bin), f"-test.coverprofile={Path(self.coverage_dir) / self.__class__.__name__}-{uuid4()}.out", *command[1:]]
313314

315+
if self.faketime:
316+
command = ["faketime", os.environ.get("TEST_FAKETIME", "2025-01-02 03:04:05")] + command
317+
314318
environ = os.environ.copy()
315319
environ["LC_ALL"] = "C"
316320
environ.update(self.environmentOverride)

system/run.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non
5050
if not coverage_dir:
5151
coverage_dir = mkdtemp(suffix="aptly-coverage")
5252

53+
failed = False
5354
for test in tests:
5455
orig_stdout = sys.stdout
5556
orig_stderr = sys.stderr
@@ -157,8 +158,15 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non
157158

158159
t.shutdown()
159160

161+
if failed:
162+
break
163+
if failed:
164+
break
165+
160166
sys.stdout = orig_stdout
161167
sys.stderr = orig_stderr
168+
if failed:
169+
break
162170

163171
if lastBase is not None:
164172
lastBase.shutdown_class()

system/s3_lib.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ class S3Test(BaseTest):
2424
s3Overrides = {}
2525

2626
def fixture_available(self):
27-
return super(S3Test, self).fixture_available() and s3_conn is not None
27+
return super(S3Test, self).fixture_available() and \
28+
'AWS_SECRET_ACCESS_KEY' in os.environ and 'AWS_ACCESS_KEY_ID' in os.environ and \
29+
os.environ['AWS_SECRET_ACCESS_KEY'] != "" and os.environ['AWS_ACCESS_KEY_ID'] != ""
2830

2931
def prepare(self):
3032
self.bucket_name = "aptly-sys-test-" + str(uuid.uuid1())

system/t04_mirror/create.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ class CreateMirror31Test(BaseTest):
416416
runCmd = "aptly mirror create --keyring=aptlytest-gpg1.gpg mirror11 http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch"
417417
configOverride = {"gpgProvider": "internal", "max-tries": 1}
418418
fixtureGpg = True
419+
faketime = True
419420

420421
def outputMatchPrepare(self, s):
421422
return re.sub(r'Signature made .* using', '', s)

0 commit comments

Comments
 (0)