mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-06-15 07:00:52 +00:00
Compare commits
376 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d78c467b7 | |||
| 909c5bb116 | |||
| 06bcce22d4 | |||
| b07665da50 | |||
| 034131c7a9 | |||
| c9b1d723c3 | |||
| 9346901001 | |||
| 6416440ed3 | |||
| cb3c0519d6 | |||
| be7c232204 | |||
| 5ff470fd59 | |||
| 8f76f4c24d | |||
| 57c93177ad | |||
| 2a7a04ee32 | |||
| 7ccb256841 | |||
| 00e17d82fe | |||
| 867310e8f1 | |||
| fbac933c30 | |||
| 1ab61457a5 | |||
| 9b1a0ec2da | |||
| 5cfc5a6b8e | |||
| 4655d7c188 | |||
| 88ee47045f | |||
| 84c8e5cf22 | |||
| 813d9c660d | |||
| 057fcaa598 | |||
| 0b6af54eee | |||
| 66e6d3ac6f | |||
| 0aebd14f13 | |||
| b909bccfcd | |||
| 62c92e4256 | |||
| 2f0b2cf4de | |||
| 79bd81e937 | |||
| b1cb14e921 | |||
| 091b6f9948 | |||
| 213fbccead | |||
| f06e428caf | |||
| 8b521fc722 | |||
| 7895edd100 | |||
| 5e3460ae62 | |||
| 58480d747f | |||
| 7716d4236d | |||
| 6a8723484d | |||
| ec4503c941 | |||
| bd95012687 | |||
| d4da3d5440 | |||
| dff6bbb165 | |||
| c9bae4c454 | |||
| 95ef905ca9 | |||
| 6a5f494a1f | |||
| 009087e58f | |||
| c73aa0e255 | |||
| 90e7008b8d | |||
| d7c51530f5 | |||
| c55c0f6e3c | |||
| d9099b7e6b | |||
| 444c7f8af1 | |||
| 9a1a401248 | |||
| 56f5254aa9 | |||
| 4f1838bb74 | |||
| 37841fb205 | |||
| 0164827907 | |||
| fa8e8ab6fb | |||
| 2a87554581 | |||
| 298e09e0b9 | |||
| 4ecbaf5a62 | |||
| 562820b625 | |||
| 2d86506183 | |||
| ef75ff8600 | |||
| 62b324eb65 | |||
| 4c40f4dc0a | |||
| cfdb720ef4 | |||
| 5de38a987a | |||
| c62670ea51 | |||
| 9d0b3a186e | |||
| a5702371ef | |||
| 02227d7233 | |||
| 877ffbe376 | |||
| 37b7c5aa91 | |||
| 3e7978180e | |||
| 4360fb00d7 | |||
| d90825f4f0 | |||
| 5be757e35e | |||
| e51c1894bf | |||
| 24fcde56b6 | |||
| 7390e19e03 | |||
| 0aa0c0a995 | |||
| 1d10dd6ce7 | |||
| e28fa416ab | |||
| d6c7b1d770 | |||
| 92ea4a2505 | |||
| 3e5e0fc119 | |||
| 9fa4248e3b | |||
| d958a146f7 | |||
| 125a7c2c07 | |||
| d403150d77 | |||
| 4e6c52ec2a | |||
| 90ffa6883a | |||
| 4a85be68a0 | |||
| 19e4040b17 | |||
| 767323fde9 | |||
| 5be8231598 | |||
| e9f1947156 | |||
| 3d8c906c7f | |||
| f1c0205e21 | |||
| 7d124ac5c0 | |||
| abebdb94a5 | |||
| 7b45c4d380 | |||
| 6cbd80566f | |||
| 274bb2732a | |||
| 5935c7d5a0 | |||
| 4ab07fef23 | |||
| e7abc4d39a | |||
| 71b045366d | |||
| 7abac9537f | |||
| 9d64dc2fd9 | |||
| 6e1b49daa8 | |||
| 7bbfe88008 | |||
| 4c5db7d98c | |||
| cab280ebc0 | |||
| 2f78cf5129 | |||
| bc3755dcf7 | |||
| a5730feb9d | |||
| b8f084b1dc | |||
| 9fc50e347b | |||
| 034ab23ff1 | |||
| 865a0c92eb | |||
| b3a4eb8897 | |||
| 33536bd69f | |||
| 10bed44763 | |||
| 0111596d50 | |||
| 1df6ce531c | |||
| 1ad63d1786 | |||
| 895cf5e5c6 | |||
| 9b53deb97f | |||
| 6e9e9a6e31 | |||
| 90343b21d3 | |||
| 2369a2dadf | |||
| 237f43f8ba | |||
| 7fcac4ed49 | |||
| d52f325d99 | |||
| 921dfaddb9 | |||
| f642f3fde4 | |||
| f401cea76a | |||
| 76d3b27842 | |||
| 35ad56ff7f | |||
| 3bfc305df8 | |||
| 5ab866f0db | |||
| 12855db1a0 | |||
| e094d79b85 | |||
| 96394ecf38 | |||
| e9600f9d66 | |||
| 94ec8c4548 | |||
| bde3dcc5f5 | |||
| df10066c16 | |||
| 6b52a72359 | |||
| 608e0d8610 | |||
| 94c1b2b755 | |||
| c31ab7b43f | |||
| 0bbf61df95 | |||
| 1b58b88b02 | |||
| 61ef1fe798 | |||
| 863ec4dae9 | |||
| 5aa5b8d9cb | |||
| 37750cefda | |||
| 7be60cd8be | |||
| 606b701b00 | |||
| fe2f17d38a | |||
| 4aeba31a6a | |||
| 2086d424bd | |||
| 520eeea355 | |||
| dad2527182 | |||
| 9a3922fe17 | |||
| 9c2e95d614 | |||
| baba1165ff | |||
| a0610292a7 | |||
| de7f169043 | |||
| 97b7143f6d | |||
| 520b50e49b | |||
| 70cbc12ac7 | |||
| 9a01c64f68 | |||
| 673da76e55 | |||
| 146daa22a7 | |||
| 9150a75886 | |||
| 92bff40eb4 | |||
| 3fd90c74de | |||
| 5039f76fe8 | |||
| 15e14b2a93 | |||
| d41157bd54 | |||
| cfe853e791 | |||
| 4fa420699b | |||
| 2b9a7914fd | |||
| 3bf957c313 | |||
| 25dfd98672 | |||
| a1fd350573 | |||
| bbf5db745f | |||
| 884d695273 | |||
| 65a984ec2b | |||
| e06ecf5092 | |||
| 3bbd61d75b | |||
| 69f851124c | |||
| 5fef06100c | |||
| 40ba4ce958 | |||
| a8aeaff2a3 | |||
| d8fea9f142 | |||
| f33a9dccf8 | |||
| 5c4f97f88e | |||
| 4c7796ca56 | |||
| e6e102a95c | |||
| fee581c722 | |||
| f398ffb183 | |||
| 60e578bad9 | |||
| 55fc2f4d0c | |||
| dc74239275 | |||
| 75ad96e9ca | |||
| 265d1373a1 | |||
| 814ee037d3 | |||
| 4ab22a5fc5 | |||
| 6222250104 | |||
| f2b328395d | |||
| f3732b1683 | |||
| 82ddc7f9ce | |||
| 5031adcb7f | |||
| c5322ff2f6 | |||
| a1f1cf307b | |||
| 1b0cad0f1b | |||
| 29c2603d61 | |||
| ea9657dae0 | |||
| b61875fa9c | |||
| 1308c2f774 | |||
| 63bc8282a0 | |||
| cd0626e825 | |||
| dc4b4a86a4 | |||
| bb6df8ee63 | |||
| 199b5ab9b8 | |||
| 5719d6fcdd | |||
| 29e4ea6ec0 | |||
| 491c8ebdd1 | |||
| 3afb6e47bf | |||
| f767136371 | |||
| 86162f0ef5 | |||
| f03f8378b9 | |||
| 074b35755d | |||
| a92de0d9bd | |||
| f966258772 | |||
| 7938eebdcd | |||
| dccf1acb78 | |||
| 20278a7b5d | |||
| d4268fd4c6 | |||
| e7af54999f | |||
| 916d5a22c2 | |||
| abdd341369 | |||
| a802160318 | |||
| a5c7bde1a3 | |||
| 217a8a8e92 | |||
| e85459c529 | |||
| 2edaf38386 | |||
| cc7f75370f | |||
| 829d9924c3 | |||
| 164cefe2a2 | |||
| 9ac2e25739 | |||
| 1de4d69922 | |||
| dbc5ba9458 | |||
| 1459919984 | |||
| 11b9382e56 | |||
| c49c3cac30 | |||
| 165c79394b | |||
| 8fa7bc9206 | |||
| 842cbc0e44 | |||
| 014f4c49d1 | |||
| c9f52ab9f4 | |||
| c44eb676b0 | |||
| d29fbb8acf | |||
| 1566f9a229 | |||
| b65434650c | |||
| 4ba3e0b941 | |||
| 535d7149bd | |||
| e8df80555c | |||
| d978ae5a15 | |||
| 8de7940335 | |||
| b6a1adf90e | |||
| 910b969894 | |||
| 476f17b84c | |||
| 1fe6cbdb4c | |||
| fb9f92e99e | |||
| 056182923a | |||
| be0e5f1dad | |||
| 5add1af33b | |||
| bda3b8dad2 | |||
| 5679b4b1db | |||
| 7730890e7c | |||
| 7d2ddd44c0 | |||
| ba8c42d70b | |||
| 09ad0121c6 | |||
| 23e839c80b | |||
| bed9fffa94 | |||
| 4c3e0f8b3c | |||
| c3bd6a3eea | |||
| 93294dcb18 | |||
| 9492994b8c | |||
| 0394a30dd7 | |||
| 83c0c03257 | |||
| 4c5aa19a8f | |||
| 8adb6e37eb | |||
| 12211e127c | |||
| 4345e93446 | |||
| 29bdd9ff26 | |||
| f6225c4983 | |||
| b49bcafd67 | |||
| 3cbca50cad | |||
| 018a6bd2c7 | |||
| 8c2cd7117c | |||
| 13e67ee7ca | |||
| a40e6dd5d8 | |||
| 89fd04febb | |||
| ee9fb8dfec | |||
| 1949e77df8 | |||
| 44079e1ce2 | |||
| 76a9eeda7c | |||
| 2dd9a49f05 | |||
| aa7a862631 | |||
| 66d1f3878b | |||
| 4f12259bc0 | |||
| c926f2bf05 | |||
| 1ee35b841d | |||
| 1a802d7428 | |||
| 056c3d6dad | |||
| 64760ea779 | |||
| d1027dd016 | |||
| 37862fdb5c | |||
| bbc8eae484 | |||
| 8fd4a508f8 | |||
| 26ac72d142 | |||
| bbebb43808 | |||
| 35deb90803 | |||
| 9fdc90cf13 | |||
| 6fe53d9508 | |||
| 9141f1b343 | |||
| 80804b9b49 | |||
| 034a67ff7e | |||
| 8d6cc854aa | |||
| 0ed02327b0 | |||
| fac32a6def | |||
| 1d0a51886f | |||
| e65a50161d | |||
| 950bfadaba | |||
| fbac926044 | |||
| 7efd573010 | |||
| 2b589d1ded | |||
| 63762ba279 | |||
| fa01fd6ed5 | |||
| 57598ec9de | |||
| 5ccd8663f6 | |||
| d663c8e3cc | |||
| 22a9d9a369 | |||
| b6ebdd9c7b | |||
| 94ac71d4ce | |||
| 9666e1cf41 | |||
| 35bd883d40 | |||
| 8faca75d06 | |||
| b1deaba0bd | |||
| 32417bbb7e | |||
| b3596e7471 | |||
| 9bbd6b21b9 | |||
| 7d35c5c5bb | |||
| 7d1f66e537 | |||
| 2e9f8b8064 | |||
| 5703f81bac | |||
| 7f8edee078 | |||
| 991aa67efb | |||
| f6e8e05dad | |||
| 09c07b24dc | |||
| 3699db53b5 | |||
| 0b2469eef2 | |||
| 1d892e6b99 | |||
| f4e87ed80b |
@@ -26,8 +26,6 @@ _testmain.go
|
|||||||
*.test
|
*.test
|
||||||
|
|
||||||
coverage.txt
|
coverage.txt
|
||||||
coverage.out
|
|
||||||
coverage.html
|
|
||||||
|
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
|||||||
@@ -70,17 +70,9 @@ List of contributors, in chronological order:
|
|||||||
* Gordian Schoenherr (https://github.com/schoenherrg)
|
* Gordian Schoenherr (https://github.com/schoenherrg)
|
||||||
* Silke Hofstra (https://github.com/silkeh)
|
* Silke Hofstra (https://github.com/silkeh)
|
||||||
* Itay Porezky (https://github.com/itayporezky)
|
* Itay Porezky (https://github.com/itayporezky)
|
||||||
* Alejandro Guijarro Monerris (https://github.com/alguimodd)
|
|
||||||
* JupiterRider (https://github.com/JupiterRider)
|
* JupiterRider (https://github.com/JupiterRider)
|
||||||
* Agustin Henze (https://github.com/agustinhenze)
|
|
||||||
* Tobias Assarsson (https://github.com/daedaluz)
|
* Tobias Assarsson (https://github.com/daedaluz)
|
||||||
* Yaksh Bariya (https://github.com/thunder-coding)
|
* Yaksh Bariya (https://github.com/thunder-coding)
|
||||||
* Juan Calderon-Perez (https://github.com/gaby)
|
|
||||||
* Ato Araki (https://github.com/atotto)
|
|
||||||
* Roman Lebedev (https://github.com/LebedevRI)
|
|
||||||
* Brian Witt (https://github.com/bwitt)
|
* Brian Witt (https://github.com/bwitt)
|
||||||
* Ales Bregar (https://github.com/abregar)
|
* Ales Bregar (https://github.com/abregar)
|
||||||
* Tim Foerster (https://github.com/tonobo)
|
* Tim Foerster (https://github.com/tonobo)
|
||||||
* Zhang Xiao (https://github.com/xzhang1)
|
|
||||||
* Tom Nguyen (https://github.com/lecafard)
|
|
||||||
* Philip Cramer (https://github.com/PhilipCramer)
|
|
||||||
|
|||||||
@@ -75,9 +75,9 @@ azurite-start:
|
|||||||
azurite-stop:
|
azurite-stop:
|
||||||
@kill `cat ~/.azurite.pid`
|
@kill `cat ~/.azurite.pid`
|
||||||
|
|
||||||
swagger: swagger-install
|
swagger: #swagger-install
|
||||||
# Generate swagger docs
|
# Generate swagger docs
|
||||||
@PATH=$(BINPATH)/:$(PATH) swag init --propertyStrategy pascalcase --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf
|
#@PATH=$(BINPATH)/:$(PATH) swag init --propertyStrategy pascalcase --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf
|
||||||
|
|
||||||
etcd-install:
|
etcd-install:
|
||||||
# Install etcd
|
# Install etcd
|
||||||
|
|||||||
-201
@@ -1,7 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -13,23 +12,6 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type gpgKeyInfo struct {
|
|
||||||
// 16-character key ID (short form)
|
|
||||||
KeyID string `json:"KeyID" example:"8B48AD6246925553"`
|
|
||||||
// Full fingerprint
|
|
||||||
Fingerprint string `json:"Fingerprint" example:"D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0"`
|
|
||||||
// Key validity (u=unknown, f=fulltrust, m=marginal, n=never)
|
|
||||||
Validity string `json:"Validity" example:"u"`
|
|
||||||
// User ID(s) associated with this key
|
|
||||||
UserIDs []string `json:"UserIDs" example:"John Doe <john@example.com>"`
|
|
||||||
// Creation date (Unix timestamp format from gpg)
|
|
||||||
CreatedAt string `json:"CreatedAt" example:"2023-01-15"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type gpgKeyListResponse struct {
|
|
||||||
Keys []gpgKeyInfo `json:"Keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type gpgAddKeyParams struct {
|
type gpgAddKeyParams struct {
|
||||||
// Keyring for adding the keys (default: trustedkeys.gpg)
|
// Keyring for adding the keys (default: trustedkeys.gpg)
|
||||||
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
|
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
|
||||||
@@ -43,14 +25,6 @@ type gpgAddKeyParams struct {
|
|||||||
GpgKeyID string `json:"GpgKeyID" example:"EF0F382A1A7B6500 8B48AD6246925553"`
|
GpgKeyID string `json:"GpgKeyID" example:"EF0F382A1A7B6500 8B48AD6246925553"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type gpgDeleteKeyParams struct {
|
|
||||||
// Keyring to delete keys from (default: trustedkeys.gpg)
|
|
||||||
Keyring string `json:"Keyring" example:"trustedkeys.gpg"`
|
|
||||||
|
|
||||||
// Key ID or fingerprint to delete
|
|
||||||
GpgKeyID string `json:"GpgKeyID" example:"8B48AD6246925553"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary Add GPG Keys
|
// @Summary Add GPG Keys
|
||||||
// @Description **Adds GPG keys to aptly keyring**
|
// @Description **Adds GPG keys to aptly keyring**
|
||||||
// @Description
|
// @Description
|
||||||
@@ -134,178 +108,3 @@ func apiGPGAddKey(c *gin.Context) {
|
|||||||
|
|
||||||
c.JSON(200, string(out))
|
c.JSON(200, string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary List GPG Keys
|
|
||||||
// @Description **Lists all GPG keys in aptly keyring**
|
|
||||||
// @Description
|
|
||||||
// @Description Returns all public keys currently installed in the aptly GPG keyring.
|
|
||||||
// @Description
|
|
||||||
// @Tags Mirrors
|
|
||||||
// @Param keyring query string false "Keyring file to list keys from (default: trustedkeys.gpg)" example(trustedkeys.gpg)
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} gpgKeyListResponse "OK"
|
|
||||||
// @Failure 400 {object} Error "Bad Request"
|
|
||||||
// @Router /api/gpg/keys [get]
|
|
||||||
func apiGPGListKeys(c *gin.Context) {
|
|
||||||
keyring := c.DefaultQuery("keyring", "trustedkeys.gpg")
|
|
||||||
keyring = utils.SanitizePath(keyring)
|
|
||||||
|
|
||||||
finder := pgp.GPGDefaultFinder()
|
|
||||||
gpg, _, err := finder.FindGPG()
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"--no-default-keyring",
|
|
||||||
"--with-colons",
|
|
||||||
"--keyring", keyring,
|
|
||||||
"--list-keys",
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(gpg, args...)
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 400, fmt.Errorf("failed to list keys: %s", string(out)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := parseGPGOutput(string(out))
|
|
||||||
c.JSON(200, gpgKeyListResponse{Keys: keys})
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary Delete GPG Key
|
|
||||||
// @Description **Deletes a GPG key from aptly keyring**
|
|
||||||
// @Description
|
|
||||||
// @Description Removes a public key from the aptly GPG keyring. This is useful for removing
|
|
||||||
// @Description compromised keys or cleaning up obsolete keys.
|
|
||||||
// @Description
|
|
||||||
// @Tags Mirrors
|
|
||||||
// @Consume json
|
|
||||||
// @Param request body gpgDeleteKeyParams true "Parameters"
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} string "OK"
|
|
||||||
// @Failure 400 {object} Error "Bad Request"
|
|
||||||
// @Router /api/gpg/key [delete]
|
|
||||||
func apiGPGDeleteKey(c *gin.Context) {
|
|
||||||
b := gpgDeleteKeyParams{}
|
|
||||||
if c.Bind(&b) != nil {
|
|
||||||
AbortWithJSONError(c, 400, fmt.Errorf("invalid request body"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(strings.TrimSpace(b.GpgKeyID)) == 0 {
|
|
||||||
AbortWithJSONError(c, 400, fmt.Errorf("GpgKeyID is required"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b.GpgKeyID = utils.SanitizePath(b.GpgKeyID)
|
|
||||||
// b.Keyring can be an absolute path
|
|
||||||
|
|
||||||
finder := pgp.GPGDefaultFinder()
|
|
||||||
gpg, _, err := finder.FindGPG()
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"--no-default-keyring",
|
|
||||||
"--allow-non-selfsigned-uid",
|
|
||||||
"--batch",
|
|
||||||
"--yes",
|
|
||||||
}
|
|
||||||
|
|
||||||
keyring := "trustedkeys.gpg"
|
|
||||||
if len(b.Keyring) > 0 {
|
|
||||||
keyring = b.Keyring
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, "--keyring", keyring)
|
|
||||||
args = append(args, "--delete-keys", b.GpgKeyID)
|
|
||||||
|
|
||||||
cmd := exec.Command(gpg, args...)
|
|
||||||
fmt.Printf("running %s %s\n", gpg, strings.Join(args, " "))
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 400, fmt.Errorf("failed to delete key: %s", string(out)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseGPGOutput parses the output of `gpg --with-colons --list-keys`
|
|
||||||
// and returns a structured list of keys
|
|
||||||
func parseGPGOutput(output string) []gpgKeyInfo {
|
|
||||||
var keys []gpgKeyInfo
|
|
||||||
var currentKey *gpgKeyInfo
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(output))
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(line, ":")
|
|
||||||
if len(parts) < 10 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
recordType := parts[0]
|
|
||||||
|
|
||||||
// pub: public key record
|
|
||||||
if recordType == "pub" {
|
|
||||||
// Save previous key if it exists
|
|
||||||
if currentKey != nil && currentKey.KeyID != "" {
|
|
||||||
keys = append(keys, *currentKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new key entry
|
|
||||||
// Format: pub:trust:length:algo:keyid:created:expires:uidhash:...
|
|
||||||
keyID := parts[4]
|
|
||||||
if len(keyID) >= 16 {
|
|
||||||
keyID = keyID[len(keyID)-16:] // Last 16 chars = short key ID
|
|
||||||
}
|
|
||||||
validity := parts[1]
|
|
||||||
createdAt := parts[5]
|
|
||||||
|
|
||||||
currentKey = &gpgKeyInfo{
|
|
||||||
KeyID: keyID,
|
|
||||||
Validity: validity,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
UserIDs: []string{},
|
|
||||||
Fingerprint: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// uid: user ID record
|
|
||||||
if recordType == "uid" && currentKey != nil {
|
|
||||||
// Format: uid:trust:created:expires:keyid:uidhash:uidtype:validity:userID:...
|
|
||||||
if len(parts) >= 10 {
|
|
||||||
userID := parts[9]
|
|
||||||
if userID != "" {
|
|
||||||
currentKey.UserIDs = append(currentKey.UserIDs, userID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fpr: fingerprint record
|
|
||||||
if recordType == "fpr" && currentKey != nil {
|
|
||||||
// Format: fpr:::::::::fingerprint:
|
|
||||||
if len(parts) >= 10 {
|
|
||||||
fingerprint := parts[9]
|
|
||||||
currentKey.Fingerprint = fingerprint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't forget the last key
|
|
||||||
if currentKey != nil && currentKey.KeyID != "" {
|
|
||||||
keys = append(keys, *currentKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|||||||
-451
@@ -1,451 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
. "gopkg.in/check.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GPGSuite struct {
|
|
||||||
APISuite
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Suite(&GPGSuite{})
|
|
||||||
|
|
||||||
func (s *GPGSuite) withFakeGPG(c *C, scriptBody string, test func(scriptPath string)) {
|
|
||||||
tempDir, err := os.MkdirTemp("", "aptly-fake-gpg")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
defer func() { _ = os.RemoveAll(tempDir) }()
|
|
||||||
|
|
||||||
scriptPath := filepath.Join(tempDir, "gpg")
|
|
||||||
err = os.WriteFile(scriptPath, []byte(scriptBody), 0o755)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
oldPath := os.Getenv("PATH")
|
|
||||||
err = os.Setenv("PATH", tempDir+string(os.PathListSeparator)+oldPath)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
defer func() { _ = os.Setenv("PATH", oldPath) }()
|
|
||||||
|
|
||||||
test(scriptPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *GPGSuite) fakeGPGScript(c *C, listOutput string, deleteOutput string, deleteError string) string {
|
|
||||||
return "#!/bin/sh\n" +
|
|
||||||
"if [ \"$1\" = \"--version\" ]; then\n" +
|
|
||||||
" echo 'gpg (GnuPG) 2.2.27'\n" +
|
|
||||||
" exit 0\n" +
|
|
||||||
"fi\n" +
|
|
||||||
"args=\"$*\"\n" +
|
|
||||||
"if printf '%s' \"$args\" | grep -q -- '--list-keys'; then\n" +
|
|
||||||
" cat <<'EOF'\n" + listOutput + "\nEOF\n" +
|
|
||||||
" exit 0\n" +
|
|
||||||
"fi\n" +
|
|
||||||
"if printf '%s' \"$args\" | grep -q -- '--delete-keys'; then\n" +
|
|
||||||
" if [ -n \"" + strings.ReplaceAll(deleteError, "\n", "") + "\" ]; then\n" +
|
|
||||||
" echo '" + strings.ReplaceAll(deleteError, "'", "'\\''") + "'\n" +
|
|
||||||
" exit 1\n" +
|
|
||||||
" fi\n" +
|
|
||||||
" cat <<'EOF'\n" + deleteOutput + "\nEOF\n" +
|
|
||||||
" exit 0\n" +
|
|
||||||
"fi\n" +
|
|
||||||
"echo 'unexpected invocation' >&2\n" +
|
|
||||||
"exit 1\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputEmpty tests parsing of empty GPG output
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputEmpty(c *C) {
|
|
||||||
output := ""
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputSingleKeyMinimal tests parsing a single key with minimal fields
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputSingleKeyMinimal(c *C) {
|
|
||||||
// Minimal valid GPG output with one key
|
|
||||||
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 1)
|
|
||||||
|
|
||||||
key := keys[0]
|
|
||||||
c.Check(key.KeyID, Equals, "8B48AD6246925553")
|
|
||||||
c.Check(key.Validity, Equals, "u")
|
|
||||||
c.Check(key.CreatedAt, Equals, "1611864000")
|
|
||||||
c.Check(key.Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
|
|
||||||
c.Check(key.UserIDs, DeepEquals, []string{"John Doe <john@example.com>"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputMultipleKeys tests parsing multiple keys
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputMultipleKeys(c *C) {
|
|
||||||
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
|
|
||||||
pub:f:2048:1:A1B2C3D4E5F67890:1580592000:1612128000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:f::::1580592000::0987654321::Jane Smith <jane@example.com>::::::::::0:
|
|
||||||
fpr:::::::::E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 2)
|
|
||||||
|
|
||||||
// First key
|
|
||||||
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
|
|
||||||
c.Check(keys[0].Validity, Equals, "u")
|
|
||||||
c.Check(keys[0].UserIDs, DeepEquals, []string{"John Doe <john@example.com>"})
|
|
||||||
|
|
||||||
// Second key
|
|
||||||
c.Check(keys[1].KeyID, Equals, "A1B2C3D4E5F67890")
|
|
||||||
c.Check(keys[1].Validity, Equals, "f")
|
|
||||||
c.Check(keys[1].UserIDs, DeepEquals, []string{"Jane Smith <jane@example.com>"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputMultipleUIDs tests a key with multiple user IDs
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputMultipleUIDs(c *C) {
|
|
||||||
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
|
||||||
uid:u::::1611864000::1234567891::John Doe <john.doe@company.com>::::::::::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 1)
|
|
||||||
|
|
||||||
key := keys[0]
|
|
||||||
c.Check(key.UserIDs, HasLen, 2)
|
|
||||||
c.Check(key.UserIDs, DeepEquals, []string{
|
|
||||||
"John Doe <john@example.com>",
|
|
||||||
"John Doe <john.doe@company.com>",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputMalformedLines tests that malformed lines are skipped
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputMalformedLines(c *C) {
|
|
||||||
// Mix of valid and invalid lines (too few fields)
|
|
||||||
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
invalid:line:with:only:three:fields
|
|
||||||
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 1)
|
|
||||||
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputEmptyLines tests that empty lines are skipped
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputEmptyLines(c *C) {
|
|
||||||
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
|
|
||||||
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
|
||||||
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 1)
|
|
||||||
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputKeyWithoutUID tests a public key without user ID
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputKeyWithoutUID(c *C) {
|
|
||||||
// Key without uid record (should still be included)
|
|
||||||
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 1)
|
|
||||||
|
|
||||||
key := keys[0]
|
|
||||||
c.Check(key.KeyID, Equals, "8B48AD6246925553")
|
|
||||||
c.Check(key.UserIDs, HasLen, 0)
|
|
||||||
c.Check(key.Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputVariousValidity tests different validity values
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputVariousValidity(c *C) {
|
|
||||||
output := `pub:u:4096:1:KEY1111111111111:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:u::::1611864000::1234567890::Key1::::::::::0:
|
|
||||||
fpr:::::::::1111111111111111111111111111111111111111:
|
|
||||||
pub:f:4096:1:KEY2222222222222:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:f::::1611864000::1234567891::Key2::::::::::0:
|
|
||||||
fpr:::::::::2222222222222222222222222222222222222222:
|
|
||||||
pub:m:4096:1:KEY3333333333333:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:m::::1611864000::1234567892::Key3::::::::::0:
|
|
||||||
fpr:::::::::3333333333333333333333333333333333333333:
|
|
||||||
pub:n:4096:1:KEY4444444444444:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:n::::1611864000::1234567893::Key4::::::::::0:
|
|
||||||
fpr:::::::::4444444444444444444444444444444444444444:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 4)
|
|
||||||
|
|
||||||
validities := []string{"u", "f", "m", "n"}
|
|
||||||
for i, validity := range validities {
|
|
||||||
c.Check(keys[i].Validity, Equals, validity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputShortKeyID tests that key IDs are shortened to 16 chars
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputShortKeyID(c *C) {
|
|
||||||
// 40-character key ID that should be shortened to last 16 chars
|
|
||||||
longKeyID := "0123456789ABCDEF0123456789ABCDEF8B48AD62"
|
|
||||||
output := `pub:u:4096:1:` + longKeyID + `:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 1)
|
|
||||||
// Should extract the last 16 characters: 89ABCDEF8B48AD62
|
|
||||||
c.Check(keys[0].KeyID, Equals, "89ABCDEF8B48AD62")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputSpecialCharactersInUID tests user IDs with special characters
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputSpecialCharactersInUID(c *C) {
|
|
||||||
// UID with Unicode characters and special formatting
|
|
||||||
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:u::::1611864000::1234567890::J\xc3\xb6hn D\xc3\xb6\xc3\xa9 (D\xc3\xbcss) <john@example.com>::::::::::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 1)
|
|
||||||
// Should preserve the encoded special characters
|
|
||||||
c.Check(keys[0].UserIDs, HasLen, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAPIGPGListKeysDefaultKeyring tests the HTTP endpoint with default keyring
|
|
||||||
func (s *GPGSuite) TestAPIGPGListKeysDefaultKeyring(c *C) {
|
|
||||||
s.withFakeGPG(c, s.fakeGPGScript(c, `pub:u:4096:1:8B48AD6246925553:1611864000:::::
|
|
||||||
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`, "", ""), func(_ string) {
|
|
||||||
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(response.Code, Equals, 200)
|
|
||||||
|
|
||||||
var result gpgKeyListResponse
|
|
||||||
err = json.NewDecoder(response.Body).Decode(&result)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(result.Keys, HasLen, 1)
|
|
||||||
c.Check(result.Keys[0].KeyID, Equals, "8B48AD6246925553")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAPIGPGListKeysWithKeyringParam tests the HTTP endpoint with custom keyring parameter
|
|
||||||
func (s *GPGSuite) TestAPIGPGListKeysWithKeyringParam(c *C) {
|
|
||||||
argFile, err := os.CreateTemp("", "aptly-gpg-args")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
_ = argFile.Close()
|
|
||||||
defer func() { _ = os.Remove(argFile.Name()) }()
|
|
||||||
|
|
||||||
script := "#!/bin/sh\n" +
|
|
||||||
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
|
|
||||||
"printf '%s\n' \"$@\" > '" + argFile.Name() + "'\n" +
|
|
||||||
"if printf '%s' \"$*\" | grep -q -- '--list-keys'; then\n" +
|
|
||||||
"cat <<'EOF'\n" +
|
|
||||||
"pub:u:4096:1:8B48AD6246925553:1611864000:::::\n" +
|
|
||||||
"fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:\n" +
|
|
||||||
"EOF\n" +
|
|
||||||
"exit 0\n" +
|
|
||||||
"fi\n" +
|
|
||||||
"exit 1\n"
|
|
||||||
|
|
||||||
s.withFakeGPG(c, script, func(_ string) {
|
|
||||||
response, reqErr := s.HTTPRequest("GET", "/api/gpg/keys?keyring=/custom.gpg", nil)
|
|
||||||
c.Assert(reqErr, IsNil)
|
|
||||||
c.Check(response.Code, Equals, 200)
|
|
||||||
|
|
||||||
argBytes, readErr := os.ReadFile(argFile.Name())
|
|
||||||
c.Assert(readErr, IsNil)
|
|
||||||
c.Check(string(argBytes), Matches, `(?s).*--keyring\ncustom\.gpg\n.*`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAPIGPGListKeysResponseFormat tests that the response has the correct structure
|
|
||||||
func (s *GPGSuite) TestAPIGPGListKeysResponseFormat(c *C) {
|
|
||||||
s.withFakeGPG(c, s.fakeGPGScript(c, `pub:f:4096:1:A1B2C3D4E5F67890:1611864000:::::
|
|
||||||
uid:f::::1611864000::1234567890::Jane Smith <jane@example.com>::::::::::0:
|
|
||||||
fpr:::::::::E9F0A1B2C3D4E5F6A7B8C9D0E1F2A3B4:`, "", ""), func(_ string) {
|
|
||||||
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(response.Code, Equals, 200)
|
|
||||||
|
|
||||||
var result gpgKeyListResponse
|
|
||||||
err = json.NewDecoder(response.Body).Decode(&result)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(result.Keys, HasLen, 1)
|
|
||||||
c.Check(result.Keys[0].KeyID, Equals, "A1B2C3D4E5F67890")
|
|
||||||
c.Check(result.Keys[0].Validity, Equals, "f")
|
|
||||||
c.Check(result.Keys[0].CreatedAt, Equals, "1611864000")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputEdgeCaseUIDWithoutFields tests UID record with missing fields
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputEdgeCaseUIDWithoutFields(c *C) {
|
|
||||||
// UID record with fewer than 10 fields
|
|
||||||
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:u::::1611864000::1234567890:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 1)
|
|
||||||
// Should not have user ID since it's in field 9 and this record is too short
|
|
||||||
c.Check(keys[0].UserIDs, HasLen, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputFingerprintWithoutCurrentKey tests FPR record appearing before any PUB
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputFingerprintWithoutCurrentKey(c *C) {
|
|
||||||
// FPR record without a preceding PUB (should be ignored)
|
|
||||||
output := `fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
|
|
||||||
pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 1)
|
|
||||||
// Should only have one key with the correct fingerprint
|
|
||||||
c.Check(keys[0].Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputComplexRealWorldExample tests real-world-like GPG output
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputComplexRealWorldExample(c *C) {
|
|
||||||
// Real-world GPG output with multiple keys, UIDs, and other record types (sig, sub)
|
|
||||||
// Note: sub and sig records are skipped as we only care about pub/uid/fpr
|
|
||||||
realWorldOutput := `tru::1:1611864000:0:3:1:5
|
|
||||||
pub:u:4096:1:8B48AD6246925553:1611864000:2023-01-15T00:00:00:::::scESC:::::::23::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:
|
|
||||||
uid:u::::1611864000::1234567890::John Doe <john@example.com>::::::::::0:
|
|
||||||
uid:u::::1611864100::1234567891::John Doe <john@work.com>::::::::::0:
|
|
||||||
pub:f:2048:1:1234567890123456:1580592000:2022-12-31T00:00:00::u:::scESC:::::::23::0:
|
|
||||||
fpr:::::::::F4E3D2C1B0A9F8E7D6C5B4A3F2E1D0C9:
|
|
||||||
uid:f::::1580592000::0987654321::Maintainer Key <maint@example.com>::::::::::0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(realWorldOutput)
|
|
||||||
c.Check(keys, HasLen, 2)
|
|
||||||
|
|
||||||
// First key should have 2 UIDs
|
|
||||||
c.Check(keys[0].KeyID, Equals, "8B48AD6246925553")
|
|
||||||
c.Check(keys[0].UserIDs, HasLen, 2)
|
|
||||||
c.Check(keys[0].Fingerprint, Equals, "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0")
|
|
||||||
|
|
||||||
// Second key should have 1 UID
|
|
||||||
c.Check(keys[1].KeyID, Equals, "1234567890123456")
|
|
||||||
c.Check(keys[1].UserIDs, HasLen, 1)
|
|
||||||
c.Check(keys[1].Fingerprint, Equals, "F4E3D2C1B0A9F8E7D6C5B4A3F2E1D0C9")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestParseGPGOutputConsecutiveEmptyUIDs tests handling of consecutive empty user ID fields
|
|
||||||
func (s *GPGSuite) TestParseGPGOutputConsecutiveEmptyUIDs(c *C) {
|
|
||||||
output := `pub:u:4096:1:8B48AD6246925553:1611864000:1643400000:uidhash:::scESC:::::::23::0:
|
|
||||||
uid:u::::1611864000::1234567890:::::::::::0:
|
|
||||||
uid:u::::1611864000::1234567891::John Doe <john@example.com>::::::::::0:
|
|
||||||
fpr:::::::::D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0:`
|
|
||||||
|
|
||||||
keys := parseGPGOutput(output)
|
|
||||||
c.Check(keys, HasLen, 1)
|
|
||||||
// Should skip empty UID but include the non-empty one
|
|
||||||
c.Check(keys[0].UserIDs, HasLen, 1)
|
|
||||||
c.Check(keys[0].UserIDs[0], Equals, "John Doe <john@example.com>")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestGPGDeleteKeyParamsValidation tests gpgDeleteKeyParams validation
|
|
||||||
func (s *GPGSuite) TestGPGDeleteKeyParamsValidation(c *C) {
|
|
||||||
// This is a unit test that validates parameter structure (no HTTP needed)
|
|
||||||
params := gpgDeleteKeyParams{
|
|
||||||
Keyring: "custom.gpg",
|
|
||||||
GpgKeyID: "8B48AD6246925553",
|
|
||||||
}
|
|
||||||
c.Check(params.Keyring, Equals, "custom.gpg")
|
|
||||||
c.Check(params.GpgKeyID, Equals, "8B48AD6246925553")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAPIGPGDeleteKeyMissingKeyID tests delete with missing key ID parameter
|
|
||||||
func (s *GPGSuite) TestAPIGPGDeleteKeyMissingKeyID(c *C) {
|
|
||||||
body, err := json.Marshal(map[string]string{
|
|
||||||
"Keyring": "trustedkeys.gpg",
|
|
||||||
// GpgKeyID is missing
|
|
||||||
})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
response, err := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(response.Code, Equals, 400)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAPIGPGDeleteKeyInvalidJSON tests delete with invalid JSON request
|
|
||||||
func (s *GPGSuite) TestAPIGPGDeleteKeyInvalidJSON(c *C) {
|
|
||||||
response, err := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader([]byte("invalid json")))
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(response.Code, Equals, 400)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAPIGPGDeleteKeySuccess tests successful key deletion
|
|
||||||
func (s *GPGSuite) TestAPIGPGDeleteKeySuccess(c *C) {
|
|
||||||
argFile, err := os.CreateTemp("", "aptly-gpg-delete-args")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
_ = argFile.Close()
|
|
||||||
defer func() { _ = os.Remove(argFile.Name()) }()
|
|
||||||
|
|
||||||
script := "#!/bin/sh\n" +
|
|
||||||
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
|
|
||||||
"printf '%s\n' \"$@\" > '" + argFile.Name() + "'\n" +
|
|
||||||
"if printf '%s' \"$*\" | grep -q -- '--delete-keys'; then\n" +
|
|
||||||
"echo 'deleted'\n" +
|
|
||||||
"exit 0\n" +
|
|
||||||
"fi\n" +
|
|
||||||
"exit 1\n"
|
|
||||||
|
|
||||||
s.withFakeGPG(c, script, func(_ string) {
|
|
||||||
body, marshalErr := json.Marshal(gpgDeleteKeyParams{
|
|
||||||
Keyring: "/trustedkeys.gpg",
|
|
||||||
GpgKeyID: "8B48AD6246925553",
|
|
||||||
})
|
|
||||||
c.Assert(marshalErr, IsNil)
|
|
||||||
|
|
||||||
response, reqErr := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
|
|
||||||
c.Assert(reqErr, IsNil)
|
|
||||||
c.Check(response.Code, Equals, 200)
|
|
||||||
c.Check(response.Body.String(), Matches, `"deleted\\n"`)
|
|
||||||
|
|
||||||
argBytes, readErr := os.ReadFile(argFile.Name())
|
|
||||||
c.Assert(readErr, IsNil)
|
|
||||||
argText := string(argBytes)
|
|
||||||
c.Check(argText, Matches, `(?s).*--batch\n--yes\n.*`)
|
|
||||||
c.Check(argText, Matches, `(?s).*--keyring\n/trustedkeys\.gpg\n.*`)
|
|
||||||
c.Check(argText, Matches, `(?s).*--delete-keys\n8B48AD6246925553\n.*`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAPIGPGListKeysCommandFailure tests list error propagation from gpg
|
|
||||||
func (s *GPGSuite) TestAPIGPGListKeysCommandFailure(c *C) {
|
|
||||||
script := "#!/bin/sh\n" +
|
|
||||||
"if [ \"$1\" = \"--version\" ]; then echo 'gpg (GnuPG) 2.2.27'; exit 0; fi\n" +
|
|
||||||
"if printf '%s' \"$*\" | grep -q -- '--list-keys'; then\n" +
|
|
||||||
"echo 'keyring missing'\n" +
|
|
||||||
"exit 1\n" +
|
|
||||||
"fi\n" +
|
|
||||||
"exit 1\n"
|
|
||||||
|
|
||||||
s.withFakeGPG(c, script, func(_ string) {
|
|
||||||
response, err := s.HTTPRequest("GET", "/api/gpg/keys", nil)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(response.Code, Equals, 400)
|
|
||||||
c.Check(response.Body.String(), Matches, `(?s).*failed to list keys: keyring missing.*`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAPIGPGDeleteKeyCommandFailure tests delete error propagation from gpg
|
|
||||||
func (s *GPGSuite) TestAPIGPGDeleteKeyCommandFailure(c *C) {
|
|
||||||
s.withFakeGPG(c, s.fakeGPGScript(c, "", "", "delete failed"), func(_ string) {
|
|
||||||
body, err := json.Marshal(gpgDeleteKeyParams{
|
|
||||||
Keyring: "trustedkeys.gpg",
|
|
||||||
GpgKeyID: "8B48AD6246925553",
|
|
||||||
})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
response, reqErr := s.HTTPRequest("DELETE", "/api/gpg/key", bytes.NewReader(body))
|
|
||||||
c.Assert(reqErr, IsNil)
|
|
||||||
c.Check(response.Code, Equals, 400)
|
|
||||||
c.Check(response.Body.String(), Matches, `(?s).*failed to delete key: delete failed.*`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
+51
-193
@@ -31,61 +31,22 @@ func getVerifier(keyRings []string) (pgp.Verifier, error) {
|
|||||||
return verifier, nil
|
return verifier, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// stringSlicesEqual compares two string slices for equality (order matters)
|
|
||||||
func stringSlicesEqual(a, b []string) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := range a {
|
|
||||||
if a[i] != b[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// uniqueStrings returns a new slice with only unique strings from the input, sorted
|
|
||||||
func uniqueStrings(input []string) []string {
|
|
||||||
if len(input) == 0 {
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
seen := make(map[string]struct{}, len(input))
|
|
||||||
result := make([]string, 0, len(input))
|
|
||||||
for _, s := range input {
|
|
||||||
if _, ok := seen[s]; !ok {
|
|
||||||
seen[s] = struct{}{}
|
|
||||||
result = append(result, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary List Mirrors
|
// @Summary List Mirrors
|
||||||
// @Description **Show list of currently available mirrors**
|
// @Description **Show list of currently available mirrors**
|
||||||
// @Description Each mirror is returned as in “show” API.
|
// @Description Each mirror is returned as in “show” API.
|
||||||
// @Tags Mirrors
|
// @Tags Mirrors
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {array} remoteRepoResponse
|
// @Success 200 {array} deb.RemoteRepo
|
||||||
// @Router /api/mirrors [get]
|
// @Router /api/mirrors [get]
|
||||||
func apiMirrorsList(c *gin.Context) {
|
func apiMirrorsList(c *gin.Context) {
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.RemoteRepoCollection()
|
collection := collectionFactory.RemoteRepoCollection()
|
||||||
|
|
||||||
result := []remoteRepoResponse{}
|
result := []*deb.RemoteRepo{}
|
||||||
err := collection.ForEach(func(repo *deb.RemoteRepo) error {
|
_ = collection.ForEach(func(repo *deb.RemoteRepo) error {
|
||||||
err := collection.LoadComplete(repo)
|
result = append(result, repo)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, newRemoteRepoResponse(repo))
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, result)
|
c.JSON(200, result)
|
||||||
}
|
}
|
||||||
@@ -111,8 +72,6 @@ type mirrorCreateParams struct {
|
|||||||
DownloadUdebs bool ` json:"DownloadUdebs"`
|
DownloadUdebs bool ` json:"DownloadUdebs"`
|
||||||
// Set "true" to mirror installer files
|
// Set "true" to mirror installer files
|
||||||
DownloadInstaller bool ` json:"DownloadInstaller"`
|
DownloadInstaller bool ` json:"DownloadInstaller"`
|
||||||
// Set "true" to mirror AppStream (DEP-11) metadata
|
|
||||||
DownloadAppStream bool ` json:"DownloadAppStream"`
|
|
||||||
// Set "true" to include dependencies of matching packages when filtering
|
// Set "true" to include dependencies of matching packages when filtering
|
||||||
FilterWithDeps bool ` json:"FilterWithDeps"`
|
FilterWithDeps bool ` json:"FilterWithDeps"`
|
||||||
// Set "true" to skip if the given components are in the Release file
|
// Set "true" to skip if the given components are in the Release file
|
||||||
@@ -164,7 +123,7 @@ func apiMirrorsCreate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repo, err := deb.NewRemoteRepo(b.Name, b.ArchiveURL, b.Distribution, b.Components, b.Architectures,
|
repo, err := deb.NewRemoteRepo(b.Name, b.ArchiveURL, b.Distribution, b.Components, b.Architectures,
|
||||||
b.DownloadSources, b.DownloadUdebs, b.DownloadInstaller, b.DownloadAppStream)
|
b.DownloadSources, b.DownloadUdebs, b.DownloadInstaller)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err))
|
AbortWithJSONError(c, 400, fmt.Errorf("unable to create mirror: %s", err))
|
||||||
@@ -216,9 +175,9 @@ func apiMirrorsDrop(c *gin.Context) {
|
|||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
force := c.Request.URL.Query().Get("force") == "1"
|
force := c.Request.URL.Query().Get("force") == "1"
|
||||||
|
|
||||||
|
// Phase 1: Pre-task validation (shallow load for 404 check only)
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
mirrorCollection := collectionFactory.RemoteRepoCollection()
|
mirrorCollection := collectionFactory.RemoteRepoCollection()
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
|
||||||
|
|
||||||
repo, err := mirrorCollection.ByName(name)
|
repo, err := mirrorCollection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -228,21 +187,34 @@ func apiMirrorsDrop(c *gin.Context) {
|
|||||||
|
|
||||||
resources := []string{string(repo.Key())}
|
resources := []string{string(repo.Key())}
|
||||||
taskName := fmt.Sprintf("Delete mirror %s", name)
|
taskName := fmt.Sprintf("Delete mirror %s", name)
|
||||||
|
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err := repo.CheckLock()
|
// Phase 2: Inside task lock - create fresh collections
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskMirrorCollection := taskCollectionFactory.RemoteRepoCollection()
|
||||||
|
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
|
|
||||||
|
// Fresh load after lock acquired
|
||||||
|
repo, err := taskMirrorCollection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.CheckLock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !force {
|
if !force {
|
||||||
snapshots := snapshotCollection.ByRemoteRepoSource(repo)
|
// Fresh checks with current collections
|
||||||
|
snapshots := taskSnapshotCollection.ByRemoteRepoSource(repo)
|
||||||
|
|
||||||
if len(snapshots) > 0 {
|
if len(snapshots) > 0 {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusForbidden, Value: nil}, fmt.Errorf("won't delete mirror with snapshots, use 'force=1' to override")
|
return &task.ProcessReturnValue{Code: http.StatusForbidden, Value: nil}, fmt.Errorf("won't delete mirror with snapshots, use 'force=1' to override")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mirrorCollection.Drop(repo)
|
err = taskMirrorCollection.Drop(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %v", err)
|
||||||
}
|
}
|
||||||
@@ -273,7 +245,6 @@ func apiMirrorsShow(c *gin.Context) {
|
|||||||
err = collection.LoadComplete(repo)
|
err = collection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
|
AbortWithJSONError(c, 500, fmt.Errorf("unable to show: %s", err))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, repo)
|
c.JSON(200, repo)
|
||||||
@@ -372,128 +343,6 @@ func apiMirrorsPackages(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mirrorEditParams struct {
|
|
||||||
// Package query that is applied to mirror packages
|
|
||||||
Filter *string ` json:"Filter" example:"xserver-xorg"`
|
|
||||||
// Set "true" to include dependencies of matching packages when filtering
|
|
||||||
FilterWithDeps *bool ` json:"FilterWithDeps"`
|
|
||||||
// Set "true" to mirror installer files
|
|
||||||
DownloadInstaller *bool `json:"DownloadInstaller"`
|
|
||||||
// Set "true" to mirror source packages
|
|
||||||
DownloadSources *bool ` json:"DownloadSources"`
|
|
||||||
// Set "true" to mirror udeb files
|
|
||||||
DownloadUdebs *bool ` json:"DownloadUdebs"`
|
|
||||||
// URL of the archive to mirror
|
|
||||||
ArchiveURL *string ` json:"ArchiveURL" example:"http://deb.debian.org/debian"`
|
|
||||||
// Comma separated list of architectures
|
|
||||||
Architectures *[]string `json:"Architectures" example:"amd64"`
|
|
||||||
// Gpg keyring(s) for verifying Release file if a mirror update is required.
|
|
||||||
Keyrings []string ` json:"Keyrings" example:"trustedkeys.gpg"`
|
|
||||||
// Set "true" to skip the verification of Release file signatures
|
|
||||||
IgnoreSignatures *bool ` json:"IgnoreSignatures"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Summary Edit Mirror
|
|
||||||
// @Description **Edit mirror config**
|
|
||||||
// @Tags Mirrors
|
|
||||||
// @Param name path string true "mirror name to edit"
|
|
||||||
// @Consume json
|
|
||||||
// @Param request body mirrorEditParams true "Parameters"
|
|
||||||
// @Produce json
|
|
||||||
// @Success 200 {object} deb.RemoteRepo "Mirror was edited successfully"
|
|
||||||
// @Failure 400 {object} Error "Bad Request"
|
|
||||||
// @Failure 404 {object} Error "Mirror not found"
|
|
||||||
// @Failure 409 {object} Error "Aptly db locked"
|
|
||||||
// @Failure 500 {object} Error "Internal Error"
|
|
||||||
// @Router /api/mirrors/{name} [post]
|
|
||||||
func apiMirrorsEdit(c *gin.Context) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
b mirrorEditParams
|
|
||||||
repo *deb.RemoteRepo
|
|
||||||
)
|
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
|
||||||
collection := collectionFactory.RemoteRepoCollection()
|
|
||||||
|
|
||||||
name := c.Params.ByName("name")
|
|
||||||
repo, err = collection.ByName(name)
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 404, fmt.Errorf("unable to edit: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = repo.CheckLock()
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 409, fmt.Errorf("unable to edit: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Bind(&b) != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchMirror := false
|
|
||||||
ignoreSignatures := context.Config().GpgDisableVerify
|
|
||||||
|
|
||||||
if b.Filter != nil {
|
|
||||||
repo.Filter = *b.Filter
|
|
||||||
}
|
|
||||||
if b.FilterWithDeps != nil {
|
|
||||||
repo.FilterWithDeps = *b.FilterWithDeps
|
|
||||||
}
|
|
||||||
if b.DownloadInstaller != nil {
|
|
||||||
repo.DownloadInstaller = *b.DownloadInstaller
|
|
||||||
}
|
|
||||||
if b.DownloadSources != nil {
|
|
||||||
repo.DownloadSources = *b.DownloadSources
|
|
||||||
}
|
|
||||||
if b.DownloadUdebs != nil {
|
|
||||||
repo.DownloadUdebs = *b.DownloadUdebs
|
|
||||||
}
|
|
||||||
if b.ArchiveURL != nil && *b.ArchiveURL != repo.ArchiveRoot {
|
|
||||||
repo.SetArchiveRoot(*b.ArchiveURL)
|
|
||||||
fetchMirror = true
|
|
||||||
}
|
|
||||||
if b.Architectures != nil {
|
|
||||||
uniqueArchitectures := uniqueStrings(*b.Architectures)
|
|
||||||
if !stringSlicesEqual(uniqueArchitectures, uniqueStrings(repo.Architectures)) {
|
|
||||||
repo.Architectures = uniqueArchitectures
|
|
||||||
fetchMirror = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if b.IgnoreSignatures != nil {
|
|
||||||
ignoreSignatures = *b.IgnoreSignatures
|
|
||||||
}
|
|
||||||
|
|
||||||
if repo.IsFlat() && repo.DownloadUdebs {
|
|
||||||
AbortWithJSONError(c, 400, fmt.Errorf("unable to edit: flat mirrors don't support udebs"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if fetchMirror {
|
|
||||||
verifier, err := getVerifier(b.Keyrings)
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 500, fmt.Errorf("unable to initialize GPG verifier: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = repo.Fetch(context.Downloader(), verifier, ignoreSignatures)
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 500, fmt.Errorf("unable to edit: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = collection.Update(repo)
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 500, fmt.Errorf("unable to edit: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
type mirrorUpdateParams struct {
|
type mirrorUpdateParams struct {
|
||||||
// Change mirror name to `Name`
|
// Change mirror name to `Name`
|
||||||
Name string ` json:"Name" example:"mirror1"`
|
Name string ` json:"Name" example:"mirror1"`
|
||||||
@@ -507,8 +356,6 @@ type mirrorUpdateParams struct {
|
|||||||
ForceUpdate bool ` json:"ForceUpdate"`
|
ForceUpdate bool ` json:"ForceUpdate"`
|
||||||
// Set "true" to skip downloading already downloaded packages
|
// Set "true" to skip downloading already downloaded packages
|
||||||
SkipExistingPackages bool ` json:"SkipExistingPackages"`
|
SkipExistingPackages bool ` json:"SkipExistingPackages"`
|
||||||
// Set "true" to download only the latest version per package/architecture
|
|
||||||
LatestOnly bool ` json:"LatestOnly"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Update Mirror
|
// @Summary Update Mirror
|
||||||
@@ -535,7 +382,8 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.RemoteRepoCollection()
|
collection := collectionFactory.RemoteRepoCollection()
|
||||||
|
|
||||||
remote, err = collection.ByName(c.Params.ByName("name"))
|
name := c.Params.ByName("name")
|
||||||
|
remote, err = collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, 404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
@@ -550,6 +398,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-task validation of new name if provided
|
||||||
if b.Name != remote.Name {
|
if b.Name != remote.Name {
|
||||||
_, err = collection.ByName(b.Name)
|
_, err = collection.ByName(b.Name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -566,9 +415,26 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
|
|
||||||
resources := []string{string(remote.Key())}
|
resources := []string{string(remote.Key())}
|
||||||
maybeRunTaskInBackground(c, "Update mirror "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, "Update mirror "+b.Name, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
// Phase 2: Inside task lock - create fresh factory
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.RemoteRepoCollection()
|
||||||
|
|
||||||
|
// Fresh load after lock acquired (use captured `name` variable, not gin context)
|
||||||
|
remote, err := taskCollection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fresh rename check inside lock (if renaming)
|
||||||
|
if b.Name != remote.Name {
|
||||||
|
_, err := taskCollection.ByName(b.Name)
|
||||||
|
if err == nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: mirror %s already exists", b.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
downloader := context.NewDownloader(out)
|
downloader := context.NewDownloader(out)
|
||||||
err := remote.Fetch(downloader, verifier, b.IgnoreSignatures)
|
err = remote.Fetch(downloader, verifier, b.IgnoreSignatures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
@@ -580,19 +446,11 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = remote.DownloadPackageIndexes(out, downloader, verifier, collectionFactory, b.IgnoreSignatures, remote.SkipComponentCheck)
|
err = remote.DownloadPackageIndexes(out, downloader, verifier, taskCollectionFactory, b.IgnoreSignatures, remote.SkipComponentCheck)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if remote.DownloadAppStream && !remote.IsFlat() {
|
|
||||||
err = remote.DownloadAppStreamFiles(out, downloader,
|
|
||||||
context.PackagePool(), collectionFactory.ChecksumCollection(nil), b.IgnoreChecksums)
|
|
||||||
if err != nil {
|
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if remote.Filter != "" {
|
if remote.Filter != "" {
|
||||||
var filterQuery deb.PackageQuery
|
var filterQuery deb.PackageQuery
|
||||||
|
|
||||||
@@ -607,8 +465,8 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
|
queue, downloadSize, err := remote.BuildDownloadQueue(context.PackagePool(), taskCollectionFactory.PackageCollection(),
|
||||||
collectionFactory.ChecksumCollection(nil), b.SkipExistingPackages, b.LatestOnly)
|
taskCollectionFactory.ChecksumCollection(nil), b.SkipExistingPackages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
@@ -618,12 +476,12 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
e := context.ReOpenDatabase()
|
e := context.ReOpenDatabase()
|
||||||
if e == nil {
|
if e == nil {
|
||||||
remote.MarkAsIdle()
|
remote.MarkAsIdle()
|
||||||
_ = collection.Update(remote)
|
_ = taskCollection.Update(remote)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
remote.MarkAsUpdating()
|
remote.MarkAsUpdating()
|
||||||
err = collection.Update(remote)
|
err = taskCollection.Update(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
@@ -727,7 +585,7 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// and import it back to the pool
|
// and import it back to the pool
|
||||||
task.File.PoolPath, err = context.PackagePool().Import(task.TempDownPath, task.File.Filename, &task.File.Checksums, true, collectionFactory.ChecksumCollection(nil))
|
task.File.PoolPath, err = context.PackagePool().Import(task.TempDownPath, task.File.Filename, &task.File.Checksums, true, taskCollectionFactory.ChecksumCollection(nil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import file: %s", err)
|
//return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to import file: %s", err)
|
||||||
pushError(err)
|
pushError(err)
|
||||||
@@ -780,8 +638,8 @@ func apiMirrorsUpdate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Msgf("%s: Finalizing download...", b.Name)
|
log.Info().Msgf("%s: Finalizing download...", b.Name)
|
||||||
_ = remote.FinalizeDownload(collectionFactory, out)
|
_ = remote.FinalizeDownload(taskCollectionFactory, out)
|
||||||
err = collectionFactory.RemoteRepoCollection().Update(remote)
|
err = taskCollection.Update(remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-66
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/deb"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
@@ -18,10 +17,7 @@ var _ = Suite(&MirrorSuite{})
|
|||||||
func (s *MirrorSuite) TestGetMirrors(c *C) {
|
func (s *MirrorSuite) TestGetMirrors(c *C) {
|
||||||
response, _ := s.HTTPRequest("GET", "/api/mirrors", nil)
|
response, _ := s.HTTPRequest("GET", "/api/mirrors", nil)
|
||||||
c.Check(response.Code, Equals, 200)
|
c.Check(response.Code, Equals, 200)
|
||||||
|
c.Check(response.Body.String(), Equals, "[]")
|
||||||
var mirrors []map[string]interface{}
|
|
||||||
err := json.Unmarshal(response.Body.Bytes(), &mirrors)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MirrorSuite) TestDeleteMirrorNonExisting(c *C) {
|
func (s *MirrorSuite) TestDeleteMirrorNonExisting(c *C) {
|
||||||
@@ -30,21 +26,6 @@ func (s *MirrorSuite) TestDeleteMirrorNonExisting(c *C) {
|
|||||||
c.Check(response.Body.String(), Equals, "{\"error\":\"unable to drop: mirror with name does-not-exist not found\"}")
|
c.Check(response.Body.String(), Equals, "{\"error\":\"unable to drop: mirror with name does-not-exist not found\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MirrorSuite) TestCreateMirrorFlatWithAppStream(c *C) {
|
|
||||||
body, err := json.Marshal(gin.H{
|
|
||||||
"Name": "test-flat-appstream",
|
|
||||||
"ArchiveURL": "http://example.com/repo/",
|
|
||||||
"Distribution": "./",
|
|
||||||
"DownloadAppStream": true,
|
|
||||||
})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
response, err := s.HTTPRequest("POST", "/api/mirrors", bytes.NewReader(body))
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(response.Code, Equals, 400)
|
|
||||||
c.Check(response.Body.String(), Matches, ".*AppStream.*flat.*")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MirrorSuite) TestCreateMirror(c *C) {
|
func (s *MirrorSuite) TestCreateMirror(c *C) {
|
||||||
c.ExpectFailure("Need to mock downloads")
|
c.ExpectFailure("Need to mock downloads")
|
||||||
body, err := json.Marshal(gin.H{
|
body, err := json.Marshal(gin.H{
|
||||||
@@ -57,49 +38,3 @@ func (s *MirrorSuite) TestCreateMirror(c *C) {
|
|||||||
c.Check(response.Code, Equals, 400)
|
c.Check(response.Code, Equals, 400)
|
||||||
c.Check(response.Body.String(), Equals, "")
|
c.Check(response.Body.String(), Equals, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MirrorSuite) TestGetMirrorsIncludesNumPackages(c *C) {
|
|
||||||
collection := s.context.NewCollectionFactory().RemoteRepoCollection()
|
|
||||||
|
|
||||||
repo, err := deb.NewRemoteRepo("count-mirror", "http://example.com/debian", "stable", []string{"main"}, []string{}, false, false, false, false)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
err = collection.Add(repo)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
putRawDBValue(c, &s.APISuite, repo.RefKey(), makePackageRefList(c).Encode())
|
|
||||||
|
|
||||||
response, err := s.HTTPRequest("GET", "/api/mirrors", nil)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(response.Code, Equals, 200)
|
|
||||||
|
|
||||||
var mirrors []map[string]interface{}
|
|
||||||
err = json.Unmarshal(response.Body.Bytes(), &mirrors)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
found := false
|
|
||||||
for _, mirror := range mirrors {
|
|
||||||
if mirror["Name"] == "count-mirror" {
|
|
||||||
found = true
|
|
||||||
value, ok := mirror["NumPackages"]
|
|
||||||
c.Assert(ok, Equals, true)
|
|
||||||
c.Assert(value, Equals, float64(2))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, Equals, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MirrorSuite) TestGetMirrorsReturns500OnCorruptRefList(c *C) {
|
|
||||||
collection := s.context.NewCollectionFactory().RemoteRepoCollection()
|
|
||||||
|
|
||||||
repo, err := deb.NewRemoteRepo("broken-mirror", "http://example.com/debian", "stable", []string{"main"}, []string{}, false, false, false, false)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(collection.Add(repo), IsNil)
|
|
||||||
putRawDBValue(c, &s.APISuite, repo.RefKey(), []byte("not-msgpack"))
|
|
||||||
|
|
||||||
response, err := s.HTTPRequest("GET", "/api/mirrors", nil)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(response.Code, Equals, 500)
|
|
||||||
c.Assert(response.Body.String(), Matches, ".*unable to show:.*")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import "github.com/aptly-dev/aptly/deb"
|
|
||||||
|
|
||||||
type remoteRepoResponse struct {
|
|
||||||
*deb.RemoteRepo
|
|
||||||
NumPackages int `json:"NumPackages"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type localRepoResponse struct {
|
|
||||||
*deb.LocalRepo
|
|
||||||
NumPackages int `json:"NumPackages"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type snapshotResponse struct {
|
|
||||||
*deb.Snapshot
|
|
||||||
NumPackages int `json:"NumPackages"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRemoteRepoResponse(repo *deb.RemoteRepo) remoteRepoResponse {
|
|
||||||
return remoteRepoResponse{RemoteRepo: repo, NumPackages: repo.NumPackages()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newLocalRepoResponse(repo *deb.LocalRepo) localRepoResponse {
|
|
||||||
return localRepoResponse{LocalRepo: repo, NumPackages: repo.NumPackages()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSnapshotResponse(snapshot *deb.Snapshot) snapshotResponse {
|
|
||||||
return snapshotResponse{Snapshot: snapshot, NumPackages: snapshot.NumPackages()}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/aptly-dev/aptly/deb"
|
|
||||||
. "gopkg.in/check.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makePackageRefList(c *C) *deb.PackageRefList {
|
|
||||||
list := deb.NewPackageList()
|
|
||||||
c.Assert(list.Add(&deb.Package{Name: "libcount", Version: "1.0", Architecture: "amd64"}), IsNil)
|
|
||||||
c.Assert(list.Add(&deb.Package{Name: "appcount", Version: "2.0", Architecture: "all"}), IsNil)
|
|
||||||
return deb.NewPackageRefListFromPackageList(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
func putRawDBValue(c *C, s *APISuite, key []byte, value []byte) {
|
|
||||||
db, err := s.context.Database()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(db.Put(key, value), IsNil)
|
|
||||||
}
|
|
||||||
+321
-236
@@ -160,10 +160,6 @@ type publishedRepoCreateParams struct {
|
|||||||
Sources []sourceParams `binding:"required" json:"Sources"`
|
Sources []sourceParams `binding:"required" json:"Sources"`
|
||||||
// Distribution name, if missing Aptly would try to guess from sources
|
// Distribution name, if missing Aptly would try to guess from sources
|
||||||
Distribution string ` json:"Distribution" example:"bookworm"`
|
Distribution string ` json:"Distribution" example:"bookworm"`
|
||||||
// Value of Label: field in published repository stanza
|
|
||||||
Label string ` json:"Label" example:""`
|
|
||||||
// Value of Origin: field in published repository stanza
|
|
||||||
Origin string ` json:"Origin" example:""`
|
|
||||||
// when publishing, overwrite files in pool/ directory without notice
|
// when publishing, overwrite files in pool/ directory without notice
|
||||||
ForceOverwrite bool ` json:"ForceOverwrite" example:"false"`
|
ForceOverwrite bool ` json:"ForceOverwrite" example:"false"`
|
||||||
// Override list of published architectures
|
// Override list of published architectures
|
||||||
@@ -182,12 +178,8 @@ type publishedRepoCreateParams struct {
|
|||||||
SkipBz2 *bool ` json:"SkipBz2" example:"false"`
|
SkipBz2 *bool ` json:"SkipBz2" example:"false"`
|
||||||
// Provide index files by hash
|
// Provide index files by hash
|
||||||
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
||||||
// An optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file.
|
|
||||||
SignedBy *string ` json:"SignedBy" example:""`
|
|
||||||
// Enable multiple packages with the same filename in different distributions
|
// Enable multiple packages with the same filename in different distributions
|
||||||
MultiDist *bool ` json:"MultiDist" example:"false"`
|
MultiDist *bool ` json:"MultiDist" example:"false"`
|
||||||
// Version of the release
|
|
||||||
Version string ` json:"Version" example:""`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Create Published Repository
|
// @Summary Create Published Repository
|
||||||
@@ -200,7 +192,7 @@ type publishedRepoCreateParams struct {
|
|||||||
// @Description **Example:**
|
// @Description **Example:**
|
||||||
// @Description ```
|
// @Description ```
|
||||||
// @Description $ curl -X POST -H 'Content-Type: application/json' --data '{"Distribution": "wheezy", "Sources": [{"Name": "aptly-repo"}]}' http://localhost:8080/api/publish//repos
|
// @Description $ curl -X POST -H 'Content-Type: application/json' --data '{"Distribution": "wheezy", "Sources": [{"Name": "aptly-repo"}]}' http://localhost:8080/api/publish//repos
|
||||||
// @Description {"Architectures":["i386"],"Distribution":"wheezy","Label":"","Origin":"","Prefix":".","SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],"Storage":""}
|
// @Description {"Architectures":["i386"],"Distribution":"wheezy","Prefix":".","SourceKind":"local","Sources":[{"Component":"main","Name":"aptly-repo"}],"Storage":""}
|
||||||
// @Description ```
|
// @Description ```
|
||||||
// @Description
|
// @Description
|
||||||
// @Description See also: `aptly publish create`
|
// @Description See also: `aptly publish create`
|
||||||
@@ -267,7 +259,7 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources = append(resources, string(snapshot.ResourceKey()))
|
resources = append(resources, string(snapshot.Key()))
|
||||||
sources = append(sources, snapshot)
|
sources = append(sources, snapshot)
|
||||||
}
|
}
|
||||||
} else if b.SourceKind == deb.SourceLocalRepo {
|
} else if b.SourceKind == deb.SourceLocalRepo {
|
||||||
@@ -298,11 +290,24 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
multiDist = *b.MultiDist
|
multiDist = *b.MultiDist
|
||||||
}
|
}
|
||||||
|
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
// Non-MultiDist publishes share a single pool/ directory under the
|
||||||
|
// prefix. Lock at the prefix level so that concurrent publish/drop
|
||||||
|
// operations on sibling distributions cannot race during cleanup.
|
||||||
|
if !multiDist {
|
||||||
|
storagePrefix := prefix
|
||||||
|
if storage != "" {
|
||||||
|
storagePrefix = storage + ":" + prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
resources = append(resources, deb.PrefixPoolLockKey(storagePrefix))
|
||||||
|
}
|
||||||
|
|
||||||
taskName := fmt.Sprintf("Publish %s repository %s/%s with components \"%s\" and sources \"%s\"",
|
taskName := fmt.Sprintf("Publish %s repository %s/%s with components \"%s\" and sources \"%s\"",
|
||||||
b.SourceKind, param, b.Distribution, strings.Join(components, `", "`), strings.Join(names, `", "`))
|
b.SourceKind, param, b.Distribution, strings.Join(components, `", "`), strings.Join(names, `", "`))
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, detail *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
taskDetail := task.PublishDetail{
|
taskDetail := task.PublishDetail{
|
||||||
Detail: detail,
|
Detail: detail,
|
||||||
}
|
}
|
||||||
@@ -314,10 +319,10 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
for _, source := range sources {
|
for _, source := range sources {
|
||||||
switch s := source.(type) {
|
switch s := source.(type) {
|
||||||
case *deb.Snapshot:
|
case *deb.Snapshot:
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
snapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
err = snapshotCollection.LoadComplete(s)
|
err = snapshotCollection.LoadComplete(s)
|
||||||
case *deb.LocalRepo:
|
case *deb.LocalRepo:
|
||||||
localCollection := collectionFactory.LocalRepoCollection()
|
localCollection := taskCollectionFactory.LocalRepoCollection()
|
||||||
err = localCollection.LoadComplete(s)
|
err = localCollection.LoadComplete(s)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unexpected type for source: %T", source)
|
err = fmt.Errorf("unexpected type for source: %T", source)
|
||||||
@@ -327,23 +332,17 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, collectionFactory, multiDist)
|
published, err := deb.NewPublishedRepo(storage, prefix, b.Distribution, b.Architectures, components, sources, taskCollectionFactory, multiDist)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resources = append(resources, string(published.Key()))
|
|
||||||
|
|
||||||
if b.Origin != "" {
|
|
||||||
published.Origin = b.Origin
|
|
||||||
}
|
|
||||||
if b.NotAutomatic != "" {
|
if b.NotAutomatic != "" {
|
||||||
published.NotAutomatic = b.NotAutomatic
|
published.NotAutomatic = b.NotAutomatic
|
||||||
}
|
}
|
||||||
if b.ButAutomaticUpgrades != "" {
|
if b.ButAutomaticUpgrades != "" {
|
||||||
published.ButAutomaticUpgrades = b.ButAutomaticUpgrades
|
published.ButAutomaticUpgrades = b.ButAutomaticUpgrades
|
||||||
}
|
}
|
||||||
published.Label = b.Label
|
|
||||||
|
|
||||||
published.SkipContents = context.Config().SkipContentsPublishing
|
published.SkipContents = context.Config().SkipContentsPublishing
|
||||||
if b.SkipContents != nil {
|
if b.SkipContents != nil {
|
||||||
@@ -359,26 +358,18 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
|||||||
published.AcquireByHash = *b.AcquireByHash
|
published.AcquireByHash = *b.AcquireByHash
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.SignedBy != nil {
|
duplicate := taskCollection.CheckDuplicate(published)
|
||||||
published.SignedBy = *b.SignedBy
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Version != "" {
|
|
||||||
published.Version = b.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
duplicate := collection.CheckDuplicate(published)
|
|
||||||
if duplicate != nil {
|
if duplicate != nil {
|
||||||
_ = collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
|
_ = taskCollectionFactory.PublishedRepoCollection().LoadComplete(duplicate, taskCollectionFactory)
|
||||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("prefix/distribution already used by another published repo: %s", duplicate)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, publishOutput, b.ForceOverwrite, context.SkelPath())
|
err = published.Publish(context.PackagePool(), context, taskCollectionFactory, signer, publishOutput, b.ForceOverwrite, context.SkelPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to publish: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.Add(published)
|
err = taskCollection.Add(published)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -402,16 +393,8 @@ type publishedRepoUpdateSwitchParams struct {
|
|||||||
Snapshots []sourceParams ` json:"Snapshots"`
|
Snapshots []sourceParams ` json:"Snapshots"`
|
||||||
// Provide index files by hash
|
// Provide index files by hash
|
||||||
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
||||||
// An optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file
|
|
||||||
SignedBy *string ` json:"SignedBy" example:""`
|
|
||||||
// Enable multiple packages with the same filename in different distributions
|
// Enable multiple packages with the same filename in different distributions
|
||||||
MultiDist *bool ` json:"MultiDist" example:"false"`
|
MultiDist *bool ` json:"MultiDist" example:"false"`
|
||||||
// Value of Label: field in published repository stanza
|
|
||||||
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
|
// @Summary Update Published Repository
|
||||||
@@ -458,6 +441,7 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
|
localRepoCollection := collectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -465,64 +449,76 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resources := []string{string(published.Key())}
|
||||||
|
|
||||||
if published.SourceKind == deb.SourceLocalRepo {
|
if published.SourceKind == deb.SourceLocalRepo {
|
||||||
if len(b.Snapshots) > 0 {
|
if len(b.Snapshots) > 0 {
|
||||||
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
|
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("snapshots shouldn't be given when updating local repo"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if published.SourceKind == deb.SourceSnapshot {
|
for _, uuid := range published.Sources {
|
||||||
for _, snapshotInfo := range b.Snapshots {
|
repo, err2 := localRepoCollection.ByUUID(uuid)
|
||||||
_, err2 := snapshotCollection.ByName(snapshotInfo.Name)
|
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, err2)
|
AbortWithJSONError(c, http.StatusNotFound, err2)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
resources = append(resources, string(repo.Key()))
|
||||||
|
}
|
||||||
|
} else if published.SourceKind == deb.SourceSnapshot {
|
||||||
|
for _, snapshotInfo := range b.Snapshots {
|
||||||
|
snapshot, err2 := snapshotCollection.ByName(snapshotInfo.Name)
|
||||||
|
if err2 != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusNotFound, err2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resources = append(resources, string(snapshot.Key()))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type"))
|
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unknown published repository type"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.SkipContents != nil {
|
// Non-MultiDist distributions share a single pool/ directory under the
|
||||||
published.SkipContents = *b.SkipContents
|
// prefix. Acquire the prefix-level pool lock so that concurrent updates
|
||||||
|
// on sibling distributions are serialised and cannot race during cleanup.
|
||||||
|
if !published.MultiDist {
|
||||||
|
resources = append(resources, deb.PrefixPoolLockKey(published.StoragePrefix()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.SkipBz2 != nil {
|
// Field mutations and fresh DB load are deferred to inside the task so
|
||||||
published.SkipBz2 = *b.SkipBz2
|
// they always operate on a consistent state after the lock is held.
|
||||||
}
|
|
||||||
|
|
||||||
if b.AcquireByHash != nil {
|
|
||||||
published.AcquireByHash = *b.AcquireByHash
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.SignedBy != nil {
|
|
||||||
published.SignedBy = *b.SignedBy
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.MultiDist != nil {
|
|
||||||
published.MultiDist = *b.MultiDist
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Label != nil {
|
|
||||||
published.Label = *b.Label
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Origin != nil {
|
|
||||||
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)
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.LoadComplete(published, collectionFactory)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture MultiDist before mutations to detect a false→true transition.
|
||||||
|
prevMultiDist := published.MultiDist
|
||||||
|
|
||||||
|
// Apply field mutations on the freshly loaded object.
|
||||||
|
if b.SkipContents != nil {
|
||||||
|
published.SkipContents = *b.SkipContents
|
||||||
|
}
|
||||||
|
if b.SkipBz2 != nil {
|
||||||
|
published.SkipBz2 = *b.SkipBz2
|
||||||
|
}
|
||||||
|
if b.AcquireByHash != nil {
|
||||||
|
published.AcquireByHash = *b.AcquireByHash
|
||||||
|
}
|
||||||
|
if b.MultiDist != nil {
|
||||||
|
published.MultiDist = *b.MultiDist
|
||||||
|
}
|
||||||
|
|
||||||
revision := published.ObtainRevision()
|
revision := published.ObtainRevision()
|
||||||
sources := revision.Sources
|
sources := revision.Sources
|
||||||
|
|
||||||
@@ -534,17 +530,17 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := published.Update(collectionFactory, out)
|
result, err := published.Update(taskCollectionFactory, out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
err = published.Publish(context.PackagePool(), context, taskCollectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.Update(published)
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -552,10 +548,19 @@ func apiPublishUpdateSwitch(c *gin.Context) {
|
|||||||
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
||||||
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
||||||
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
||||||
err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out)
|
err = taskCollection.CleanupPrefixComponentFiles(context, published, cleanComponents, taskCollectionFactory, out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
// When MultiDist is toggled, the old pool layout still has files that
|
||||||
|
// CleanupPrefixComponentFiles won't touch (it only scans the new layout).
|
||||||
|
// Run a second pass over the previous layout to remove stale files.
|
||||||
|
if prevMultiDist != published.MultiDist {
|
||||||
|
err = taskCollection.CleanupAfterMultiDistToggle(context, published, prevMultiDist, cleanComponents, taskCollectionFactory, out)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to clean legacy pool: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
|
||||||
@@ -600,10 +605,19 @@ func apiPublishDrop(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resources := []string{string(published.Key())}
|
resources := []string{string(published.Key())}
|
||||||
|
// Non-MultiDist distributions share a single pool/ directory under the
|
||||||
|
// prefix. Acquire the prefix-level pool lock so that a drop cannot race
|
||||||
|
// with a concurrent update or drop of a sibling distribution during cleanup.
|
||||||
|
if !published.MultiDist {
|
||||||
|
resources = append(resources, deb.PrefixPoolLockKey(published.StoragePrefix()))
|
||||||
|
}
|
||||||
taskName := fmt.Sprintf("Delete published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
taskName := fmt.Sprintf("Delete published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err := collection.Remove(context, storage, prefix, distribution,
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
collectionFactory, out, force, skipCleanup)
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
err := taskCollection.Remove(context, storage, prefix, distribution,
|
||||||
|
taskCollectionFactory, out, force, skipCleanup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to drop: %s", err)
|
||||||
}
|
}
|
||||||
@@ -639,43 +653,52 @@ func apiPublishAddSource(c *gin.Context) {
|
|||||||
storage, prefix := deb.ParsePrefix(param)
|
storage, prefix := deb.ParsePrefix(param)
|
||||||
distribution := slashEscape(c.Params.ByName("distribution"))
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
||||||
|
|
||||||
|
if c.Bind(&b) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly (no LoadComplete) to verify existence and obtain the
|
||||||
|
// resource key and task name. The actual mutation is performed inside
|
||||||
|
// the task on a freshly loaded copy to prevent lost-update races.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to create: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to create: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(published, collectionFactory)
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to create: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Bind(&b) != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
revision := published.ObtainRevision()
|
|
||||||
sources := revision.Sources
|
|
||||||
|
|
||||||
component := b.Component
|
|
||||||
name := b.Name
|
|
||||||
|
|
||||||
_, exists := sources[component]
|
|
||||||
if exists {
|
|
||||||
AbortWithJSONError(c, http.StatusBadRequest, fmt.Errorf("unable to create: Component '%s' already exists", component))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sources[component] = name
|
|
||||||
|
|
||||||
resources := []string{string(published.Key())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.Update(published)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to create: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
revision := published.ObtainRevision()
|
||||||
|
sources := revision.Sources
|
||||||
|
|
||||||
|
component := b.Component
|
||||||
|
name := b.Name
|
||||||
|
|
||||||
|
_, exists := sources[component]
|
||||||
|
if exists {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("unable to create: Component '%s' already exists", component)
|
||||||
|
}
|
||||||
|
|
||||||
|
sources[component] = name
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -757,39 +780,48 @@ func apiPublishSetSources(c *gin.Context) {
|
|||||||
storage, prefix := deb.ParsePrefix(param)
|
storage, prefix := deb.ParsePrefix(param)
|
||||||
distribution := slashEscape(c.Params.ByName("distribution"))
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
||||||
|
|
||||||
|
if c.Bind(&b) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full load and mutation happen inside the task.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(published, collectionFactory)
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Bind(&b) != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
revision := published.ObtainRevision()
|
|
||||||
sources := make(map[string]string, len(b))
|
|
||||||
revision.Sources = sources
|
|
||||||
|
|
||||||
for _, source := range b {
|
|
||||||
component := source.Component
|
|
||||||
name := source.Name
|
|
||||||
sources[component] = name
|
|
||||||
}
|
|
||||||
|
|
||||||
resources := []string{string(published.Key())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.Update(published)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
revision := published.ObtainRevision()
|
||||||
|
sources := make(map[string]string, len(b))
|
||||||
|
revision.Sources = sources
|
||||||
|
|
||||||
|
for _, source := range b {
|
||||||
|
component := source.Component
|
||||||
|
name := source.Name
|
||||||
|
sources[component] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -822,24 +854,33 @@ func apiPublishDropChanges(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full load and DropRevision happen inside the task.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(published, collectionFactory)
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to delete: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
published.DropRevision()
|
|
||||||
|
|
||||||
resources := []string{string(published.Key())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.Update(published)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
published.DropRevision()
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -875,51 +916,58 @@ func apiPublishUpdateSource(c *gin.Context) {
|
|||||||
param := slashEscape(c.Params.ByName("prefix"))
|
param := slashEscape(c.Params.ByName("prefix"))
|
||||||
storage, prefix := deb.ParsePrefix(param)
|
storage, prefix := deb.ParsePrefix(param)
|
||||||
distribution := slashEscape(c.Params.ByName("distribution"))
|
distribution := slashEscape(c.Params.ByName("distribution"))
|
||||||
component := slashEscape(c.Params.ByName("component"))
|
urlComponent := slashEscape(c.Params.ByName("component"))
|
||||||
|
|
||||||
|
// Default component to the URL path segment; the body may rename it.
|
||||||
|
b.Component = urlComponent
|
||||||
|
if c.Bind(&b) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full load and mutation happen inside the task.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(published, collectionFactory)
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
revision := published.ObtainRevision()
|
|
||||||
sources := revision.Sources
|
|
||||||
|
|
||||||
_, exists := sources[component]
|
|
||||||
if !exists {
|
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: Component '%s' does not exist", component))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Component = component
|
|
||||||
b.Name = revision.Sources[component]
|
|
||||||
|
|
||||||
if c.Bind(&b) != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Component != component {
|
|
||||||
delete(sources, component)
|
|
||||||
}
|
|
||||||
|
|
||||||
component = b.Component
|
|
||||||
name := b.Name
|
|
||||||
sources[component] = name
|
|
||||||
|
|
||||||
resources := []string{string(published.Key())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.Update(published)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
revision := published.ObtainRevision()
|
||||||
|
sources := revision.Sources
|
||||||
|
|
||||||
|
_, exists := sources[urlComponent]
|
||||||
|
if !exists {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to update: Component '%s' does not exist", urlComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Component != urlComponent {
|
||||||
|
delete(sources, urlComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
newComponent := b.Component
|
||||||
|
name := b.Name
|
||||||
|
sources[newComponent] = name
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -956,33 +1004,41 @@ func apiPublishRemoveSource(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full load and mutation happen inside the task.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(published, collectionFactory)
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to delete: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
revision := published.ObtainRevision()
|
|
||||||
sources := revision.Sources
|
|
||||||
|
|
||||||
_, exists := sources[component]
|
|
||||||
if !exists {
|
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to delete: Component '%s' does not exist", component))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(sources, component)
|
|
||||||
|
|
||||||
resources := []string{string(published.Key())}
|
resources := []string{string(published.Key())}
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.Update(published)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to delete: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
revision := published.ObtainRevision()
|
||||||
|
sources := revision.Sources
|
||||||
|
|
||||||
|
_, exists := sources[component]
|
||||||
|
if !exists {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("unable to delete: Component '%s' does not exist", component)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(sources, component)
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -1004,16 +1060,8 @@ type publishedRepoUpdateParams struct {
|
|||||||
SkipCleanup *bool ` json:"SkipCleanup" example:"false"`
|
SkipCleanup *bool ` json:"SkipCleanup" example:"false"`
|
||||||
// Provide index files by hash
|
// Provide index files by hash
|
||||||
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
AcquireByHash *bool ` json:"AcquireByHash" example:"false"`
|
||||||
// An optional field containing a comma separated list of OpenPGP key fingerprints to be used for validating the next Release file
|
|
||||||
SignedBy *string ` json:"SignedBy" example:""`
|
|
||||||
// Enable multiple packages with the same filename in different distributions
|
// Enable multiple packages with the same filename in different distributions
|
||||||
MultiDist *bool ` json:"MultiDist" example:"false"`
|
MultiDist *bool ` json:"MultiDist" example:"false"`
|
||||||
// Value of Label: field in published repository stanza
|
|
||||||
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
|
// @Summary Update Published Repository
|
||||||
@@ -1054,64 +1102,92 @@ func apiPublishUpdate(c *gin.Context) {
|
|||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.PublishedRepoCollection()
|
collection := collectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full load and field mutations happen inside the task.
|
||||||
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
published, err := collection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("unable to update: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(published, collectionFactory)
|
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to update: %s", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.SkipContents != nil {
|
|
||||||
published.SkipContents = *b.SkipContents
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.SkipBz2 != nil {
|
|
||||||
published.SkipBz2 = *b.SkipBz2
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.AcquireByHash != nil {
|
|
||||||
published.AcquireByHash = *b.AcquireByHash
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.SignedBy != nil {
|
|
||||||
published.SignedBy = *b.SignedBy
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.MultiDist != nil {
|
|
||||||
published.MultiDist = *b.MultiDist
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Label != nil {
|
|
||||||
published.Label = *b.Label
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Origin != nil {
|
|
||||||
published.Origin = *b.Origin
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.Version != nil {
|
|
||||||
published.Version = *b.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
resources := []string{string(published.Key())}
|
resources := []string{string(published.Key())}
|
||||||
|
|
||||||
|
// Non-MultiDist distributions share a single pool/ directory under the
|
||||||
|
// prefix. Acquire the prefix-level pool lock so that concurrent updates
|
||||||
|
// on sibling distributions are serialised and cannot race during cleanup.
|
||||||
|
if !published.MultiDist {
|
||||||
|
resources = append(resources, deb.PrefixPoolLockKey(published.StoragePrefix()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock source repos / snapshots the same way apiPublishUpdateSwitch does,
|
||||||
|
// because published.Update() reads from them and concurrent modification
|
||||||
|
// would produce an inconsistent view.
|
||||||
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
|
localRepoCollection := collectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
|
if published.SourceKind == deb.SourceLocalRepo {
|
||||||
|
for _, uuid := range published.Sources {
|
||||||
|
repo, err2 := localRepoCollection.ByUUID(uuid)
|
||||||
|
if err2 != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusNotFound, err2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resources = append(resources, string(repo.Key()))
|
||||||
|
}
|
||||||
|
} else if published.SourceKind == deb.SourceSnapshot {
|
||||||
|
for _, uuid := range published.Sources {
|
||||||
|
snapshot, err2 := snapshotCollection.ByUUID(uuid)
|
||||||
|
if err2 != nil {
|
||||||
|
AbortWithJSONError(c, http.StatusNotFound, err2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resources = append(resources, string(snapshot.Key()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
taskName := fmt.Sprintf("Update published %s repository %s/%s", published.SourceKind, published.StoragePrefix(), published.Distribution)
|
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) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
result, err := published.Update(collectionFactory, out)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
published, err := taskCollection.ByStoragePrefixDistribution(storage, prefix, distribution)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
err = taskCollection.LoadComplete(published, taskCollectionFactory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.Update(published)
|
// Capture MultiDist before mutations to detect a false→true transition.
|
||||||
|
prevMultiDist := published.MultiDist
|
||||||
|
|
||||||
|
// Apply field mutations on the freshly loaded object.
|
||||||
|
if b.SkipContents != nil {
|
||||||
|
published.SkipContents = *b.SkipContents
|
||||||
|
}
|
||||||
|
if b.SkipBz2 != nil {
|
||||||
|
published.SkipBz2 = *b.SkipBz2
|
||||||
|
}
|
||||||
|
if b.AcquireByHash != nil {
|
||||||
|
published.AcquireByHash = *b.AcquireByHash
|
||||||
|
}
|
||||||
|
if b.MultiDist != nil {
|
||||||
|
published.MultiDist = *b.MultiDist
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := published.Update(taskCollectionFactory, out)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = published.Publish(context.PackagePool(), context, taskCollectionFactory, signer, out, b.ForceOverwrite, context.SkelPath())
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.Update(published)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save to DB: %s", err)
|
||||||
}
|
}
|
||||||
@@ -1119,10 +1195,19 @@ func apiPublishUpdate(c *gin.Context) {
|
|||||||
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
if b.SkipCleanup == nil || !*b.SkipCleanup {
|
||||||
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
cleanComponents := make([]string, 0, len(result.UpdatedSources)+len(result.RemovedSources))
|
||||||
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
cleanComponents = append(append(cleanComponents, result.UpdatedComponents()...), result.RemovedComponents()...)
|
||||||
err = collection.CleanupPrefixComponentFiles(context, published, cleanComponents, collectionFactory, out)
|
err = taskCollection.CleanupPrefixComponentFiles(context, published, cleanComponents, taskCollectionFactory, out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
// When MultiDist is toggled, the old pool layout still has files that
|
||||||
|
// CleanupPrefixComponentFiles won't touch (it only scans the new layout).
|
||||||
|
// Run a second pass over the previous layout to remove stale files.
|
||||||
|
if prevMultiDist != published.MultiDist {
|
||||||
|
err = taskCollection.CleanupAfterMultiDistToggle(context, published, prevMultiDist, cleanComponents, taskCollectionFactory, out)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to clean legacy pool: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: published}, nil
|
||||||
|
|||||||
@@ -0,0 +1,733 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
|
ctx "github.com/aptly-dev/aptly/context"
|
||||||
|
"github.com/aptly-dev/aptly/deb"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/smira/flag"
|
||||||
|
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublishedFileMissingSuite reproduces the exact bug where:
|
||||||
|
// - Package import succeeds
|
||||||
|
// - Metadata is updated (Packages.gz shows the package)
|
||||||
|
// - Publish reports success
|
||||||
|
// - BUT the .deb file is missing from the published pool directory
|
||||||
|
// - Result: apt-get returns 404 when trying to download the package
|
||||||
|
type PublishedFileMissingSuite struct {
|
||||||
|
context *ctx.AptlyContext
|
||||||
|
flags *flag.FlagSet
|
||||||
|
configFile *os.File
|
||||||
|
router http.Handler
|
||||||
|
tempDir string
|
||||||
|
poolPath string
|
||||||
|
publicPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Suite(&PublishedFileMissingSuite{})
|
||||||
|
|
||||||
|
func (s *PublishedFileMissingSuite) SetUpSuite(c *C) {
|
||||||
|
aptly.Version = "publishedFileMissingTest"
|
||||||
|
|
||||||
|
tempDir, err := os.MkdirTemp("", "aptly-published-missing-test")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
s.tempDir = tempDir
|
||||||
|
s.poolPath = filepath.Join(tempDir, "pool")
|
||||||
|
s.publicPath = filepath.Join(tempDir, "public")
|
||||||
|
|
||||||
|
file, err := os.CreateTemp("", "aptly-published-missing-config")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
s.configFile = file
|
||||||
|
|
||||||
|
config := gin.H{
|
||||||
|
"rootDir": tempDir,
|
||||||
|
"downloadDir": filepath.Join(tempDir, "download"),
|
||||||
|
"architectures": []string{"amd64"},
|
||||||
|
"dependencyFollowSuggests": false,
|
||||||
|
"dependencyFollowRecommends": false,
|
||||||
|
"gpgDisableSign": true,
|
||||||
|
"gpgDisableVerify": true,
|
||||||
|
"gpgProvider": "internal",
|
||||||
|
"skipLegacyPool": true,
|
||||||
|
"enableMetricsEndpoint": false,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonString, err := json.Marshal(config)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
_, err = file.Write(jsonString)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
flags := flag.NewFlagSet("publishedFileMissingTestFlags", flag.ContinueOnError)
|
||||||
|
flags.Bool("no-lock", true, "disable database locking for test")
|
||||||
|
flags.Int("db-open-attempts", 3, "dummy")
|
||||||
|
flags.String("config", s.configFile.Name(), "config file")
|
||||||
|
flags.String("architectures", "", "dummy")
|
||||||
|
s.flags = flags
|
||||||
|
|
||||||
|
context, err := ctx.NewContext(s.flags)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
s.context = context
|
||||||
|
s.router = Router(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedFileMissingSuite) TearDownSuite(c *C) {
|
||||||
|
if s.configFile != nil {
|
||||||
|
_ = os.Remove(s.configFile.Name())
|
||||||
|
}
|
||||||
|
if s.context != nil {
|
||||||
|
s.context.Shutdown()
|
||||||
|
}
|
||||||
|
if s.tempDir != "" {
|
||||||
|
_ = os.RemoveAll(s.tempDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedFileMissingSuite) SetUpTest(c *C) {
|
||||||
|
collectionFactory := s.context.NewCollectionFactory()
|
||||||
|
|
||||||
|
localRepoCollection := collectionFactory.LocalRepoCollection()
|
||||||
|
_ = localRepoCollection.ForEach(func(repo *deb.LocalRepo) error {
|
||||||
|
_ = localRepoCollection.Drop(repo)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
publishedCollection := collectionFactory.PublishedRepoCollection()
|
||||||
|
_ = publishedCollection.ForEach(func(published *deb.PublishedRepo) error {
|
||||||
|
_ = publishedCollection.Remove(s.context, published.Storage, published.Prefix,
|
||||||
|
published.Distribution, collectionFactory, nil, true, true)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedFileMissingSuite) TearDownTest(c *C) {
|
||||||
|
s.SetUpTest(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedFileMissingSuite) httpRequest(c *C, method string, url string, body []byte) *httptest.ResponseRecorder {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
var req *http.Request
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if body != nil {
|
||||||
|
req, err = http.NewRequest(method, url, bytes.NewReader(body))
|
||||||
|
} else {
|
||||||
|
req, err = http.NewRequest(method, url, nil)
|
||||||
|
}
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
s.router.ServeHTTP(w, req)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedFileMissingSuite) createDebPackage(c *C, uploadID, packageName, version string) {
|
||||||
|
uploadPath := s.context.UploadPath()
|
||||||
|
uploadDir := filepath.Join(uploadPath, uploadID)
|
||||||
|
err := os.MkdirAll(uploadDir, 0755)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
tempDir, err := os.MkdirTemp("", "deb-build")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer func() { _ = os.RemoveAll(tempDir) }()
|
||||||
|
|
||||||
|
debianDir := filepath.Join(tempDir, "DEBIAN")
|
||||||
|
err = os.MkdirAll(debianDir, 0755)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
controlContent := fmt.Sprintf(`Package: %s
|
||||||
|
Version: %s
|
||||||
|
Section: libs
|
||||||
|
Priority: optional
|
||||||
|
Architecture: amd64
|
||||||
|
Maintainer: Test <test@example.com>
|
||||||
|
Description: Test package
|
||||||
|
Test package for published file missing bug.
|
||||||
|
`, packageName, version)
|
||||||
|
|
||||||
|
err = os.WriteFile(filepath.Join(debianDir, "control"), []byte(controlContent), 0644)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
usrDir := filepath.Join(tempDir, "usr", "lib")
|
||||||
|
err = os.MkdirAll(usrDir, 0755)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
err = os.WriteFile(filepath.Join(usrDir, "lib.so"), []byte("library"), 0644)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
debFile := filepath.Join(uploadDir, fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||||
|
cmd := exec.Command("dpkg-deb", "--build", tempDir, debFile)
|
||||||
|
err = cmd.Run()
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPublishedFileGoMissing reproduces the exact production bug
|
||||||
|
func (s *PublishedFileMissingSuite) TestPublishedFileGoMissing(c *C) {
|
||||||
|
c.Log("=== Reproducing: Package in metadata but 404 on download ===")
|
||||||
|
|
||||||
|
// Create and publish a repository
|
||||||
|
repoName := "test-repo"
|
||||||
|
distribution := "bullseye"
|
||||||
|
|
||||||
|
createBody, _ := json.Marshal(gin.H{
|
||||||
|
"Name": repoName,
|
||||||
|
"DefaultDistribution": distribution,
|
||||||
|
"DefaultComponent": "main",
|
||||||
|
})
|
||||||
|
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
|
||||||
|
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create repo: %s", resp.Body.String()))
|
||||||
|
|
||||||
|
publishBody, _ := json.Marshal(gin.H{
|
||||||
|
"SourceKind": "local",
|
||||||
|
"Distribution": distribution,
|
||||||
|
"Architectures": []string{"amd64"},
|
||||||
|
"Sources": []gin.H{
|
||||||
|
{"Component": "main", "Name": repoName},
|
||||||
|
},
|
||||||
|
"Signing": gin.H{"Skip": true},
|
||||||
|
})
|
||||||
|
resp = s.httpRequest(c, "POST", "/api/publish/hrt", publishBody)
|
||||||
|
c.Assert(resp.Code, Equals, 201, Commentf("Failed to publish: %s", resp.Body.String()))
|
||||||
|
|
||||||
|
// Create package
|
||||||
|
packageName := "hrt-libblobbyclient1"
|
||||||
|
version := "20250926.152427+hrtdeb11"
|
||||||
|
uploadID := "test-upload-1"
|
||||||
|
|
||||||
|
s.createDebPackage(c, uploadID, packageName, version)
|
||||||
|
|
||||||
|
// Add package
|
||||||
|
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoName, uploadID), nil)
|
||||||
|
c.Assert(resp.Code, Equals, 200, Commentf("Failed to add package: %s", resp.Body.String()))
|
||||||
|
|
||||||
|
// Update publish
|
||||||
|
updateBody, _ := json.Marshal(gin.H{
|
||||||
|
"Signing": gin.H{"Skip": true},
|
||||||
|
"ForceOverwrite": true,
|
||||||
|
})
|
||||||
|
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/hrt/%s", distribution), updateBody)
|
||||||
|
c.Assert(resp.Code, Equals, 200, Commentf("Failed to update publish: %s", resp.Body.String()))
|
||||||
|
|
||||||
|
// Now check if the file is actually accessible in the published location
|
||||||
|
publishedStorage := s.context.GetPublishedStorage("")
|
||||||
|
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
|
||||||
|
|
||||||
|
// Expected file path: hrt/pool/main/h/hrt-libblobbyclient1/hrt-libblobbyclient1_20250926.152427+hrtdeb11_amd64.deb
|
||||||
|
expectedPath := filepath.Join(publicPath, "hrt", "pool", "main", "h", packageName,
|
||||||
|
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||||
|
|
||||||
|
c.Logf("Checking for published file at: %s", expectedPath)
|
||||||
|
|
||||||
|
fileInfo, err := os.Stat(expectedPath)
|
||||||
|
fileExists := err == nil
|
||||||
|
|
||||||
|
c.Logf("File exists: %v", fileExists)
|
||||||
|
if fileExists {
|
||||||
|
c.Logf("File size: %d bytes", fileInfo.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check metadata
|
||||||
|
resp = s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repoName), nil)
|
||||||
|
var packages []string
|
||||||
|
err = json.Unmarshal(resp.Body.Bytes(), &packages)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
c.Logf("Packages in metadata: %d", len(packages))
|
||||||
|
|
||||||
|
// THE BUG: Metadata says package exists, but file is missing from published location
|
||||||
|
if len(packages) > 0 && !fileExists {
|
||||||
|
c.Logf("★★★ BUG REPRODUCED! ★★★")
|
||||||
|
c.Logf("Metadata shows %d package(s) but file is missing at: %s", len(packages), expectedPath)
|
||||||
|
c.Logf("This is exactly what causes: 404 Not Found [IP: 10.20.72.62 3142]")
|
||||||
|
|
||||||
|
c.Fatal("BUG CONFIRMED: Package in metadata but missing from published directory!")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Assert(fileExists, Equals, true, Commentf(
|
||||||
|
"Published file should exist at %s when package is in metadata", expectedPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConcurrentPublishRace tries to trigger the race with concurrent publishes
|
||||||
|
func (s *PublishedFileMissingSuite) TestConcurrentPublishRace(c *C) {
|
||||||
|
c.Log("=== Testing concurrent publish race condition ===")
|
||||||
|
|
||||||
|
const numIterations = 4
|
||||||
|
|
||||||
|
for iteration := 0; iteration < numIterations; iteration++ {
|
||||||
|
c.Logf("--- Iteration %d/%d ---", iteration+1, numIterations)
|
||||||
|
|
||||||
|
// Create repo
|
||||||
|
repoName := fmt.Sprintf("race-repo-%d", iteration)
|
||||||
|
distribution := fmt.Sprintf("dist-%d", iteration)
|
||||||
|
|
||||||
|
createBody, _ := json.Marshal(gin.H{
|
||||||
|
"Name": repoName,
|
||||||
|
"DefaultDistribution": distribution,
|
||||||
|
"DefaultComponent": "main",
|
||||||
|
})
|
||||||
|
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
|
||||||
|
c.Assert(resp.Code, Equals, 201)
|
||||||
|
|
||||||
|
publishBody, _ := json.Marshal(gin.H{
|
||||||
|
"SourceKind": "local",
|
||||||
|
"Distribution": distribution,
|
||||||
|
"Architectures": []string{"amd64"},
|
||||||
|
"Sources": []gin.H{
|
||||||
|
{"Component": "main", "Name": repoName},
|
||||||
|
},
|
||||||
|
"Signing": gin.H{"Skip": true},
|
||||||
|
})
|
||||||
|
resp = s.httpRequest(c, "POST", "/api/publish/concurrent", publishBody)
|
||||||
|
c.Assert(resp.Code, Equals, 201)
|
||||||
|
|
||||||
|
// Create multiple packages
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
numPackages := 5
|
||||||
|
|
||||||
|
for i := 0; i < numPackages; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(idx int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
packageName := fmt.Sprintf("pkg-%d-%d", iteration, idx)
|
||||||
|
version := "1.0.0"
|
||||||
|
uploadID := fmt.Sprintf("upload-%d-%d", iteration, idx)
|
||||||
|
|
||||||
|
s.createDebPackage(c, uploadID, packageName, version)
|
||||||
|
|
||||||
|
// Add package
|
||||||
|
resp := s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoName, uploadID), nil)
|
||||||
|
c.Logf("Package %d add: %d", idx, resp.Code)
|
||||||
|
|
||||||
|
// Small delay
|
||||||
|
time.Sleep(time.Duration(5+idx*2) * time.Millisecond)
|
||||||
|
|
||||||
|
// Publish
|
||||||
|
updateBody, _ := json.Marshal(gin.H{
|
||||||
|
"Signing": gin.H{"Skip": true},
|
||||||
|
"ForceOverwrite": true,
|
||||||
|
})
|
||||||
|
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/concurrent/%s", distribution), updateBody)
|
||||||
|
c.Logf("Publish %d: %d", idx, resp.Code)
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Check all packages
|
||||||
|
resp = s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repoName), nil)
|
||||||
|
var packages []string
|
||||||
|
err := json.Unmarshal(resp.Body.Bytes(), &packages)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
// Check published files
|
||||||
|
publishedStorage := s.context.GetPublishedStorage("")
|
||||||
|
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
|
||||||
|
|
||||||
|
missingFiles := []string{}
|
||||||
|
for i := 0; i < numPackages; i++ {
|
||||||
|
packageName := fmt.Sprintf("pkg-%d-%d", iteration, i)
|
||||||
|
version := "1.0.0"
|
||||||
|
|
||||||
|
// Calculate pool path
|
||||||
|
poolSubdir := string(packageName[0])
|
||||||
|
expectedPath := filepath.Join(publicPath, "concurrent", "pool", "main", poolSubdir, packageName,
|
||||||
|
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||||
|
|
||||||
|
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
|
||||||
|
missingFiles = append(missingFiles, expectedPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missingFiles) > 0 {
|
||||||
|
c.Logf("★★★ BUG DETECTED in iteration %d/%d! ★★★", iteration+1, numIterations)
|
||||||
|
c.Logf("Metadata shows %d packages, but %d files are MISSING:", len(packages), len(missingFiles))
|
||||||
|
for i, f := range missingFiles {
|
||||||
|
c.Logf(" [iter %d] File MISSING %d/%d: %s", iteration+1, i+1, len(missingFiles), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Fatalf("BUG REPRODUCED in iteration %d/%d: %d published files missing", iteration+1, numIterations, len(missingFiles))
|
||||||
|
} else {
|
||||||
|
c.Logf("[iter %d/%d] All %d files present - OK", iteration+1, numIterations, numPackages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Logf("All %d iterations passed - bug not reproduced with current timing", numIterations)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestIdenticalPackageRace tests the specific case of identical SHA256 packages
|
||||||
|
func (s *PublishedFileMissingSuite) TestIdenticalPackageRace(c *C) {
|
||||||
|
c.Log("=== AGGRESSIVE test: identical package (same SHA256) race ===")
|
||||||
|
|
||||||
|
const numIterations = 4
|
||||||
|
packageName := "shared-package"
|
||||||
|
|
||||||
|
for iter := 0; iter < numIterations; iter++ {
|
||||||
|
c.Logf("Iteration %d/%d", iter+1, numIterations)
|
||||||
|
|
||||||
|
// Create two repos that will get the SAME package (unique per iteration)
|
||||||
|
repos := []string{fmt.Sprintf("identical-a-%d", iter), fmt.Sprintf("identical-b-%d", iter)}
|
||||||
|
dists := []string{fmt.Sprintf("dist-a-%d", iter), fmt.Sprintf("dist-b-%d", iter)}
|
||||||
|
|
||||||
|
for i := range repos {
|
||||||
|
createBody, _ := json.Marshal(gin.H{
|
||||||
|
"Name": repos[i],
|
||||||
|
"DefaultDistribution": dists[i],
|
||||||
|
"DefaultComponent": "main",
|
||||||
|
})
|
||||||
|
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
|
||||||
|
c.Assert(resp.Code, Equals, 201)
|
||||||
|
|
||||||
|
publishBody, _ := json.Marshal(gin.H{
|
||||||
|
"SourceKind": "local",
|
||||||
|
"Distribution": dists[i],
|
||||||
|
"Architectures": []string{"amd64"},
|
||||||
|
"Sources": []gin.H{
|
||||||
|
{"Component": "main", "Name": repos[i]},
|
||||||
|
},
|
||||||
|
"Signing": gin.H{"Skip": true},
|
||||||
|
"SkipBz2": true,
|
||||||
|
})
|
||||||
|
resp = s.httpRequest(c, "POST", "/api/publish/identical", publishBody)
|
||||||
|
c.Assert(resp.Code, Equals, 201)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create IDENTICAL package file with UNIQUE VERSION per iteration
|
||||||
|
version := fmt.Sprintf("1.0.%d", iter)
|
||||||
|
uploadID1 := fmt.Sprintf("identical-upload-1-%d", iter)
|
||||||
|
uploadID2 := fmt.Sprintf("identical-upload-2-%d", iter)
|
||||||
|
|
||||||
|
s.createDebPackage(c, uploadID1, packageName, version)
|
||||||
|
|
||||||
|
// Copy to second upload (same SHA256)
|
||||||
|
uploadPath := s.context.UploadPath()
|
||||||
|
src := filepath.Join(uploadPath, uploadID1, fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||||
|
destDir := filepath.Join(uploadPath, uploadID2)
|
||||||
|
err := os.MkdirAll(destDir, 0755)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
dest := filepath.Join(destDir, fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||||
|
|
||||||
|
srcData, readErr := os.ReadFile(src)
|
||||||
|
c.Assert(readErr, IsNil)
|
||||||
|
err = os.WriteFile(dest, srcData, 0644)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
// Race: add and publish both simultaneously
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repos[0], uploadID1), nil)
|
||||||
|
updateBody, _ := json.Marshal(gin.H{"Signing": gin.H{"Skip": true}, "ForceOverwrite": true, "SkipBz2": true})
|
||||||
|
s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/identical/%s", dists[0]), updateBody)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repos[1], uploadID2), nil)
|
||||||
|
updateBody, _ := json.Marshal(gin.H{"Signing": gin.H{"Skip": true}, "ForceOverwrite": true, "SkipBz2": true})
|
||||||
|
s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/identical/%s", dists[1]), updateBody)
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
c.Logf("[iter %d] All operations complete", iter)
|
||||||
|
|
||||||
|
// Check the shared pool location
|
||||||
|
publishedStorage := s.context.GetPublishedStorage("")
|
||||||
|
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
|
||||||
|
|
||||||
|
poolSubdir := string(packageName[0])
|
||||||
|
sharedPoolPath := filepath.Join(publicPath, "identical", "pool", "main", poolSubdir, packageName,
|
||||||
|
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||||
|
|
||||||
|
fileInfo, err := os.Stat(sharedPoolPath)
|
||||||
|
fileExists := err == nil
|
||||||
|
|
||||||
|
if fileExists {
|
||||||
|
c.Logf("[iter %d] File EXISTS at %s (size: %d)", iter, sharedPoolPath, fileInfo.Size())
|
||||||
|
} else {
|
||||||
|
c.Logf("[iter %d] File MISSING at %s (error: %v)", iter, sharedPoolPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check metadata
|
||||||
|
var packagesA, packagesB []string
|
||||||
|
resp := s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repos[0]), nil)
|
||||||
|
err = json.Unmarshal(resp.Body.Bytes(), &packagesA)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
resp = s.httpRequest(c, "GET", fmt.Sprintf("/api/repos/%s/packages", repos[1]), nil)
|
||||||
|
err = json.Unmarshal(resp.Body.Bytes(), &packagesB)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
c.Logf("[iter %d] Packages in metadata: A=%d, B=%d", iter, len(packagesA), len(packagesB))
|
||||||
|
|
||||||
|
// THE BUG: Both repos show packages in metadata, but the shared pool file is missing
|
||||||
|
if (len(packagesA) > 0 || len(packagesB) > 0) && !fileExists {
|
||||||
|
c.Logf("★★★ BUG REPRODUCED in iteration %d! ★★★", iter+1)
|
||||||
|
c.Logf("Packages in metadata A: %d, B: %d", len(packagesA), len(packagesB))
|
||||||
|
c.Logf("Shared pool file exists: %v", fileExists)
|
||||||
|
c.Logf("Pool path: %s", sharedPoolPath)
|
||||||
|
|
||||||
|
// List what files ARE in the pool directory
|
||||||
|
poolDir := filepath.Dir(sharedPoolPath)
|
||||||
|
if entries, err := os.ReadDir(poolDir); err == nil {
|
||||||
|
c.Logf("Files in pool directory %s:", poolDir)
|
||||||
|
for _, entry := range entries {
|
||||||
|
c.Logf(" - %s", entry.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Fatalf("Metadata shows packages but shared pool file is missing (iteration %d)", iter+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Logf("All %d iterations passed - bug not reproduced", numIterations)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConcurrentSnapshotPublishToSamePrefix reproduces the EXACT production bug:
|
||||||
|
// Multiple snapshots are published concurrently to the SAME prefix but different distributions.
|
||||||
|
// Example from production logs:
|
||||||
|
// - trixie-pgdg published to "external/postgres-auto/trixie"
|
||||||
|
// - bullseye-pgdg published to "external/postgres-auto/bullseye"
|
||||||
|
// Both share the same pool directory, causing cleanup race conditions.
|
||||||
|
func (s *PublishedFileMissingSuite) TestConcurrentSnapshotPublishToSamePrefix(c *C) {
|
||||||
|
const numIterations = 4
|
||||||
|
|
||||||
|
for iter := 0; iter < numIterations; iter++ {
|
||||||
|
c.Logf("--- Iteration %d/%d ---", iter+1, numIterations)
|
||||||
|
|
||||||
|
// Create two repos with different packages (simulating trixie-pgdg and bullseye-pgdg)
|
||||||
|
repoTrixie := fmt.Sprintf("trixie-pgdg-%d", iter)
|
||||||
|
repoBullseye := fmt.Sprintf("bullseye-pgdg-%d", iter)
|
||||||
|
|
||||||
|
// Create trixie repo
|
||||||
|
createBody, _ := json.Marshal(gin.H{
|
||||||
|
"Name": repoTrixie,
|
||||||
|
"DefaultDistribution": "trixie",
|
||||||
|
"DefaultComponent": "main",
|
||||||
|
})
|
||||||
|
resp := s.httpRequest(c, "POST", "/api/repos", createBody)
|
||||||
|
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create trixie repo"))
|
||||||
|
|
||||||
|
// Create bullseye repo
|
||||||
|
createBody, _ = json.Marshal(gin.H{
|
||||||
|
"Name": repoBullseye,
|
||||||
|
"DefaultDistribution": "bullseye",
|
||||||
|
"DefaultComponent": "main",
|
||||||
|
})
|
||||||
|
resp = s.httpRequest(c, "POST", "/api/repos", createBody)
|
||||||
|
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create bullseye repo"))
|
||||||
|
|
||||||
|
// Add packages to both repos
|
||||||
|
numPackages := 3
|
||||||
|
|
||||||
|
// Add packages to trixie repo
|
||||||
|
for i := 0; i < numPackages; i++ {
|
||||||
|
packageName := fmt.Sprintf("postgresql-17-trixie-pkg%d", i)
|
||||||
|
version := fmt.Sprintf("17.0.%d", iter)
|
||||||
|
uploadID := fmt.Sprintf("trixie-upload-%d-%d", iter, i)
|
||||||
|
|
||||||
|
s.createDebPackage(c, uploadID, packageName, version)
|
||||||
|
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoTrixie, uploadID), nil)
|
||||||
|
c.Assert(resp.Code, Equals, 200, Commentf("Failed to add package to trixie"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add packages to bullseye repo
|
||||||
|
for i := 0; i < numPackages; i++ {
|
||||||
|
packageName := fmt.Sprintf("postgresql-17-bullseye-pkg%d", i)
|
||||||
|
version := fmt.Sprintf("17.0.%d", iter)
|
||||||
|
uploadID := fmt.Sprintf("bullseye-upload-%d-%d", iter, i)
|
||||||
|
|
||||||
|
s.createDebPackage(c, uploadID, packageName, version)
|
||||||
|
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/file/%s?noRemove=0", repoBullseye, uploadID), nil)
|
||||||
|
c.Assert(resp.Code, Equals, 200, Commentf("Failed to add package to bullseye"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create snapshots from both repos
|
||||||
|
snapshotTrixie := fmt.Sprintf("%s-snap", repoTrixie)
|
||||||
|
snapshotBullseye := fmt.Sprintf("%s-snap", repoBullseye)
|
||||||
|
|
||||||
|
createSnapshotBody, _ := json.Marshal(gin.H{"Name": snapshotTrixie})
|
||||||
|
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/snapshots", repoTrixie), createSnapshotBody)
|
||||||
|
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create trixie snapshot"))
|
||||||
|
|
||||||
|
createSnapshotBody, _ = json.Marshal(gin.H{"Name": snapshotBullseye})
|
||||||
|
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/repos/%s/snapshots", repoBullseye), createSnapshotBody)
|
||||||
|
c.Assert(resp.Code, Equals, 201, Commentf("Failed to create bullseye snapshot"))
|
||||||
|
|
||||||
|
// Publish both snapshots CONCURRENTLY to the SAME prefix
|
||||||
|
// This mimics production where both are published to "external/postgres-auto"
|
||||||
|
// Use the SAME prefix across all iterations to trigger the race more aggressively
|
||||||
|
sharedPrefix := "postgres-auto"
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var trixiePublishCode, bullseyePublishCode int
|
||||||
|
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
// Publish or update trixie snapshot
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
var resp *httptest.ResponseRecorder
|
||||||
|
if iter == 0 {
|
||||||
|
// First iteration: CREATE
|
||||||
|
publishBody, _ := json.Marshal(gin.H{
|
||||||
|
"SourceKind": "snapshot",
|
||||||
|
"Distribution": "trixie",
|
||||||
|
"Architectures": []string{"amd64"},
|
||||||
|
"Sources": []gin.H{
|
||||||
|
{"Name": snapshotTrixie},
|
||||||
|
},
|
||||||
|
"Signing": gin.H{"Skip": true},
|
||||||
|
"SkipBz2": true,
|
||||||
|
"ForceOverwrite": true,
|
||||||
|
"SkipCleanup": false, // Force cleanup to run
|
||||||
|
})
|
||||||
|
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/publish/%s", sharedPrefix), publishBody)
|
||||||
|
} else {
|
||||||
|
// Subsequent iterations: UPDATE (this is what happens in production)
|
||||||
|
updateBody, _ := json.Marshal(gin.H{
|
||||||
|
"Snapshots": []gin.H{
|
||||||
|
{"Component": "main", "Name": snapshotTrixie},
|
||||||
|
},
|
||||||
|
"Signing": gin.H{"Skip": true},
|
||||||
|
"SkipBz2": true,
|
||||||
|
"ForceOverwrite": true,
|
||||||
|
"SkipCleanup": false,
|
||||||
|
})
|
||||||
|
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/%s/trixie", sharedPrefix), updateBody)
|
||||||
|
}
|
||||||
|
trixiePublishCode = resp.Code
|
||||||
|
c.Logf("[iter %d] Trixie publish/update completed: %d", iter, resp.Code)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Publish or update bullseye snapshot
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
var resp *httptest.ResponseRecorder
|
||||||
|
if iter == 0 {
|
||||||
|
// First iteration: CREATE
|
||||||
|
publishBody, _ := json.Marshal(gin.H{
|
||||||
|
"SourceKind": "snapshot",
|
||||||
|
"Distribution": "bullseye",
|
||||||
|
"Architectures": []string{"amd64"},
|
||||||
|
"Sources": []gin.H{
|
||||||
|
{"Name": snapshotBullseye},
|
||||||
|
},
|
||||||
|
"Signing": gin.H{"Skip": true},
|
||||||
|
"SkipBz2": true,
|
||||||
|
"ForceOverwrite": true,
|
||||||
|
"SkipCleanup": false,
|
||||||
|
})
|
||||||
|
resp = s.httpRequest(c, "POST", fmt.Sprintf("/api/publish/%s", sharedPrefix), publishBody)
|
||||||
|
} else {
|
||||||
|
// Subsequent iterations: UPDATE
|
||||||
|
updateBody, _ := json.Marshal(gin.H{
|
||||||
|
"Snapshots": []gin.H{
|
||||||
|
{"Component": "main", "Name": snapshotBullseye},
|
||||||
|
},
|
||||||
|
"Signing": gin.H{"Skip": true},
|
||||||
|
"SkipBz2": true,
|
||||||
|
"ForceOverwrite": true,
|
||||||
|
"SkipCleanup": false,
|
||||||
|
})
|
||||||
|
resp = s.httpRequest(c, "PUT", fmt.Sprintf("/api/publish/%s/bullseye", sharedPrefix), updateBody)
|
||||||
|
}
|
||||||
|
bullseyePublishCode = resp.Code
|
||||||
|
c.Logf("[iter %d] Bullseye publish/update completed: %d", iter, resp.Code)
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
// Verify publishes succeeded (201 for create, 200 for update)
|
||||||
|
expectedCode := 201
|
||||||
|
if iter > 0 {
|
||||||
|
expectedCode = 200
|
||||||
|
}
|
||||||
|
c.Assert(trixiePublishCode, Equals, expectedCode, Commentf("Trixie publish/update should succeed"))
|
||||||
|
c.Assert(bullseyePublishCode, Equals, expectedCode, Commentf("Bullseye publish/update should succeed"))
|
||||||
|
|
||||||
|
// Verify ALL package files exist in the published pool
|
||||||
|
publishedStorage := s.context.GetPublishedStorage("")
|
||||||
|
publicPath := publishedStorage.(aptly.FileSystemPublishedStorage).PublicPath()
|
||||||
|
|
||||||
|
missingFiles := []string{}
|
||||||
|
expectedFiles := []string{}
|
||||||
|
|
||||||
|
// Check trixie packages
|
||||||
|
for i := 0; i < numPackages; i++ {
|
||||||
|
packageName := fmt.Sprintf("postgresql-17-trixie-pkg%d", i)
|
||||||
|
version := fmt.Sprintf("17.0.%d", iter)
|
||||||
|
|
||||||
|
poolSubdir := string(packageName[0])
|
||||||
|
expectedPath := filepath.Join(publicPath, sharedPrefix, "pool", "main", poolSubdir, packageName,
|
||||||
|
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||||
|
|
||||||
|
expectedFiles = append(expectedFiles, expectedPath)
|
||||||
|
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
|
||||||
|
missingFiles = append(missingFiles, fmt.Sprintf("TRIXIE: %s", filepath.Base(expectedPath)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bullseye packages
|
||||||
|
for i := 0; i < numPackages; i++ {
|
||||||
|
packageName := fmt.Sprintf("postgresql-17-bullseye-pkg%d", i)
|
||||||
|
version := fmt.Sprintf("17.0.%d", iter)
|
||||||
|
|
||||||
|
poolSubdir := string(packageName[0])
|
||||||
|
expectedPath := filepath.Join(publicPath, sharedPrefix, "pool", "main", poolSubdir, packageName,
|
||||||
|
fmt.Sprintf("%s_%s_amd64.deb", packageName, version))
|
||||||
|
|
||||||
|
expectedFiles = append(expectedFiles, expectedPath)
|
||||||
|
if _, err := os.Stat(expectedPath); os.IsNotExist(err) {
|
||||||
|
missingFiles = append(missingFiles, fmt.Sprintf("BULLSEYE: %s", filepath.Base(expectedPath)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUG: Files from one distribution are deleted by the other's cleanup
|
||||||
|
if len(missingFiles) > 0 {
|
||||||
|
c.Logf("★★★ BUG REPRODUCED in iteration %d/%d! ★★★", iter+1, numIterations)
|
||||||
|
c.Logf("Both publishes to prefix '%s' succeeded, but %d files are MISSING:", sharedPrefix, len(missingFiles))
|
||||||
|
for i, f := range missingFiles {
|
||||||
|
c.Logf(" Missing file %d/%d: %s", i+1, len(missingFiles), f)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Logf("\nThis reproduces the exact production bug where:")
|
||||||
|
c.Logf(" 1. Mirror updates complete successfully")
|
||||||
|
c.Logf(" 2. Snapshots are created")
|
||||||
|
c.Logf(" 3. Both snapshots publish to same prefix (different distributions)")
|
||||||
|
c.Logf(" 4. Cleanup from one publish DELETES files from the other")
|
||||||
|
c.Logf(" 5. Result: apt-get returns 404 when downloading packages")
|
||||||
|
|
||||||
|
// List what's actually in the pool
|
||||||
|
poolDir := filepath.Join(publicPath, sharedPrefix, "pool", "main")
|
||||||
|
if entries, err := os.ReadDir(poolDir); err == nil {
|
||||||
|
c.Logf("\nActual pool directory contents (%s):", poolDir)
|
||||||
|
for _, entry := range entries {
|
||||||
|
c.Logf(" - %s/", entry.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Fatalf("BUG CONFIRMED (iteration %d/%d): %d files missing from shared pool",
|
||||||
|
iter+1, numIterations, len(missingFiles))
|
||||||
|
} else {
|
||||||
|
c.Logf("[iter %d/%d] All %d files present - OK", iter+1, numIterations, len(expectedFiles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Logf("✓ All %d iterations passed - no files missing", numIterations)
|
||||||
|
}
|
||||||
+168
-80
@@ -69,26 +69,17 @@ func reposServeInAPIMode(c *gin.Context) {
|
|||||||
// @Description Each repo is returned as in “show” API.
|
// @Description Each repo is returned as in “show” API.
|
||||||
// @Tags Repos
|
// @Tags Repos
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {array} localRepoResponse
|
// @Success 200 {array} deb.LocalRepo
|
||||||
// @Router /api/repos [get]
|
// @Router /api/repos [get]
|
||||||
func apiReposList(c *gin.Context) {
|
func apiReposList(c *gin.Context) {
|
||||||
result := []localRepoResponse{}
|
result := []*deb.LocalRepo{}
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.LocalRepoCollection()
|
collection := collectionFactory.LocalRepoCollection()
|
||||||
err := collection.ForEach(func(r *deb.LocalRepo) error {
|
_ = collection.ForEach(func(r *deb.LocalRepo) error {
|
||||||
err := collection.LoadComplete(r)
|
result = append(result, r)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, newLocalRepoResponse(r))
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, result)
|
c.JSON(200, result)
|
||||||
}
|
}
|
||||||
@@ -131,46 +122,62 @@ func apiReposCreate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repo := deb.NewLocalRepo(b.Name, b.Comment)
|
// Handler: Pre-task validations (shallow)
|
||||||
repo.DefaultComponent = b.DefaultComponent
|
|
||||||
repo.DefaultDistribution = b.DefaultDistribution
|
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
|
|
||||||
|
var resources []string
|
||||||
if b.FromSnapshot != "" {
|
if b.FromSnapshot != "" {
|
||||||
var snapshot *deb.Snapshot
|
snapshot, err := collectionFactory.SnapshotCollection().ByName(b.FromSnapshot)
|
||||||
|
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
|
||||||
|
|
||||||
snapshot, err := snapshotCollection.ByName(b.FromSnapshot)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("source snapshot not found: %s", err))
|
AbortWithJSONError(c, http.StatusNotFound, fmt.Errorf("source snapshot not found: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
resources = append(resources, string(snapshot.Key()))
|
||||||
|
}
|
||||||
|
|
||||||
err = snapshotCollection.LoadComplete(snapshot)
|
taskName := fmt.Sprintf("Create repository %s", b.Name)
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, http.StatusInternalServerError, fmt.Errorf("unable to load source snapshot: %s", err))
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
return
|
// Task: Create fresh collection and check/create ATOMIC inside task
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
|
// Check duplicate inside lock
|
||||||
|
if _, err := taskCollection.ByName(b.Name); err == nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil},
|
||||||
|
fmt.Errorf("local repo with name %s already exists", b.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.UpdateRefList(snapshot.RefList())
|
// Create repo
|
||||||
}
|
repo := deb.NewLocalRepo(b.Name, b.Comment)
|
||||||
|
repo.DefaultComponent = b.DefaultComponent
|
||||||
|
repo.DefaultDistribution = b.DefaultDistribution
|
||||||
|
|
||||||
localRepoCollection := collectionFactory.LocalRepoCollection()
|
if b.FromSnapshot != "" {
|
||||||
|
snapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
|
|
||||||
if _, err := localRepoCollection.ByName(b.Name); err == nil {
|
snapshot, err := snapshotCollection.ByName(b.FromSnapshot)
|
||||||
AbortWithJSONError(c, http.StatusConflict, fmt.Errorf("local repo with name %s already exists", b.Name))
|
if err != nil {
|
||||||
return
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil},
|
||||||
}
|
fmt.Errorf("source snapshot not found: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
err := localRepoCollection.Add(repo)
|
err = snapshotCollection.LoadComplete(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, http.StatusInternalServerError, err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil},
|
||||||
return
|
fmt.Errorf("unable to load source snapshot: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, repo)
|
repo.UpdateRefList(snapshot.RefList())
|
||||||
|
}
|
||||||
|
|
||||||
|
err := taskCollection.Add(repo)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusCreated, Value: repo}, nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type reposEditParams struct {
|
type reposEditParams struct {
|
||||||
@@ -201,6 +208,8 @@ func apiReposEdit(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load shallowly for 404 check and resource key.
|
||||||
|
// Mutation and duplicate check happen inside the task for atomicity.
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.LocalRepoCollection()
|
collection := collectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
@@ -212,31 +221,53 @@ func apiReposEdit(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if b.Name != nil && *b.Name != name {
|
if b.Name != nil && *b.Name != name {
|
||||||
_, err := collection.ByName(*b.Name)
|
if _, err = collection.ByName(*b.Name); err == nil {
|
||||||
if err == nil {
|
AbortWithJSONError(c, 409, fmt.Errorf("unable to rename: local repo %q already exists", *b.Name))
|
||||||
// already exists
|
|
||||||
AbortWithJSONError(c, 404, fmt.Errorf("local repo with name %q already exists", *b.Name))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
repo.Name = *b.Name
|
|
||||||
}
|
|
||||||
if b.Comment != nil {
|
|
||||||
repo.Comment = *b.Comment
|
|
||||||
}
|
|
||||||
if b.DefaultDistribution != nil {
|
|
||||||
repo.DefaultDistribution = *b.DefaultDistribution
|
|
||||||
}
|
|
||||||
if b.DefaultComponent != nil {
|
|
||||||
repo.DefaultComponent = *b.DefaultComponent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.Update(repo)
|
resources := []string{string(repo.Key())}
|
||||||
if err != nil {
|
taskName := fmt.Sprintf("Edit repository %s", name)
|
||||||
AbortWithJSONError(c, 500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, repo)
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
// Task: Create fresh collection inside task after lock
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
|
// Fresh load after lock acquired
|
||||||
|
repo, err := taskCollection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and update ATOMIC (inside lock)
|
||||||
|
if b.Name != nil && *b.Name != name {
|
||||||
|
_, err := taskCollection.ByName(*b.Name)
|
||||||
|
if err == nil {
|
||||||
|
// already exists
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil},
|
||||||
|
fmt.Errorf("local repo with name %q already exists", *b.Name)
|
||||||
|
}
|
||||||
|
repo.Name = *b.Name
|
||||||
|
}
|
||||||
|
if b.Comment != nil {
|
||||||
|
repo.Comment = *b.Comment
|
||||||
|
}
|
||||||
|
if b.DefaultDistribution != nil {
|
||||||
|
repo.DefaultDistribution = *b.DefaultDistribution
|
||||||
|
}
|
||||||
|
if b.DefaultComponent != nil {
|
||||||
|
repo.DefaultComponent = *b.DefaultComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.Update(repo)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: repo}, nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/repos/:name
|
// GET /api/repos/:name
|
||||||
@@ -278,10 +309,10 @@ func apiReposDrop(c *gin.Context) {
|
|||||||
force := c.Request.URL.Query().Get("force") == "1"
|
force := c.Request.URL.Query().Get("force") == "1"
|
||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
|
|
||||||
|
// Load shallowly for 404 check, resource key, and task name.
|
||||||
|
// Full checks (published/snapshots) happen inside the task.
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.LocalRepoCollection()
|
collection := collectionFactory.LocalRepoCollection()
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
|
||||||
publishedCollection := collectionFactory.PublishedRepoCollection()
|
|
||||||
|
|
||||||
repo, err := collection.ByName(name)
|
repo, err := collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -292,19 +323,32 @@ func apiReposDrop(c *gin.Context) {
|
|||||||
resources := []string{string(repo.Key())}
|
resources := []string{string(repo.Key())}
|
||||||
taskName := fmt.Sprintf("Delete repo %s", name)
|
taskName := fmt.Sprintf("Delete repo %s", name)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
published := publishedCollection.ByLocalRepo(repo)
|
// Task: Create fresh collections inside task after lock acquired
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.LocalRepoCollection()
|
||||||
|
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
|
taskPublishedCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Re-read repo with fresh collection after lock
|
||||||
|
repo, err := taskCollection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check with fresh collections
|
||||||
|
published := taskPublishedCollection.ByLocalRepo(repo)
|
||||||
if len(published) > 0 {
|
if len(published) > 0 {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo is published")
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo is published")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !force {
|
if !force {
|
||||||
snapshots := snapshotCollection.ByLocalRepoSource(repo)
|
snapshots := taskSnapshotCollection.ByLocalRepoSource(repo)
|
||||||
if len(snapshots) > 0 {
|
if len(snapshots) > 0 {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo has snapshots, use ?force=1 to override")
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop, local repo has snapshots, use ?force=1 to override")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, collection.Drop(repo)
|
return &task.ProcessReturnValue{Code: http.StatusOK, Value: gin.H{}}, taskCollection.Drop(repo)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,10 +405,13 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load shallowly for 404 check and resource key.
|
||||||
|
// Full load and mutations happen inside the task.
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.LocalRepoCollection()
|
collection := collectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
repo, err := collection.ByName(c.Params.ByName("name"))
|
name := c.Params.ByName("name")
|
||||||
|
repo, err := collection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, 404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
@@ -373,13 +420,23 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
|||||||
resources := []string{string(repo.Key())}
|
resources := []string{string(repo.Key())}
|
||||||
|
|
||||||
maybeRunTaskInBackground(c, taskNamePrefix+repo.Name, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskNamePrefix+repo.Name, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.LoadComplete(repo)
|
// Task: Create fresh factory and collection inside task after lock
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
|
// Fresh load after lock acquired (use captured `name` variable, not gin context)
|
||||||
|
repo, err := taskCollection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Printf("Loading packages...\n")
|
out.Printf("Loading packages...\n")
|
||||||
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil)
|
list, err := deb.NewPackageListFromRefList(repo.RefList(), taskCollectionFactory.PackageCollection(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
@@ -388,7 +445,7 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
|||||||
for _, ref := range b.PackageRefs {
|
for _, ref := range b.PackageRefs {
|
||||||
var p *deb.Package
|
var p *deb.Package
|
||||||
|
|
||||||
p, err = collectionFactory.PackageCollection().ByKey([]byte(ref))
|
p, err = taskCollectionFactory.PackageCollection().ByKey([]byte(ref))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == database.ErrNotFound {
|
if err == database.ErrNotFound {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("packages %s: %s", ref, err)
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("packages %s: %s", ref, err)
|
||||||
@@ -404,7 +461,7 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
|||||||
|
|
||||||
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
||||||
|
|
||||||
err = collectionFactory.LocalRepoCollection().Update(repo)
|
err = taskCollection.Update(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
|
||||||
}
|
}
|
||||||
@@ -511,6 +568,8 @@ func apiReposPackageFromDir(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load shallowly for 404 check and resource key.
|
||||||
|
// Full load and mutations happen inside the task.
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.LocalRepoCollection()
|
collection := collectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
@@ -534,7 +593,17 @@ func apiReposPackageFromDir(c *gin.Context) {
|
|||||||
resources := []string{string(repo.Key())}
|
resources := []string{string(repo.Key())}
|
||||||
resources = append(resources, sources...)
|
resources = append(resources, sources...)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collection.LoadComplete(repo)
|
// Task: Create fresh factory and collection inside task after lock
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.LocalRepoCollection()
|
||||||
|
|
||||||
|
// Fresh load after lock acquired
|
||||||
|
repo, err := taskCollection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
@@ -555,13 +624,13 @@ func apiReposPackageFromDir(c *gin.Context) {
|
|||||||
|
|
||||||
packageFiles, otherFiles, failedFiles = deb.CollectPackageFiles(sources, reporter)
|
packageFiles, otherFiles, failedFiles = deb.CollectPackageFiles(sources, reporter)
|
||||||
|
|
||||||
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil)
|
list, err = deb.NewPackageListFromRefList(repo.RefList(), taskCollectionFactory.PackageCollection(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(),
|
processedFiles, failedFiles2, err = deb.ImportPackageFiles(list, packageFiles, forceReplace, verifier, context.PackagePool(),
|
||||||
collectionFactory.PackageCollection(), reporter, nil, collectionFactory.ChecksumCollection)
|
taskCollectionFactory.PackageCollection(), reporter, nil, taskCollectionFactory.ChecksumCollection)
|
||||||
failedFiles = append(failedFiles, failedFiles2...)
|
failedFiles = append(failedFiles, failedFiles2...)
|
||||||
processedFiles = append(processedFiles, otherFiles...)
|
processedFiles = append(processedFiles, otherFiles...)
|
||||||
|
|
||||||
@@ -571,7 +640,7 @@ func apiReposPackageFromDir(c *gin.Context) {
|
|||||||
|
|
||||||
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
repo.UpdateRefList(deb.NewPackageRefListFromPackageList(list))
|
||||||
|
|
||||||
err = collectionFactory.LocalRepoCollection().Update(repo)
|
err = taskCollection.Update(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
|
||||||
}
|
}
|
||||||
@@ -650,6 +719,8 @@ func apiReposCopyPackage(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load shallowly for 404 check and resource keys.
|
||||||
|
// Full load and mutations happen inside the task.
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
dstRepo, err := collectionFactory.LocalRepoCollection().ByName(dstRepoName)
|
dstRepo, err := collectionFactory.LocalRepoCollection().ByName(dstRepoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -673,12 +744,26 @@ func apiReposCopyPackage(c *gin.Context) {
|
|||||||
resources := []string{string(dstRepo.Key()), string(srcRepo.Key())}
|
resources := []string{string(dstRepo.Key()), string(srcRepo.Key())}
|
||||||
|
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collectionFactory.LocalRepoCollection().LoadComplete(dstRepo)
|
// Task: Create fresh factory and collections inside task after lock
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
|
||||||
|
// Fresh load of both repos after lock acquired
|
||||||
|
dstRepo, err := taskCollectionFactory.LocalRepoCollection().ByName(dstRepoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("dest repo error: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("dest repo error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collectionFactory.LocalRepoCollection().LoadComplete(srcRepo)
|
srcRepo, err := taskCollectionFactory.LocalRepoCollection().ByName(srcRepoName)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("src repo error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollectionFactory.LocalRepoCollection().LoadComplete(dstRepo)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("dest repo error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskCollectionFactory.LocalRepoCollection().LoadComplete(srcRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("src repo error: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, fmt.Errorf("src repo error: %s", err)
|
||||||
}
|
}
|
||||||
@@ -691,12 +776,12 @@ func apiReposCopyPackage(c *gin.Context) {
|
|||||||
RemovedLines: []string{},
|
RemovedLines: []string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), collectionFactory.PackageCollection(), context.Progress())
|
dstList, err := deb.NewPackageListFromRefList(dstRepo.RefList(), taskCollectionFactory.PackageCollection(), context.Progress())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in dest: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in dest: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srcList, err := deb.NewPackageListFromRefList(srcRefList, collectionFactory.PackageCollection(), context.Progress())
|
srcList, err := deb.NewPackageListFromRefList(srcRefList, taskCollectionFactory.PackageCollection(), context.Progress())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in src: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to load packages in src: %s", err)
|
||||||
}
|
}
|
||||||
@@ -764,7 +849,7 @@ func apiReposCopyPackage(c *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList))
|
dstRepo.UpdateRefList(deb.NewPackageRefListFromPackageList(dstList))
|
||||||
|
|
||||||
err = collectionFactory.LocalRepoCollection().Update(dstRepo)
|
err = taskCollectionFactory.LocalRepoCollection().Update(dstRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to save: %s", err)
|
||||||
}
|
}
|
||||||
@@ -867,6 +952,9 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
|||||||
resources = append(resources, sources...)
|
resources = append(resources, sources...)
|
||||||
|
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(out aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
// Task: Create fresh factory and collection inside task after lock
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
verifier = context.GetVerifier()
|
verifier = context.GetVerifier()
|
||||||
@@ -882,8 +970,8 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
|||||||
changesFiles, failedFiles = deb.CollectChangesFiles(sources, reporter)
|
changesFiles, failedFiles = deb.CollectChangesFiles(sources, reporter)
|
||||||
_, failedFiles2, err = deb.ImportChangesFiles(
|
_, failedFiles2, err = deb.ImportChangesFiles(
|
||||||
changesFiles, reporter, acceptUnsigned, ignoreSignature, forceReplace, noRemoveFiles, verifier,
|
changesFiles, reporter, acceptUnsigned, ignoreSignature, forceReplace, noRemoveFiles, verifier,
|
||||||
repoTemplate, context.Progress(), collectionFactory.LocalRepoCollection(), collectionFactory.PackageCollection(),
|
repoTemplate, context.Progress(), taskCollectionFactory.LocalRepoCollection(), taskCollectionFactory.PackageCollection(),
|
||||||
context.PackagePool(), collectionFactory.ChecksumCollection, nil, query.Parse)
|
context.PackagePool(), taskCollectionFactory.ChecksumCollection, nil, query.Parse)
|
||||||
failedFiles = append(failedFiles, failedFiles2...)
|
failedFiles = append(failedFiles, failedFiles2...)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/deb"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
. "gopkg.in/check.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ReposSuite struct {
|
|
||||||
APISuite
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Suite(&ReposSuite{})
|
|
||||||
|
|
||||||
func (s *ReposSuite) TestGetReposIncludesNumPackages(c *C) {
|
|
||||||
collection := s.context.NewCollectionFactory().LocalRepoCollection()
|
|
||||||
repo := deb.NewLocalRepo("count-repo-list", "")
|
|
||||||
repo.UpdateRefList(makePackageRefList(c))
|
|
||||||
c.Assert(collection.Add(repo), IsNil)
|
|
||||||
|
|
||||||
response, err := s.HTTPRequest("GET", "/api/repos", nil)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(response.Code, Equals, 200)
|
|
||||||
|
|
||||||
var repos []map[string]interface{}
|
|
||||||
err = json.Unmarshal(response.Body.Bytes(), &repos)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
found := false
|
|
||||||
for _, repo := range repos {
|
|
||||||
if repo["Name"] == "count-repo-list" {
|
|
||||||
found = true
|
|
||||||
value, ok := repo["NumPackages"]
|
|
||||||
c.Assert(ok, Equals, true)
|
|
||||||
c.Assert(value, Equals, float64(2))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, Equals, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ReposSuite) TestGetReposReturns500OnCorruptRefList(c *C) {
|
|
||||||
body, err := json.Marshal(gin.H{"Name": "broken-repo-list"})
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
response, err := s.HTTPRequest("POST", "/api/repos", bytes.NewReader(body))
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(response.Code, Equals, 201)
|
|
||||||
|
|
||||||
collection := s.context.NewCollectionFactory().LocalRepoCollection()
|
|
||||||
repo, err := collection.ByName("broken-repo-list")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
putRawDBValue(c, &s.APISuite, repo.RefKey(), []byte("not-msgpack"))
|
|
||||||
|
|
||||||
response, err = s.HTTPRequest("GET", "/api/repos", nil)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(response.Code, Equals, 500)
|
|
||||||
c.Assert(response.Body.String(), Matches, ".*msgpack.*|.*decode.*")
|
|
||||||
}
|
|
||||||
+26
-29
@@ -11,9 +11,9 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/docs"
|
// _ "github.com/aptly-dev/aptly/docs" // import docs
|
||||||
swaggerFiles "github.com/swaggo/files"
|
// swaggerFiles "github.com/swaggo/files"
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
// ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var context *ctx.AptlyContext
|
var context *ctx.AptlyContext
|
||||||
@@ -31,21 +31,21 @@ func apiMetricsGet() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func redirectSwagger(c *gin.Context) {
|
// func redirectSwagger(c *gin.Context) {
|
||||||
if c.Request.URL.Path == "/docs/index.html" {
|
// if c.Request.URL.Path == "/docs/index.html" {
|
||||||
c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
// c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
if c.Request.URL.Path == "/docs/" {
|
// if c.Request.URL.Path == "/docs/" {
|
||||||
c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
// c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
if c.Request.URL.Path == "/docs" {
|
// if c.Request.URL.Path == "/docs" {
|
||||||
c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
// c.Redirect(http.StatusMovedPermanently, "/docs.html")
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
c.Next()
|
// c.Next()
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Router returns prebuilt with routes http.Handler
|
// Router returns prebuilt with routes http.Handler
|
||||||
func Router(c *ctx.AptlyContext) http.Handler {
|
func Router(c *ctx.AptlyContext) http.Handler {
|
||||||
@@ -69,14 +69,14 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
|
|
||||||
router.Use(gin.Recovery(), gin.ErrorLogger())
|
router.Use(gin.Recovery(), gin.ErrorLogger())
|
||||||
|
|
||||||
if c.Config().EnableSwaggerEndpoint {
|
// if c.Config().EnableSwaggerEndpoint {
|
||||||
router.GET("docs.html", func(c *gin.Context) {
|
// router.GET("docs.html", func(c *gin.Context) {
|
||||||
c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
|
// c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
|
||||||
})
|
// })
|
||||||
router.Use(redirectSwagger)
|
// router.Use(redirectSwagger)
|
||||||
url := ginSwagger.URL("/docs/doc.json")
|
// url := ginSwagger.URL("/docs/doc.json")
|
||||||
router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
|
// router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
|
||||||
}
|
// }
|
||||||
|
|
||||||
if c.Config().EnableMetricsEndpoint {
|
if c.Config().EnableMetricsEndpoint {
|
||||||
MetricsCollectorRegistrar.Register(router)
|
MetricsCollectorRegistrar.Register(router)
|
||||||
@@ -164,15 +164,12 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
api.GET("/mirrors/:name", apiMirrorsShow)
|
api.GET("/mirrors/:name", apiMirrorsShow)
|
||||||
api.GET("/mirrors/:name/packages", apiMirrorsPackages)
|
api.GET("/mirrors/:name/packages", apiMirrorsPackages)
|
||||||
api.POST("/mirrors", apiMirrorsCreate)
|
api.POST("/mirrors", apiMirrorsCreate)
|
||||||
api.POST("/mirrors/:name", apiMirrorsEdit)
|
|
||||||
api.PUT("/mirrors/:name", apiMirrorsUpdate)
|
api.PUT("/mirrors/:name", apiMirrorsUpdate)
|
||||||
api.DELETE("/mirrors/:name", apiMirrorsDrop)
|
api.DELETE("/mirrors/:name", apiMirrorsDrop)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
api.GET("/gpg/keys", apiGPGListKeys)
|
|
||||||
api.POST("/gpg/key", apiGPGAddKey)
|
api.POST("/gpg/key", apiGPGAddKey)
|
||||||
api.DELETE("/gpg/key", apiGPGDeleteKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
+165
-75
@@ -20,7 +20,7 @@ import (
|
|||||||
// @Description Each snapshot is returned as in “show” API.
|
// @Description Each snapshot is returned as in “show” API.
|
||||||
// @Tags Snapshots
|
// @Tags Snapshots
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {array} snapshotResponse
|
// @Success 200 {array} deb.Snapshot
|
||||||
// @Router /api/snapshots [get]
|
// @Router /api/snapshots [get]
|
||||||
func apiSnapshotsList(c *gin.Context) {
|
func apiSnapshotsList(c *gin.Context) {
|
||||||
SortMethodString := c.Request.URL.Query().Get("sort")
|
SortMethodString := c.Request.URL.Query().Get("sort")
|
||||||
@@ -32,20 +32,11 @@ func apiSnapshotsList(c *gin.Context) {
|
|||||||
SortMethodString = "name"
|
SortMethodString = "name"
|
||||||
}
|
}
|
||||||
|
|
||||||
result := []snapshotResponse{}
|
result := []*deb.Snapshot{}
|
||||||
err := collection.ForEachSorted(SortMethodString, func(snapshot *deb.Snapshot) error {
|
_ = collection.ForEachSorted(SortMethodString, func(snapshot *deb.Snapshot) error {
|
||||||
err := collection.LoadComplete(snapshot)
|
result = append(result, snapshot)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, newSnapshotResponse(snapshot))
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
AbortWithJSONError(c, 500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, result)
|
c.JSON(200, result)
|
||||||
}
|
}
|
||||||
@@ -83,26 +74,33 @@ func apiSnapshotsCreateFromMirror(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.RemoteRepoCollection()
|
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
|
||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
|
|
||||||
repo, err = collection.ByName(name)
|
repo, err = collectionFactory.RemoteRepoCollection().ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, 404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// including snapshot resource key
|
// including snapshot resource key
|
||||||
resources := []string{string(repo.Key()), "S" + b.Name}
|
resources := []string{string(repo.Key())}
|
||||||
taskName := fmt.Sprintf("Create snapshot of mirror %s", name)
|
taskName := fmt.Sprintf("Create snapshot of mirror %s", name)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err := repo.CheckLock()
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskMirrorCollection := taskCollectionFactory.RemoteRepoCollection()
|
||||||
|
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
|
|
||||||
|
repo, err := taskMirrorCollection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.CheckLock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collection.LoadComplete(repo)
|
err = taskMirrorCollection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
@@ -116,7 +114,7 @@ func apiSnapshotsCreateFromMirror(c *gin.Context) {
|
|||||||
snapshot.Description = b.Description
|
snapshot.Description = b.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
err = snapshotCollection.Add(snapshot)
|
err = taskSnapshotCollection.Add(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
||||||
}
|
}
|
||||||
@@ -165,6 +163,7 @@ func apiSnapshotsCreate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 1: Pre-task validation (shallow load for 404 checks only)
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
var resources []string
|
var resources []string
|
||||||
@@ -178,37 +177,62 @@ func apiSnapshotsCreate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources = append(resources, string(sources[i].ResourceKey()))
|
resources = append(resources, string(sources[i].Key()))
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeRunTaskInBackground(c, "Create snapshot "+b.Name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, "Create snapshot "+b.Name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
for i := range sources {
|
// Phase 2: Inside task lock - create fresh factory
|
||||||
err = snapshotCollection.LoadComplete(sources[i])
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
|
taskPackageCollection := taskCollectionFactory.PackageCollection()
|
||||||
|
|
||||||
|
// Fresh load of all sources after lock acquired
|
||||||
|
freshSources := make([]*deb.Snapshot, len(b.SourceSnapshots))
|
||||||
|
for i := range b.SourceSnapshots {
|
||||||
|
freshSources[i], err = taskSnapshotCollection.ByName(b.SourceSnapshots[i])
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
// LoadComplete on fresh copy
|
||||||
|
err = taskSnapshotCollection.LoadComplete(freshSources[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
list := deb.NewPackageList()
|
// Merge packages from all source snapshots
|
||||||
|
var refList *deb.PackageRefList
|
||||||
|
if len(freshSources) > 0 {
|
||||||
|
refList = freshSources[0].RefList()
|
||||||
|
for i := 1; i < len(freshSources); i++ {
|
||||||
|
refList = refList.Merge(freshSources[i].RefList(), true, false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
refList = deb.NewPackageRefList()
|
||||||
|
}
|
||||||
|
|
||||||
// verify package refs and build package list
|
// Add any explicitly specified package refs on top
|
||||||
for _, ref := range b.PackageRefs {
|
if len(b.PackageRefs) > 0 {
|
||||||
p, err := collectionFactory.PackageCollection().ByKey([]byte(ref))
|
list := deb.NewPackageList()
|
||||||
if err != nil {
|
for _, ref := range b.PackageRefs {
|
||||||
if err == database.ErrNotFound {
|
p, err := taskPackageCollection.ByKey([]byte(ref))
|
||||||
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("package %s: %s", ref, err)
|
if err != nil {
|
||||||
|
if err == database.ErrNotFound {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("package %s: %s", ref, err)
|
||||||
|
}
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
err = list.Add(p)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
||||||
}
|
}
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
|
||||||
}
|
|
||||||
err = list.Add(p)
|
|
||||||
if err != nil {
|
|
||||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
|
||||||
}
|
}
|
||||||
|
refList = refList.Merge(deb.NewPackageRefListFromPackageList(list), true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot = deb.NewSnapshotFromRefList(b.Name, sources, deb.NewPackageRefListFromPackageList(list), b.Description)
|
snapshot = deb.NewSnapshotFromRefList(b.Name, freshSources, refList, b.Description)
|
||||||
|
|
||||||
err = snapshotCollection.Add(snapshot)
|
err = taskSnapshotCollection.Add(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
||||||
}
|
}
|
||||||
@@ -249,21 +273,28 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.LocalRepoCollection()
|
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
|
||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
|
|
||||||
repo, err = collection.ByName(name)
|
repo, err = collectionFactory.LocalRepoCollection().ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
AbortWithJSONError(c, 404, err)
|
AbortWithJSONError(c, 404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// including snapshot resource key
|
// including snapshot resource key
|
||||||
resources := []string{string(repo.Key()), "S" + b.Name}
|
resources := []string{string(repo.Key())}
|
||||||
taskName := fmt.Sprintf("Create snapshot of repo %s", name)
|
taskName := fmt.Sprintf("Create snapshot of repo %s", name)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err := collection.LoadComplete(repo)
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskRepoCollection := taskCollectionFactory.LocalRepoCollection()
|
||||||
|
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
|
|
||||||
|
repo, err := taskRepoCollection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = taskRepoCollection.LoadComplete(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
@@ -277,7 +308,7 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) {
|
|||||||
snapshot.Description = b.Description
|
snapshot.Description = b.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
err = snapshotCollection.Add(snapshot)
|
err = taskSnapshotCollection.Add(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err
|
||||||
}
|
}
|
||||||
@@ -315,6 +346,7 @@ func apiSnapshotsUpdate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 1: Pre-task validation (shallow load for 404 check only)
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
collection := collectionFactory.SnapshotCollection()
|
collection := collectionFactory.SnapshotCollection()
|
||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
@@ -325,14 +357,38 @@ func apiSnapshotsUpdate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []string{string(snapshot.ResourceKey()), "S" + b.Name}
|
// Pre-task validation of new name if provided (skip if renaming to same name)
|
||||||
taskName := fmt.Sprintf("Update snapshot %s", name)
|
if b.Name != "" && b.Name != name {
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
_, err = collection.ByName(b.Name)
|
||||||
_, err := collection.ByName(b.Name)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)
|
AbortWithJSONError(c, 409, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resources := []string{string(snapshot.Key())}
|
||||||
|
taskName := fmt.Sprintf("Update snapshot %s", name)
|
||||||
|
|
||||||
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
|
// Phase 2: Inside task lock - create fresh factory
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
|
|
||||||
|
// Fresh load after lock acquired
|
||||||
|
snapshot, err = taskCollection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fresh duplicate check inside lock
|
||||||
|
if b.Name != "" {
|
||||||
|
_, err := taskCollection.ByName(b.Name)
|
||||||
|
if err == nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to rename: snapshot %s already exists", b.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update fresh copy
|
||||||
if b.Name != "" {
|
if b.Name != "" {
|
||||||
snapshot.Name = b.Name
|
snapshot.Name = b.Name
|
||||||
}
|
}
|
||||||
@@ -341,7 +397,7 @@ func apiSnapshotsUpdate(c *gin.Context) {
|
|||||||
snapshot.Description = b.Description
|
snapshot.Description = b.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
err = collectionFactory.SnapshotCollection().Update(snapshot)
|
err = taskCollection.Update(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
@@ -395,9 +451,9 @@ func apiSnapshotsDrop(c *gin.Context) {
|
|||||||
name := c.Params.ByName("name")
|
name := c.Params.ByName("name")
|
||||||
force := c.Request.URL.Query().Get("force") == "1"
|
force := c.Request.URL.Query().Get("force") == "1"
|
||||||
|
|
||||||
|
// Phase 1: Pre-task validation (shallow load for 404 check only)
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
publishedCollection := collectionFactory.PublishedRepoCollection()
|
|
||||||
|
|
||||||
snapshot, err := snapshotCollection.ByName(name)
|
snapshot, err := snapshotCollection.ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -405,23 +461,37 @@ func apiSnapshotsDrop(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []string{string(snapshot.ResourceKey())}
|
resources := []string{string(snapshot.Key())}
|
||||||
taskName := fmt.Sprintf("Delete snapshot %s", name)
|
taskName := fmt.Sprintf("Delete snapshot %s", name)
|
||||||
|
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
published := publishedCollection.BySnapshot(snapshot)
|
// Phase 2: Inside task lock - create fresh collections
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
|
taskPublishedCollection := taskCollectionFactory.PublishedRepoCollection()
|
||||||
|
|
||||||
|
// Fresh load after lock acquired
|
||||||
|
snapshot, err := taskSnapshotCollection.ByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fresh checks with current collections
|
||||||
|
published := taskPublishedCollection.BySnapshot(snapshot)
|
||||||
|
|
||||||
if len(published) > 0 {
|
if len(published) > 0 {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop: snapshot is published")
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("unable to drop: snapshot is published")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !force {
|
if !force {
|
||||||
snapshots := snapshotCollection.BySnapshotSource(snapshot)
|
// Using fresh collection for dependency check
|
||||||
|
snapshots := taskSnapshotCollection.BySnapshotSource(snapshot)
|
||||||
if len(snapshots) > 0 {
|
if len(snapshots) > 0 {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("won't delete snapshot that was used as source for other snapshots, use ?force=1 to override")
|
return &task.ProcessReturnValue{Code: http.StatusConflict, Value: nil}, fmt.Errorf("won't delete snapshot that was used as source for other snapshots, use ?force=1 to override")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = snapshotCollection.Drop(snapshot)
|
err = taskSnapshotCollection.Drop(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
@@ -576,6 +646,7 @@ func apiSnapshotsMerge(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 1: Pre-task validation (shallow load for 404 checks only)
|
||||||
collectionFactory := context.NewCollectionFactory()
|
collectionFactory := context.NewCollectionFactory()
|
||||||
snapshotCollection := collectionFactory.SnapshotCollection()
|
snapshotCollection := collectionFactory.SnapshotCollection()
|
||||||
|
|
||||||
@@ -588,36 +659,47 @@ func apiSnapshotsMerge(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources[i] = string(sources[i].ResourceKey())
|
resources[i] = string(sources[i].Key())
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeRunTaskInBackground(c, "Merge snapshot "+name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, "Merge snapshot "+name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = snapshotCollection.LoadComplete(sources[0])
|
// Phase 2: Inside task lock - create fresh factory
|
||||||
if err != nil {
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
taskSnapshotCollection := taskCollectionFactory.SnapshotCollection()
|
||||||
}
|
|
||||||
result := sources[0].RefList()
|
// Fresh load of all sources inside task
|
||||||
for i := 1; i < len(sources); i++ {
|
freshSources := make([]*deb.Snapshot, len(body.Sources))
|
||||||
err = snapshotCollection.LoadComplete(sources[i])
|
for i := range body.Sources {
|
||||||
|
freshSources[i], err = taskSnapshotCollection.ByName(body.Sources[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
result = result.Merge(sources[i].RefList(), overrideMatching, false)
|
// LoadComplete on fresh copy
|
||||||
|
err = taskSnapshotCollection.LoadComplete(freshSources[i])
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge using fresh sources
|
||||||
|
result := freshSources[0].RefList()
|
||||||
|
for i := 1; i < len(freshSources); i++ {
|
||||||
|
result = result.Merge(freshSources[i].RefList(), overrideMatching, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if latest {
|
if latest {
|
||||||
result.FilterLatestRefs()
|
result.FilterLatestRefs()
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceDescription := make([]string, len(sources))
|
sourceDescription := make([]string, len(freshSources))
|
||||||
for i, s := range sources {
|
for i, s := range freshSources {
|
||||||
sourceDescription[i] = fmt.Sprintf("'%s'", s.Name)
|
sourceDescription[i] = fmt.Sprintf("'%s'", s.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot = deb.NewSnapshotFromRefList(name, sources, result,
|
snapshot = deb.NewSnapshotFromRefList(name, freshSources, result,
|
||||||
fmt.Sprintf("Merged from sources: %s", strings.Join(sourceDescription, ", ")))
|
fmt.Sprintf("Merged from sources: %s", strings.Join(sourceDescription, ", ")))
|
||||||
|
|
||||||
err = collectionFactory.SnapshotCollection().Add(snapshot)
|
err = taskCollectionFactory.SnapshotCollection().Add(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create snapshot: %s", err)
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, fmt.Errorf("unable to create snapshot: %s", err)
|
||||||
}
|
}
|
||||||
@@ -698,24 +780,32 @@ func apiSnapshotsPull(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := []string{string(sourceSnapshot.ResourceKey()), string(toSnapshot.ResourceKey())}
|
resources := []string{string(sourceSnapshot.Key()), string(toSnapshot.Key())}
|
||||||
taskName := fmt.Sprintf("Pull snapshot %s into %s and save as %s", body.Source, name, body.Destination)
|
taskName := fmt.Sprintf("Pull snapshot %s into %s and save as %s", body.Source, name, body.Destination)
|
||||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||||
err = collectionFactory.SnapshotCollection().LoadComplete(toSnapshot)
|
// Phase 2: Inside task lock - create fresh factory
|
||||||
|
taskCollectionFactory := context.NewCollectionFactory()
|
||||||
|
|
||||||
|
// Fresh load of snapshots after lock acquired
|
||||||
|
freshToSnapshot, err := taskCollectionFactory.SnapshotCollection().ByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
err = collectionFactory.SnapshotCollection().LoadComplete(sourceSnapshot)
|
freshSourceSnapshot, err := taskCollectionFactory.SnapshotCollection().ByName(body.Source)
|
||||||
|
if err != nil {
|
||||||
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
|
}
|
||||||
|
err = taskCollectionFactory.SnapshotCollection().LoadComplete(freshSourceSnapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert snapshots to package list
|
// convert snapshots to package list
|
||||||
toPackageList, err := deb.NewPackageListFromRefList(toSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress())
|
toPackageList, err := deb.NewPackageListFromRefList(freshToSnapshot.RefList(), taskCollectionFactory.PackageCollection(), context.Progress())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
sourcePackageList, err := deb.NewPackageListFromRefList(sourceSnapshot.RefList(), collectionFactory.PackageCollection(), context.Progress())
|
sourcePackageList, err := deb.NewPackageListFromRefList(freshSourceSnapshot.RefList(), taskCollectionFactory.PackageCollection(), context.Progress())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
@@ -812,10 +902,10 @@ func apiSnapshotsPull(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create <destination> snapshot
|
// Create <destination> snapshot
|
||||||
destinationSnapshot = deb.NewSnapshotFromPackageList(body.Destination, []*deb.Snapshot{toSnapshot, sourceSnapshot}, toPackageList,
|
destinationSnapshot = deb.NewSnapshotFromPackageList(body.Destination, []*deb.Snapshot{freshToSnapshot, freshSourceSnapshot}, toPackageList,
|
||||||
fmt.Sprintf("Pulled into '%s' with '%s' as source, pull request was: '%s'", toSnapshot.Name, sourceSnapshot.Name, strings.Join(body.Queries, ", ")))
|
fmt.Sprintf("Pulled into '%s' with '%s' as source, pull request was: '%s'", freshToSnapshot.Name, freshSourceSnapshot.Name, strings.Join(body.Queries, ", ")))
|
||||||
|
|
||||||
err = collectionFactory.SnapshotCollection().Add(destinationSnapshot)
|
err = taskCollectionFactory.SnapshotCollection().Add(destinationSnapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/deb"
|
|
||||||
. "gopkg.in/check.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SnapshotsSuite struct {
|
|
||||||
APISuite
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Suite(&SnapshotsSuite{})
|
|
||||||
|
|
||||||
func (s *SnapshotsSuite) TestGetSnapshotsIncludesNumPackages(c *C) {
|
|
||||||
collection := s.context.NewCollectionFactory().SnapshotCollection()
|
|
||||||
snapshot := deb.NewSnapshotFromRefList("count-snapshot-list", nil, makePackageRefList(c), "")
|
|
||||||
c.Assert(collection.Add(snapshot), IsNil)
|
|
||||||
|
|
||||||
response, err := s.HTTPRequest("GET", "/api/snapshots", nil)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(response.Code, Equals, 200)
|
|
||||||
|
|
||||||
var snapshots []map[string]interface{}
|
|
||||||
err = json.Unmarshal(response.Body.Bytes(), &snapshots)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
found := false
|
|
||||||
for _, snapshot := range snapshots {
|
|
||||||
if snapshot["Name"] == "count-snapshot-list" {
|
|
||||||
found = true
|
|
||||||
value, ok := snapshot["NumPackages"]
|
|
||||||
c.Assert(ok, Equals, true)
|
|
||||||
c.Assert(value, Equals, float64(2))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, Equals, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SnapshotsSuite) TestGetSnapshotsReturns500OnCorruptRefList(c *C) {
|
|
||||||
collection := s.context.NewCollectionFactory().SnapshotCollection()
|
|
||||||
snapshot := deb.NewSnapshotFromRefList("broken-snapshot-list", nil, makePackageRefList(c), "")
|
|
||||||
c.Assert(collection.Add(snapshot), IsNil)
|
|
||||||
putRawDBValue(c, &s.APISuite, snapshot.RefKey(), []byte("not-msgpack"))
|
|
||||||
|
|
||||||
response, err := s.HTTPRequest("GET", "/api/snapshots", nil)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(response.Code, Equals, 500)
|
|
||||||
c.Assert(response.Body.String(), Matches, ".*msgpack.*|.*decode.*")
|
|
||||||
}
|
|
||||||
+41
-48
@@ -5,35 +5,28 @@ package azure
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isBlobNotFound(err error) bool {
|
func isBlobNotFound(err error) bool {
|
||||||
var respErr *azcore.ResponseError
|
storageError, ok := err.(azblob.StorageError)
|
||||||
if errors.As(err, &respErr) {
|
return ok && storageError.ServiceCode() == azblob.ServiceCodeBlobNotFound
|
||||||
return respErr.StatusCode == 404 // BlobNotFound
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type azContext struct {
|
type azContext struct {
|
||||||
client *azblob.Client
|
container azblob.ContainerURL
|
||||||
container string
|
|
||||||
prefix string
|
prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAzContext(accountName, accountKey, container, prefix, endpoint string) (*azContext, error) {
|
func newAzContext(accountName, accountKey, container, prefix, endpoint string) (*azContext, error) {
|
||||||
cred, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -42,14 +35,15 @@ func newAzContext(accountName, accountKey, container, prefix, endpoint string) (
|
|||||||
endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", accountName)
|
endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", accountName)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceClient, err := azblob.NewClientWithSharedKeyCredential(endpoint, cred, nil)
|
url, err := url.Parse(fmt.Sprintf("%s/%s", endpoint, container))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
containerURL := azblob.NewContainerURL(*url, azblob.NewPipeline(credential, azblob.PipelineOptions{}))
|
||||||
|
|
||||||
result := &azContext{
|
result := &azContext{
|
||||||
client: serviceClient,
|
container: containerURL,
|
||||||
container: container,
|
|
||||||
prefix: prefix,
|
prefix: prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +54,10 @@ func (az *azContext) blobPath(path string) string {
|
|||||||
return filepath.Join(az.prefix, path)
|
return filepath.Join(az.prefix, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (az *azContext) blobURL(path string) azblob.BlobURL {
|
||||||
|
return az.container.NewBlobURL(az.blobPath(path))
|
||||||
|
}
|
||||||
|
|
||||||
func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (paths []string, md5s []string, err error) {
|
func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (paths []string, md5s []string, err error) {
|
||||||
const delimiter = "/"
|
const delimiter = "/"
|
||||||
paths = make([]string, 0, 1024)
|
paths = make([]string, 0, 1024)
|
||||||
@@ -69,33 +67,27 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
|
|||||||
prefix += delimiter
|
prefix += delimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
for marker := (azblob.Marker{}); marker.NotDone(); {
|
||||||
maxResults := int32(1)
|
listBlob, err := az.container.ListBlobsFlatSegment(
|
||||||
pager := az.client.NewListBlobsFlatPager(az.container, &azblob.ListBlobsFlatOptions{
|
context.Background(), marker, azblob.ListBlobsSegmentOptions{
|
||||||
Prefix: &prefix,
|
Prefix: prefix,
|
||||||
MaxResults: &maxResults,
|
MaxResults: 1,
|
||||||
Include: azblob.ListBlobsInclude{Metadata: true},
|
Details: azblob.BlobListingDetails{Metadata: true}})
|
||||||
})
|
|
||||||
|
|
||||||
// Iterate over each page
|
|
||||||
for pager.More() {
|
|
||||||
page, err := pager.NextPage(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, az, err)
|
return nil, nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, az, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, blob := range page.Segment.BlobItems {
|
marker = listBlob.NextMarker
|
||||||
if prefix == "" {
|
|
||||||
paths = append(paths, *blob.Name)
|
|
||||||
} else {
|
|
||||||
name := *blob.Name
|
|
||||||
paths = append(paths, name[len(prefix):])
|
|
||||||
}
|
|
||||||
b := *blob
|
|
||||||
md5 := b.Properties.ContentMD5
|
|
||||||
md5s = append(md5s, fmt.Sprintf("%x", md5))
|
|
||||||
|
|
||||||
|
for _, blob := range listBlob.Segment.BlobItems {
|
||||||
|
if prefix == "" {
|
||||||
|
paths = append(paths, blob.Name)
|
||||||
|
} else {
|
||||||
|
paths = append(paths, blob.Name[len(prefix):])
|
||||||
|
}
|
||||||
|
md5s = append(md5s, fmt.Sprintf("%x", blob.Properties.ContentMD5))
|
||||||
}
|
}
|
||||||
|
|
||||||
if progress != nil {
|
if progress != nil {
|
||||||
time.Sleep(time.Duration(500) * time.Millisecond)
|
time.Sleep(time.Duration(500) * time.Millisecond)
|
||||||
progress.AddBar(1)
|
progress.AddBar(1)
|
||||||
@@ -105,27 +97,28 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
|
|||||||
return paths, md5s, nil
|
return paths, md5s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (az *azContext) putFile(blobName string, source io.Reader, sourceMD5 string) error {
|
func (az *azContext) putFile(blob azblob.BlobURL, source io.Reader, sourceMD5 string) error {
|
||||||
uploadOptions := &azblob.UploadFileOptions{
|
uploadOptions := azblob.UploadStreamToBlockBlobOptions{
|
||||||
BlockSize: 4 * 1024 * 1024,
|
BufferSize: 4 * 1024 * 1024,
|
||||||
Concurrency: 8,
|
MaxBuffers: 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
path := az.blobPath(blobName)
|
|
||||||
if len(sourceMD5) > 0 {
|
if len(sourceMD5) > 0 {
|
||||||
decodedMD5, err := hex.DecodeString(sourceMD5)
|
decodedMD5, err := hex.DecodeString(sourceMD5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uploadOptions.HTTPHeaders = &blob.HTTPHeaders{
|
uploadOptions.BlobHTTPHeaders = azblob.BlobHTTPHeaders{
|
||||||
BlobContentMD5: decodedMD5,
|
ContentMD5: decodedMD5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
_, err := azblob.UploadStreamToBlockBlob(
|
||||||
if file, ok := source.(*os.File); ok {
|
context.Background(),
|
||||||
_, err = az.client.UploadFile(context.TODO(), az.container, path, file, uploadOptions)
|
source,
|
||||||
}
|
blob.ToBlockBlobURL(),
|
||||||
|
uploadOptions,
|
||||||
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-21
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
"github.com/aptly-dev/aptly/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -40,7 +41,10 @@ func (pool *PackagePool) buildPoolPath(filename string, checksums *utils.Checksu
|
|||||||
return filepath.Join(hash[0:2], hash[2:4], hash[4:32]+"_"+filename)
|
return filepath.Join(hash[0:2], hash[2:4], hash[4:32]+"_"+filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.ChecksumStorage) (*utils.ChecksumInfo, error) {
|
func (pool *PackagePool) ensureChecksums(
|
||||||
|
poolPath string,
|
||||||
|
checksumStorage aptly.ChecksumStorage,
|
||||||
|
) (*utils.ChecksumInfo, error) {
|
||||||
targetChecksums, err := checksumStorage.Get(poolPath)
|
targetChecksums, err := checksumStorage.Get(poolPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -48,7 +52,8 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
|
|||||||
|
|
||||||
if targetChecksums == nil {
|
if targetChecksums == nil {
|
||||||
// we don't have checksums stored yet for this file
|
// we don't have checksums stored yet for this file
|
||||||
download, err := pool.az.client.DownloadStream(context.Background(), pool.az.container, poolPath, nil)
|
blob := pool.az.blobURL(poolPath)
|
||||||
|
download, err := blob.Download(context.Background(), 0, 0, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isBlobNotFound(err) {
|
if isBlobNotFound(err) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -58,7 +63,7 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
|
|||||||
}
|
}
|
||||||
|
|
||||||
targetChecksums = &utils.ChecksumInfo{}
|
targetChecksums = &utils.ChecksumInfo{}
|
||||||
*targetChecksums, err = utils.ChecksumsForReader(download.Body)
|
*targetChecksums, err = utils.ChecksumsForReader(download.Body(azblob.RetryReaderOptions{}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error checksumming blob at %s", poolPath)
|
return nil, errors.Wrapf(err, "error checksumming blob at %s", poolPath)
|
||||||
}
|
}
|
||||||
@@ -87,49 +92,45 @@ func (pool *PackagePool) LegacyPath(_ string, _ *utils.ChecksumInfo) (string, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pool *PackagePool) Size(path string) (int64, error) {
|
func (pool *PackagePool) Size(path string) (int64, error) {
|
||||||
serviceClient := pool.az.client.ServiceClient()
|
blob := pool.az.blobURL(path)
|
||||||
containerClient := serviceClient.NewContainerClient(pool.az.container)
|
props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
blobClient := containerClient.NewBlobClient(path)
|
|
||||||
|
|
||||||
props, err := blobClient.GetProperties(context.TODO(), nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
|
return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
return *props.ContentLength, nil
|
return props.ContentLength(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) {
|
func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) {
|
||||||
|
blob := pool.az.blobURL(path)
|
||||||
|
|
||||||
temp, err := os.CreateTemp("", "blob-download")
|
temp, err := os.CreateTemp("", "blob-download")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error creating tempfile for %s", path)
|
return nil, errors.Wrap(err, "error creating temporary file for blob download")
|
||||||
}
|
}
|
||||||
defer func() { _ = os.Remove(temp.Name()) }()
|
defer func() { _ = os.Remove(temp.Name()) }()
|
||||||
|
|
||||||
_, err = pool.az.client.DownloadFile(context.TODO(), pool.az.container, path, temp, nil)
|
err = azblob.DownloadBlobToFile(context.Background(), blob, 0, 0, temp, azblob.DownloadFromBlobOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "error downloading blob %s", path)
|
return nil, errors.Wrapf(err, "error downloading blob at %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return temp, nil
|
return temp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pool *PackagePool) Remove(path string) (int64, error) {
|
func (pool *PackagePool) Remove(path string) (int64, error) {
|
||||||
serviceClient := pool.az.client.ServiceClient()
|
blob := pool.az.blobURL(path)
|
||||||
containerClient := serviceClient.NewContainerClient(pool.az.container)
|
props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
blobClient := containerClient.NewBlobClient(path)
|
|
||||||
|
|
||||||
props, err := blobClient.GetProperties(context.TODO(), nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
|
return 0, errors.Wrapf(err, "error getting props of %s from %s", path, pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = pool.az.client.DeleteBlob(context.Background(), pool.az.container, path, nil)
|
_, err = blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.Wrapf(err, "error deleting %s from %s", path, pool)
|
return 0, errors.Wrapf(err, "error deleting %s from %s", path, pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
return *props.ContentLength, nil
|
return props.ContentLength(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, _ bool, checksumStorage aptly.ChecksumStorage) (string, error) {
|
func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, _ bool, checksumStorage aptly.ChecksumStorage) (string, error) {
|
||||||
@@ -143,6 +144,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
|
|||||||
}
|
}
|
||||||
|
|
||||||
path := pool.buildPoolPath(basename, checksums)
|
path := pool.buildPoolPath(basename, checksums)
|
||||||
|
blob := pool.az.blobURL(path)
|
||||||
targetChecksums, err := pool.ensureChecksums(path, checksumStorage)
|
targetChecksums, err := pool.ensureChecksums(path, checksumStorage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -158,7 +160,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
|
|||||||
}
|
}
|
||||||
defer func() { _ = source.Close() }()
|
defer func() { _ = source.Close() }()
|
||||||
|
|
||||||
err = pool.az.putFile(path, source, checksums.MD5)
|
err = pool.az.putFile(blob, source, checksums.MD5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/files"
|
"github.com/aptly-dev/aptly/files"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
"github.com/aptly-dev/aptly/utils"
|
||||||
@@ -50,10 +50,8 @@ func (s *PackagePoolSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
|
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
publicAccessType := azblob.PublicAccessTypeContainer
|
cnt := s.pool.az.container
|
||||||
_, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{
|
_, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
|
||||||
Access: &publicAccessType,
|
|
||||||
})
|
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
||||||
|
|||||||
+57
-70
@@ -3,22 +3,19 @@ package azure
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/lease"
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
"github.com/aptly-dev/aptly/utils"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PublishedStorage abstract file system with published files (actually hosted on Azure)
|
// PublishedStorage abstract file system with published files (actually hosted on Azure)
|
||||||
type PublishedStorage struct {
|
type PublishedStorage struct {
|
||||||
// FIXME: unused ???? prefix string
|
|
||||||
az *azContext
|
az *azContext
|
||||||
pathCache map[string]map[string]string
|
pathCache map[string]map[string]string
|
||||||
}
|
}
|
||||||
@@ -67,7 +64,7 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
|||||||
}
|
}
|
||||||
defer func() { _ = source.Close() }()
|
defer func() { _ = source.Close() }()
|
||||||
|
|
||||||
err = storage.az.putFile(path, source, sourceMD5)
|
err = storage.az.putFile(storage.az.blobURL(path), source, sourceMD5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage))
|
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage))
|
||||||
}
|
}
|
||||||
@@ -77,15 +74,14 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
|||||||
|
|
||||||
// RemoveDirs removes directory structure under public path
|
// RemoveDirs removes directory structure under public path
|
||||||
func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error {
|
func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error {
|
||||||
path = storage.az.blobPath(path)
|
|
||||||
filelist, err := storage.Filelist(path)
|
filelist, err := storage.Filelist(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, filename := range filelist {
|
for _, filename := range filelist {
|
||||||
blob := filepath.Join(path, filename)
|
blob := storage.az.blobURL(filepath.Join(path, filename))
|
||||||
_, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, blob, nil)
|
_, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error deleting path %s from %s: %s", filename, storage, err)
|
return fmt.Errorf("error deleting path %s from %s: %s", filename, storage, err)
|
||||||
}
|
}
|
||||||
@@ -96,8 +92,8 @@ func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error
|
|||||||
|
|
||||||
// Remove removes single file under public path
|
// Remove removes single file under public path
|
||||||
func (storage *PublishedStorage) Remove(path string) error {
|
func (storage *PublishedStorage) Remove(path string) error {
|
||||||
path = storage.az.blobPath(path)
|
blob := storage.az.blobURL(path)
|
||||||
_, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, path, nil)
|
_, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("error deleting %s from %s: %s", path, storage, err))
|
err = errors.Wrap(err, fmt.Sprintf("error deleting %s from %s: %s", path, storage, err))
|
||||||
}
|
}
|
||||||
@@ -116,8 +112,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
|||||||
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
|
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
|
||||||
|
|
||||||
relFilePath := filepath.Join(publishedRelPath, fileName)
|
relFilePath := filepath.Join(publishedRelPath, fileName)
|
||||||
prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
|
// prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
|
||||||
poolPath := storage.az.blobPath(prefixRelFilePath)
|
// FIXME: check how to integrate publishedPrefix:
|
||||||
|
poolPath := storage.az.blobPath(fileName)
|
||||||
|
|
||||||
if storage.pathCache == nil {
|
if storage.pathCache == nil {
|
||||||
storage.pathCache = make(map[string]map[string]string)
|
storage.pathCache = make(map[string]map[string]string)
|
||||||
@@ -160,7 +157,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
|||||||
}
|
}
|
||||||
defer func() { _ = source.Close() }()
|
defer func() { _ = source.Close() }()
|
||||||
|
|
||||||
err = storage.az.putFile(relFilePath, source, sourceMD5)
|
err = storage.az.putFile(storage.az.blobURL(relFilePath), source, sourceMD5)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pathCache[relFilePath] = sourceMD5
|
pathCache[relFilePath] = sourceMD5
|
||||||
} else {
|
} else {
|
||||||
@@ -177,60 +174,57 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Internal copy or move implementation
|
// Internal copy or move implementation
|
||||||
func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata map[string]*string, move bool) error {
|
func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata azblob.Metadata, move bool) error {
|
||||||
const leaseDuration = 30
|
const leaseDuration = 30
|
||||||
leaseID := uuid.NewString()
|
|
||||||
|
|
||||||
serviceClient := storage.az.client.ServiceClient()
|
dstBlobURL := storage.az.blobURL(dst)
|
||||||
containerClient := serviceClient.NewContainerClient(storage.az.container)
|
srcBlobURL := storage.az.blobURL(src)
|
||||||
srcBlobClient := containerClient.NewBlobClient(src)
|
leaseResp, err := srcBlobURL.AcquireLease(context.Background(), "", leaseDuration, azblob.ModifiedAccessConditions{})
|
||||||
blobLeaseClient, err := lease.NewBlobClient(srcBlobClient, &lease.BlobClientOptions{LeaseID: to.Ptr(leaseID)})
|
if err != nil || leaseResp.StatusCode() != http.StatusCreated {
|
||||||
if err != nil {
|
return fmt.Errorf("error acquiring lease on source blob %s", srcBlobURL)
|
||||||
return fmt.Errorf("error acquiring lease on source blob %s", src)
|
|
||||||
}
|
}
|
||||||
|
defer func() { _, _ = srcBlobURL.BreakLease(context.Background(), azblob.LeaseBreakNaturally, azblob.ModifiedAccessConditions{}) }()
|
||||||
|
srcBlobLeaseID := leaseResp.LeaseID()
|
||||||
|
|
||||||
_, err = blobLeaseClient.AcquireLease(context.Background(), leaseDuration, nil)
|
copyResp, err := dstBlobURL.StartCopyFromURL(
|
||||||
if err != nil {
|
context.Background(),
|
||||||
return fmt.Errorf("error acquiring lease on source blob %s", src)
|
srcBlobURL.URL(),
|
||||||
}
|
metadata,
|
||||||
defer func() {
|
azblob.ModifiedAccessConditions{},
|
||||||
_, _ = blobLeaseClient.BreakLease(context.Background(), &lease.BlobBreakOptions{BreakPeriod: to.Ptr(int32(60))})
|
azblob.BlobAccessConditions{},
|
||||||
}()
|
azblob.DefaultAccessTier,
|
||||||
|
nil)
|
||||||
dstBlobClient := containerClient.NewBlobClient(dst)
|
|
||||||
copyResp, err := dstBlobClient.StartCopyFromURL(context.Background(), srcBlobClient.URL(), &blob.StartCopyFromURLOptions{
|
|
||||||
Metadata: metadata,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error copying %s -> %s in %s: %s", src, dst, storage, err)
|
return fmt.Errorf("error copying %s -> %s in %s: %s", src, dst, storage, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
copyStatus := *copyResp.CopyStatus
|
copyStatus := copyResp.CopyStatus()
|
||||||
for {
|
for {
|
||||||
if copyStatus == blob.CopyStatusTypeSuccess {
|
if copyStatus == azblob.CopyStatusSuccess {
|
||||||
if move {
|
if move {
|
||||||
_, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, src, &blob.DeleteOptions{
|
_, err = srcBlobURL.Delete(
|
||||||
AccessConditions: &blob.AccessConditions{
|
context.Background(),
|
||||||
LeaseAccessConditions: &blob.LeaseAccessConditions{
|
azblob.DeleteSnapshotsOptionNone,
|
||||||
LeaseID: &leaseID,
|
azblob.BlobAccessConditions{
|
||||||
},
|
LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
} else if copyStatus == blob.CopyStatusTypePending {
|
} else if copyStatus == azblob.CopyStatusPending {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
getMetadata, err := dstBlobClient.GetProperties(context.TODO(), nil)
|
blobPropsResp, err := dstBlobURL.GetProperties(
|
||||||
|
context.Background(),
|
||||||
|
azblob.BlobAccessConditions{LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID}},
|
||||||
|
azblob.ClientProvidedKeyOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error getting copy progress %s", dst)
|
return fmt.Errorf("error getting destination blob properties %s", dstBlobURL)
|
||||||
}
|
}
|
||||||
copyStatus = *getMetadata.CopyStatus
|
copyStatus = blobPropsResp.CopyStatus()
|
||||||
|
|
||||||
_, err = blobLeaseClient.RenewLease(context.Background(), nil)
|
_, err = srcBlobURL.RenewLease(context.Background(), srcBlobLeaseID, azblob.ModifiedAccessConditions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error renewing source blob lease %s", src)
|
return fmt.Errorf("error renewing source blob lease %s", srcBlobURL)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("error copying %s -> %s in %s: %s", dst, src, storage, copyStatus)
|
return fmt.Errorf("error copying %s -> %s in %s: %s", dst, src, storage, copyStatus)
|
||||||
@@ -245,9 +239,7 @@ func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
|
|||||||
|
|
||||||
// SymLink creates a copy of src file and adds link information as meta data
|
// SymLink creates a copy of src file and adds link information as meta data
|
||||||
func (storage *PublishedStorage) SymLink(src string, dst string) error {
|
func (storage *PublishedStorage) SymLink(src string, dst string) error {
|
||||||
metadata := make(map[string]*string)
|
return storage.internalCopyOrMoveBlob(src, dst, azblob.Metadata{"SymLink": src}, false /* move */)
|
||||||
metadata["SymLink"] = &src
|
|
||||||
return storage.internalCopyOrMoveBlob(src, dst, metadata, false /* do not remove src */)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HardLink using symlink functionality as hard links do not exist
|
// HardLink using symlink functionality as hard links do not exist
|
||||||
@@ -257,33 +249,28 @@ func (storage *PublishedStorage) HardLink(src string, dst string) error {
|
|||||||
|
|
||||||
// FileExists returns true if path exists
|
// FileExists returns true if path exists
|
||||||
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
|
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
|
||||||
serviceClient := storage.az.client.ServiceClient()
|
blob := storage.az.blobURL(path)
|
||||||
containerClient := serviceClient.NewContainerClient(storage.az.container)
|
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
blobClient := containerClient.NewBlobClient(path)
|
|
||||||
_, err := blobClient.GetProperties(context.Background(), nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isBlobNotFound(err) {
|
if isBlobNotFound(err) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return false, fmt.Errorf("error checking if blob %s exists: %v", path, err)
|
return false, err
|
||||||
|
} else if resp.StatusCode() == http.StatusOK {
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
return true, nil
|
return false, fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadLink returns the symbolic link pointed to by path.
|
// ReadLink returns the symbolic link pointed to by path.
|
||||||
// This simply reads text file created with SymLink
|
// This simply reads text file created with SymLink
|
||||||
func (storage *PublishedStorage) ReadLink(path string) (string, error) {
|
func (storage *PublishedStorage) ReadLink(path string) (string, error) {
|
||||||
serviceClient := storage.az.client.ServiceClient()
|
blob := storage.az.blobURL(path)
|
||||||
containerClient := serviceClient.NewContainerClient(storage.az.container)
|
resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
blobClient := containerClient.NewBlobClient(path)
|
|
||||||
props, err := blobClient.GetProperties(context.Background(), nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get blob properties: %v", err)
|
return "", err
|
||||||
|
} else if resp.StatusCode() != http.StatusOK {
|
||||||
|
return "", fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
|
||||||
}
|
}
|
||||||
|
return resp.NewMetadata()["SymLink"], nil
|
||||||
metadata := props.Metadata
|
|
||||||
if originalBlob, exists := metadata["original_blob"]; exists {
|
|
||||||
return *originalBlob, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("error reading link %s: %v", path, err)
|
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-26
@@ -1,7 +1,6 @@
|
|||||||
package azure
|
package azure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@@ -9,9 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
|
||||||
"github.com/aptly-dev/aptly/files"
|
"github.com/aptly-dev/aptly/files"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
"github.com/aptly-dev/aptly/utils"
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
@@ -69,10 +66,8 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
|
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
publicAccessType := azblob.PublicAccessTypeContainer
|
cnt := s.storage.az.container
|
||||||
_, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{
|
_, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
|
||||||
Access: &publicAccessType,
|
|
||||||
})
|
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
||||||
@@ -80,39 +75,41 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) TearDownTest(c *C) {
|
func (s *PublishedStorageSuite) TearDownTest(c *C) {
|
||||||
_, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil)
|
cnt := s.storage.az.container
|
||||||
|
_, err := cnt.Delete(context.Background(), azblob.ContainerAccessConditions{})
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
|
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
|
||||||
resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil)
|
blob := s.storage.az.container.NewBlobURL(path)
|
||||||
|
resp, err := blob.Download(context.Background(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
data, err := io.ReadAll(resp.Body)
|
body := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3})
|
||||||
|
data, err := io.ReadAll(body)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
|
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
|
||||||
serviceClient := s.storage.az.client.ServiceClient()
|
_, err := s.storage.az.container.NewBlobURL(path).GetProperties(
|
||||||
containerClient := serviceClient.NewContainerClient(s.storage.az.container)
|
context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
blobClient := containerClient.NewBlobClient(path)
|
|
||||||
_, err := blobClient.GetProperties(context.Background(), nil)
|
|
||||||
c.Assert(err, NotNil)
|
c.Assert(err, NotNil)
|
||||||
|
storageError, ok := err.(azblob.StorageError)
|
||||||
storageError, ok := err.(*azcore.ResponseError)
|
|
||||||
c.Assert(ok, Equals, true)
|
c.Assert(ok, Equals, true)
|
||||||
c.Assert(storageError.StatusCode, Equals, 404)
|
c.Assert(string(storageError.ServiceCode()), Equals, string(string(azblob.StorageErrorCodeBlobNotFound)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
|
func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
|
||||||
hash := md5.Sum(data)
|
hash := md5.Sum(data)
|
||||||
uploadOptions := &azblob.UploadStreamOptions{
|
_, err := azblob.UploadBufferToBlockBlob(
|
||||||
HTTPHeaders: &blob.HTTPHeaders{
|
context.Background(),
|
||||||
BlobContentMD5: hash[:],
|
data,
|
||||||
},
|
s.storage.az.container.NewBlockBlobURL(path),
|
||||||
}
|
azblob.UploadToBlockBlobOptions{
|
||||||
reader := bytes.NewReader(data)
|
BlobHTTPHeaders: azblob.BlobHTTPHeaders{
|
||||||
_, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions)
|
ContentMD5: hash[:],
|
||||||
|
},
|
||||||
|
})
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +330,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
|
|||||||
|
|
||||||
// 2nd link from pool, providing wrong path for source file
|
// 2nd link from pool, providing wrong path for source file
|
||||||
//
|
//
|
||||||
// this test should check that file already exists in Azure and skip upload (which would fail if not skipped)
|
// this test should check that file already exists in S3 and skip upload (which would fail if not skipped)
|
||||||
s.prefixedStorage.pathCache = nil
|
s.prefixedStorage.pathCache = nil
|
||||||
err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false)
|
err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ func aptlyDBCleanup(cmd *commander.Command, args []string) error {
|
|||||||
|
|
||||||
// collect information about references packages...
|
// collect information about references packages...
|
||||||
existingPackageRefs := deb.NewPackageRefList()
|
existingPackageRefs := deb.NewPackageRefList()
|
||||||
referencedAppStreamFiles := []string{}
|
|
||||||
|
|
||||||
// used only in verbose mode to report package use source
|
// used only in verbose mode to report package use source
|
||||||
packageRefSources := map[string][]string{}
|
packageRefSources := map[string][]string{}
|
||||||
@@ -56,10 +55,6 @@ func aptlyDBCleanup(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, poolPath := range repo.AppStreamFiles {
|
|
||||||
referencedAppStreamFiles = append(referencedAppStreamFiles, poolPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -123,11 +118,6 @@ func aptlyDBCleanup(cmd *commander.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, poolPath := range snapshot.AppStreamFiles {
|
|
||||||
referencedAppStreamFiles = append(referencedAppStreamFiles, poolPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -246,7 +236,6 @@ func aptlyDBCleanup(cmd *commander.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
referencedFiles = append(referencedFiles, referencedAppStreamFiles...)
|
|
||||||
sort.Strings(referencedFiles)
|
sort.Strings(referencedFiles)
|
||||||
context.Progress().ShutdownBar()
|
context.Progress().ShutdownBar()
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
|||||||
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
|
downloadSources := LookupOption(context.Config().DownloadSourcePackages, context.Flags(), "with-sources")
|
||||||
downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
|
downloadUdebs := context.Flags().Lookup("with-udebs").Value.Get().(bool)
|
||||||
downloadInstaller := context.Flags().Lookup("with-installer").Value.Get().(bool)
|
downloadInstaller := context.Flags().Lookup("with-installer").Value.Get().(bool)
|
||||||
downloadAppStream := context.Flags().Lookup("with-appstream").Value.Get().(bool)
|
|
||||||
ignoreSignatures := context.Config().GpgDisableVerify
|
ignoreSignatures := context.Config().GpgDisableVerify
|
||||||
if context.Flags().IsSet("ignore-signatures") {
|
if context.Flags().IsSet("ignore-signatures") {
|
||||||
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
|
ignoreSignatures = context.Flags().Lookup("ignore-signatures").Value.Get().(bool)
|
||||||
@@ -42,7 +41,7 @@ func aptlyMirrorCreate(cmd *commander.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(),
|
repo, err := deb.NewRemoteRepo(mirrorName, archiveURL, distribution, components, context.ArchitecturesList(),
|
||||||
downloadSources, downloadUdebs, downloadInstaller, downloadAppStream)
|
downloadSources, downloadUdebs, downloadInstaller)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create mirror: %s", err)
|
return fmt.Errorf("unable to create mirror: %s", err)
|
||||||
}
|
}
|
||||||
@@ -101,7 +100,6 @@ Example:
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
||||||
cmd.Flag.Bool("with-appstream", false, "download AppStream (DEP-11) metadata")
|
|
||||||
cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files")
|
cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files")
|
||||||
cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages")
|
cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages")
|
||||||
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
|
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
|
|||||||
repo.Filter = flag.Value.String() // allows file/stdin with @
|
repo.Filter = flag.Value.String() // allows file/stdin with @
|
||||||
case "filter-with-deps":
|
case "filter-with-deps":
|
||||||
repo.FilterWithDeps = flag.Value.Get().(bool)
|
repo.FilterWithDeps = flag.Value.Get().(bool)
|
||||||
case "with-appstream":
|
|
||||||
repo.DownloadAppStream = flag.Value.Get().(bool)
|
|
||||||
case "with-installer":
|
case "with-installer":
|
||||||
repo.DownloadInstaller = flag.Value.Get().(bool)
|
repo.DownloadInstaller = flag.Value.Get().(bool)
|
||||||
case "with-sources":
|
case "with-sources":
|
||||||
@@ -55,10 +53,6 @@ func aptlyMirrorEdit(cmd *commander.Command, args []string) error {
|
|||||||
return fmt.Errorf("unable to edit: flat mirrors don't support udebs")
|
return fmt.Errorf("unable to edit: flat mirrors don't support udebs")
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo.IsFlat() && repo.DownloadAppStream {
|
|
||||||
return fmt.Errorf("unable to edit: flat mirrors don't support AppStream (DEP-11) metadata")
|
|
||||||
}
|
|
||||||
|
|
||||||
if repo.Filter != "" {
|
if repo.Filter != "" {
|
||||||
_, err = query.Parse(repo.Filter)
|
_, err = query.Parse(repo.Filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -113,7 +107,6 @@ Example:
|
|||||||
AddStringOrFileFlag(&cmd.Flag, "filter", "", "filter packages in mirror, use '@file' to read filter from file or '@-' for stdin")
|
AddStringOrFileFlag(&cmd.Flag, "filter", "", "filter packages in mirror, use '@file' to read filter from file or '@-' for stdin")
|
||||||
cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well")
|
cmd.Flag.Bool("filter-with-deps", false, "when filtering, include dependencies of matching packages as well")
|
||||||
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
||||||
cmd.Flag.Bool("with-appstream", false, "download AppStream (DEP-11) metadata")
|
|
||||||
cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files")
|
cmd.Flag.Bool("with-installer", false, "download additional not packaged installer files")
|
||||||
cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages")
|
cmd.Flag.Bool("with-sources", false, "download source packages in addition to binary packages")
|
||||||
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
|
cmd.Flag.Bool("with-udebs", false, "download .udeb packages (Debian installer support)")
|
||||||
|
|||||||
@@ -61,11 +61,6 @@ func aptlyMirrorShowTxt(_ *commander.Command, args []string) error {
|
|||||||
downloadUdebs = Yes
|
downloadUdebs = Yes
|
||||||
}
|
}
|
||||||
fmt.Printf("Download .udebs: %s\n", downloadUdebs)
|
fmt.Printf("Download .udebs: %s\n", downloadUdebs)
|
||||||
downloadAppStream := No
|
|
||||||
if repo.DownloadAppStream {
|
|
||||||
downloadAppStream = Yes
|
|
||||||
}
|
|
||||||
fmt.Printf("Download AppStream: %s\n", downloadAppStream)
|
|
||||||
if repo.Filter != "" {
|
if repo.Filter != "" {
|
||||||
fmt.Printf("Filter: %s\n", repo.Filter)
|
fmt.Printf("Filter: %s\n", repo.Filter)
|
||||||
filterWithDeps := No
|
filterWithDeps := No
|
||||||
|
|||||||
+1
-12
@@ -64,15 +64,6 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
return fmt.Errorf("unable to update: %s", err)
|
return fmt.Errorf("unable to update: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo.DownloadAppStream && !repo.IsFlat() {
|
|
||||||
context.Progress().Printf("Downloading AppStream metadata...\n")
|
|
||||||
err = repo.DownloadAppStreamFiles(context.Progress(), context.Downloader(),
|
|
||||||
context.PackagePool(), collectionFactory.ChecksumCollection(nil), ignoreChecksums)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to update: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if repo.Filter != "" {
|
if repo.Filter != "" {
|
||||||
context.Progress().Printf("Applying filter...\n")
|
context.Progress().Printf("Applying filter...\n")
|
||||||
var filterQuery deb.PackageQuery
|
var filterQuery deb.PackageQuery
|
||||||
@@ -96,11 +87,10 @@ func aptlyMirrorUpdate(cmd *commander.Command, args []string) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
skipExistingPackages := context.Flags().Lookup("skip-existing-packages").Value.Get().(bool)
|
skipExistingPackages := context.Flags().Lookup("skip-existing-packages").Value.Get().(bool)
|
||||||
latestOnly := context.Flags().Lookup("latest").Value.Get().(bool)
|
|
||||||
|
|
||||||
context.Progress().Printf("Building download queue...\n")
|
context.Progress().Printf("Building download queue...\n")
|
||||||
queue, downloadSize, err = repo.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
|
queue, downloadSize, err = repo.BuildDownloadQueue(context.PackagePool(), collectionFactory.PackageCollection(),
|
||||||
collectionFactory.ChecksumCollection(nil), skipExistingPackages, latestOnly)
|
collectionFactory.ChecksumCollection(nil), skipExistingPackages)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to update: %s", err)
|
return fmt.Errorf("unable to update: %s", err)
|
||||||
@@ -302,7 +292,6 @@ Example:
|
|||||||
cmd.Flag.Bool("ignore-checksums", false, "ignore checksum mismatches while downloading package files and metadata")
|
cmd.Flag.Bool("ignore-checksums", false, "ignore checksum mismatches while downloading package files and metadata")
|
||||||
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
cmd.Flag.Bool("ignore-signatures", false, "disable verification of Release file signatures")
|
||||||
cmd.Flag.Bool("skip-existing-packages", false, "do not check file existence for packages listed in the internal database of the mirror")
|
cmd.Flag.Bool("skip-existing-packages", false, "do not check file existence for packages listed in the internal database of the mirror")
|
||||||
cmd.Flag.Bool("latest", false, "download only latest version of each package (per architecture)")
|
|
||||||
cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)")
|
cmd.Flag.Int64("download-limit", 0, "limit download speed (kbytes/sec)")
|
||||||
cmd.Flag.String("downloader", "default", "downloader to use (e.g. grab)")
|
cmd.Flag.String("downloader", "default", "downloader to use (e.g. grab)")
|
||||||
cmd.Flag.Int("max-tries", 1, "max download tries till process fails with download error")
|
cmd.Flag.Int("max-tries", 1, "max download tries till process fails with download error")
|
||||||
|
|||||||
@@ -51,9 +51,7 @@ Example:
|
|||||||
cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)")
|
cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)")
|
||||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
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.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.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
cmd.Flag.String("version", "", "version of the release")
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,18 +150,10 @@ func aptlyPublishSnapshotOrRepo(cmd *commander.Command, args []string) error {
|
|||||||
published.AcquireByHash = context.Flags().Lookup("acquire-by-hash").Value.Get().(bool)
|
published.AcquireByHash = context.Flags().Lookup("acquire-by-hash").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.Flags().IsSet("signed-by") {
|
|
||||||
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if context.Flags().IsSet("multi-dist") {
|
if context.Flags().IsSet("multi-dist") {
|
||||||
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
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)
|
duplicate := collectionFactory.PublishedRepoCollection().CheckDuplicate(published)
|
||||||
if duplicate != nil {
|
if duplicate != nil {
|
||||||
_ = collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
|
_ = collectionFactory.PublishedRepoCollection().LoadComplete(duplicate, collectionFactory)
|
||||||
@@ -255,8 +247,6 @@ Example:
|
|||||||
cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)")
|
cmd.Flag.String("codename", "", "codename to publish (defaults to distribution)")
|
||||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
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.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")
|
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
@@ -99,14 +99,6 @@ func aptlyPublishSwitch(cmd *commander.Command, args []string) error {
|
|||||||
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.Flags().IsSet("signed-by") {
|
|
||||||
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") {
|
if context.Flags().IsSet("multi-dist") {
|
||||||
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
@@ -170,8 +162,6 @@ This command would switch published repository (with one component) named ppa/wh
|
|||||||
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
|
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
|
||||||
cmd.Flag.String("component", "", "component names to update (for multi-component publishing, separate components with commas)")
|
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.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("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")
|
cmd.Flag.Bool("multi-dist", false, "enable multiple packages with the same filename in different distributions")
|
||||||
|
|
||||||
|
|||||||
@@ -60,26 +60,10 @@ func aptlyPublishUpdate(cmd *commander.Command, args []string) error {
|
|||||||
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
published.SkipBz2 = context.Flags().Lookup("skip-bz2").Value.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.Flags().IsSet("signed-by") {
|
|
||||||
published.SignedBy = context.Flags().Lookup("signed-by").Value.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if context.Flags().IsSet("origin") {
|
|
||||||
published.Origin = context.Flags().Lookup("origin").Value.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if context.Flags().IsSet("label") {
|
|
||||||
published.Label = context.Flags().Lookup("label").Value.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if context.Flags().IsSet("multi-dist") {
|
if context.Flags().IsSet("multi-dist") {
|
||||||
published.MultiDist = context.Flags().Lookup("multi-dist").Value.Get().(bool)
|
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())
|
err = published.Publish(context.PackagePool(), context, collectionFactory, signer, context.Progress(), forceOverwrite, context.SkelPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to publish: %s", err)
|
return fmt.Errorf("unable to publish: %s", err)
|
||||||
@@ -141,12 +125,8 @@ Example:
|
|||||||
cmd.Flag.Bool("skip-contents", false, "don't generate Contents indexes")
|
cmd.Flag.Bool("skip-contents", false, "don't generate Contents indexes")
|
||||||
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
|
cmd.Flag.Bool("skip-bz2", false, "don't generate bzipped indexes")
|
||||||
cmd.Flag.Bool("force-overwrite", false, "overwrite files in package pool in case of mismatch")
|
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.Bool("skip-cleanup", false, "don't remove unreferenced files in prefix/component")
|
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")
|
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
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,6 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
|
|||||||
$keyring \
|
$keyring \
|
||||||
"-with-sources=[download source packages in addition to binary packages]:$bool" \
|
"-with-sources=[download source packages in addition to binary packages]:$bool" \
|
||||||
"-with-udebs=[download .udeb packages (Debian installer support)]:$bool" \
|
"-with-udebs=[download .udeb packages (Debian installer support)]:$bool" \
|
||||||
"-with-appstream=[download AppStream (DEP-11) metadata]:$bool" \
|
|
||||||
"(-)2:new mirror name: " ":archive url:_urls" ":distribution:($dists)" "*:components:_values -s ' ' components $components"
|
"(-)2:new mirror name: " ":archive url:_urls" ":distribution:($dists)" "*:components:_values -s ' ' components $components"
|
||||||
;;
|
;;
|
||||||
list)
|
list)
|
||||||
@@ -212,7 +211,6 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
|
|||||||
$keyring \
|
$keyring \
|
||||||
"-max-tries=[max download tries till process fails with download error]:number: " \
|
"-max-tries=[max download tries till process fails with download error]:number: " \
|
||||||
"-skip-existing-packages=[do not check file existence for packages listed in the internal database of the mirror]:$bool" \
|
"-skip-existing-packages=[do not check file existence for packages listed in the internal database of the mirror]:$bool" \
|
||||||
"-latest=[download only latest version of each package (per architecture)]:$bool" \
|
|
||||||
"(-)2:mirror name:$mirrors"
|
"(-)2:mirror name:$mirrors"
|
||||||
;;
|
;;
|
||||||
rename)
|
rename)
|
||||||
@@ -225,7 +223,6 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
|
|||||||
"-filter-with-deps=[when filtering, include dependencies of matching packages as well]:$bool" \
|
"-filter-with-deps=[when filtering, include dependencies of matching packages as well]:$bool" \
|
||||||
"-with-sources=[download source packages in addition to binary packages]:$bool" \
|
"-with-sources=[download source packages in addition to binary packages]:$bool" \
|
||||||
"-with-udebs=[download .udeb packages (Debian installer support)]:$bool" \
|
"-with-udebs=[download .udeb packages (Debian installer support)]:$bool" \
|
||||||
"-with-appstream=[download AppStream (DEP-11) metadata]:$bool" \
|
|
||||||
"(-)2:mirror name:$mirrors"
|
"(-)2:mirror name:$mirrors"
|
||||||
;;
|
;;
|
||||||
search)
|
search)
|
||||||
|
|||||||
+3
-3
@@ -203,7 +203,7 @@ _aptly()
|
|||||||
"create")
|
"create")
|
||||||
if [[ $numargs -eq 0 ]]; then
|
if [[ $numargs -eq 0 ]]; then
|
||||||
if [[ "$cur" == -* ]]; then
|
if [[ "$cur" == -* ]]; then
|
||||||
COMPREPLY=($(compgen -W "-filter= -filter-with-deps -force-components -ignore-signatures -keyring= -with-appstream -with-installer -with-sources -with-udebs" -- ${cur}))
|
COMPREPLY=($(compgen -W "-filter= -filter-with-deps -force-components -ignore-signatures -keyring= -with-installer -with-sources -with-udebs" -- ${cur}))
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -211,7 +211,7 @@ _aptly()
|
|||||||
"edit")
|
"edit")
|
||||||
if [[ $numargs -eq 0 ]]; then
|
if [[ $numargs -eq 0 ]]; then
|
||||||
if [[ "$cur" == -* ]]; then
|
if [[ "$cur" == -* ]]; then
|
||||||
COMPREPLY=($(compgen -W "-archive-url= -filter= -filter-with-deps -ignore-signatures -keyring= -with-appstream -with-installer -with-sources -with-udebs" -- ${cur}))
|
COMPREPLY=($(compgen -W "-archive-url= -filter= -filter-with-deps -ignore-signatures -keyring= -with-installer -with-sources -with-udebs" -- ${cur}))
|
||||||
else
|
else
|
||||||
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
|
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
|
||||||
fi
|
fi
|
||||||
@@ -263,7 +263,7 @@ _aptly()
|
|||||||
"update")
|
"update")
|
||||||
if [[ $numargs -eq 0 ]]; then
|
if [[ $numargs -eq 0 ]]; then
|
||||||
if [[ "$cur" == -* ]]; then
|
if [[ "$cur" == -* ]]; then
|
||||||
COMPREPLY=($(compgen -W "-force -download-limit= -downloader= -ignore-checksums -ignore-signatures -keyring= -skip-existing-packages -latest" -- ${cur}))
|
COMPREPLY=($(compgen -W "-force -download-limit= -downloader= -ignore-checksums -ignore-signatures -keyring= -skip-existing-packages" -- ${cur}))
|
||||||
else
|
else
|
||||||
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
|
COMPREPLY=($(compgen -W "$(__aptly_mirror_list)" -- ${cur}))
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
|
|||||||
configLocations := []string{homeLocation, "/usr/local/etc/aptly.conf", "/etc/aptly.conf"}
|
configLocations := []string{homeLocation, "/usr/local/etc/aptly.conf", "/etc/aptly.conf"}
|
||||||
|
|
||||||
for _, configLocation := range configLocations {
|
for _, configLocation := range configLocations {
|
||||||
|
// FIXME: check if exists, check if readable
|
||||||
err = utils.LoadConfig(configLocation, &utils.Config)
|
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||||
if os.IsPermission(err) || os.IsNotExist(err) {
|
if os.IsPermission(err) || os.IsNotExist(err) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -26,11 +26,8 @@ var (
|
|||||||
"Version",
|
"Version",
|
||||||
"Codename",
|
"Codename",
|
||||||
"Date",
|
"Date",
|
||||||
"Valid-Until",
|
|
||||||
"NotAutomatic",
|
"NotAutomatic",
|
||||||
"ButAutomaticUpgrades",
|
"ButAutomaticUpgrades",
|
||||||
"Acquire-By-Hash",
|
|
||||||
"Signed-By",
|
|
||||||
"Architectures",
|
"Architectures",
|
||||||
"Architecture",
|
"Architecture",
|
||||||
"Components",
|
"Components",
|
||||||
|
|||||||
+1
-33
@@ -172,39 +172,6 @@ func (l *PackageList) ForEach(handler func(*Package) error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterLatest creates a copy of the package list containing only the
|
|
||||||
// latest version for each package name/architecture pair.
|
|
||||||
func (l *PackageList) FilterLatest() (*PackageList, error) {
|
|
||||||
if l == nil {
|
|
||||||
return nil, fmt.Errorf("package list is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered := make(map[string]*Package, l.Len())
|
|
||||||
|
|
||||||
err := l.ForEach(func(p *Package) error {
|
|
||||||
key := p.Architecture + "|" + p.Name
|
|
||||||
|
|
||||||
if existing, found := filtered[key]; !found || CompareVersions(p.Version, existing.Version) > 0 {
|
|
||||||
filtered[key] = p
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := NewPackageListWithDuplicates(l.duplicatesAllowed, len(filtered))
|
|
||||||
|
|
||||||
for _, pkg := range filtered {
|
|
||||||
if err = result.Add(pkg); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForEachIndexed calls handler for each package in list in indexed order
|
// ForEachIndexed calls handler for each package in list in indexed order
|
||||||
func (l *PackageList) ForEachIndexed(handler func(*Package) error) error {
|
func (l *PackageList) ForEachIndexed(handler func(*Package) error) error {
|
||||||
if !l.indexed {
|
if !l.indexed {
|
||||||
@@ -631,6 +598,7 @@ func (l *PackageList) Filter(options FilterOptions) (*PackageList, error) {
|
|||||||
//
|
//
|
||||||
// when follow-all-variants is enabled, we need to try to expand anyway,
|
// when follow-all-variants is enabled, we need to try to expand anyway,
|
||||||
// as even if dependency is satisfied now, there might be other ways to satisfy dependency
|
// as even if dependency is satisfied now, there might be other ways to satisfy dependency
|
||||||
|
// FIXME: do not search twice
|
||||||
if result.Search(dep, false, true) != nil {
|
if result.Search(dep, false, true) != nil {
|
||||||
if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil {
|
if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil {
|
||||||
options.Progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true))
|
options.Progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true))
|
||||||
|
|||||||
@@ -503,52 +503,3 @@ func (s *PackageListSuite) TestArchitectures(c *C) {
|
|||||||
sort.Strings(archs)
|
sort.Strings(archs)
|
||||||
c.Check(archs, DeepEquals, []string{"amd64", "arm", "i386", "s390"})
|
c.Check(archs, DeepEquals, []string{"amd64", "arm", "i386", "s390"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackageListSuite) TestFilterLatest(c *C) {
|
|
||||||
list := NewPackageList()
|
|
||||||
|
|
||||||
older := packageStanza.Copy()
|
|
||||||
older["Version"] = "1.0"
|
|
||||||
olderPkg := NewPackageFromControlFile(older)
|
|
||||||
_ = list.Add(olderPkg)
|
|
||||||
|
|
||||||
newer := packageStanza.Copy()
|
|
||||||
newer["Version"] = "2.0"
|
|
||||||
newerPkg := NewPackageFromControlFile(newer)
|
|
||||||
_ = list.Add(newerPkg)
|
|
||||||
|
|
||||||
shared := packageStanza.Copy()
|
|
||||||
shared["Architecture"] = ArchitectureAll
|
|
||||||
shared["Version"] = "3.0"
|
|
||||||
shared["Package"] = "shared"
|
|
||||||
sharedPkg := NewPackageFromControlFile(shared)
|
|
||||||
_ = list.Add(sharedPkg)
|
|
||||||
|
|
||||||
filtered, err := list.FilterLatest()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(filtered.Len(), Equals, 2)
|
|
||||||
c.Check(filtered.Has(newerPkg), Equals, true)
|
|
||||||
c.Check(filtered.Has(sharedPkg), Equals, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PackageListSuite) TestFilterLatestPreservesDuplicatesFlag(c *C) {
|
|
||||||
list := NewPackageListWithDuplicates(true, 2)
|
|
||||||
|
|
||||||
_ = list.Add(NewPackageFromControlFile(packageStanza.Copy()))
|
|
||||||
|
|
||||||
another := packageStanza.Copy()
|
|
||||||
another["Version"] = "7.41-1"
|
|
||||||
_ = list.Add(NewPackageFromControlFile(another))
|
|
||||||
|
|
||||||
filtered, err := list.FilterLatest()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(filtered.duplicatesAllowed, Equals, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PackageListSuite) TestFilterLatestNil(c *C) {
|
|
||||||
var list *PackageList
|
|
||||||
|
|
||||||
filtered, err := list.FilterLatest()
|
|
||||||
c.Assert(err, ErrorMatches, "package list is nil")
|
|
||||||
c.Assert(filtered, IsNil)
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -59,7 +59,7 @@ func (s *PackageSuite) TestNewUdebFromPara(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PackageSuite) TestNewInstallerFromPara(c *C) {
|
func (s *PackageSuite) TestNewInstallerFromPara(c *C) {
|
||||||
repo, _ := NewRemoteRepo("yandex", "http://example.com/debian", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
repo, _ := NewRemoteRepo("yandex", "http://example.com/debian", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
downloader := http.NewFakeDownloader()
|
downloader := http.NewFakeDownloader()
|
||||||
downloader.ExpectResponse("http://example.com/debian/dists/squeeze/main/installer-i386/current/images/MANIFEST.udebs", "MANIFEST.udebs")
|
downloader.ExpectResponse("http://example.com/debian/dists/squeeze/main/installer-i386/current/images/MANIFEST.udebs", "MANIFEST.udebs")
|
||||||
downloader.ExpectResponse("http://example.com/debian/dists/squeeze/main/installer-i386/current/images/udeb.list", "udeb.list")
|
downloader.ExpectResponse("http://example.com/debian/dists/squeeze/main/installer-i386/current/images/udeb.list", "udeb.list")
|
||||||
|
|||||||
+57
-60
@@ -55,7 +55,6 @@ type PublishedRepo struct {
|
|||||||
Label string
|
Label string
|
||||||
Suite string
|
Suite string
|
||||||
Codename string
|
Codename string
|
||||||
Version string
|
|
||||||
// Architectures is a list of all architectures published
|
// Architectures is a list of all architectures published
|
||||||
Architectures []string
|
Architectures []string
|
||||||
// SourceKind is "local"/"repo"
|
// SourceKind is "local"/"repo"
|
||||||
@@ -83,11 +82,6 @@ type PublishedRepo struct {
|
|||||||
// Provide index files per hash also
|
// Provide index files per hash also
|
||||||
AcquireByHash bool
|
AcquireByHash bool
|
||||||
|
|
||||||
// An optional field containing a comma separated list
|
|
||||||
// of OpenPGP key fingerprints to be used
|
|
||||||
// for validating the next Release file
|
|
||||||
SignedBy string
|
|
||||||
|
|
||||||
// Support multiple distributions
|
// Support multiple distributions
|
||||||
MultiDist bool
|
MultiDist bool
|
||||||
|
|
||||||
@@ -527,7 +521,6 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
|
|||||||
"Origin": p.Origin,
|
"Origin": p.Origin,
|
||||||
"Suite": p.Suite,
|
"Suite": p.Suite,
|
||||||
"Codename": p.Codename,
|
"Codename": p.Codename,
|
||||||
"Version": p.Version,
|
|
||||||
"NotAutomatic": p.NotAutomatic,
|
"NotAutomatic": p.NotAutomatic,
|
||||||
"ButAutomaticUpgrades": p.ButAutomaticUpgrades,
|
"ButAutomaticUpgrades": p.ButAutomaticUpgrades,
|
||||||
"Prefix": p.Prefix,
|
"Prefix": p.Prefix,
|
||||||
@@ -537,7 +530,6 @@ func (p *PublishedRepo) MarshalJSON() ([]byte, error) {
|
|||||||
"Storage": p.Storage,
|
"Storage": p.Storage,
|
||||||
"SkipContents": p.SkipContents,
|
"SkipContents": p.SkipContents,
|
||||||
"AcquireByHash": p.AcquireByHash,
|
"AcquireByHash": p.AcquireByHash,
|
||||||
"SignedBy": p.SignedBy,
|
|
||||||
"MultiDist": p.MultiDist,
|
"MultiDist": p.MultiDist,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -612,6 +604,15 @@ func (p *PublishedRepo) Key() []byte {
|
|||||||
return []byte("U" + p.StoragePrefix() + ">>" + p.Distribution)
|
return []byte("U" + p.StoragePrefix() + ">>" + p.Distribution)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrefixPoolLockKey returns the task-queue resource key that serialises all
|
||||||
|
// publish operations sharing the same pool directory under storagePrefix.
|
||||||
|
// It must be held whenever a non-MultiDist publish may read or clean the
|
||||||
|
// shared pool, to prevent concurrent cleanup runs from deleting each other's
|
||||||
|
// files. See docs/Resource-Locking.md for the full key-namespace table.
|
||||||
|
func PrefixPoolLockKey(storagePrefix string) string {
|
||||||
|
return "P" + storagePrefix
|
||||||
|
}
|
||||||
|
|
||||||
// RefKey is a unique id for package reference list
|
// RefKey is a unique id for package reference list
|
||||||
func (p *PublishedRepo) RefKey(component string) []byte {
|
func (p *PublishedRepo) RefKey(component string) []byte {
|
||||||
return []byte("E" + p.UUID + component)
|
return []byte("E" + p.UUID + component)
|
||||||
@@ -1055,38 +1056,6 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass-through AppStream (DEP-11) files from snapshots
|
|
||||||
for component, item := range p.sourceItems {
|
|
||||||
if item.snapshot == nil || len(item.snapshot.AppStreamFiles) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := component + "/"
|
|
||||||
for relPath, poolPath := range item.snapshot.AppStreamFiles {
|
|
||||||
if !strings.HasPrefix(relPath, prefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
withinComponent := strings.TrimPrefix(relPath, prefix)
|
|
||||||
|
|
||||||
poolFile, err := packagePool.Open(poolPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to open AppStream file from pool: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bufWriter, err := indexes.SkelIndex(component, withinComponent).BufWriter()
|
|
||||||
if err != nil {
|
|
||||||
_ = poolFile.Close()
|
|
||||||
return fmt.Errorf("unable to generate AppStream index: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = bufio.NewReader(poolFile).WriteTo(bufWriter)
|
|
||||||
_ = poolFile.Close()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to write AppStream file: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
udebs := []bool{false}
|
udebs := []bool{false}
|
||||||
if hadUdebs {
|
if hadUdebs {
|
||||||
udebs = append(udebs, true)
|
udebs = append(udebs, true)
|
||||||
@@ -1111,12 +1080,6 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
if p.AcquireByHash {
|
if p.AcquireByHash {
|
||||||
release["Acquire-By-Hash"] = "yes"
|
release["Acquire-By-Hash"] = "yes"
|
||||||
}
|
}
|
||||||
if p.SignedBy != "" {
|
|
||||||
release["Signed-By"] = p.SignedBy
|
|
||||||
}
|
|
||||||
if p.Version != "" {
|
|
||||||
release["Version"] = p.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
var bufWriter *bufio.Writer
|
var bufWriter *bufio.Writer
|
||||||
bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter()
|
bufWriter, err = indexes.ReleaseIndex(component, arch, udeb).BufWriter()
|
||||||
@@ -1173,7 +1136,7 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
release["Label"] = p.GetLabel()
|
release["Label"] = p.GetLabel()
|
||||||
release["Suite"] = p.GetSuite()
|
release["Suite"] = p.GetSuite()
|
||||||
release["Codename"] = p.GetCodename()
|
release["Codename"] = p.GetCodename()
|
||||||
datetimeformat := "Mon, 2 Jan 2006 15:04:05 MST"
|
datetimeFormat := "Mon, 2 Jan 2006 15:04:05 MST"
|
||||||
|
|
||||||
publishDate := time.Now().UTC()
|
publishDate := time.Now().UTC()
|
||||||
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
|
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
|
||||||
@@ -1181,23 +1144,11 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP
|
|||||||
publishDate = time.Unix(sec, 0).UTC()
|
publishDate = time.Unix(sec, 0).UTC()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
release["Date"] = publishDate.Format(datetimeformat)
|
release["Date"] = publishDate.Format(datetimeFormat)
|
||||||
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
|
release["Architectures"] = strings.Join(utils.StrSlicesSubstract(p.Architectures, []string{ArchitectureSource}), " ")
|
||||||
if p.AcquireByHash {
|
if p.AcquireByHash {
|
||||||
release["Acquire-By-Hash"] = "yes"
|
release["Acquire-By-Hash"] = "yes"
|
||||||
}
|
}
|
||||||
if p.SignedBy != "" {
|
|
||||||
// "If the field is present, a client should only accept future updates
|
|
||||||
// to the repository that are signed with keys listed in the field.
|
|
||||||
// The field should be ignored if the Valid-Until field
|
|
||||||
// is not present or if it is expired."
|
|
||||||
release["Signed-By"] = p.SignedBy
|
|
||||||
// Let's use a century as a "forever" value.
|
|
||||||
release["Valid-Until"] = publishDate.AddDate(100, 0, 0).Format(datetimeformat)
|
|
||||||
}
|
|
||||||
if p.Version != "" {
|
|
||||||
release["Version"] = p.Version
|
|
||||||
}
|
|
||||||
release["Description"] = " Generated by aptly\n"
|
release["Description"] = " Generated by aptly\n"
|
||||||
release["MD5Sum"] = ""
|
release["MD5Sum"] = ""
|
||||||
release["SHA1"] = ""
|
release["SHA1"] = ""
|
||||||
@@ -1589,6 +1540,52 @@ func (collection *PublishedRepoCollection) listReferencedFilesByComponent(prefix
|
|||||||
return referencedFiles, nil
|
return referencedFiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CleanupAfterMultiDistToggle cleans up stale pool files left behind when the
|
||||||
|
// MultiDist flag is toggled on a published repository.
|
||||||
|
//
|
||||||
|
// - false→true: Publish() wrote packages into pool/<distribution>/<component>/
|
||||||
|
// but the old flat pool/<component>/ files were not removed because
|
||||||
|
// CleanupPrefixComponentFiles only scans the new MultiDist tree.
|
||||||
|
// A second pass with MultiDist=false cleans the legacy flat layout by
|
||||||
|
// reusing the existing orphan-detection logic (the repo is now MultiDist=true
|
||||||
|
// so it is excluded from the referenced-files scan, making its old pool
|
||||||
|
// entries appear orphaned).
|
||||||
|
//
|
||||||
|
// - true→false: Publish() wrote packages into pool/<component>/ but the old
|
||||||
|
// per-distribution pool/<distribution>/<component>/ directories were not
|
||||||
|
// removed. The orphan-detection approach cannot be used here because the
|
||||||
|
// repo's RefList still contains all packages (they just moved locations).
|
||||||
|
// Instead we directly remove each pool/<distribution>/<component>/ directory.
|
||||||
|
// This is safe because per-distribution pool dirs are exclusive to a single
|
||||||
|
// prefix+distribution combination — no other published repo can share them.
|
||||||
|
func (collection *PublishedRepoCollection) CleanupAfterMultiDistToggle(publishedStorageProvider aptly.PublishedStorageProvider,
|
||||||
|
published *PublishedRepo, prevMultiDist bool, cleanComponents []string, collectionFactory *CollectionFactory, progress aptly.Progress) error {
|
||||||
|
if prevMultiDist == published.MultiDist {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !prevMultiDist && published.MultiDist {
|
||||||
|
// false→true: use orphan-detection via the existing cleanup, but with
|
||||||
|
// MultiDist temporarily set to false so it scans the flat pool layout.
|
||||||
|
legacy := *published
|
||||||
|
legacy.MultiDist = false
|
||||||
|
return collection.CleanupPrefixComponentFiles(publishedStorageProvider, &legacy, cleanComponents, collectionFactory, progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// true→false: directly remove the per-distribution pool directories.
|
||||||
|
publishedStorage := publishedStorageProvider.GetPublishedStorage(published.Storage)
|
||||||
|
for _, component := range cleanComponents {
|
||||||
|
poolDir := filepath.Join(published.Prefix, "pool", published.Distribution, component)
|
||||||
|
if err := publishedStorage.RemoveDirs(poolDir, progress); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove the distribution-level pool dir if it is now empty.
|
||||||
|
distPoolDir := filepath.Join(published.Prefix, "pool", published.Distribution)
|
||||||
|
_ = publishedStorage.RemoveDirs(distPoolDir, progress)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair
|
// CleanupPrefixComponentFiles removes all unreferenced files in published storage under prefix/component pair
|
||||||
func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(publishedStorageProvider aptly.PublishedStorageProvider,
|
func (collection *PublishedRepoCollection) CleanupPrefixComponentFiles(publishedStorageProvider aptly.PublishedStorageProvider,
|
||||||
published *PublishedRepo, cleanComponents []string, collectionFactory *CollectionFactory, progress aptly.Progress) error {
|
published *PublishedRepo, cleanComponents []string, collectionFactory *CollectionFactory, progress aptly.Progress) error {
|
||||||
|
|||||||
+8
-79
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/aptly-dev/aptly/database"
|
"github.com/aptly-dev/aptly/database"
|
||||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||||
"github.com/aptly-dev/aptly/files"
|
"github.com/aptly-dev/aptly/files"
|
||||||
"github.com/aptly-dev/aptly/utils"
|
|
||||||
"github.com/ugorji/go/codec"
|
"github.com/ugorji/go/codec"
|
||||||
|
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
@@ -116,7 +115,7 @@ func (s *PublishedRepoSuite) SetUpTest(c *C) {
|
|||||||
|
|
||||||
s.reflist = NewPackageRefListFromPackageList(s.list)
|
s.reflist = NewPackageRefListFromPackageList(s.list)
|
||||||
|
|
||||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
repo.packageRefs = s.reflist
|
repo.packageRefs = s.reflist
|
||||||
_ = s.factory.RemoteRepoCollection().Add(repo)
|
_ = s.factory.RemoteRepoCollection().Add(repo)
|
||||||
|
|
||||||
@@ -426,81 +425,6 @@ func (s *PublishedRepoSuite) TestPublish(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublishedRepoSuite) TestPublishAppStream(c *C) {
|
|
||||||
// Components + icons
|
|
||||||
content1 := []byte("DEP-11 test content for Components-amd64.yml.gz")
|
|
||||||
tmpFile1 := filepath.Join(c.MkDir(), "Components-amd64.yml.gz")
|
|
||||||
c.Assert(os.WriteFile(tmpFile1, content1, 0644), IsNil)
|
|
||||||
|
|
||||||
checksums1 := utils.ChecksumInfo{Size: int64(len(content1))}
|
|
||||||
poolPath1, err := s.packagePool.Import(tmpFile1, "Components-amd64.yml.gz", &checksums1, false, s.cs)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
content2 := []byte("DEP-11 icons tar data")
|
|
||||||
tmpFile2 := filepath.Join(c.MkDir(), "icons-48x48.tar.gz")
|
|
||||||
c.Assert(os.WriteFile(tmpFile2, content2, 0644), IsNil)
|
|
||||||
|
|
||||||
checksums2 := utils.ChecksumInfo{Size: int64(len(content2))}
|
|
||||||
poolPath2, err := s.packagePool.Import(tmpFile2, "icons-48x48.tar.gz", &checksums2, false, s.cs)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
// Include contrib file that should be skipped
|
|
||||||
contribContent := []byte("DEP-11 contrib content")
|
|
||||||
tmpFile3 := filepath.Join(c.MkDir(), "Components-contrib.yml.gz")
|
|
||||||
c.Assert(os.WriteFile(tmpFile3, contribContent, 0644), IsNil)
|
|
||||||
|
|
||||||
checksums3 := utils.ChecksumInfo{Size: int64(len(contribContent))}
|
|
||||||
poolPath3, err := s.packagePool.Import(tmpFile3, "Components-contrib.yml.gz", &checksums3, false, s.cs)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
s.snapshot.AppStreamFiles = map[string]string{
|
|
||||||
"main/dep11/Components-amd64.yml.gz": poolPath1,
|
|
||||||
"main/dep11/icons-48x48.tar.gz": poolPath2,
|
|
||||||
"contrib/dep11/Components-amd64.yml.gz": poolPath3,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
// Both main files should exist
|
|
||||||
appstreamPath1 := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/dep11/Components-amd64.yml.gz")
|
|
||||||
c.Check(appstreamPath1, PathExists)
|
|
||||||
actual1, err := os.ReadFile(appstreamPath1)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(actual1, DeepEquals, content1)
|
|
||||||
|
|
||||||
appstreamPath2 := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main/dep11/icons-48x48.tar.gz")
|
|
||||||
c.Check(appstreamPath2, PathExists)
|
|
||||||
actual2, err := os.ReadFile(appstreamPath2)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(actual2, DeepEquals, content2)
|
|
||||||
|
|
||||||
// Contrib file should not appear
|
|
||||||
contribPath := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/contrib/dep11/Components-amd64.yml.gz")
|
|
||||||
_, statErr := os.Stat(contribPath)
|
|
||||||
c.Check(os.IsNotExist(statErr), Equals, true)
|
|
||||||
|
|
||||||
// Release file should reference AppStream files
|
|
||||||
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"))
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
defer func() { _ = rf.Close() }()
|
|
||||||
|
|
||||||
cfr := NewControlFileReader(rf, true, false)
|
|
||||||
st, err := cfr.ReadStanza()
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
c.Check(st["MD5Sum"], Matches, "(?s).*main/dep11/Components-amd64\\.yml\\.gz.*")
|
|
||||||
c.Check(st["SHA256"], Matches, "(?s).*main/dep11/Components-amd64\\.yml\\.gz.*")
|
|
||||||
|
|
||||||
// Pool open error
|
|
||||||
s.snapshot.AppStreamFiles = map[string]string{
|
|
||||||
"main/dep11/Components-amd64.yml.gz": "nonexistent/pool/path",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
|
|
||||||
c.Assert(err, ErrorMatches, "unable to open AppStream file from pool.*")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
|
func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
|
||||||
err := s.repo.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
|
err := s.repo.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@@ -873,7 +797,10 @@ func (s *PublishedRepoCollectionSuite) TestListReferencedFiles(c *C) {
|
|||||||
snap3 := NewSnapshotFromRefList("snap3", []*Snapshot{}, s.snap2.RefList(), "desc3")
|
snap3 := NewSnapshotFromRefList("snap3", []*Snapshot{}, s.snap2.RefList(), "desc3")
|
||||||
_ = s.snapshotCollection.Add(snap3)
|
_ = s.snapshotCollection.Add(snap3)
|
||||||
|
|
||||||
// Ensure that adding a second publish point with matching files doesn't give duplicate results.
|
// When a second publish point references the same package (snap3 is a clone of snap2,
|
||||||
|
// both containing p3/lonely-strangers), listReferencedFilesByComponent deduplicates by
|
||||||
|
// package ref so the file appears only once. StrSlicesSubstract handles a single entry
|
||||||
|
// correctly, so no duplicate is needed for cleanup safety.
|
||||||
repo3, err := NewPublishedRepo("", "", "anaconda-2", []string{}, []string{"main"}, []interface{}{snap3}, s.factory, false)
|
repo3, err := NewPublishedRepo("", "", "anaconda-2", []string{}, []string{"main"}, []interface{}{snap3}, s.factory, false)
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
c.Check(s.collection.Add(repo3), IsNil)
|
c.Check(s.collection.Add(repo3), IsNil)
|
||||||
@@ -888,7 +815,9 @@ func (s *PublishedRepoCollectionSuite) TestListReferencedFiles(c *C) {
|
|||||||
"a/alien-arena/alien-arena-common_7.40-2_i386.deb",
|
"a/alien-arena/alien-arena-common_7.40-2_i386.deb",
|
||||||
"a/alien-arena/mars-invaders_7.40-2_i386.deb",
|
"a/alien-arena/mars-invaders_7.40-2_i386.deb",
|
||||||
},
|
},
|
||||||
"main": {"a/alien-arena/lonely-strangers_7.40-2_i386.deb"},
|
"main": {
|
||||||
|
"a/alien-arena/lonely-strangers_7.40-2_i386.deb",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-101
@@ -70,10 +70,6 @@ type RemoteRepo struct {
|
|||||||
DownloadUdebs bool
|
DownloadUdebs bool
|
||||||
// Should we download installer files?
|
// Should we download installer files?
|
||||||
DownloadInstaller bool
|
DownloadInstaller bool
|
||||||
// Should we download AppStream (DEP-11) metadata?
|
|
||||||
DownloadAppStream bool
|
|
||||||
// AppStream files: relative path (e.g. "main/dep11/Components-amd64.yml.gz") → pool path
|
|
||||||
AppStreamFiles map[string]string `codec:"AppStreamFiles" json:"-"`
|
|
||||||
// Packages for json output
|
// Packages for json output
|
||||||
Packages []string `codec:"-" json:",omitempty"`
|
Packages []string `codec:"-" json:",omitempty"`
|
||||||
// "Snapshot" of current list of packages
|
// "Snapshot" of current list of packages
|
||||||
@@ -86,7 +82,7 @@ type RemoteRepo struct {
|
|||||||
|
|
||||||
// NewRemoteRepo creates new instance of Debian remote repository with specified params
|
// NewRemoteRepo creates new instance of Debian remote repository with specified params
|
||||||
func NewRemoteRepo(name string, archiveRoot string, distribution string, components []string,
|
func NewRemoteRepo(name string, archiveRoot string, distribution string, components []string,
|
||||||
architectures []string, downloadSources bool, downloadUdebs bool, downloadInstaller bool, downloadAppStream bool) (*RemoteRepo, error) {
|
architectures []string, downloadSources bool, downloadUdebs bool, downloadInstaller bool) (*RemoteRepo, error) {
|
||||||
result := &RemoteRepo{
|
result := &RemoteRepo{
|
||||||
UUID: uuid.NewString(),
|
UUID: uuid.NewString(),
|
||||||
Name: name,
|
Name: name,
|
||||||
@@ -97,7 +93,6 @@ func NewRemoteRepo(name string, archiveRoot string, distribution string, compone
|
|||||||
DownloadSources: downloadSources,
|
DownloadSources: downloadSources,
|
||||||
DownloadUdebs: downloadUdebs,
|
DownloadUdebs: downloadUdebs,
|
||||||
DownloadInstaller: downloadInstaller,
|
DownloadInstaller: downloadInstaller,
|
||||||
DownloadAppStream: downloadAppStream,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := result.prepare()
|
err := result.prepare()
|
||||||
@@ -116,9 +111,6 @@ func NewRemoteRepo(name string, archiveRoot string, distribution string, compone
|
|||||||
if result.DownloadUdebs {
|
if result.DownloadUdebs {
|
||||||
return nil, fmt.Errorf("debian-installer udebs aren't supported for flat repos")
|
return nil, fmt.Errorf("debian-installer udebs aren't supported for flat repos")
|
||||||
}
|
}
|
||||||
if result.DownloadAppStream {
|
|
||||||
return nil, fmt.Errorf("AppStream (DEP-11) metadata isn't supported for flat repos")
|
|
||||||
}
|
|
||||||
result.Components = nil
|
result.Components = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +147,6 @@ func (repo *RemoteRepo) String() string {
|
|||||||
if repo.DownloadInstaller {
|
if repo.DownloadInstaller {
|
||||||
srcFlag += " [installer]"
|
srcFlag += " [installer]"
|
||||||
}
|
}
|
||||||
if repo.DownloadAppStream {
|
|
||||||
srcFlag += " [appstream]"
|
|
||||||
}
|
|
||||||
distribution := repo.Distribution
|
distribution := repo.Distribution
|
||||||
if distribution == "" {
|
if distribution == "" {
|
||||||
distribution = "./"
|
distribution = "./"
|
||||||
@@ -275,82 +264,6 @@ func (repo *RemoteRepo) InstallerPath(component string, architecture string) str
|
|||||||
return fmt.Sprintf("%s/installer-%s/current/images/SHA256SUMS", component, architecture)
|
return fmt.Sprintf("%s/installer-%s/current/images/SHA256SUMS", component, architecture)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppStreamPaths returns dep11 file paths from ReleaseFiles for a given component
|
|
||||||
func (repo *RemoteRepo) AppStreamPaths(component string) []string {
|
|
||||||
prefix := component + "/dep11/"
|
|
||||||
var paths []string
|
|
||||||
for path := range repo.ReleaseFiles {
|
|
||||||
if strings.HasPrefix(path, prefix) {
|
|
||||||
paths = append(paths, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(paths)
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
|
|
||||||
// DownloadAppStreamFiles downloads AppStream (DEP-11) metadata files and imports them into the pool
|
|
||||||
func (repo *RemoteRepo) DownloadAppStreamFiles(progress aptly.Progress, d aptly.Downloader,
|
|
||||||
packagePool aptly.PackagePool, checksumStorage aptly.ChecksumStorage, ignoreChecksums bool) error {
|
|
||||||
|
|
||||||
repo.AppStreamFiles = make(map[string]string)
|
|
||||||
|
|
||||||
for _, component := range repo.Components {
|
|
||||||
paths := repo.AppStreamPaths(component)
|
|
||||||
if len(paths) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, relativePath := range paths {
|
|
||||||
info, ok := repo.ReleaseFiles[relativePath]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
url := repo.IndexesRootURL().ResolveReference(&url.URL{Path: relativePath}).String()
|
|
||||||
|
|
||||||
if progress != nil {
|
|
||||||
progress.Printf("Downloading AppStream file %s...\n", relativePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDir, err := os.MkdirTemp("", "aptly-appstream-*")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to create temp dir for AppStream file %s: %s", relativePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempPath := path.Join(tempDir, path.Base(relativePath))
|
|
||||||
|
|
||||||
var expected *utils.ChecksumInfo
|
|
||||||
if !ignoreChecksums {
|
|
||||||
expected = &info
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.DownloadWithChecksum(gocontext.TODO(), url, tempPath, expected, ignoreChecksums)
|
|
||||||
if err != nil {
|
|
||||||
_ = os.RemoveAll(tempDir)
|
|
||||||
// Skip files that are not found (some repos list dep11 files but don't serve them)
|
|
||||||
if herr, ok := err.(*http.Error); ok && (herr.Code == 404 || herr.Code == 403) {
|
|
||||||
if progress != nil {
|
|
||||||
progress.ColoredPrintf("@y[!]@| @!skipping AppStream file %s: not found@|", relativePath)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Errorf("unable to download AppStream file %s: %s", relativePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
basename := path.Base(relativePath)
|
|
||||||
poolPath, err := packagePool.Import(tempPath, basename, &info, true, checksumStorage)
|
|
||||||
_ = os.RemoveAll(tempDir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to import AppStream file %s: %s", relativePath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repo.AppStreamFiles[relativePath] = poolPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PackageURL returns URL of package file relative to repository root
|
// PackageURL returns URL of package file relative to repository root
|
||||||
// architecture
|
// architecture
|
||||||
func (repo *RemoteRepo) PackageURL(filename string) *url.URL {
|
func (repo *RemoteRepo) PackageURL(filename string) *url.URL {
|
||||||
@@ -699,19 +612,7 @@ func (repo *RemoteRepo) ApplyFilter(dependencyOptions int, filterQuery PackageQu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuildDownloadQueue builds queue, discards current PackageList
|
// BuildDownloadQueue builds queue, discards current PackageList
|
||||||
func (repo *RemoteRepo) BuildDownloadQueue(packagePool aptly.PackagePool, packageCollection *PackageCollection, checksumStorage aptly.ChecksumStorage, skipExistingPackages, latestOnly bool) (queue []PackageDownloadTask, downloadSize int64, err error) {
|
func (repo *RemoteRepo) BuildDownloadQueue(packagePool aptly.PackagePool, packageCollection *PackageCollection, checksumStorage aptly.ChecksumStorage, skipExistingPackages bool) (queue []PackageDownloadTask, downloadSize int64, err error) {
|
||||||
if repo.packageList == nil {
|
|
||||||
err = fmt.Errorf("package list is empty, please (re)download package indexes")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if latestOnly {
|
|
||||||
repo.packageList, err = repo.packageList.FilterLatest()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
queue = make([]PackageDownloadTask, 0, repo.packageList.Len())
|
queue = make([]PackageDownloadTask, 0, repo.packageList.Len())
|
||||||
seen := make(map[string]int, repo.packageList.Len())
|
seen := make(map[string]int, repo.packageList.Len())
|
||||||
|
|
||||||
|
|||||||
+26
-164
@@ -2,7 +2,6 @@ package deb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -91,8 +90,8 @@ type RemoteRepoSuite struct {
|
|||||||
var _ = Suite(&RemoteRepoSuite{})
|
var _ = Suite(&RemoteRepoSuite{})
|
||||||
|
|
||||||
func (s *RemoteRepoSuite) SetUpTest(c *C) {
|
func (s *RemoteRepoSuite) SetUpTest(c *C) {
|
||||||
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
s.flat, _ = NewRemoteRepo("exp42", "http://repos.express42.com/virool/precise/", "./", []string{}, []string{}, false, false, false, false)
|
s.flat, _ = NewRemoteRepo("exp42", "http://repos.express42.com/virool/precise/", "./", []string{}, []string{}, false, false, false)
|
||||||
s.downloader = http.NewFakeDownloader().ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile)
|
s.downloader = http.NewFakeDownloader().ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/Release", exampleReleaseFile)
|
||||||
s.progress = console.NewProgress(false)
|
s.progress = console.NewProgress(false)
|
||||||
s.db, _ = goleveldb.NewOpenDB(c.MkDir())
|
s.db, _ = goleveldb.NewOpenDB(c.MkDir())
|
||||||
@@ -109,7 +108,7 @@ func (s *RemoteRepoSuite) TearDownTest(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRepoSuite) TestInvalidURL(c *C) {
|
func (s *RemoteRepoSuite) TestInvalidURL(c *C) {
|
||||||
_, err := NewRemoteRepo("s", "http://lolo%2", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
_, err := NewRemoteRepo("s", "http://lolo%2", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
c.Assert(err, ErrorMatches, ".*(hexadecimal escape in host|percent-encoded characters in host|invalid URL escape).*")
|
c.Assert(err, ErrorMatches, ".*(hexadecimal escape in host|percent-encoded characters in host|invalid URL escape).*")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,15 +117,12 @@ func (s *RemoteRepoSuite) TestFlatCreation(c *C) {
|
|||||||
c.Check(s.flat.Distribution, Equals, "./")
|
c.Check(s.flat.Distribution, Equals, "./")
|
||||||
c.Check(s.flat.Components, IsNil)
|
c.Check(s.flat.Components, IsNil)
|
||||||
|
|
||||||
flat2, _ := NewRemoteRepo("flat2", "http://pkg.jenkins-ci.org/debian-stable", "binary/", []string{}, []string{}, false, false, false, false)
|
flat2, _ := NewRemoteRepo("flat2", "http://pkg.jenkins-ci.org/debian-stable", "binary/", []string{}, []string{}, false, false, false)
|
||||||
c.Check(flat2.IsFlat(), Equals, true)
|
c.Check(flat2.IsFlat(), Equals, true)
|
||||||
c.Check(flat2.Distribution, Equals, "./binary/")
|
c.Check(flat2.Distribution, Equals, "./binary/")
|
||||||
|
|
||||||
_, err := NewRemoteRepo("fl", "http://some.repo/", "./", []string{"main"}, []string{}, false, false, false, false)
|
_, err := NewRemoteRepo("fl", "http://some.repo/", "./", []string{"main"}, []string{}, false, false, false)
|
||||||
c.Check(err, ErrorMatches, "components aren't supported for flat repos")
|
c.Check(err, ErrorMatches, "components aren't supported for flat repos")
|
||||||
|
|
||||||
_, err = NewRemoteRepo("fl", "http://some.repo/", "./", []string{}, []string{}, false, false, false, true)
|
|
||||||
c.Check(err, ErrorMatches, "AppStream \\(DEP-11\\) metadata isn't supported for flat repos")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRepoSuite) TestString(c *C) {
|
func (s *RemoteRepoSuite) TestString(c *C) {
|
||||||
@@ -139,43 +135,6 @@ func (s *RemoteRepoSuite) TestString(c *C) {
|
|||||||
s.flat.DownloadSources = true
|
s.flat.DownloadSources = true
|
||||||
c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze [src] [udeb] [installer]")
|
c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze [src] [udeb] [installer]")
|
||||||
c.Check(s.flat.String(), Equals, "[exp42]: http://repos.express42.com/virool/precise/ ./ [src]")
|
c.Check(s.flat.String(), Equals, "[exp42]: http://repos.express42.com/virool/precise/ ./ [src]")
|
||||||
|
|
||||||
s.repo.DownloadAppStream = true
|
|
||||||
c.Check(s.repo.String(), Equals, "[yandex]: http://mirror.yandex.ru/debian/ squeeze [src] [udeb] [installer] [appstream]")
|
|
||||||
|
|
||||||
// AppStream is not supported for flat repos, so no flat test here
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RemoteRepoSuite) TestAppStreamPaths(c *C) {
|
|
||||||
s.repo.ReleaseFiles = nil
|
|
||||||
c.Check(s.repo.AppStreamPaths("main"), DeepEquals, []string(nil))
|
|
||||||
|
|
||||||
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
|
|
||||||
"main/binary-amd64/Packages": {Size: 100},
|
|
||||||
"main/dep11/Components-amd64.yml.gz": {Size: 200},
|
|
||||||
"main/dep11/Components-i386.yml.gz": {Size: 300},
|
|
||||||
"main/dep11/icons-48x48.tar.gz": {Size: 400},
|
|
||||||
"contrib/dep11/Components-amd64.yml.gz": {Size: 500},
|
|
||||||
"main/source/Sources": {Size: 600},
|
|
||||||
}
|
|
||||||
|
|
||||||
paths := s.repo.AppStreamPaths("main")
|
|
||||||
c.Check(paths, DeepEquals, []string{
|
|
||||||
"main/dep11/Components-amd64.yml.gz",
|
|
||||||
"main/dep11/Components-i386.yml.gz",
|
|
||||||
"main/dep11/icons-48x48.tar.gz",
|
|
||||||
})
|
|
||||||
|
|
||||||
paths = s.repo.AppStreamPaths("contrib")
|
|
||||||
c.Check(paths, DeepEquals, []string{
|
|
||||||
"contrib/dep11/Components-amd64.yml.gz",
|
|
||||||
})
|
|
||||||
|
|
||||||
paths = s.repo.AppStreamPaths("non-free")
|
|
||||||
c.Check(paths, DeepEquals, []string(nil))
|
|
||||||
|
|
||||||
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{}
|
|
||||||
c.Check(s.repo.AppStreamPaths("main"), DeepEquals, []string(nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRepoSuite) TestNumPackages(c *C) {
|
func (s *RemoteRepoSuite) TestNumPackages(c *C) {
|
||||||
@@ -277,13 +236,13 @@ func (s *RemoteRepoSuite) TestFetchNullVerifier2(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRepoSuite) TestFetchWrongArchitecture(c *C) {
|
func (s *RemoteRepoSuite) TestFetchWrongArchitecture(c *C) {
|
||||||
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"}, false, false, false, false)
|
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{"xyz"}, false, false, false)
|
||||||
err := s.repo.Fetch(s.downloader, nil, true)
|
err := s.repo.Fetch(s.downloader, nil, true)
|
||||||
c.Assert(err, ErrorMatches, "architecture xyz not available in repo.*")
|
c.Assert(err, ErrorMatches, "architecture xyz not available in repo.*")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRepoSuite) TestFetchWrongComponent(c *C) {
|
func (s *RemoteRepoSuite) TestFetchWrongComponent(c *C) {
|
||||||
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"}, false, false, false, false)
|
s.repo, _ = NewRemoteRepo("s", "http://mirror.yandex.ru/debian/", "squeeze", []string{"xyz"}, []string{"i386"}, false, false, false)
|
||||||
err := s.repo.Fetch(s.downloader, nil, true)
|
err := s.repo.Fetch(s.downloader, nil, true)
|
||||||
c.Assert(err, ErrorMatches, "component xyz not available in repo.*")
|
c.Assert(err, ErrorMatches, "component xyz not available in repo.*")
|
||||||
}
|
}
|
||||||
@@ -322,7 +281,7 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(s.downloader.Empty(), Equals, true)
|
c.Assert(s.downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(3))
|
c.Check(size, Equals, int64(3))
|
||||||
c.Check(queue, HasLen, 1)
|
c.Check(queue, HasLen, 1)
|
||||||
@@ -349,7 +308,7 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(s.downloader.Empty(), Equals, true)
|
c.Assert(s.downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
|
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(0))
|
c.Check(size, Equals, int64(0))
|
||||||
c.Check(queue, HasLen, 0)
|
c.Check(queue, HasLen, 0)
|
||||||
@@ -370,7 +329,7 @@ func (s *RemoteRepoSuite) TestDownload(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(s.downloader.Empty(), Equals, true)
|
c.Assert(s.downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(3))
|
c.Check(size, Equals, int64(3))
|
||||||
c.Check(queue, HasLen, 1)
|
c.Check(queue, HasLen, 1)
|
||||||
@@ -397,7 +356,7 @@ func (s *RemoteRepoSuite) TestDownloadWithInstaller(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(s.downloader.Empty(), Equals, true)
|
c.Assert(s.downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(3)+int64(len(exampleInstallerManifestFile)))
|
c.Check(size, Equals, int64(3)+int64(len(exampleInstallerManifestFile)))
|
||||||
c.Check(queue, HasLen, 2)
|
c.Check(queue, HasLen, 2)
|
||||||
@@ -423,35 +382,6 @@ func (s *RemoteRepoSuite) TestDownloadWithInstaller(c *C) {
|
|||||||
c.Check(pkg.Name, Equals, "installer")
|
c.Check(pkg.Name, Equals, "installer")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRepoSuite) TestBuildDownloadQueueLatestOnly(c *C) {
|
|
||||||
s.repo.Architectures = []string{"i386"}
|
|
||||||
|
|
||||||
err := s.repo.Fetch(s.downloader, nil, true)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.bz2", &http.Error{Code: 404})
|
|
||||||
s.downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages.gz", &http.Error{Code: 404})
|
|
||||||
s.downloader.ExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/binary-i386/Packages", examplePackagesFile)
|
|
||||||
|
|
||||||
err = s.repo.DownloadPackageIndexes(s.progress, s.downloader, nil, s.collectionFactory, true, false)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(s.downloader.Empty(), Equals, true)
|
|
||||||
|
|
||||||
stanza := packageStanza.Copy()
|
|
||||||
stanza["Package"] = "amanda-client"
|
|
||||||
stanza["Version"] = "1:3.4.0-1"
|
|
||||||
stanza["Filename"] = "pool/main/a/amanda/amanda-client_3.4.0-1_i386.deb"
|
|
||||||
|
|
||||||
newest := NewPackageFromControlFile(stanza)
|
|
||||||
_ = s.repo.packageList.Add(newest)
|
|
||||||
|
|
||||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, true)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(queue, HasLen, 1)
|
|
||||||
c.Check(queue[0].File.DownloadURL(), Equals, "pool/main/a/amanda/amanda-client_3.4.0-1_i386.deb")
|
|
||||||
c.Check(size, Equals, int64(187518))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
||||||
s.repo.Architectures = []string{"i386"}
|
s.repo.Architectures = []string{"i386"}
|
||||||
s.repo.DownloadSources = true
|
s.repo.DownloadSources = true
|
||||||
@@ -470,7 +400,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(s.downloader.Empty(), Equals, true)
|
c.Assert(s.downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
queue, size, err := s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(15))
|
c.Check(size, Equals, int64(15))
|
||||||
c.Check(queue, HasLen, 4)
|
c.Check(queue, HasLen, 4)
|
||||||
@@ -514,7 +444,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(s.downloader.Empty(), Equals, true)
|
c.Assert(s.downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
|
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(0))
|
c.Check(size, Equals, int64(0))
|
||||||
c.Check(queue, HasLen, 0)
|
c.Check(queue, HasLen, 0)
|
||||||
@@ -539,7 +469,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSources(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(s.downloader.Empty(), Equals, true)
|
c.Assert(s.downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
queue, size, err = s.repo.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(15))
|
c.Check(size, Equals, int64(15))
|
||||||
c.Check(queue, HasLen, 4)
|
c.Check(queue, HasLen, 4)
|
||||||
@@ -563,7 +493,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(downloader.Empty(), Equals, true)
|
c.Assert(downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(3))
|
c.Check(size, Equals, int64(3))
|
||||||
c.Check(queue, HasLen, 1)
|
c.Check(queue, HasLen, 1)
|
||||||
@@ -591,7 +521,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(downloader.Empty(), Equals, true)
|
c.Assert(downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
|
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(0))
|
c.Check(size, Equals, int64(0))
|
||||||
c.Check(queue, HasLen, 0)
|
c.Check(queue, HasLen, 0)
|
||||||
@@ -613,7 +543,7 @@ func (s *RemoteRepoSuite) TestDownloadFlat(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(downloader.Empty(), Equals, true)
|
c.Assert(downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(3))
|
c.Check(size, Equals, int64(3))
|
||||||
c.Check(queue, HasLen, 1)
|
c.Check(queue, HasLen, 1)
|
||||||
@@ -644,7 +574,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(downloader.Empty(), Equals, true)
|
c.Assert(downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
queue, size, err := s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(15))
|
c.Check(size, Equals, int64(15))
|
||||||
c.Check(queue, HasLen, 4)
|
c.Check(queue, HasLen, 4)
|
||||||
@@ -690,7 +620,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(downloader.Empty(), Equals, true)
|
c.Assert(downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true, false)
|
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, true)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(0))
|
c.Check(size, Equals, int64(0))
|
||||||
c.Check(queue, HasLen, 0)
|
c.Check(queue, HasLen, 0)
|
||||||
@@ -716,7 +646,7 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(downloader.Empty(), Equals, true)
|
c.Assert(downloader.Empty(), Equals, true)
|
||||||
|
|
||||||
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false, false)
|
queue, size, err = s.flat.BuildDownloadQueue(s.packagePool, s.collectionFactory.PackageCollection(), s.cs, false)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Check(size, Equals, int64(15))
|
c.Check(size, Equals, int64(15))
|
||||||
c.Check(queue, HasLen, 4)
|
c.Check(queue, HasLen, 4)
|
||||||
@@ -725,74 +655,6 @@ func (s *RemoteRepoSuite) TestDownloadWithSourcesFlat(c *C) {
|
|||||||
c.Assert(s.flat.packageRefs, NotNil)
|
c.Assert(s.flat.packageRefs, NotNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRepoSuite) TestDownloadAppStreamFiles(c *C) {
|
|
||||||
// No dep11 entries
|
|
||||||
s.repo.Components = []string{"main"}
|
|
||||||
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
|
|
||||||
"main/binary-amd64/Packages": {Size: 100},
|
|
||||||
"main/source/Sources": {Size: 200},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.repo.DownloadAppStreamFiles(s.progress, s.downloader, s.packagePool, s.cs, false)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(s.repo.AppStreamFiles, HasLen, 0)
|
|
||||||
|
|
||||||
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
|
|
||||||
"main/dep11/Components-amd64.yml.gz": {Size: 16},
|
|
||||||
"main/dep11/icons-48x48.tar.gz": {Size: 16},
|
|
||||||
"main/binary-amd64/Packages": {Size: 100},
|
|
||||||
}
|
|
||||||
|
|
||||||
downloader := http.NewFakeDownloader()
|
|
||||||
downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/Components-amd64.yml.gz", "dep11-components")
|
|
||||||
downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/icons-48x48.tar.gz", "dep11-icons-data")
|
|
||||||
|
|
||||||
err = s.repo.DownloadAppStreamFiles(s.progress, downloader, s.packagePool, s.cs, false)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(s.repo.AppStreamFiles, HasLen, 2)
|
|
||||||
c.Check(s.repo.AppStreamFiles["main/dep11/Components-amd64.yml.gz"], Not(Equals), "")
|
|
||||||
c.Check(s.repo.AppStreamFiles["main/dep11/icons-48x48.tar.gz"], Not(Equals), "")
|
|
||||||
|
|
||||||
// 404 skipped
|
|
||||||
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
|
|
||||||
"main/dep11/Components-amd64.yml.gz": {Size: 16},
|
|
||||||
"main/dep11/icons-48x48.tar.gz": {Size: 15},
|
|
||||||
}
|
|
||||||
|
|
||||||
downloader = http.NewFakeDownloader()
|
|
||||||
downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/Components-amd64.yml.gz", "dep11-components")
|
|
||||||
downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/icons-48x48.tar.gz", &http.Error{Code: 404, URL: "http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/icons-48x48.tar.gz"})
|
|
||||||
|
|
||||||
err = s.repo.DownloadAppStreamFiles(s.progress, downloader, s.packagePool, s.cs, false)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(s.repo.AppStreamFiles, HasLen, 1)
|
|
||||||
c.Check(s.repo.AppStreamFiles["main/dep11/Components-amd64.yml.gz"], Not(Equals), "")
|
|
||||||
|
|
||||||
// Generic download error propagated
|
|
||||||
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
|
|
||||||
"main/dep11/Components-amd64.yml.gz": {Size: 18},
|
|
||||||
}
|
|
||||||
|
|
||||||
downloader = http.NewFakeDownloader()
|
|
||||||
downloader.ExpectError("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/Components-amd64.yml.gz", fmt.Errorf("connection refused"))
|
|
||||||
|
|
||||||
err = s.repo.DownloadAppStreamFiles(s.progress, downloader, s.packagePool, s.cs, false)
|
|
||||||
c.Assert(err, ErrorMatches, "unable to download AppStream file.*connection refused")
|
|
||||||
|
|
||||||
// Bypass checksum validation
|
|
||||||
s.repo.ReleaseFiles = map[string]utils.ChecksumInfo{
|
|
||||||
"main/dep11/Components-amd64.yml.gz": {Size: 999, MD5: "bad", SHA256: "bad"},
|
|
||||||
}
|
|
||||||
|
|
||||||
downloader = http.NewFakeDownloader()
|
|
||||||
downloader.AnyExpectResponse("http://mirror.yandex.ru/debian/dists/squeeze/main/dep11/Components-amd64.yml.gz", "dep11-components")
|
|
||||||
|
|
||||||
err = s.repo.DownloadAppStreamFiles(s.progress, downloader, s.packagePool, s.cs, true)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(s.repo.AppStreamFiles, HasLen, 1)
|
|
||||||
c.Check(s.repo.AppStreamFiles["main/dep11/Components-amd64.yml.gz"], Not(Equals), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoteRepoCollectionSuite struct {
|
type RemoteRepoCollectionSuite struct {
|
||||||
PackageListMixinSuite
|
PackageListMixinSuite
|
||||||
db database.Storage
|
db database.Storage
|
||||||
@@ -815,7 +677,7 @@ func (s *RemoteRepoCollectionSuite) TestAddByName(c *C) {
|
|||||||
_, err := s.collection.ByName("yandex")
|
_, err := s.collection.ByName("yandex")
|
||||||
c.Assert(err, ErrorMatches, "*.not found")
|
c.Assert(err, ErrorMatches, "*.not found")
|
||||||
|
|
||||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
c.Assert(s.collection.Add(repo), IsNil)
|
c.Assert(s.collection.Add(repo), IsNil)
|
||||||
c.Assert(s.collection.Add(repo), ErrorMatches, ".*already exists")
|
c.Assert(s.collection.Add(repo), ErrorMatches, ".*already exists")
|
||||||
|
|
||||||
@@ -833,7 +695,7 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) {
|
|||||||
_, err := s.collection.ByUUID("some-uuid")
|
_, err := s.collection.ByUUID("some-uuid")
|
||||||
c.Assert(err, ErrorMatches, "*.not found")
|
c.Assert(err, ErrorMatches, "*.not found")
|
||||||
|
|
||||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
c.Assert(s.collection.Add(repo), IsNil)
|
c.Assert(s.collection.Add(repo), IsNil)
|
||||||
|
|
||||||
r, err := s.collection.ByUUID(repo.UUID)
|
r, err := s.collection.ByUUID(repo.UUID)
|
||||||
@@ -847,7 +709,7 @@ func (s *RemoteRepoCollectionSuite) TestByUUID(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
||||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
c.Assert(s.collection.Update(repo), IsNil)
|
c.Assert(s.collection.Update(repo), IsNil)
|
||||||
|
|
||||||
collection := NewRemoteRepoCollection(s.db)
|
collection := NewRemoteRepoCollection(s.db)
|
||||||
@@ -868,7 +730,7 @@ func (s *RemoteRepoCollectionSuite) TestUpdateLoadComplete(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRepoCollectionSuite) TestForEachAndLen(c *C) {
|
func (s *RemoteRepoCollectionSuite) TestForEachAndLen(c *C) {
|
||||||
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
repo, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
_ = s.collection.Add(repo)
|
_ = s.collection.Add(repo)
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
@@ -890,10 +752,10 @@ func (s *RemoteRepoCollectionSuite) TestForEachAndLen(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRepoCollectionSuite) TestDrop(c *C) {
|
func (s *RemoteRepoCollectionSuite) TestDrop(c *C) {
|
||||||
repo1, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
repo1, _ := NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
_ = s.collection.Add(repo1)
|
_ = s.collection.Add(repo1)
|
||||||
|
|
||||||
repo2, _ := NewRemoteRepo("tyndex", "http://mirror.yandex.ru/debian/", "wheezy", []string{"main"}, []string{}, false, false, false, false)
|
repo2, _ := NewRemoteRepo("tyndex", "http://mirror.yandex.ru/debian/", "wheezy", []string{"main"}, []string{}, false, false, false)
|
||||||
_ = s.collection.Add(repo2)
|
_ = s.collection.Add(repo2)
|
||||||
|
|
||||||
r1, _ := s.collection.ByUUID(repo1.UUID)
|
r1, _ := s.collection.ByUUID(repo1.UUID)
|
||||||
|
|||||||
+7
-31
@@ -40,9 +40,6 @@ type Snapshot struct {
|
|||||||
NotAutomatic string
|
NotAutomatic string
|
||||||
ButAutomaticUpgrades string
|
ButAutomaticUpgrades string
|
||||||
|
|
||||||
// AppStream files: relative path → pool path (pass-through from mirror)
|
|
||||||
AppStreamFiles map[string]string `json:",omitempty"`
|
|
||||||
|
|
||||||
packageRefs *PackageRefList
|
packageRefs *PackageRefList
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +59,6 @@ func NewSnapshotFromRepository(name string, repo *RemoteRepo) (*Snapshot, error)
|
|||||||
Origin: repo.Meta["Origin"],
|
Origin: repo.Meta["Origin"],
|
||||||
NotAutomatic: repo.Meta["NotAutomatic"],
|
NotAutomatic: repo.Meta["NotAutomatic"],
|
||||||
ButAutomaticUpgrades: repo.Meta["ButAutomaticUpgrades"],
|
ButAutomaticUpgrades: repo.Meta["ButAutomaticUpgrades"],
|
||||||
AppStreamFiles: repo.AppStreamFiles,
|
|
||||||
packageRefs: repo.packageRefs,
|
packageRefs: repo.packageRefs,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -98,28 +94,14 @@ func NewSnapshotFromRefList(name string, sources []*Snapshot, list *PackageRefLi
|
|||||||
sourceUUIDs[i] = sources[i].UUID
|
sourceUUIDs[i] = sources[i].UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge AppStreamFiles from all source snapshots
|
|
||||||
var mergedAppStream map[string]string
|
|
||||||
for _, source := range sources {
|
|
||||||
if len(source.AppStreamFiles) > 0 {
|
|
||||||
if mergedAppStream == nil {
|
|
||||||
mergedAppStream = make(map[string]string)
|
|
||||||
}
|
|
||||||
for k, v := range source.AppStreamFiles {
|
|
||||||
mergedAppStream[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Snapshot{
|
return &Snapshot{
|
||||||
UUID: uuid.NewString(),
|
UUID: uuid.NewString(),
|
||||||
Name: name,
|
Name: name,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
SourceKind: "snapshot",
|
SourceKind: "snapshot",
|
||||||
SourceIDs: sourceUUIDs,
|
SourceIDs: sourceUUIDs,
|
||||||
Description: description,
|
Description: description,
|
||||||
AppStreamFiles: mergedAppStream,
|
packageRefs: list,
|
||||||
packageRefs: list,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,12 +125,6 @@ func (s *Snapshot) Key() []byte {
|
|||||||
return []byte("S" + s.UUID)
|
return []byte("S" + s.UUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceKey is a unique identifier of the resource
|
|
||||||
// this snapshot uses. Instead of uuid it uses name
|
|
||||||
// which needs to be unique as well.
|
|
||||||
func (s *Snapshot) ResourceKey() []byte {
|
|
||||||
return []byte("S" + s.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefKey is a unique id for package reference list
|
// RefKey is a unique id for package reference list
|
||||||
func (s *Snapshot) RefKey() []byte {
|
func (s *Snapshot) RefKey() []byte {
|
||||||
|
|||||||
+4
-60
@@ -19,7 +19,7 @@ var _ = Suite(&SnapshotSuite{})
|
|||||||
|
|
||||||
func (s *SnapshotSuite) SetUpTest(c *C) {
|
func (s *SnapshotSuite) SetUpTest(c *C) {
|
||||||
s.SetUpPackages()
|
s.SetUpPackages()
|
||||||
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
s.repo, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
s.repo.packageRefs = s.reflist
|
s.repo.packageRefs = s.reflist
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,62 +101,6 @@ func (s *SnapshotSuite) TestEncodeDecode(c *C) {
|
|||||||
c.Assert(snapshot2.packageRefs, IsNil)
|
c.Assert(snapshot2.packageRefs, IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SnapshotSuite) TestSnapshotFromRepositoryAppStream(c *C) {
|
|
||||||
s.repo.AppStreamFiles = map[string]string{
|
|
||||||
"main/dep11/Components-amd64.yml.gz": "ab/cd/Components-amd64.yml.gz",
|
|
||||||
"main/dep11/icons-48x48.tar.gz": "ef/gh/icons-48x48.tar.gz",
|
|
||||||
}
|
|
||||||
snapshot, err := NewSnapshotFromRepository("snap-as", s.repo)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(snapshot.AppStreamFiles, DeepEquals, s.repo.AppStreamFiles)
|
|
||||||
|
|
||||||
s.repo.AppStreamFiles = nil
|
|
||||||
snapshot2, err := NewSnapshotFromRepository("snap-no-as", s.repo)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Check(snapshot2.AppStreamFiles, IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SnapshotSuite) TestSnapshotFromRefListAppStreamMerge(c *C) {
|
|
||||||
snap1, _ := NewSnapshotFromRepository("snap1", s.repo)
|
|
||||||
snap1.AppStreamFiles = map[string]string{
|
|
||||||
"main/dep11/Components-amd64.yml.gz": "aa/bb/Components-amd64.yml.gz",
|
|
||||||
"main/dep11/icons-48x48.tar.gz": "cc/dd/icons-48x48.tar.gz",
|
|
||||||
}
|
|
||||||
|
|
||||||
snap2, _ := NewSnapshotFromRepository("snap2", s.repo)
|
|
||||||
snap2.AppStreamFiles = map[string]string{
|
|
||||||
"contrib/dep11/Components-amd64.yml.gz": "ee/ff/Components-amd64.yml.gz",
|
|
||||||
"main/dep11/Components-amd64.yml.gz": "xx/yy/Components-amd64.yml.gz",
|
|
||||||
}
|
|
||||||
|
|
||||||
merged := NewSnapshotFromRefList("merged", []*Snapshot{snap1, snap2}, s.reflist, "Merged")
|
|
||||||
|
|
||||||
c.Check(len(merged.AppStreamFiles), Equals, 3)
|
|
||||||
c.Check(merged.AppStreamFiles["main/dep11/icons-48x48.tar.gz"], Equals, "cc/dd/icons-48x48.tar.gz")
|
|
||||||
c.Check(merged.AppStreamFiles["contrib/dep11/Components-amd64.yml.gz"], Equals, "ee/ff/Components-amd64.yml.gz")
|
|
||||||
c.Check(merged.AppStreamFiles["main/dep11/Components-amd64.yml.gz"], Equals, "xx/yy/Components-amd64.yml.gz")
|
|
||||||
|
|
||||||
snap3, _ := NewSnapshotFromRepository("snap3", s.repo)
|
|
||||||
snap3.AppStreamFiles = nil
|
|
||||||
snap4, _ := NewSnapshotFromRepository("snap4", s.repo)
|
|
||||||
snap4.AppStreamFiles = nil
|
|
||||||
merged2 := NewSnapshotFromRefList("merged2", []*Snapshot{snap3, snap4}, s.reflist, "Merged2")
|
|
||||||
c.Check(merged2.AppStreamFiles, IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SnapshotSuite) TestEncodeDecodeAppStream(c *C) {
|
|
||||||
snapshot, _ := NewSnapshotFromRepository("snap-as-enc", s.repo)
|
|
||||||
snapshot.AppStreamFiles = map[string]string{
|
|
||||||
"main/dep11/Components-amd64.yml.gz": "ab/cd/Components-amd64.yml.gz",
|
|
||||||
"main/dep11/icons-48x48.tar.gz": "ef/gh/icons-48x48.tar.gz",
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded := &Snapshot{}
|
|
||||||
c.Assert(decoded.Decode(snapshot.Encode()), IsNil)
|
|
||||||
c.Check(decoded.Name, Equals, snapshot.Name)
|
|
||||||
c.Check(decoded.AppStreamFiles, DeepEquals, snapshot.AppStreamFiles)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SnapshotCollectionSuite struct {
|
type SnapshotCollectionSuite struct {
|
||||||
PackageListMixinSuite
|
PackageListMixinSuite
|
||||||
db database.Storage
|
db database.Storage
|
||||||
@@ -174,11 +118,11 @@ func (s *SnapshotCollectionSuite) SetUpTest(c *C) {
|
|||||||
s.collection = NewSnapshotCollection(s.db)
|
s.collection = NewSnapshotCollection(s.db)
|
||||||
s.SetUpPackages()
|
s.SetUpPackages()
|
||||||
|
|
||||||
s.repo1, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false, false)
|
s.repo1, _ = NewRemoteRepo("yandex", "http://mirror.yandex.ru/debian/", "squeeze", []string{"main"}, []string{}, false, false, false)
|
||||||
s.repo1.packageRefs = s.reflist
|
s.repo1.packageRefs = s.reflist
|
||||||
s.snapshot1, _ = NewSnapshotFromRepository("snap1", s.repo1)
|
s.snapshot1, _ = NewSnapshotFromRepository("snap1", s.repo1)
|
||||||
|
|
||||||
s.repo2, _ = NewRemoteRepo("android", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false, false, false, false)
|
s.repo2, _ = NewRemoteRepo("android", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false, false, false)
|
||||||
s.repo2.packageRefs = s.reflist
|
s.repo2.packageRefs = s.reflist
|
||||||
s.snapshot2, _ = NewSnapshotFromRepository("snap2", s.repo2)
|
s.snapshot2, _ = NewSnapshotFromRepository("snap2", s.repo2)
|
||||||
|
|
||||||
@@ -279,7 +223,7 @@ func (s *SnapshotCollectionSuite) TestFindByRemoteRepoSource(c *C) {
|
|||||||
c.Check(s.collection.ByRemoteRepoSource(s.repo1), DeepEquals, []*Snapshot{s.snapshot1})
|
c.Check(s.collection.ByRemoteRepoSource(s.repo1), DeepEquals, []*Snapshot{s.snapshot1})
|
||||||
c.Check(s.collection.ByRemoteRepoSource(s.repo2), DeepEquals, []*Snapshot{s.snapshot2})
|
c.Check(s.collection.ByRemoteRepoSource(s.repo2), DeepEquals, []*Snapshot{s.snapshot2})
|
||||||
|
|
||||||
repo3, _ := NewRemoteRepo("other", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false, false, false, false)
|
repo3, _ := NewRemoteRepo("other", "http://mirror.yandex.ru/debian/", "lenny", []string{"main"}, []string{}, false, false, false)
|
||||||
|
|
||||||
c.Check(s.collection.ByRemoteRepoSource(repo3), DeepEquals, []*Snapshot(nil))
|
c.Check(s.collection.ByRemoteRepoSource(repo3), DeepEquals, []*Snapshot(nil))
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+33
@@ -0,0 +1,33 @@
|
|||||||
|
aptly (1.6.0+ds1-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
- aptly-api: configuration file is now /etc/aptly.conf, and `rootDir`
|
||||||
|
defaults to `~/.aptly`
|
||||||
|
|
||||||
|
- aptly-api: default port is 8080, as declared in
|
||||||
|
`/etc/default/aptly-api`
|
||||||
|
|
||||||
|
- aptly: swagger support is disabled, but will be re-enabled after the
|
||||||
|
corresponding golang packages make it to unstable
|
||||||
|
|
||||||
|
- aptly: default format is yaml, but the old format is still supported
|
||||||
|
for now
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sun, 29 Dec 2024 08:46:07 +0100
|
||||||
|
|
||||||
|
aptly (1.3.0+ds1-2.2) unstable; urgency=medium
|
||||||
|
|
||||||
|
This version tries to fix the database backwards compatibility,
|
||||||
|
so you don't need to rebuild the database if you upgrade from
|
||||||
|
aptly <= 1.3.0-6.
|
||||||
|
|
||||||
|
However some fields are missing, like created time of a snapshot.
|
||||||
|
|
||||||
|
-- Shengjing Zhu <zhsj@debian.org> Sat, 13 Apr 2019 23:26:39 +0800
|
||||||
|
|
||||||
|
aptly (1.3.0+ds1-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* The database created by aptly <= 1.3.0-6 is not compatible
|
||||||
|
with greater versions. Users must create a new database.
|
||||||
|
The `aptly db recover` will not fix the issue.
|
||||||
|
|
||||||
|
-- Alexandre Viau <aviau@debian.org> Fri, 26 Oct 2018 13:20:53 -0400
|
||||||
Vendored
+2
-1
@@ -83,8 +83,9 @@ serve_in_api_mode: false
|
|||||||
# Enable metrics for Prometheus client
|
# Enable metrics for Prometheus client
|
||||||
enable_metrics_endpoint: false
|
enable_metrics_endpoint: false
|
||||||
|
|
||||||
|
# Not implemented in this version.
|
||||||
# Enable API documentation on /docs
|
# Enable API documentation on /docs
|
||||||
enable_swagger_endpoint: false
|
#enable_swagger_endpoint: false
|
||||||
|
|
||||||
# OBSOLETE: use via url param ?_async=true
|
# OBSOLETE: use via url param ?_async=true
|
||||||
async_api: false
|
async_api: false
|
||||||
|
|||||||
Vendored
+514
-43
@@ -1,51 +1,522 @@
|
|||||||
aptly (1.6.2) stable; urgency=medium
|
aptly (1.6.2-3) unstable; urgency=medium
|
||||||
|
|
||||||
* doc: add swagger doc for /api/gpg/key (https://github.com/aptly-dev/aptly/pull/1456)
|
[ Sébastien Delafond ]
|
||||||
* bash-completion: include global options in aptly command completions (https://github.com/aptly-dev/aptly/pull/1452)
|
* tests: disable t04_mirror/create/CreateMirror18Test (Closes: #1135740)
|
||||||
* Bump golang.org/x/net from 0.33.0 to 0.38.0 (https://github.com/aptly-dev/aptly/pull/1443)
|
* tests: disable t12_api/gpg/GPGAPITestAddKey (Closes: #1135672)
|
||||||
* Bump golang.org/x/crypto from 0.31.0 to 0.35.0 (https://github.com/aptly-dev/aptly/pull/1441)
|
* d/control: bump-up Standards-Version
|
||||||
* Remove corrupt package references in `db recover` (https://github.com/aptly-dev/aptly/pull/1445)
|
|
||||||
* Fix upload of unchanged packages in S3 (https://github.com/aptly-dev/aptly/pull/1440)
|
|
||||||
* use go 1.24 (https://github.com/aptly-dev/aptly/pull/1439)
|
|
||||||
|
|
||||||
-- André Roth <neolynx@gmail.com> Mon, 09 Jun 2025 13:45:15 +0200
|
-- Sebastien Delafond <seb@debian.org> Tue, 05 May 2026 18:14:44 +0200
|
||||||
|
|
||||||
aptly (1.6.1) stable; urgency=medium
|
aptly (1.6.2-2) unstable; urgency=medium
|
||||||
|
|
||||||
* update golang-github-syndtr-goleveldb-dev dependency (v1.0.1-0.20220721030215-126854af5e6d) to fix segfault on arm64
|
[ Sébastien Delafond ]
|
||||||
(bug in golang-github-golang-snappy-dev)
|
* Remove Built-Using
|
||||||
* allow snapshotting empty mirrors again (regression)
|
* Mark patches as "Forwarded: not-needed"
|
||||||
* debian compliance: add postrm (note: `apt purge aptly-api` will remove all data in ~aptly-api/)
|
|
||||||
* update other dependencies (x/net 0.33.0, gin-gonic/gin 1.9.1)
|
|
||||||
|
|
||||||
-- André Roth <neolynx@gmail.com> Sat, 15 Feb 2025 13:03:16 +0100
|
-- Sebastien Delafond <seb@debian.org> Fri, 21 Nov 2025 15:46:51 +0100
|
||||||
|
|
||||||
aptly (1.6.0) stable; urgency=medium
|
aptly (1.6.2-1) unstable; urgency=medium
|
||||||
|
|
||||||
* support reading filters from file or stdin
|
[ Sébastien Delafond ]
|
||||||
* fix mirroring source packages
|
* d/watch: v5
|
||||||
* support yaml config per default
|
* Bump up Standards-Version
|
||||||
* provide swagger API documentation
|
* Remove +ds suffix
|
||||||
* provide API for querying aptly storage usage
|
* Add Static-Built-Using
|
||||||
* provide snapshot pull via API
|
* New upstream version 1.6.2
|
||||||
* support creating repos from snapshots
|
|
||||||
* fix mirroring flat remote repos
|
|
||||||
* support skeleton files for publishing
|
|
||||||
* use new azure sdk
|
|
||||||
* support updating the components of a published repo
|
|
||||||
* support publishing multiple distributions (-multi-dist)
|
|
||||||
* support etcd database
|
|
||||||
* allow slash (/) in distribution names
|
|
||||||
* support for storing the "local" pool on Azure
|
|
||||||
* provide copy package API
|
|
||||||
* fix publish concurrency and improve performance
|
|
||||||
* improved mirroring
|
|
||||||
* fix download throttling
|
|
||||||
* fix resuming package downloads
|
|
||||||
* fix ignoring signatures
|
|
||||||
* fix packages dependency resolution (Virtual Packages, version numbers in Provides)
|
|
||||||
* improved S3 support and performance
|
|
||||||
* fix race condition with goleveldb
|
|
||||||
* use go 1.22
|
|
||||||
|
|
||||||
-- André Roth <neolynx@gmail.com> Tue, 24 Dec 2024 17:44:35 +0100
|
-- Sebastien Delafond <seb@debian.org> Wed, 24 Sep 2025 06:19:54 +0200
|
||||||
|
|
||||||
|
aptly (1.6.1+ds1-3) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* tests: declare needs-internet for system-test
|
||||||
|
* tests: disable unit test TestVerifyClearsigned & system test
|
||||||
|
CreateMirror31Test (Closes: #1108828)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Tue, 08 Jul 2025 14:12:52 +0200
|
||||||
|
|
||||||
|
aptly (1.6.1+ds1-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Do not re-publish unchanged files to S3 every single time (Closes: #1104299)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Mon, 28 Apr 2025 15:38:43 +0200
|
||||||
|
|
||||||
|
aptly (1.6.1+ds1-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* New upstream version 1.6.1
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Mon, 24 Feb 2025 09:04:35 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1-8) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* tests: disable riscv64-flaky EditRepo4Test
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Fri, 21 Feb 2025 06:54:18 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1-7) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* tests: clean way to disable single tests, disable s390-flaky CreateMirror35Test
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 20 Feb 2025 07:26:41 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1-6) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* tests: disable system-test's etcd tests as the corresponding fixture is also arch-specific
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Wed, 19 Feb 2025 14:02:19 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1-5) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* tests: do not use upstream's etcd installer
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Wed, 19 Feb 2025 07:20:44 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1-4) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* postrm: remove aptly-api user and home directory on purge
|
||||||
|
* tests: use extensive coverage from make's test and system-test
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Tue, 18 Feb 2025 10:36:13 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1-3) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* aptly.conf: fix s3 example
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Mon, 13 Jan 2025 14:51:29 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Document disabled swagger in default config file
|
||||||
|
* Rediff patches: swagger references in man page
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Mon, 30 Dec 2024 11:11:07 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Official 1.6 release
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Fri, 27 Dec 2024 14:23:29 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1~beta3-1) experimental; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Latest upstream version
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Wed, 11 Dec 2024 18:16:19 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1~beta2-1) experimental; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Latest upstream version
|
||||||
|
|
||||||
|
[ André Roth ]
|
||||||
|
* set systemd service file limit to 32768
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 21 Nov 2024 16:08:05 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1~beta1-1) experimental; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Latest upstream version
|
||||||
|
* Disable "use new azure-sdk"
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 18:52:06 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1~alpha5-1) experimental; urgency=medium
|
||||||
|
|
||||||
|
* Sort out dependencies on gpg*
|
||||||
|
* Reduce diff with upstream some more
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 15:59:23 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1~alpha4-1) experimental; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Remove build-dep on git
|
||||||
|
* Adjust systemd unit for aptly-api
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 14:51:49 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1~alpha3-1) experimental; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Latest upstream version
|
||||||
|
* Remove useless maintainer scripts
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sun, 17 Nov 2024 07:50:06 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1~alpha2-1) experimental; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Latest upstream version
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sat, 16 Nov 2024 14:42:10 +0100
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1~alpha1-2) experimental; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Add zsh completion
|
||||||
|
* aptly-api: revert arch:all, so mv_conffile does the right thing
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Wed, 16 Oct 2024 09:10:05 +0200
|
||||||
|
|
||||||
|
aptly (1.6.0+ds1~alpha1-1) experimental; urgency=medium
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* d/patches: remove old patch, and disable swagger support
|
||||||
|
* d/control: bump up Standards-Version
|
||||||
|
* Adjust packaging for 1.6.0~alpha1
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Tue, 15 Oct 2024 14:40:57 +0200
|
||||||
|
|
||||||
|
aptly (1.5.0+ds1-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Shengjing Zhu ]
|
||||||
|
* Replace golang-gopkg-cheggaaa-pb.v1-dev with
|
||||||
|
golang-github-cheggaaa-pb-dev (Closes: #1050900)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Mon, 04 Sep 2023 08:49:36 +0200
|
||||||
|
|
||||||
|
aptly (1.5.0+ds1-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Team upload.
|
||||||
|
* New upstream release (Closes: #1022721), including fix for "Order of
|
||||||
|
fields in Packages/Sources is unpredictable" (Closes: #907121).
|
||||||
|
|
||||||
|
-- Roland Mas <lolando@debian.org> Tue, 31 Jan 2023 14:47:04 +0100
|
||||||
|
|
||||||
|
aptly (1.4.0+ds1-7) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Team upload.
|
||||||
|
* Add support for zstd compression (Closes: #1010465)
|
||||||
|
|
||||||
|
-- Anton Gladky <gladk@debian.org> Tue, 17 May 2022 22:42:29 +0200
|
||||||
|
|
||||||
|
aptly (1.4.0+ds1-6) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Conflict on gpgv1 (Closes: #990821)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 04 Nov 2021 10:24:53 +0100
|
||||||
|
|
||||||
|
aptly (1.4.0+ds1-5) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Conflict on gnupg1 (Closes: #990821)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 14 Oct 2021 18:43:04 +0200
|
||||||
|
|
||||||
|
aptly (1.4.0+ds1-4) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Install correct bash completion snippet (Closes: #984979)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 11 Mar 2021 15:20:57 +0100
|
||||||
|
|
||||||
|
aptly (1.4.0+ds1-3) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Fix s3 etag issue (Closes: #983877)
|
||||||
|
* Bump-up d/watch version
|
||||||
|
* Bump-up Standards-Version
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Wed, 03 Mar 2021 10:50:51 +0100
|
||||||
|
|
||||||
|
aptly (1.4.0+ds1-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Use pipeline from salsa-ci-team
|
||||||
|
* Allow reprotest failure
|
||||||
|
* Pass version from d/rules (Closes: #968585)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Fri, 21 Aug 2020 10:13:44 +0200
|
||||||
|
|
||||||
|
aptly (1.4.0+ds1-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 1.4.0+ds1
|
||||||
|
* Rediff patches
|
||||||
|
* Depend on gnupg 2
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sun, 22 Dec 2019 15:16:25 +0100
|
||||||
|
|
||||||
|
aptly (1.3.0+ds1-4) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Debian Janitor ]
|
||||||
|
* Rename obsolete path debian/tests/control.autodep8 to debian/tests/control.
|
||||||
|
* Use secure URI in Homepage field.
|
||||||
|
* Bump debhelper from old 11 to 12.
|
||||||
|
* Set debhelper-compat version in Build-Depends.
|
||||||
|
|
||||||
|
[ Sébastien Delafond ]
|
||||||
|
* Bump up Standards-Version
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sun, 22 Dec 2019 14:10:19 +0100
|
||||||
|
|
||||||
|
aptly (1.3.0+ds1-3) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Build-Depend on golang-golang-x-tools-dev instead of golang-go.tools (Closes: #945884)
|
||||||
|
* Lintian fix
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sat, 21 Dec 2019 10:29:09 +0100
|
||||||
|
|
||||||
|
aptly (1.3.0+ds1-2.3) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Non-maintainer upload.
|
||||||
|
* Remove myself from uploaders.
|
||||||
|
|
||||||
|
-- Alexandre Viau <aviau@debian.org> Sun, 15 Sep 2019 19:27:47 -0400
|
||||||
|
|
||||||
|
aptly (1.3.0+ds1-2.2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Non-maintainer upload.
|
||||||
|
* Add patch to fix DB backwards compatibility (Closes: #911924)
|
||||||
|
* Fix struct field tag typo
|
||||||
|
* Update debian/NEWS about DB compatibility
|
||||||
|
|
||||||
|
-- Shengjing Zhu <zhsj@debian.org> Tue, 16 Apr 2019 00:18:23 +0800
|
||||||
|
|
||||||
|
aptly (1.3.0+ds1-2.1) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Shengjing Zhu ]
|
||||||
|
* Non-maintainer upload.
|
||||||
|
* Add patch to fix UUID struct field not encoded in msgpack (Closes: #923866)
|
||||||
|
|
||||||
|
[ Tobias Frost ]
|
||||||
|
* Prepare upload.
|
||||||
|
|
||||||
|
-- Tobias Frost <tobi@debian.org> Fri, 05 Apr 2019 17:19:14 +0200
|
||||||
|
|
||||||
|
aptly (1.3.0+ds1-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Add NEWS to warn about database compatibility.
|
||||||
|
|
||||||
|
-- Alexandre Viau <aviau@debian.org> Fri, 26 Oct 2018 13:22:38 -0400
|
||||||
|
|
||||||
|
aptly (1.3.0+ds1-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
[ Ondřej Nový ]
|
||||||
|
* d/changelog: Remove trailing whitespaces
|
||||||
|
|
||||||
|
[ Alexandre Viau ]
|
||||||
|
* d/watch: Append +ds suffix.
|
||||||
|
* d/copyright: ignore vendor/*. (Closes: #902128)
|
||||||
|
* d/copyright: remove vendor/* sections.
|
||||||
|
* d/copyright: MIT -> Expat.
|
||||||
|
* d/control: add vendor/* build dependencies.
|
||||||
|
* Patch: Use Debian's uuid package.
|
||||||
|
* Patch: Use Debian's lzma package.
|
||||||
|
* d/rules: remove trailing whitespace.
|
||||||
|
|
||||||
|
-- Alexandre Viau <aviau@debian.org> Mon, 15 Oct 2018 11:54:03 -0400
|
||||||
|
|
||||||
|
aptly (1.3.0-6) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Combine autodep8 and autopkgtest.
|
||||||
|
|
||||||
|
-- Alexandre Viau <aviau@debian.org> Fri, 29 Jun 2018 18:11:41 -0400
|
||||||
|
|
||||||
|
aptly (1.3.0-5) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Fix syntax-error-in-dep5-copyright.
|
||||||
|
* Fix unnecessary-testsuite-autopkgtest-field.
|
||||||
|
* Fix broken autopkgtest.
|
||||||
|
* Depend on gpgv1.
|
||||||
|
|
||||||
|
-- Alexandre Viau <aviau@debian.org> Tue, 26 Jun 2018 23:01:36 -0400
|
||||||
|
|
||||||
|
aptly (1.3.0-4) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Document #902128 in debian/copyright
|
||||||
|
* Point debian/watch to new git repo on GitHub
|
||||||
|
* Add simple autopkgtest
|
||||||
|
* Add debian/.gitlab-ci.yml
|
||||||
|
* Depend on gnupg1 (Closes: #902419)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Tue, 26 Jun 2018 14:24:34 +0200
|
||||||
|
|
||||||
|
aptly (1.3.0-3) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Create aptly-api package. (Closes: #902032)
|
||||||
|
|
||||||
|
-- Alexandre Viau <aviau@debian.org> Fri, 22 Jun 2018 13:51:50 -0400
|
||||||
|
|
||||||
|
aptly (1.3.0-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Team upload, many thanks to Alexandre Viau for this work
|
||||||
|
* Switch to dh-golang. (Closes: #902038)
|
||||||
|
* Fix vcs-field-not-canonical.
|
||||||
|
* Fix insecure-copyright-format-uri.
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Fri, 22 Jun 2018 13:53:11 +0200
|
||||||
|
|
||||||
|
aptly (1.3.0-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Fix copyright
|
||||||
|
* New upstream version 1.3.0
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 21 Jun 2018 15:14:57 +0200
|
||||||
|
|
||||||
|
aptly (1.2.0-4) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Enable Built-Using in debian/control (thanks M. Staperberg)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Tue, 06 Mar 2018 10:10:42 +0100
|
||||||
|
|
||||||
|
aptly (1.2.0-3) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Switch to DEP 14
|
||||||
|
* Update Vcs-* to point to salsa.d.o
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Fri, 16 Feb 2018 17:02:25 +0100
|
||||||
|
|
||||||
|
aptly (1.2.0-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Team upload.
|
||||||
|
* Build-Depend on golang-any, not golang
|
||||||
|
* Set XS-Go-Import-Path
|
||||||
|
|
||||||
|
-- Michael Stapelberg <stapelberg@debian.org> Sat, 10 Feb 2018 18:41:37 +0100
|
||||||
|
|
||||||
|
aptly (1.2.0-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 1.2.0
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Wed, 03 Jan 2018 13:43:30 +0100
|
||||||
|
|
||||||
|
aptly (1.1.1-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Support both uncompressed control.tar, and xz-compressed
|
||||||
|
control.tar.xz. Thanks to Boyuan Yang for the patch (Closes: 879718)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 02 Nov 2017 13:23:29 +0100
|
||||||
|
|
||||||
|
aptly (1.1.1-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 1.1.1
|
||||||
|
* Use bash-completion snippet from upstream
|
||||||
|
* Bump up Standards-Version
|
||||||
|
* Update debian/copyright
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 02 Nov 2017 09:03:23 +0100
|
||||||
|
|
||||||
|
aptly (1.0.1-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Imported Upstream version 1.0.1
|
||||||
|
* Update debian/copyright
|
||||||
|
* Adapt debian/rules
|
||||||
|
* Update Vcs-Git
|
||||||
|
* Bump-up Standards-Version
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Tue, 18 Jul 2017 11:53:29 +0200
|
||||||
|
|
||||||
|
aptly (0.9.7-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Imported new upstream version 0.9.7
|
||||||
|
* Add new licenses and copyrights info
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Tue, 24 May 2016 09:17:08 +0200
|
||||||
|
|
||||||
|
aptly (0.9.6-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Import new upstream version 0.9.6
|
||||||
|
* Add dependency on xz-utils
|
||||||
|
* Licenses and copyrights
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Wed, 10 Feb 2016 18:28:51 +0100
|
||||||
|
|
||||||
|
aptly (0.9.5-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Remove empty component in GOPATH (Closes: #793838)
|
||||||
|
|
||||||
|
[ Sebastien Delafond ]
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sun, 16 Aug 2015 18:42:22 +0200
|
||||||
|
|
||||||
|
aptly (0.9.5-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Imported Upstream version 0.9.5
|
||||||
|
* Up-to-date bash completion
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Fri, 15 May 2015 10:46:51 +0200
|
||||||
|
|
||||||
|
aptly (0.9.1-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Imported Upstream version 0.9.1
|
||||||
|
* Document licenses and copyrights for new dependencies
|
||||||
|
* Suggest graphviz
|
||||||
|
* Proper name for bash-completion file
|
||||||
|
* Bump-up Standards-Version
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sat, 02 May 2015 12:57:16 +0200
|
||||||
|
|
||||||
|
aptly (0.8-3) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Correct Vcs-Browser entry (Closes: #764622)
|
||||||
|
* Correct dependency from "gpg" to "gnupg" (Closes: #764619)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 09 Oct 2014 18:41:38 +0200
|
||||||
|
|
||||||
|
aptly (0.8-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Add missing dependencies on bzip2, gpg and gpgv
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 09 Oct 2014 11:15:21 +0200
|
||||||
|
|
||||||
|
aptly (0.8-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Imported Upstream version 0.8
|
||||||
|
* Document new copyrights and licenses
|
||||||
|
* Add bash completion snippet
|
||||||
|
* Reference full paths in debian/copyright, and remove unused
|
||||||
|
references, so lintian does not complain
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Sat, 04 Oct 2014 17:46:24 +0200
|
||||||
|
|
||||||
|
aptly (0.7.1-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Imported Upstream version 0.7.1
|
||||||
|
* Add copyright information for new libraries
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Wed, 10 Sep 2014 10:03:27 +0200
|
||||||
|
|
||||||
|
aptly (0.5-5) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Turn off verbose mode
|
||||||
|
* Correct Vcs-* information
|
||||||
|
* gocov is licensed MIT, not BSD-3
|
||||||
|
* Add missing MIT license text
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Tue, 09 Sep 2014 09:20:40 +0200
|
||||||
|
|
||||||
|
aptly (0.5-4) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Collect licenses by going over files one by one
|
||||||
|
* Use packaged golang-go.tools
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Thu, 10 Jul 2014 00:49:09 +0200
|
||||||
|
|
||||||
|
aptly (0.5-3) unstable; urgency=low
|
||||||
|
|
||||||
|
* Licensing:
|
||||||
|
- goleveldb is BSD-2, not BSD-3
|
||||||
|
- _vendor/src/code.google.com/p/gographviz/scanner/scanner is BSD-3,
|
||||||
|
copyright 2009 The Go Authors
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Wed, 28 May 2014 10:04:01 +0200
|
||||||
|
|
||||||
|
aptly (0.5-2) unstable; urgency=low
|
||||||
|
|
||||||
|
* Go interpreter not needed at runtime
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Fri, 02 May 2014 12:04:02 +0200
|
||||||
|
|
||||||
|
aptly (0.5-1) unstable; urgency=low
|
||||||
|
|
||||||
|
* Initial release (Closes: #746343)
|
||||||
|
|
||||||
|
-- Sebastien Delafond <seb@debian.org> Tue, 29 Apr 2014 15:47:31 +0200
|
||||||
|
|||||||
Vendored
+7
-16
@@ -1,7 +1,7 @@
|
|||||||
Source: aptly
|
Source: aptly
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: André Roth <neolynx@gmail.com>
|
Maintainer: Sebastien Delafond <seb@debian.org>
|
||||||
Build-Depends: bash-completion,
|
Build-Depends: bash-completion,
|
||||||
debhelper-compat (= 13),
|
debhelper-compat (= 13),
|
||||||
dh-golang,
|
dh-golang,
|
||||||
@@ -77,20 +77,18 @@ Build-Depends: bash-completion,
|
|||||||
golang-go.uber-zap-dev,
|
golang-go.uber-zap-dev,
|
||||||
golang-etcd-server-dev (>= 3.5.15-7),
|
golang-etcd-server-dev (>= 3.5.15-7),
|
||||||
golang-gopkg-yaml.v3-dev,
|
golang-gopkg-yaml.v3-dev,
|
||||||
git
|
Standards-Version: 4.7.4
|
||||||
Standards-Version: 4.7.0
|
|
||||||
Homepage: https://www.aptly.info
|
Homepage: https://www.aptly.info
|
||||||
Vcs-Git: https://github.com/aptly-dev/aptly.git
|
Vcs-Git: https://salsa.debian.org/debian/aptly.git
|
||||||
Vcs-Browser: https://github.com/aptly-dev/aptly
|
Vcs-Browser: https://salsa.debian.org/debian/aptly
|
||||||
XS-Go-Import-Path: github.com/aptly-dev/aptly
|
XS-Go-Import-Path: github.com/aptly-dev/aptly
|
||||||
Testsuite: autopkgtest-pkg-go
|
|
||||||
|
|
||||||
Package: aptly
|
Package: aptly
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: ${misc:Depends}, ${shlibs:Depends}, bzip2, xz-utils, gpgv, gpg
|
Depends: ${misc:Depends}, ${shlibs:Depends}, bzip2, xz-utils, gpgv, gpg
|
||||||
Suggests: graphviz
|
Static-Built-Using: ${misc:Static-Built-Using}
|
||||||
Conflicts: gnupg1, gpgv1
|
Conflicts: gnupg1, gpgv1
|
||||||
Built-Using: ${misc:Static-Built-Using}, ${misc:Built-Using}
|
Suggests: graphviz
|
||||||
Description: Swiss army knife for Debian repository management - main package
|
Description: Swiss army knife for Debian repository management - main package
|
||||||
It offers several features making it easy to manage Debian package
|
It offers several features making it easy to manage Debian package
|
||||||
repositories:
|
repositories:
|
||||||
@@ -107,7 +105,7 @@ Description: Swiss army knife for Debian repository management - main package
|
|||||||
This is the main package, it contains the aptly command-line utility.
|
This is the main package, it contains the aptly command-line utility.
|
||||||
|
|
||||||
Package: aptly-api
|
Package: aptly-api
|
||||||
Architecture: any
|
Architecture: all
|
||||||
Depends: ${misc:Depends}, aptly
|
Depends: ${misc:Depends}, aptly
|
||||||
Description: Swiss army knife for Debian repository management - API
|
Description: Swiss army knife for Debian repository management - API
|
||||||
It offers several features making it easy to manage Debian package
|
It offers several features making it easy to manage Debian package
|
||||||
@@ -123,10 +121,3 @@ Description: Swiss army knife for Debian repository management - API
|
|||||||
- merge two or more snapshots into one
|
- merge two or more snapshots into one
|
||||||
.
|
.
|
||||||
This package contains the aptly-api service.
|
This package contains the aptly-api service.
|
||||||
|
|
||||||
Package: aptly-dbg
|
|
||||||
Architecture: any
|
|
||||||
Depends: ${misc:Depends}
|
|
||||||
Built-Using: ${misc:Static-Built-Using}, ${misc:Built-Using}
|
|
||||||
Description: Debian repository management tool (debug files)
|
|
||||||
Debug symbols for aptly
|
|
||||||
|
|||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
debian-branch = debian/master
|
||||||
|
upstream-branch = upstream/latest
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
usr/bin/files
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
From: =?utf-8?q?Andr=C3=A9_Roth?= <neolynx@gmail.com>
|
||||||
|
Date: Tue, 15 Oct 2024 12:09:33 +0200
|
||||||
|
Subject: Disable swagger
|
||||||
|
|
||||||
|
This can be enabled once the following modules make it into Debian:
|
||||||
|
|
||||||
|
- github.com/swaggo/files v1.0.1
|
||||||
|
- github.com/swaggo/gin-swagger v1.6.0
|
||||||
|
- github.com/swaggo/swag v1.16.3
|
||||||
|
|
||||||
|
Forwarded: not-needed
|
||||||
|
---
|
||||||
|
api/router.go | 22 +++++++++++-----------
|
||||||
|
docs/index.go | 10 ----------
|
||||||
|
docs/index.go.disabled | 10 ++++++++++
|
||||||
|
man/aptly.1 | 3 ++-
|
||||||
|
man/aptly.1.ronn.tmpl | 3 ++-
|
||||||
|
5 files changed, 25 insertions(+), 23 deletions(-)
|
||||||
|
delete mode 100644 docs/index.go
|
||||||
|
create mode 100644 docs/index.go.disabled
|
||||||
|
|
||||||
|
diff --git a/api/router.go b/api/router.go
|
||||||
|
index 3cd7d42..cf53cd2 100644
|
||||||
|
--- a/api/router.go
|
||||||
|
+++ b/api/router.go
|
||||||
|
@@ -11,9 +11,9 @@
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
- "github.com/aptly-dev/aptly/docs"
|
||||||
|
- swaggerFiles "github.com/swaggo/files"
|
||||||
|
- ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
|
+ // _ "github.com/aptly-dev/aptly/docs" // import docs
|
||||||
|
+ // swaggerFiles "github.com/swaggo/files"
|
||||||
|
+ // ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var context *ctx.AptlyContext
|
||||||
|
@@ -63,14 +63,14 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
||||||
|
|
||||||
|
router.Use(gin.Recovery(), gin.ErrorLogger())
|
||||||
|
|
||||||
|
- if c.Config().EnableSwaggerEndpoint {
|
||||||
|
- router.GET("docs.html", func(c *gin.Context) {
|
||||||
|
- c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
|
||||||
|
- })
|
||||||
|
- router.Use(redirectSwagger)
|
||||||
|
- url := ginSwagger.URL("/docs/doc.json")
|
||||||
|
- router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
|
||||||
|
- }
|
||||||
|
+ // if c.Config().EnableSwaggerEndpoint {
|
||||||
|
+ // router.GET("docs.html", func(c *gin.Context) {
|
||||||
|
+ // c.Data(http.StatusOK, "text/html; charset=utf-8", docs.DocsHTML)
|
||||||
|
+ // })
|
||||||
|
+ // router.Use(redirectSwagger)
|
||||||
|
+ // url := ginSwagger.URL("/docs/doc.json")
|
||||||
|
+ // router.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url))
|
||||||
|
+ // }
|
||||||
|
|
||||||
|
if c.Config().EnableMetricsEndpoint {
|
||||||
|
MetricsCollectorRegistrar.Register(router)
|
||||||
|
diff --git a/docs/index.go b/docs/index.go
|
||||||
|
deleted file mode 100644
|
||||||
|
index ca4c914..0000000
|
||||||
|
--- a/docs/index.go
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,10 +0,0 @@
|
||||||
|
-package docs
|
||||||
|
-
|
||||||
|
-import (
|
||||||
|
- _ "embed" // embed html below
|
||||||
|
-
|
||||||
|
- _ "github.com/swaggo/swag" // make sure swag is in go.mod
|
||||||
|
-)
|
||||||
|
-
|
||||||
|
-//go:embed docs.html
|
||||||
|
-var DocsHTML []byte
|
||||||
|
diff --git a/docs/index.go.disabled b/docs/index.go.disabled
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..ca4c914
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/docs/index.go.disabled
|
||||||
|
@@ -0,0 +1,10 @@
|
||||||
|
+package docs
|
||||||
|
+
|
||||||
|
+import (
|
||||||
|
+ _ "embed" // embed html below
|
||||||
|
+
|
||||||
|
+ _ "github.com/swaggo/swag" // make sure swag is in go.mod
|
||||||
|
+)
|
||||||
|
+
|
||||||
|
+//go:embed docs.html
|
||||||
|
+var DocsHTML []byte
|
||||||
|
diff --git a/man/aptly.1 b/man/aptly.1
|
||||||
|
index bd6ad22..11ed990 100644
|
||||||
|
--- a/man/aptly.1
|
||||||
|
+++ b/man/aptly.1
|
||||||
|
@@ -111,8 +111,9 @@ The legacy json configuration is still supported (and also supports comments):
|
||||||
|
// Enable metrics for Prometheus client
|
||||||
|
"enableMetricsEndpoint": false,
|
||||||
|
|
||||||
|
+ // Not implemented in this version\.
|
||||||
|
// Enable API documentation on /docs
|
||||||
|
- "enableSwaggerEndpoint": false,
|
||||||
|
+ //"enableSwaggerEndpoint": false,
|
||||||
|
|
||||||
|
// OBSOLETE: use via url param ?_async=true
|
||||||
|
"AsyncAPI": false,
|
||||||
|
diff --git a/man/aptly.1.ronn.tmpl b/man/aptly.1.ronn.tmpl
|
||||||
|
index 203cc7f..ed2c87c 100644
|
||||||
|
--- a/man/aptly.1.ronn.tmpl
|
||||||
|
+++ b/man/aptly.1.ronn.tmpl
|
||||||
|
@@ -100,8 +100,9 @@ The legacy json configuration is still supported (and also supports comments):
|
||||||
|
// Enable metrics for Prometheus client
|
||||||
|
"enableMetricsEndpoint": false,
|
||||||
|
|
||||||
|
+ // Not implemented in this version.
|
||||||
|
// Enable API documentation on /docs
|
||||||
|
- "enableSwaggerEndpoint": false,
|
||||||
|
+ //"enableSwaggerEndpoint": false,
|
||||||
|
|
||||||
|
// OBSOLETE: use via url param ?_async=true
|
||||||
|
"AsyncAPI": false,
|
||||||
+880
@@ -0,0 +1,880 @@
|
|||||||
|
From: =?utf-8?q?Andr=C3=A9_Roth?= <neolynx@gmail.com>
|
||||||
|
Date: Sun, 17 Nov 2024 17:58:04 +0100
|
||||||
|
Subject: Disable new azure-sdk
|
||||||
|
|
||||||
|
This reverts commit e2cbd637b82a153a6756f2af0519e8fe769ee9ab.
|
||||||
|
|
||||||
|
We can enable this once the golang-github-azure-azure-sdk-for-go-dev
|
||||||
|
packages are upgraded in Debian:
|
||||||
|
|
||||||
|
- github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0
|
||||||
|
- github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
|
||||||
|
- github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
||||||
|
|
||||||
|
Forwarded: not-needed
|
||||||
|
---
|
||||||
|
azure/azure.go | 89 +++++++++++++++-----------------
|
||||||
|
azure/package_pool.go | 45 ++++++++--------
|
||||||
|
azure/package_pool_test.go | 8 ++-
|
||||||
|
azure/public.go | 125 +++++++++++++++++++++------------------------
|
||||||
|
azure/public_test.go | 49 +++++++++---------
|
||||||
|
context/context.go | 1 +
|
||||||
|
deb/list.go | 1 +
|
||||||
|
go.mod | 8 +--
|
||||||
|
go.sum | 52 +++++++++++--------
|
||||||
|
9 files changed, 185 insertions(+), 193 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/azure/azure.go b/azure/azure.go
|
||||||
|
index 3f12678..b313f90 100644
|
||||||
|
--- a/azure/azure.go
|
||||||
|
+++ b/azure/azure.go
|
||||||
|
@@ -5,35 +5,28 @@
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
- "errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
- "os"
|
||||||
|
+ "net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
- "github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||||
|
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||||
|
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||||
|
+ "github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isBlobNotFound(err error) bool {
|
||||||
|
- var respErr *azcore.ResponseError
|
||||||
|
- if errors.As(err, &respErr) {
|
||||||
|
- return respErr.StatusCode == 404 // BlobNotFound
|
||||||
|
- }
|
||||||
|
- return false
|
||||||
|
+ storageError, ok := err.(azblob.StorageError)
|
||||||
|
+ return ok && storageError.ServiceCode() == azblob.ServiceCodeBlobNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
type azContext struct {
|
||||||
|
- client *azblob.Client
|
||||||
|
- container string
|
||||||
|
+ container azblob.ContainerURL
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAzContext(accountName, accountKey, container, prefix, endpoint string) (*azContext, error) {
|
||||||
|
- cred, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||||
|
+ credential, err := azblob.NewSharedKeyCredential(accountName, accountKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
@@ -42,14 +35,15 @@ func newAzContext(accountName, accountKey, container, prefix, endpoint string) (
|
||||||
|
endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", accountName)
|
||||||
|
}
|
||||||
|
|
||||||
|
- serviceClient, err := azblob.NewClientWithSharedKeyCredential(endpoint, cred, nil)
|
||||||
|
+ url, err := url.Parse(fmt.Sprintf("%s/%s", endpoint, container))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
+ containerURL := azblob.NewContainerURL(*url, azblob.NewPipeline(credential, azblob.PipelineOptions{}))
|
||||||
|
+
|
||||||
|
result := &azContext{
|
||||||
|
- client: serviceClient,
|
||||||
|
- container: container,
|
||||||
|
+ container: containerURL,
|
||||||
|
prefix: prefix,
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -60,6 +54,10 @@ func (az *azContext) blobPath(path string) string {
|
||||||
|
return filepath.Join(az.prefix, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
+func (az *azContext) blobURL(path string) azblob.BlobURL {
|
||||||
|
+ return az.container.NewBlobURL(az.blobPath(path))
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (paths []string, md5s []string, err error) {
|
||||||
|
const delimiter = "/"
|
||||||
|
paths = make([]string, 0, 1024)
|
||||||
|
@@ -69,33 +67,27 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
|
||||||
|
prefix += delimiter
|
||||||
|
}
|
||||||
|
|
||||||
|
- ctx := context.Background()
|
||||||
|
- maxResults := int32(1)
|
||||||
|
- pager := az.client.NewListBlobsFlatPager(az.container, &azblob.ListBlobsFlatOptions{
|
||||||
|
- Prefix: &prefix,
|
||||||
|
- MaxResults: &maxResults,
|
||||||
|
- Include: azblob.ListBlobsInclude{Metadata: true},
|
||||||
|
- })
|
||||||
|
-
|
||||||
|
- // Iterate over each page
|
||||||
|
- for pager.More() {
|
||||||
|
- page, err := pager.NextPage(ctx)
|
||||||
|
+ for marker := (azblob.Marker{}); marker.NotDone(); {
|
||||||
|
+ listBlob, err := az.container.ListBlobsFlatSegment(
|
||||||
|
+ context.Background(), marker, azblob.ListBlobsSegmentOptions{
|
||||||
|
+ Prefix: prefix,
|
||||||
|
+ MaxResults: 1,
|
||||||
|
+ Details: azblob.BlobListingDetails{Metadata: true}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error listing under prefix %s in %s: %s", prefix, az, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
- for _, blob := range page.Segment.BlobItems {
|
||||||
|
- if prefix == "" {
|
||||||
|
- paths = append(paths, *blob.Name)
|
||||||
|
- } else {
|
||||||
|
- name := *blob.Name
|
||||||
|
- paths = append(paths, name[len(prefix):])
|
||||||
|
- }
|
||||||
|
- b := *blob
|
||||||
|
- md5 := b.Properties.ContentMD5
|
||||||
|
- md5s = append(md5s, fmt.Sprintf("%x", md5))
|
||||||
|
+ marker = listBlob.NextMarker
|
||||||
|
|
||||||
|
+ for _, blob := range listBlob.Segment.BlobItems {
|
||||||
|
+ if prefix == "" {
|
||||||
|
+ paths = append(paths, blob.Name)
|
||||||
|
+ } else {
|
||||||
|
+ paths = append(paths, blob.Name[len(prefix):])
|
||||||
|
+ }
|
||||||
|
+ md5s = append(md5s, fmt.Sprintf("%x", blob.Properties.ContentMD5))
|
||||||
|
}
|
||||||
|
+
|
||||||
|
if progress != nil {
|
||||||
|
time.Sleep(time.Duration(500) * time.Millisecond)
|
||||||
|
progress.AddBar(1)
|
||||||
|
@@ -105,27 +97,28 @@ func (az *azContext) internalFilelist(prefix string, progress aptly.Progress) (p
|
||||||
|
return paths, md5s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-func (az *azContext) putFile(blobName string, source io.Reader, sourceMD5 string) error {
|
||||||
|
- uploadOptions := &azblob.UploadFileOptions{
|
||||||
|
- BlockSize: 4 * 1024 * 1024,
|
||||||
|
- Concurrency: 8,
|
||||||
|
+func (az *azContext) putFile(blob azblob.BlobURL, source io.Reader, sourceMD5 string) error {
|
||||||
|
+ uploadOptions := azblob.UploadStreamToBlockBlobOptions{
|
||||||
|
+ BufferSize: 4 * 1024 * 1024,
|
||||||
|
+ MaxBuffers: 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
- path := az.blobPath(blobName)
|
||||||
|
if len(sourceMD5) > 0 {
|
||||||
|
decodedMD5, err := hex.DecodeString(sourceMD5)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
- uploadOptions.HTTPHeaders = &blob.HTTPHeaders{
|
||||||
|
- BlobContentMD5: decodedMD5,
|
||||||
|
+ uploadOptions.BlobHTTPHeaders = azblob.BlobHTTPHeaders{
|
||||||
|
+ ContentMD5: decodedMD5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- var err error
|
||||||
|
- if file, ok := source.(*os.File); ok {
|
||||||
|
- _, err = az.client.UploadFile(context.TODO(), az.container, path, file, uploadOptions)
|
||||||
|
- }
|
||||||
|
+ _, err := azblob.UploadStreamToBlockBlob(
|
||||||
|
+ context.Background(),
|
||||||
|
+ source,
|
||||||
|
+ blob.ToBlockBlobURL(),
|
||||||
|
+ uploadOptions,
|
||||||
|
+ )
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
diff --git a/azure/package_pool.go b/azure/package_pool.go
|
||||||
|
index 97be8e6..6d7af1a 100644
|
||||||
|
--- a/azure/package_pool.go
|
||||||
|
+++ b/azure/package_pool.go
|
||||||
|
@@ -5,6 +5,7 @@
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
+ "github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
|
"github.com/aptly-dev/aptly/utils"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
@@ -40,7 +41,10 @@ func (pool *PackagePool) buildPoolPath(filename string, checksums *utils.Checksu
|
||||||
|
return filepath.Join(hash[0:2], hash[2:4], hash[4:32]+"_"+filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
-func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.ChecksumStorage) (*utils.ChecksumInfo, error) {
|
||||||
|
+func (pool *PackagePool) ensureChecksums(
|
||||||
|
+ poolPath string,
|
||||||
|
+ checksumStorage aptly.ChecksumStorage,
|
||||||
|
+) (*utils.ChecksumInfo, error) {
|
||||||
|
targetChecksums, err := checksumStorage.Get(poolPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
@@ -48,7 +52,8 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
|
||||||
|
|
||||||
|
if targetChecksums == nil {
|
||||||
|
// we don't have checksums stored yet for this file
|
||||||
|
- download, err := pool.az.client.DownloadStream(context.Background(), pool.az.container, poolPath, nil)
|
||||||
|
+ blob := pool.az.blobURL(poolPath)
|
||||||
|
+ download, err := blob.Download(context.Background(), 0, 0, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if isBlobNotFound(err) {
|
||||||
|
return nil, nil
|
||||||
|
@@ -58,7 +63,7 @@ func (pool *PackagePool) ensureChecksums(poolPath string, checksumStorage aptly.
|
||||||
|
}
|
||||||
|
|
||||||
|
targetChecksums = &utils.ChecksumInfo{}
|
||||||
|
- *targetChecksums, err = utils.ChecksumsForReader(download.Body)
|
||||||
|
+ *targetChecksums, err = utils.ChecksumsForReader(download.Body(azblob.RetryReaderOptions{}))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "error checksumming blob at %s", poolPath)
|
||||||
|
}
|
||||||
|
@@ -87,49 +92,46 @@ func (pool *PackagePool) LegacyPath(_ string, _ *utils.ChecksumInfo) (string, er
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) Size(path string) (int64, error) {
|
||||||
|
- serviceClient := pool.az.client.ServiceClient()
|
||||||
|
- containerClient := serviceClient.NewContainerClient(pool.az.container)
|
||||||
|
- blobClient := containerClient.NewBlobClient(path)
|
||||||
|
-
|
||||||
|
- props, err := blobClient.GetProperties(context.TODO(), nil)
|
||||||
|
+ blob := pool.az.blobURL(path)
|
||||||
|
+ props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
- return *props.ContentLength, nil
|
||||||
|
+ return props.ContentLength(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) {
|
||||||
|
+ blob := pool.az.blobURL(path)
|
||||||
|
+
|
||||||
|
temp, err := os.CreateTemp("", "blob-download")
|
||||||
|
if err != nil {
|
||||||
|
- return nil, errors.Wrapf(err, "error creating tempfile for %s", path)
|
||||||
|
+ return nil, errors.Wrap(err, "error creating temporary file for blob download")
|
||||||
|
}
|
||||||
|
+
|
||||||
|
defer func () { _ = os.Remove(temp.Name()) }()
|
||||||
|
|
||||||
|
- _, err = pool.az.client.DownloadFile(context.TODO(), pool.az.container, path, temp, nil)
|
||||||
|
+ err = azblob.DownloadBlobToFile(context.Background(), blob, 0, 0, temp, azblob.DownloadFromBlobOptions{})
|
||||||
|
if err != nil {
|
||||||
|
- return nil, errors.Wrapf(err, "error downloading blob %s", path)
|
||||||
|
+ return nil, errors.Wrapf(err, "error downloading blob at %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) Remove(path string) (int64, error) {
|
||||||
|
- serviceClient := pool.az.client.ServiceClient()
|
||||||
|
- containerClient := serviceClient.NewContainerClient(pool.az.container)
|
||||||
|
- blobClient := containerClient.NewBlobClient(path)
|
||||||
|
-
|
||||||
|
- props, err := blobClient.GetProperties(context.TODO(), nil)
|
||||||
|
+ blob := pool.az.blobURL(path)
|
||||||
|
+ props, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
|
if err != nil {
|
||||||
|
- return 0, errors.Wrapf(err, "error examining %s from %s", path, pool)
|
||||||
|
+ return 0, errors.Wrapf(err, "error getting props of %s from %s", path, pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
- _, err = pool.az.client.DeleteBlob(context.Background(), pool.az.container, path, nil)
|
||||||
|
+ _, err = blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrapf(err, "error deleting %s from %s", path, pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
- return *props.ContentLength, nil
|
||||||
|
+ return props.ContentLength(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, _ bool, checksumStorage aptly.ChecksumStorage) (string, error) {
|
||||||
|
@@ -143,6 +145,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
|
||||||
|
}
|
||||||
|
|
||||||
|
path := pool.buildPoolPath(basename, checksums)
|
||||||
|
+ blob := pool.az.blobURL(path)
|
||||||
|
targetChecksums, err := pool.ensureChecksums(path, checksumStorage)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
@@ -158,7 +161,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check
|
||||||
|
}
|
||||||
|
defer func() { _ = source.Close() }()
|
||||||
|
|
||||||
|
- err = pool.az.putFile(path, source, checksums.MD5)
|
||||||
|
+ err = pool.az.putFile(blob, source, checksums.MD5)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
diff --git a/azure/package_pool_test.go b/azure/package_pool_test.go
|
||||||
|
index ef562cb..6b1341d 100644
|
||||||
|
--- a/azure/package_pool_test.go
|
||||||
|
+++ b/azure/package_pool_test.go
|
||||||
|
@@ -7,7 +7,7 @@
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||||
|
+ "github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
|
"github.com/aptly-dev/aptly/files"
|
||||||
|
"github.com/aptly-dev/aptly/utils"
|
||||||
|
@@ -50,10 +50,8 @@ func (s *PackagePoolSuite) SetUpTest(c *C) {
|
||||||
|
|
||||||
|
s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
- publicAccessType := azblob.PublicAccessTypeContainer
|
||||||
|
- _, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{
|
||||||
|
- Access: &publicAccessType,
|
||||||
|
- })
|
||||||
|
+ cnt := s.pool.az.container
|
||||||
|
+ _, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
||||||
|
diff --git a/azure/public.go b/azure/public.go
|
||||||
|
index 6775e14..efd2e7a 100644
|
||||||
|
--- a/azure/public.go
|
||||||
|
+++ b/azure/public.go
|
||||||
|
@@ -3,22 +3,21 @@
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
+ "net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
- "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||||
|
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||||
|
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/lease"
|
||||||
|
+ "github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
|
"github.com/aptly-dev/aptly/aptly"
|
||||||
|
"github.com/aptly-dev/aptly/utils"
|
||||||
|
- "github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublishedStorage abstract file system with published files (actually hosted on Azure)
|
||||||
|
type PublishedStorage struct {
|
||||||
|
// FIXME: unused ???? prefix string
|
||||||
|
+ container azblob.ContainerURL
|
||||||
|
az *azContext
|
||||||
|
pathCache map[string]map[string]string
|
||||||
|
}
|
||||||
|
@@ -67,7 +66,7 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
||||||
|
}
|
||||||
|
defer func() { _ = source.Close() }()
|
||||||
|
|
||||||
|
- err = storage.az.putFile(path, source, sourceMD5)
|
||||||
|
+ err = storage.az.putFile(storage.az.blobURL(path), source, sourceMD5)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage))
|
||||||
|
}
|
||||||
|
@@ -77,15 +76,14 @@ func (storage *PublishedStorage) PutFile(path string, sourceFilename string) err
|
||||||
|
|
||||||
|
// RemoveDirs removes directory structure under public path
|
||||||
|
func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error {
|
||||||
|
- path = storage.az.blobPath(path)
|
||||||
|
filelist, err := storage.Filelist(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, filename := range filelist {
|
||||||
|
- blob := filepath.Join(path, filename)
|
||||||
|
- _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, blob, nil)
|
||||||
|
+ blob := storage.az.blobURL(filepath.Join(path, filename))
|
||||||
|
+ _, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error deleting path %s from %s: %s", filename, storage, err)
|
||||||
|
}
|
||||||
|
@@ -96,8 +94,8 @@ func (storage *PublishedStorage) RemoveDirs(path string, _ aptly.Progress) error
|
||||||
|
|
||||||
|
// Remove removes single file under public path
|
||||||
|
func (storage *PublishedStorage) Remove(path string) error {
|
||||||
|
- path = storage.az.blobPath(path)
|
||||||
|
- _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, path, nil)
|
||||||
|
+ blob := storage.az.blobURL(path)
|
||||||
|
+ _, err := blob.Delete(context.Background(), azblob.DeleteSnapshotsOptionNone, azblob.BlobAccessConditions{})
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, fmt.Sprintf("error deleting %s from %s: %s", path, storage, err))
|
||||||
|
}
|
||||||
|
@@ -116,8 +114,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
||||||
|
sourcePath string, sourceChecksums utils.ChecksumInfo, force bool) error {
|
||||||
|
|
||||||
|
relFilePath := filepath.Join(publishedRelPath, fileName)
|
||||||
|
- prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
|
||||||
|
- poolPath := storage.az.blobPath(prefixRelFilePath)
|
||||||
|
+ // prefixRelFilePath := filepath.Join(publishedPrefix, relFilePath)
|
||||||
|
+ // FIXME: check how to integrate publishedPrefix:
|
||||||
|
+ poolPath := storage.az.blobPath(fileName)
|
||||||
|
|
||||||
|
if storage.pathCache == nil {
|
||||||
|
storage.pathCache = make(map[string]map[string]string)
|
||||||
|
@@ -160,7 +159,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
||||||
|
}
|
||||||
|
defer func() { _ = source.Close() }()
|
||||||
|
|
||||||
|
- err = storage.az.putFile(relFilePath, source, sourceMD5)
|
||||||
|
+ err = storage.az.putFile(storage.az.blobURL(relFilePath), source, sourceMD5)
|
||||||
|
if err == nil {
|
||||||
|
pathCache[relFilePath] = sourceMD5
|
||||||
|
} else {
|
||||||
|
@@ -177,60 +176,59 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal copy or move implementation
|
||||||
|
-func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata map[string]*string, move bool) error {
|
||||||
|
+func (storage *PublishedStorage) internalCopyOrMoveBlob(src, dst string, metadata azblob.Metadata, move bool) error {
|
||||||
|
const leaseDuration = 30
|
||||||
|
- leaseID := uuid.NewString()
|
||||||
|
|
||||||
|
- serviceClient := storage.az.client.ServiceClient()
|
||||||
|
- containerClient := serviceClient.NewContainerClient(storage.az.container)
|
||||||
|
- srcBlobClient := containerClient.NewBlobClient(src)
|
||||||
|
- blobLeaseClient, err := lease.NewBlobClient(srcBlobClient, &lease.BlobClientOptions{LeaseID: to.Ptr(leaseID)})
|
||||||
|
- if err != nil {
|
||||||
|
- return fmt.Errorf("error acquiring lease on source blob %s", src)
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- _, err = blobLeaseClient.AcquireLease(context.Background(), leaseDuration, nil)
|
||||||
|
- if err != nil {
|
||||||
|
- return fmt.Errorf("error acquiring lease on source blob %s", src)
|
||||||
|
+ dstBlobURL := storage.az.blobURL(dst)
|
||||||
|
+ srcBlobURL := storage.az.blobURL(src)
|
||||||
|
+ leaseResp, err := srcBlobURL.AcquireLease(context.Background(), "", leaseDuration, azblob.ModifiedAccessConditions{})
|
||||||
|
+ if err != nil || leaseResp.StatusCode() != http.StatusCreated {
|
||||||
|
+ return fmt.Errorf("error acquiring lease on source blob %s", srcBlobURL)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
- _, _ = blobLeaseClient.BreakLease(context.Background(), &lease.BlobBreakOptions{BreakPeriod: to.Ptr(int32(60))})
|
||||||
|
+ _, _ = srcBlobURL.BreakLease(context.Background(), azblob.LeaseBreakNaturally, azblob.ModifiedAccessConditions{})
|
||||||
|
}()
|
||||||
|
+ srcBlobLeaseID := leaseResp.LeaseID()
|
||||||
|
|
||||||
|
- dstBlobClient := containerClient.NewBlobClient(dst)
|
||||||
|
- copyResp, err := dstBlobClient.StartCopyFromURL(context.Background(), srcBlobClient.URL(), &blob.StartCopyFromURLOptions{
|
||||||
|
- Metadata: metadata,
|
||||||
|
- })
|
||||||
|
-
|
||||||
|
+ copyResp, err := dstBlobURL.StartCopyFromURL(
|
||||||
|
+ context.Background(),
|
||||||
|
+ srcBlobURL.URL(),
|
||||||
|
+ metadata,
|
||||||
|
+ azblob.ModifiedAccessConditions{},
|
||||||
|
+ azblob.BlobAccessConditions{},
|
||||||
|
+ azblob.DefaultAccessTier,
|
||||||
|
+ nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error copying %s -> %s in %s: %s", src, dst, storage, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
- copyStatus := *copyResp.CopyStatus
|
||||||
|
+ copyStatus := copyResp.CopyStatus()
|
||||||
|
for {
|
||||||
|
- if copyStatus == blob.CopyStatusTypeSuccess {
|
||||||
|
+ if copyStatus == azblob.CopyStatusSuccess {
|
||||||
|
if move {
|
||||||
|
- _, err := storage.az.client.DeleteBlob(context.Background(), storage.az.container, src, &blob.DeleteOptions{
|
||||||
|
- AccessConditions: &blob.AccessConditions{
|
||||||
|
- LeaseAccessConditions: &blob.LeaseAccessConditions{
|
||||||
|
- LeaseID: &leaseID,
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- })
|
||||||
|
+ _, err = srcBlobURL.Delete(
|
||||||
|
+ context.Background(),
|
||||||
|
+ azblob.DeleteSnapshotsOptionNone,
|
||||||
|
+ azblob.BlobAccessConditions{
|
||||||
|
+ LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID},
|
||||||
|
+ })
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
- } else if copyStatus == blob.CopyStatusTypePending {
|
||||||
|
+ } else if copyStatus == azblob.CopyStatusPending {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
- getMetadata, err := dstBlobClient.GetProperties(context.TODO(), nil)
|
||||||
|
+ blobPropsResp, err := dstBlobURL.GetProperties(
|
||||||
|
+ context.Background(),
|
||||||
|
+ azblob.BlobAccessConditions{LeaseAccessConditions: azblob.LeaseAccessConditions{LeaseID: srcBlobLeaseID}},
|
||||||
|
+ azblob.ClientProvidedKeyOptions{})
|
||||||
|
if err != nil {
|
||||||
|
- return fmt.Errorf("error getting copy progress %s", dst)
|
||||||
|
+ return fmt.Errorf("error getting destination blob properties %s", dstBlobURL)
|
||||||
|
}
|
||||||
|
- copyStatus = *getMetadata.CopyStatus
|
||||||
|
+ copyStatus = blobPropsResp.CopyStatus()
|
||||||
|
|
||||||
|
- _, err = blobLeaseClient.RenewLease(context.Background(), nil)
|
||||||
|
+ _, err = srcBlobURL.RenewLease(context.Background(), srcBlobLeaseID, azblob.ModifiedAccessConditions{})
|
||||||
|
if err != nil {
|
||||||
|
- return fmt.Errorf("error renewing source blob lease %s", src)
|
||||||
|
+ return fmt.Errorf("error renewing source blob lease %s", srcBlobURL)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("error copying %s -> %s in %s: %s", dst, src, storage, copyStatus)
|
||||||
|
@@ -245,9 +243,7 @@ func (storage *PublishedStorage) RenameFile(oldName, newName string) error {
|
||||||
|
|
||||||
|
// SymLink creates a copy of src file and adds link information as meta data
|
||||||
|
func (storage *PublishedStorage) SymLink(src string, dst string) error {
|
||||||
|
- metadata := make(map[string]*string)
|
||||||
|
- metadata["SymLink"] = &src
|
||||||
|
- return storage.internalCopyOrMoveBlob(src, dst, metadata, false /* do not remove src */)
|
||||||
|
+ return storage.internalCopyOrMoveBlob(src, dst, azblob.Metadata{"SymLink": src}, false /* move */)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HardLink using symlink functionality as hard links do not exist
|
||||||
|
@@ -257,33 +253,28 @@ func (storage *PublishedStorage) HardLink(src string, dst string) error {
|
||||||
|
|
||||||
|
// FileExists returns true if path exists
|
||||||
|
func (storage *PublishedStorage) FileExists(path string) (bool, error) {
|
||||||
|
- serviceClient := storage.az.client.ServiceClient()
|
||||||
|
- containerClient := serviceClient.NewContainerClient(storage.az.container)
|
||||||
|
- blobClient := containerClient.NewBlobClient(path)
|
||||||
|
- _, err := blobClient.GetProperties(context.Background(), nil)
|
||||||
|
+ blob := storage.az.blobURL(path)
|
||||||
|
+ resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if isBlobNotFound(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
- return false, fmt.Errorf("error checking if blob %s exists: %v", path, err)
|
||||||
|
+ return false, err
|
||||||
|
+ } else if resp.StatusCode() == http.StatusOK {
|
||||||
|
+ return true, nil
|
||||||
|
}
|
||||||
|
- return true, nil
|
||||||
|
+ return false, fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLink returns the symbolic link pointed to by path.
|
||||||
|
// This simply reads text file created with SymLink
|
||||||
|
func (storage *PublishedStorage) ReadLink(path string) (string, error) {
|
||||||
|
- serviceClient := storage.az.client.ServiceClient()
|
||||||
|
- containerClient := serviceClient.NewContainerClient(storage.az.container)
|
||||||
|
- blobClient := containerClient.NewBlobClient(path)
|
||||||
|
- props, err := blobClient.GetProperties(context.Background(), nil)
|
||||||
|
+ blob := storage.az.blobURL(path)
|
||||||
|
+ resp, err := blob.GetProperties(context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
|
if err != nil {
|
||||||
|
- return "", fmt.Errorf("failed to get blob properties: %v", err)
|
||||||
|
+ return "", err
|
||||||
|
+ } else if resp.StatusCode() != http.StatusOK {
|
||||||
|
+ return "", fmt.Errorf("error checking if blob %s exists %d", blob, resp.StatusCode())
|
||||||
|
}
|
||||||
|
-
|
||||||
|
- metadata := props.Metadata
|
||||||
|
- if originalBlob, exists := metadata["original_blob"]; exists {
|
||||||
|
- return *originalBlob, nil
|
||||||
|
- }
|
||||||
|
- return "", fmt.Errorf("error reading link %s: %v", path, err)
|
||||||
|
+ return resp.NewMetadata()["SymLink"], nil
|
||||||
|
}
|
||||||
|
diff --git a/azure/public_test.go b/azure/public_test.go
|
||||||
|
index 5c912c5..f58ad51 100644
|
||||||
|
--- a/azure/public_test.go
|
||||||
|
+++ b/azure/public_test.go
|
||||||
|
@@ -7,11 +7,8 @@
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
- "bytes"
|
||||||
|
|
||||||
|
- "github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||||
|
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||||
|
- "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||||
|
+ "github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
|
"github.com/aptly-dev/aptly/files"
|
||||||
|
"github.com/aptly-dev/aptly/utils"
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
|
@@ -69,10 +66,8 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
||||||
|
|
||||||
|
s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
- publicAccessType := azblob.PublicAccessTypeContainer
|
||||||
|
- _, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{
|
||||||
|
- Access: &publicAccessType,
|
||||||
|
- })
|
||||||
|
+ cnt := s.storage.az.container
|
||||||
|
+ _, err = cnt.Create(context.Background(), azblob.Metadata{}, azblob.PublicAccessContainer)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
|
s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint)
|
||||||
|
@@ -80,39 +75,41 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedStorageSuite) TearDownTest(c *C) {
|
||||||
|
- _, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil)
|
||||||
|
+ cnt := s.storage.az.container
|
||||||
|
+ _, err := cnt.Delete(context.Background(), azblob.ContainerAccessConditions{})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte {
|
||||||
|
- resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil)
|
||||||
|
+ blob := s.storage.az.container.NewBlobURL(path)
|
||||||
|
+ resp, err := blob.Download(context.Background(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false, azblob.ClientProvidedKeyOptions{})
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
- data, err := io.ReadAll(resp.Body)
|
||||||
|
+ body := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: 3})
|
||||||
|
+ data, err := io.ReadAll(body)
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) {
|
||||||
|
- serviceClient := s.storage.az.client.ServiceClient()
|
||||||
|
- containerClient := serviceClient.NewContainerClient(s.storage.az.container)
|
||||||
|
- blobClient := containerClient.NewBlobClient(path)
|
||||||
|
- _, err := blobClient.GetProperties(context.Background(), nil)
|
||||||
|
+ _, err := s.storage.az.container.NewBlobURL(path).GetProperties(
|
||||||
|
+ context.Background(), azblob.BlobAccessConditions{}, azblob.ClientProvidedKeyOptions{})
|
||||||
|
c.Assert(err, NotNil)
|
||||||
|
-
|
||||||
|
- storageError, ok := err.(*azcore.ResponseError)
|
||||||
|
+ storageError, ok := err.(azblob.StorageError)
|
||||||
|
c.Assert(ok, Equals, true)
|
||||||
|
- c.Assert(storageError.StatusCode, Equals, 404)
|
||||||
|
+ c.Assert(string(storageError.ServiceCode()), Equals, string(string(azblob.StorageErrorCodeBlobNotFound)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) {
|
||||||
|
hash := md5.Sum(data)
|
||||||
|
- uploadOptions := &azblob.UploadStreamOptions{
|
||||||
|
- HTTPHeaders: &blob.HTTPHeaders{
|
||||||
|
- BlobContentMD5: hash[:],
|
||||||
|
- },
|
||||||
|
- }
|
||||||
|
- reader := bytes.NewReader(data)
|
||||||
|
- _, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions)
|
||||||
|
+ _, err := azblob.UploadBufferToBlockBlob(
|
||||||
|
+ context.Background(),
|
||||||
|
+ data,
|
||||||
|
+ s.storage.az.container.NewBlockBlobURL(path),
|
||||||
|
+ azblob.UploadToBlockBlobOptions{
|
||||||
|
+ BlobHTTPHeaders: azblob.BlobHTTPHeaders{
|
||||||
|
+ ContentMD5: hash[:],
|
||||||
|
+ },
|
||||||
|
+ })
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -333,7 +330,7 @@ func (s *PublishedStorageSuite) TestLinkFromPool(c *C) {
|
||||||
|
|
||||||
|
// 2nd link from pool, providing wrong path for source file
|
||||||
|
//
|
||||||
|
- // this test should check that file already exists in Azure and skip upload (which would fail if not skipped)
|
||||||
|
+ // this test should check that file already exists in S3 and skip upload (which would fail if not skipped)
|
||||||
|
s.prefixedStorage.pathCache = nil
|
||||||
|
err = s.prefixedStorage.LinkFromPool("", filepath.Join("pool", "main", "m/mars-invaders"), "mars-invaders_1.03.deb", pool, "wrong-looks-like-pathcache-doesnt-work", cksum1, false)
|
||||||
|
c.Check(err, IsNil)
|
||||||
|
diff --git a/context/context.go b/context/context.go
|
||||||
|
index 0ffc3f7..503cad2 100644
|
||||||
|
--- a/context/context.go
|
||||||
|
+++ b/context/context.go
|
||||||
|
@@ -100,6 +100,7 @@ func (context *AptlyContext) config() *utils.ConfigStructure {
|
||||||
|
configLocations := []string{homeLocation, "/usr/local/etc/aptly.conf", "/etc/aptly.conf"}
|
||||||
|
|
||||||
|
for _, configLocation := range configLocations {
|
||||||
|
+ // FIXME: check if exists, check if readable
|
||||||
|
err = utils.LoadConfig(configLocation, &utils.Config)
|
||||||
|
if os.IsPermission(err) || os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
diff --git a/deb/list.go b/deb/list.go
|
||||||
|
index 25a2d28..9eda528 100644
|
||||||
|
--- a/deb/list.go
|
||||||
|
+++ b/deb/list.go
|
||||||
|
@@ -598,6 +598,7 @@ func (l *PackageList) Filter(options FilterOptions) (*PackageList, error) {
|
||||||
|
//
|
||||||
|
// when follow-all-variants is enabled, we need to try to expand anyway,
|
||||||
|
// as even if dependency is satisfied now, there might be other ways to satisfy dependency
|
||||||
|
+ // FIXME: do not search twice
|
||||||
|
if result.Search(dep, false, true) != nil {
|
||||||
|
if options.DependencyOptions&DepVerboseResolve == DepVerboseResolve && options.Progress != nil {
|
||||||
|
options.Progress.ColoredPrintf("@{y}Already satisfied dependency@|: %s with %s", &dep, result.Search(dep, true, true))
|
||||||
|
diff --git a/go.mod b/go.mod
|
||||||
|
index 53c5e78..d7f145a 100644
|
||||||
|
--- a/go.mod
|
||||||
|
+++ b/go.mod
|
||||||
|
@@ -4,6 +4,7 @@ go 1.24
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/AlekSi/pointer v1.1.0
|
||||||
|
+ github.com/Azure/azure-storage-blob-go v0.15.0
|
||||||
|
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55
|
||||||
|
github.com/awalterschulze/gographviz v2.0.1+incompatible
|
||||||
|
github.com/cavaliergopher/grab/v3 v3.0.1
|
||||||
|
@@ -41,7 +42,7 @@ require (
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
- github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||||
|
+ github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
|
@@ -87,6 +88,7 @@ require (
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
+ github.com/mattn/go-ieproxy v0.0.9 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
@@ -96,7 +98,7 @@ require (
|
||||||
|
github.com/prometheus/common v0.59.1 // indirect
|
||||||
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
- github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
|
+ github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
|
||||||
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||||
|
@@ -115,8 +117,6 @@ require (
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
- github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
|
||||||
|
- github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
||||||
|
github.com/ProtonMail/go-crypto v1.0.0
|
||||||
|
github.com/aws/aws-sdk-go-v2 v1.32.5
|
||||||
|
github.com/aws/aws-sdk-go-v2/config v1.28.5
|
||||||
|
diff --git a/go.sum b/go.sum
|
||||||
|
index 502f4b2..453a288 100644
|
||||||
|
--- a/go.sum
|
||||||
|
+++ b/go.sum
|
||||||
|
@@ -1,17 +1,20 @@
|
||||||
|
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
|
||||||
|
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
|
||||||
|
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
|
||||||
|
-github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
|
||||||
|
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
|
||||||
|
-github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
||||||
|
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||||
|
-github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||||
|
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
||||||
|
-github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
||||||
|
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM=
|
||||||
|
-github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek=
|
||||||
|
-github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||||
|
-github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||||
|
+github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
|
||||||
|
+github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||||
|
+github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
|
||||||
|
+github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
|
||||||
|
+github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||||
|
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
|
+github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
|
||||||
|
+github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||||
|
+github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||||
|
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||||
|
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||||
|
+github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||||
|
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||||
|
+github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||||
|
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
|
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo=
|
||||||
|
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI=
|
||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
@@ -91,6 +94,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||||
|
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||||
|
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||||
|
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||||
|
@@ -127,8 +132,6 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
||||||
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
-github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
-github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
@@ -149,7 +152,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||||
|
@@ -197,6 +201,9 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
|
||||||
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
+github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||||
|
+github.com/mattn/go-ieproxy v0.0.9 h1:RvVbLiMv/Hbjf1gRaC2AQyzwbdVhdId7D2vPnXIml4k=
|
||||||
|
+github.com/mattn/go-ieproxy v0.0.9/go.mod h1:eF30/rfdQUO9EnzNIZQr0r9HiLMlZNCpJkHbmMuOAE0=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
@@ -235,8 +242,6 @@ github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||||
|
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
|
-github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
|
-github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
@@ -254,8 +259,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
-github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
|
-github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
|
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
|
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
|
||||||
|
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
||||||
|
@@ -319,6 +324,8 @@ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
+golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
|
@@ -333,14 +340,14 @@ golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
+golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
|
-golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
-golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
+golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
+golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
@@ -362,6 +369,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
+golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
From: =?utf-8?q?S=C3=A9bastien_Delafond?= <seb@debian.org>
|
||||||
|
Date: Mon, 17 Feb 2025 10:11:55 +0100
|
||||||
|
Subject: tests: no upstream's etcd install as it's arch-specific,
|
||||||
|
and no swagger-related or modules tasks
|
||||||
|
|
||||||
|
Forwarded: not-needed
|
||||||
|
---
|
||||||
|
Makefile | 11 +++--------
|
||||||
|
1 file changed, 3 insertions(+), 8 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/Makefile b/Makefile
|
||||||
|
index ffe2e8a..91f96a8 100644
|
||||||
|
--- a/Makefile
|
||||||
|
+++ b/Makefile
|
||||||
|
@@ -89,17 +89,12 @@ install:
|
||||||
|
# go install -v
|
||||||
|
@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
|
||||||
|
|
||||||
|
-test: prepare swagger etcd-install ## Run unit tests (add TEST=regex to specify which tests to run)
|
||||||
|
- @echo "\e[33m\e[1mStarting etcd ...\e[0m"
|
||||||
|
- @mkdir -p /tmp/aptly-etcd-data; system/t13_etcd/start-etcd.sh > /tmp/aptly-etcd-data/etcd.log 2>&1 &
|
||||||
|
+test: ## Run unit tests
|
||||||
|
@echo "\e[33m\e[1mRunning go test ...\e[0m"
|
||||||
|
- faketime "$(TEST_FAKETIME)" go test -v ./... -gocheck.v=true -check.f "$(TEST)" -coverprofile=unit.out; echo $$? > .unit-test.ret
|
||||||
|
- @echo "\e[33m\e[1mStopping etcd ...\e[0m"
|
||||||
|
- @pid=`cat /tmp/etcd.pid`; kill $$pid
|
||||||
|
- @rm -f /tmp/aptly-etcd-data/etcd.log
|
||||||
|
+ go test -v ./... -gocheck.v=true -coverprofile=unit.out; echo $$? > .unit-test.ret
|
||||||
|
@ret=`cat .unit-test.ret`; if [ "$$ret" = "0" ]; then echo "\n\e[32m\e[1mUnit Tests SUCCESSFUL\e[0m"; else echo "\n\e[31m\e[1mUnit Tests FAILED\e[0m"; fi; rm -f .unit-test.ret; exit $$ret
|
||||||
|
|
||||||
|
-system-test: prepare swagger etcd-install ## Run system tests
|
||||||
|
+system-test: ## Run system tests
|
||||||
|
# build coverage binary
|
||||||
|
go test -v -coverpkg="./..." -c -tags testruncli
|
||||||
|
# Download fixture-db, fixture-pool, etcd.db
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
From: =?utf-8?q?S=C3=A9bastien_Delafond?= <seb@debian.org>
|
||||||
|
Date: Wed, 24 Sep 2025 07:23:24 +0200
|
||||||
|
Subject: Revert "system-tests: abort on failure"
|
||||||
|
|
||||||
|
We'd rather have the test suite always run completely, and report
|
||||||
|
every failed test at the end.
|
||||||
|
|
||||||
|
Forwarded: not-needed
|
||||||
|
---
|
||||||
|
system/run.py | 8 --------
|
||||||
|
1 file changed, 8 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/system/run.py b/system/run.py
|
||||||
|
index 4e73fb2..599afe5 100755
|
||||||
|
--- a/system/run.py
|
||||||
|
+++ b/system/run.py
|
||||||
|
@@ -50,7 +50,6 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non
|
||||||
|
if not coverage_dir:
|
||||||
|
coverage_dir = mkdtemp(suffix="aptly-coverage")
|
||||||
|
|
||||||
|
- failed = False
|
||||||
|
for test in tests:
|
||||||
|
orig_stdout = sys.stdout
|
||||||
|
orig_stderr = sys.stderr
|
||||||
|
@@ -158,15 +157,8 @@ def run(include_long_tests=False, capture_results=False, tests=None, filters=Non
|
||||||
|
|
||||||
|
t.shutdown()
|
||||||
|
|
||||||
|
- if failed:
|
||||||
|
- break
|
||||||
|
- if failed:
|
||||||
|
- break
|
||||||
|
-
|
||||||
|
sys.stdout = orig_stdout
|
||||||
|
sys.stderr = orig_stderr
|
||||||
|
- if failed:
|
||||||
|
- break
|
||||||
|
|
||||||
|
if lastBase is not None:
|
||||||
|
lastBase.shutdown_class()
|
||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
0001-disable-swagger.patch
|
||||||
|
0002-disable-new-azure-sdk.patch
|
||||||
|
0003-tests-no-upstream-s-etcd-install-as-it-s-arch-specif.patch
|
||||||
|
0004-Revert-system-tests-abort-on-failure.patch
|
||||||
Vendored
+4
-34
@@ -2,47 +2,17 @@
|
|||||||
|
|
||||||
include /usr/share/dpkg/pkg-info.mk
|
include /usr/share/dpkg/pkg-info.mk
|
||||||
|
|
||||||
export GOPATH=$(shell pwd)/.go
|
|
||||||
export DEB_BUILD_OPTIONS=crossbuildcanrunhostbinaries
|
|
||||||
|
|
||||||
export GOARCH := $(shell if [ $(DEB_TARGET_ARCH) = "i386" ]; then echo "386"; elif [ $(DEB_TARGET_ARCH) = "armhf" ]; then echo "arm"; else echo $(DEB_TARGET_ARCH); fi)
|
|
||||||
export CGO_ENABLED=1
|
|
||||||
|
|
||||||
ifneq ($(DEB_HOST_GNU_TYPE), $(DEB_BUILD_GNU_TYPE))
|
|
||||||
export CC=$(DEB_HOST_GNU_TYPE)-gcc
|
|
||||||
endif
|
|
||||||
|
|
||||||
%:
|
%:
|
||||||
dh $@ --buildsystem=golang --with=golang,bash-completion
|
dh $@ --buildsystem=golang --with=golang,bash-completion
|
||||||
|
|
||||||
override_dh_auto_clean:
|
|
||||||
rm -rf build/
|
|
||||||
rm -rf obj-$(DEB_TARGET_GNU_TYPE)/
|
|
||||||
dh_auto_clean
|
|
||||||
|
|
||||||
override_dh_auto_test:
|
override_dh_auto_test:
|
||||||
# run during autopkgtests
|
# run during autopkgtests
|
||||||
|
|
||||||
override_dh_auto_install:
|
override_dh_auto_install:
|
||||||
dh_auto_install -- --no-source
|
dh_auto_install -- --no-source
|
||||||
|
|
||||||
override_dh_strip:
|
|
||||||
dh_strip --dbg-package=aptly-dbg
|
|
||||||
|
|
||||||
override_dh_golang: # fails on non native debian build
|
|
||||||
|
|
||||||
# override_dh_makeshlibs: # fails with cross compiling on non native debian build
|
|
||||||
|
|
||||||
override_dh_dwz: # somehow dwz works only with certain newer debhelper versions
|
|
||||||
dhver=`dpkg-query -f '$${Version}' -W debhelper`; (dpkg --compare-versions "$$dhver" lt 13 || test "$$dhver" = "13.3.4" || test "$$dhver" = "13.6ubuntu1") || dh_dwz
|
|
||||||
|
|
||||||
override_dh_shlibdeps:
|
|
||||||
ifneq ($(DEB_HOST_GNU_TYPE), $(DEB_BUILD_GNU_TYPE))
|
|
||||||
LD_LIBRARY_PATH=/usr/$(DEB_HOST_GNU_TYPE)/lib:$$LD_LIBRARY_PATH dh_shlibdeps
|
|
||||||
else
|
|
||||||
dh_shlibdeps
|
|
||||||
endif
|
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
echo $(DEB_VERSION) > VERSION
|
echo $(DEB_VERSION) > obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/VERSION
|
||||||
go build -buildmode=pie -o usr/bin/aptly
|
mkdir -p obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/debian
|
||||||
|
cp debian/aptly.conf obj-$(DEB_TARGET_GNU_TYPE)/src/github.com/aptly-dev/aptly/debian/
|
||||||
|
dh_auto_build
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
3.0 (git)
|
3.0 (quilt)
|
||||||
|
|||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# run upstream's unit tests with Debian-supplied dependencies
|
||||||
|
|
||||||
|
# FIXME: right now this fails hard because many prerequisites are only
|
||||||
|
# handled in `make test`
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
set -x
|
||||||
|
|
||||||
|
BUILD_DIR="${PWD}/_build"
|
||||||
|
DH_OPTIONS="-O--buildsystem=golang -O--builddirectory=$BUILD_DIR"
|
||||||
|
APTLY_DIR="${BUILD_DIR}/src/github.com/aptly-dev/aptly"
|
||||||
|
|
||||||
|
dpkg-source --before-build .
|
||||||
|
|
||||||
|
dh_update_autotools_config $DH_OPTIONS
|
||||||
|
dh_autoreconf $DH_OPTIONS
|
||||||
|
dh_auto_configure $DH_OPTIONS
|
||||||
|
|
||||||
|
dpkg-parsechangelog --show-field Version | perl -pe 's/\n//' >| ${APTLY_DIR}/VERSION
|
||||||
|
find . -name VERSION
|
||||||
|
|
||||||
|
mkdir -p ${APTLY_DIR}/debian
|
||||||
|
cp debian/aptly.conf ${APTLY_DIR}/debian/
|
||||||
|
|
||||||
|
dh_auto_build $DH_OPTIONS
|
||||||
|
|
||||||
|
export PATH=${BUILD_DIR}/bin:$PATH
|
||||||
|
|
||||||
|
dh_auto_test $DH_OPTIONS --no-parallel
|
||||||
Vendored
+6
-3
@@ -1,4 +1,7 @@
|
|||||||
# This file is an addition to the autodep8 tests.
|
Tests: unit-test
|
||||||
|
Depends: @, @builddeps@, ca-certificates, curl, git, gpg, gpg-agent
|
||||||
Test-Command: HOME=/tmp aptly repo create autopkgtest
|
|
||||||
Restrictions: allow-stderr
|
Restrictions: allow-stderr
|
||||||
|
|
||||||
|
Tests: system-test
|
||||||
|
Depends: @, @builddeps@, ca-certificates, curl, dirmngr, git, gpg, gpg-agent, gpgconf, graphviz, procps, python3, python3-requests-unixsocket, python3-termcolor, python3-swiftclient, python3-boto3, python3-azure-storage, python3-etcd3, python3-plyvel, sudo, zip
|
||||||
|
Restrictions: allow-stderr, needs-internet
|
||||||
|
|||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
TMP_DIR="$(mktemp -d)"
|
||||||
|
APTLY_SRC_DIR="${TMP_DIR}/src/github.com/aptly-dev/aptly"
|
||||||
|
|
||||||
|
# apply patches
|
||||||
|
dpkg-source --before-build .
|
||||||
|
|
||||||
|
# copy source to GOPATH-compatible dir
|
||||||
|
mkdir -p $(dirname $APTLY_SRC_DIR)
|
||||||
|
cp -a . $APTLY_SRC_DIR
|
||||||
|
|
||||||
|
# not in a git tree, so we need to generate the version ourselves
|
||||||
|
dpkg-parsechangelog --show-field Version | perl -pe 's/\n//' > ${APTLY_SRC_DIR}/VERSION
|
||||||
|
|
||||||
|
# use only apt-supplied go libraries
|
||||||
|
export GOPATH="${TMP_DIR}:/usr/share/gocode"
|
||||||
|
export GO111MODULE=off
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# run upstream's integration tests
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
. debian/tests/setup
|
||||||
|
|
||||||
|
## env
|
||||||
|
TESTS_DIR="${APTLY_SRC_DIR}/system"
|
||||||
|
|
||||||
|
## functions
|
||||||
|
disable_test() {
|
||||||
|
local file=${1}.py
|
||||||
|
local name=$2
|
||||||
|
local reason=$3
|
||||||
|
|
||||||
|
echo "${name}.skipTest = 'Debian autopkgtest: $reason'" >> ${TESTS_DIR}/${file}
|
||||||
|
}
|
||||||
|
|
||||||
|
## main
|
||||||
|
export USER=root # for t07/RootDirInaccessible
|
||||||
|
|
||||||
|
disable_test t01_version/version VersionTest "version"
|
||||||
|
disable_test t02_config/config CreateConfigTest "different conf"
|
||||||
|
disable_test t04_mirror/create CreateMirror31Test "public key not found"
|
||||||
|
disable_test t04_mirror/create CreateMirror35Test "flaky on s390"
|
||||||
|
disable_test t07_serve/serve Serve1Test "minor html diff"
|
||||||
|
disable_test t09_repo/edit EditRepo4Test "flaky on riscv64"
|
||||||
|
disable_test t10_task/run RunTask1Test "version"
|
||||||
|
disable_test t12_api/docs TaskAPITestSwaggerDocs "no recent swag"
|
||||||
|
disable_test t12_api/gpg GPGAPITestAddKey "flaky on s390"
|
||||||
|
disable_test t12_api/unix_socket UnixSocketAPITest "type mismatch"
|
||||||
|
disable_test t12_api/version VersionAPITest "type mismatch"
|
||||||
|
disable_test t14_graph/graph CreateGraphTest "no viewer"
|
||||||
|
disable_test t14_graph/graph CreateGraphOutputTest "no viewer"
|
||||||
|
|
||||||
|
# etcd fixture is entirely arch-specific
|
||||||
|
rm -fr ${TESTS_DIR}/t13_etcd
|
||||||
|
|
||||||
|
make -C $APTLY_SRC_DIR system-test
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# run upstream's unit tests with their full etcd fixtures, etc
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
. debian/tests/setup
|
||||||
|
|
||||||
|
# FIXME: errors with non-constant format string in call to
|
||||||
|
# github.com/aptly-dev/aptly/s3.fatalError
|
||||||
|
rm ${APTLY_SRC_DIR}/s3/server_test.go
|
||||||
|
rm ${APTLY_SRC_DIR}/s3/public_test.go
|
||||||
|
|
||||||
|
# upstream's etcd fixture is arch-specific
|
||||||
|
rm ${APTLY_SRC_DIR}/database/etcddb/database_test.go
|
||||||
|
|
||||||
|
# TestVerifyClearsigned fails because of an extra signature
|
||||||
|
perl -i -pe 's/(TestVerifyClearsigned)/No$1/' ${APTLY_SRC_DIR}/pgp/verify_test.go
|
||||||
|
|
||||||
|
make -C $APTLY_SRC_DIR test
|
||||||
Vendored
+6
-5
@@ -1,5 +1,6 @@
|
|||||||
version=4
|
Version: 5
|
||||||
opts=\
|
Template: Github
|
||||||
repacksuffix=+ds1,\
|
Owner: aptly-dev
|
||||||
dversionmangle=s/\+ds\d*$// \
|
Project: aptly
|
||||||
https://github.com/aptly-dev/aptly/tags .*/v(\d[\d\.]*)\.tar\.gz debian uupdate
|
Dversion-Mangle: s/\+ds\d*$//
|
||||||
|
Repacksuffix: +ds1
|
||||||
|
|||||||
-209
@@ -1,209 +0,0 @@
|
|||||||
# GPG Keys Management
|
|
||||||
|
|
||||||
GPG keys are used by aptly to verify the authenticity of remote repository Release files when creating mirrors. This document describes the API endpoints for managing GPG keys in the aptly keyring.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Aptly uses GNU Privacy Guard (GPG) to verify signed repository metadata. You must add the repository's GPG public key to aptly's keyring before creating mirrors that verify signatures.
|
|
||||||
|
|
||||||
Keys are stored in the aptly keyring (default: `trustedkeys.gpg`). You can have multiple keyrings and specify which one to use via the `Keyring` parameter.
|
|
||||||
|
|
||||||
## API Endpoints
|
|
||||||
|
|
||||||
### List GPG Keys
|
|
||||||
|
|
||||||
**GET /api/gpg/keys**
|
|
||||||
|
|
||||||
Lists all public GPG keys currently installed in the aptly keyring.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `keyring` (query, optional): Keyring file to list keys from. Default: `trustedkeys.gpg`
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Keys": [
|
|
||||||
{
|
|
||||||
"KeyID": "8B48AD6246925553",
|
|
||||||
"Fingerprint": "D8E8F5A516E7A2C4F3E4B5A6C7D8E9F0",
|
|
||||||
"Validity": "f",
|
|
||||||
"UserIDs": ["John Doe <john@example.com>"],
|
|
||||||
"CreatedAt": "1611864000"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Status Codes:**
|
|
||||||
- `200 OK`: Keys successfully retrieved
|
|
||||||
- `400 Bad Request`: GPG execution failed or invalid parameters
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
curl http://localhost:8080/api/gpg/keys
|
|
||||||
curl "http://localhost:8080/api/gpg/keys?keyring=custom.gpg"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Add GPG Key
|
|
||||||
|
|
||||||
**POST /api/gpg/key**
|
|
||||||
|
|
||||||
Adds a GPG public key to the aptly keyring. Keys can be added in two ways:
|
|
||||||
1. Provide the ASCII-armored key directly
|
|
||||||
2. Provide a key server and key ID(s) to download from
|
|
||||||
|
|
||||||
**Request Body:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Keyring": "trustedkeys.gpg",
|
|
||||||
"GpgKeyArmor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...",
|
|
||||||
"Keyserver": "hkp://keyserver.ubuntu.com:80",
|
|
||||||
"GpgKeyID": "8B48AD6246925553"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `Keyring` (optional): Keyring file to add keys to. Default: `trustedkeys.gpg`
|
|
||||||
- `GpgKeyArmor` (optional): ASCII-armored GPG public key
|
|
||||||
- `Keyserver` (optional): Keyserver URL (e.g., `hkp://keyserver.ubuntu.com:80`)
|
|
||||||
- `GpgKeyID` (optional): Space-separated key IDs to download from keyserver
|
|
||||||
|
|
||||||
**Status Codes:**
|
|
||||||
- `200 OK`: Key successfully added
|
|
||||||
- `400 Bad Request`: Invalid parameters or GPG execution failed
|
|
||||||
|
|
||||||
**Example - From ASCII Key:**
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:8080/api/gpg/key \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"GpgKeyArmor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nVersion: GnuPG v2\n...\n-----END PGP PUBLIC KEY BLOCK-----"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example - From Keyserver:**
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:8080/api/gpg/key \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"Keyserver": "hkp://keyserver.ubuntu.com:80",
|
|
||||||
"GpgKeyID": "8B48AD6246925553 A1B2C3D4E5F67890"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Delete GPG Key
|
|
||||||
|
|
||||||
**DELETE /api/gpg/key**
|
|
||||||
|
|
||||||
Removes a GPG key from the aptly keyring.
|
|
||||||
|
|
||||||
**Request Body:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Keyring": "trustedkeys.gpg",
|
|
||||||
"GpgKeyID": "8B48AD6246925553"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `Keyring` (optional): Keyring file to delete from. Default: `trustedkeys.gpg`
|
|
||||||
- `GpgKeyID` (required): Key ID or fingerprint to delete
|
|
||||||
|
|
||||||
**Status Codes:**
|
|
||||||
- `200 OK`: Key successfully deleted
|
|
||||||
- `400 Bad Request`: Invalid parameters or GPG execution failed
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
curl -X DELETE http://localhost:8080/api/gpg/key \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"GpgKeyID": "8B48AD6246925553"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
|
|
||||||
### 1. Verify Downloaded Repository Metadata
|
|
||||||
|
|
||||||
Before creating a mirror from a signed repository, add the repository's GPG key:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Add the key from a keyserver
|
|
||||||
curl -X POST http://localhost:8080/api/gpg/key \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"Keyserver": "hkp://keyserver.ubuntu.com:80",
|
|
||||||
"GpgKeyID": "EB9B46B91F2D3B7E"
|
|
||||||
}'
|
|
||||||
|
|
||||||
# Now create a mirror with signature verification
|
|
||||||
# (signature verification configured in mirror settings)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Manage Multiple Keyrings
|
|
||||||
|
|
||||||
Aptly supports using different keyrings for different purposes. For example, one for Debian repositories and another for custom internal repositories:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Add key to Debian keyring
|
|
||||||
curl -X POST http://localhost:8080/api/gpg/key \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"Keyring": "debian-keys.gpg",
|
|
||||||
"Keyserver": "hkp://keyserver.ubuntu.com:80",
|
|
||||||
"GpgKeyID": "EB9B46B91F2D3B7E"
|
|
||||||
}'
|
|
||||||
|
|
||||||
# List keys in Debian keyring
|
|
||||||
curl "http://localhost:8080/api/gpg/keys?keyring=debian-keys.gpg"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Remove Compromised Keys
|
|
||||||
|
|
||||||
If a GPG key is compromised, remove it from the keyring immediately:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X DELETE http://localhost:8080/api/gpg/key \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"GpgKeyID": "COMPROMISED_KEY_ID"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Key Validity Values
|
|
||||||
|
|
||||||
Keys retrieved from `GET /api/gpg/keys` have a `Validity` field with the following possible values:
|
|
||||||
|
|
||||||
- `u` — Unknown validity
|
|
||||||
- `f` — Full trust
|
|
||||||
- `m` — Marginal trust
|
|
||||||
- `n` — Never trust
|
|
||||||
- `-` — Trust not set
|
|
||||||
|
|
||||||
The trust level is typically managed in your GPG configuration and does not affect aptly's ability to verify signatures.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
**"failed to list keys"**
|
|
||||||
- Check that the keyring file exists and is readable
|
|
||||||
- Verify GPG is installed and configured
|
|
||||||
|
|
||||||
**"unable to delete key: no public key"**
|
|
||||||
- The key might not exist in the keyring
|
|
||||||
- Verify the key ID is correct by listing keys first
|
|
||||||
|
|
||||||
**"invalid request body"**
|
|
||||||
- Ensure the JSON is properly formatted
|
|
||||||
- For POST requests, provide either `GpgKeyArmor` or (`Keyserver` + `GpgKeyID`)
|
|
||||||
- For DELETE requests, `GpgKeyID` is required
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
package files
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aptly-dev/aptly/aptly"
|
|
||||||
"github.com/aptly-dev/aptly/utils"
|
|
||||||
|
|
||||||
. "gopkg.in/check.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LinkFromPoolConcurrencySuite struct {
|
|
||||||
root string
|
|
||||||
poolDir string
|
|
||||||
storage *PublishedStorage
|
|
||||||
pool *PackagePool
|
|
||||||
cs aptly.ChecksumStorage
|
|
||||||
testFile string
|
|
||||||
testContent []byte
|
|
||||||
testChecksums utils.ChecksumInfo
|
|
||||||
srcPoolPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Suite(&LinkFromPoolConcurrencySuite{})
|
|
||||||
|
|
||||||
func (s *LinkFromPoolConcurrencySuite) SetUpTest(c *C) {
|
|
||||||
s.root = c.MkDir()
|
|
||||||
s.poolDir = filepath.Join(s.root, "pool")
|
|
||||||
publishDir := filepath.Join(s.root, "public")
|
|
||||||
|
|
||||||
// Create package pool and published storage
|
|
||||||
s.pool = NewPackagePool(s.poolDir, true)
|
|
||||||
s.storage = NewPublishedStorage(publishDir, "copy", "checksum")
|
|
||||||
s.cs = NewMockChecksumStorage()
|
|
||||||
|
|
||||||
// Create test file content
|
|
||||||
s.testContent = []byte("test package content for concurrency testing")
|
|
||||||
s.testFile = filepath.Join(s.root, "test-package.deb")
|
|
||||||
|
|
||||||
err := os.WriteFile(s.testFile, s.testContent, 0644)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
// Calculate checksums
|
|
||||||
md5sum, err := utils.MD5ChecksumForFile(s.testFile)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
s.testChecksums = utils.ChecksumInfo{
|
|
||||||
Size: int64(len(s.testContent)),
|
|
||||||
MD5: md5sum,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import the test file into the pool
|
|
||||||
s.srcPoolPath, err = s.pool.Import(s.testFile, "test-package.deb", &s.testChecksums, false, s.cs)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LinkFromPoolConcurrencySuite) TestLinkFromPoolConcurrency(c *C) {
|
|
||||||
// Test concurrent LinkFromPool operations to ensure no race conditions
|
|
||||||
concurrency := 5000
|
|
||||||
iterations := 10
|
|
||||||
|
|
||||||
for iter := 0; iter < iterations; iter++ {
|
|
||||||
c.Logf("Iteration %d: Testing concurrent LinkFromPool with %d goroutines", iter+1, concurrency)
|
|
||||||
|
|
||||||
destPath := fmt.Sprintf("main/t/test%d", iter)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
errors := make(chan error, concurrency)
|
|
||||||
successes := make(chan struct{}, concurrency)
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
// Launch concurrent LinkFromPool operations
|
|
||||||
for i := 0; i < concurrency; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
// Use force=true to test the most vulnerable code path (remove-then-create)
|
|
||||||
err := s.storage.LinkFromPool(
|
|
||||||
"", // publishedPrefix
|
|
||||||
destPath, // publishedRelPath
|
|
||||||
"test-package.deb", // fileName
|
|
||||||
s.pool, // sourcePool
|
|
||||||
s.srcPoolPath, // sourcePath
|
|
||||||
s.testChecksums, // sourceChecksums
|
|
||||||
true, // force - this triggers vulnerable remove-then-create pattern
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errors <- fmt.Errorf("goroutine %d failed: %v", id, err)
|
|
||||||
} else {
|
|
||||||
successes <- struct{}{}
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for completion
|
|
||||||
wg.Wait()
|
|
||||||
duration := time.Since(start)
|
|
||||||
|
|
||||||
close(errors)
|
|
||||||
close(successes)
|
|
||||||
|
|
||||||
// Count results
|
|
||||||
errorCount := 0
|
|
||||||
successCount := 0
|
|
||||||
var firstError error
|
|
||||||
|
|
||||||
for err := range errors {
|
|
||||||
errorCount++
|
|
||||||
if firstError == nil {
|
|
||||||
firstError = err
|
|
||||||
}
|
|
||||||
c.Logf("Race condition error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for range successes {
|
|
||||||
successCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Logf("Results: %d successes, %d errors, took %v", successCount, errorCount, duration)
|
|
||||||
|
|
||||||
// Assert no race conditions occurred
|
|
||||||
if errorCount > 0 {
|
|
||||||
c.Fatalf("Race condition detected in iteration %d! "+
|
|
||||||
"Errors: %d out of %d operations (%.1f%% failure rate). "+
|
|
||||||
"First error: %v. "+
|
|
||||||
"This indicates the fix is not working properly.",
|
|
||||||
iter+1, errorCount, concurrency,
|
|
||||||
float64(errorCount)/float64(concurrency)*100, firstError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the final file exists and has correct content
|
|
||||||
finalFile := filepath.Join(s.storage.rootPath, destPath, "test-package.deb")
|
|
||||||
_, err := os.Stat(finalFile)
|
|
||||||
c.Assert(err, IsNil, Commentf("Final file should exist after concurrent operations"))
|
|
||||||
|
|
||||||
content, err := os.ReadFile(finalFile)
|
|
||||||
c.Assert(err, IsNil, Commentf("Should be able to read final file"))
|
|
||||||
c.Assert(content, DeepEquals, s.testContent, Commentf("File content should be intact after concurrent operations"))
|
|
||||||
|
|
||||||
c.Logf("✓ Iteration %d: No race conditions detected", iter+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Logf("SUCCESS: Handled %d total concurrent operations across %d iterations with no race conditions",
|
|
||||||
concurrency*iterations, iterations)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LinkFromPoolConcurrencySuite) TestLinkFromPoolConcurrencyDifferentFiles(c *C) {
|
|
||||||
// Test concurrent operations on different files to ensure no blocking
|
|
||||||
concurrency := 10
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
errors := make(chan error, concurrency)
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
// Launch concurrent operations on different destination files
|
|
||||||
for i := 0; i < concurrency; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
destPath := fmt.Sprintf("main/t/test-file-%d", id)
|
|
||||||
|
|
||||||
err := s.storage.LinkFromPool(
|
|
||||||
"", // publishedPrefix
|
|
||||||
destPath, // publishedRelPath
|
|
||||||
"test-package.deb", // fileName
|
|
||||||
s.pool, // sourcePool
|
|
||||||
s.srcPoolPath, // sourcePath
|
|
||||||
s.testChecksums, // sourceChecksums
|
|
||||||
false, // force
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errors <- fmt.Errorf("goroutine %d failed: %v", id, err)
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for completion
|
|
||||||
wg.Wait()
|
|
||||||
duration := time.Since(start)
|
|
||||||
|
|
||||||
close(errors)
|
|
||||||
|
|
||||||
// Count errors
|
|
||||||
errorCount := 0
|
|
||||||
for err := range errors {
|
|
||||||
errorCount++
|
|
||||||
c.Logf("Error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(errorCount, Equals, 0, Commentf("No errors should occur when linking to different files"))
|
|
||||||
c.Logf("SUCCESS: %d concurrent operations on different files completed in %v", concurrency, duration)
|
|
||||||
|
|
||||||
// Verify all files were created correctly
|
|
||||||
for i := 0; i < concurrency; i++ {
|
|
||||||
finalFile := filepath.Join(s.storage.rootPath, fmt.Sprintf("main/t/test-file-%d", i), "test-package.deb")
|
|
||||||
_, err := os.Stat(finalFile)
|
|
||||||
c.Assert(err, IsNil, Commentf("File %d should exist", i))
|
|
||||||
|
|
||||||
content, err := os.ReadFile(finalFile)
|
|
||||||
c.Assert(err, IsNil, Commentf("Should be able to read file %d", i))
|
|
||||||
c.Assert(content, DeepEquals, s.testContent, Commentf("File %d content should be correct", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LinkFromPoolConcurrencySuite) TestLinkFromPoolWithoutForceNoConcurrencyIssues(c *C) {
|
|
||||||
// Test that when force=false, concurrent operations fail gracefully without corruption
|
|
||||||
concurrency := 20
|
|
||||||
destPath := "main/t/single-dest"
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
errors := make(chan error, concurrency)
|
|
||||||
successes := make(chan struct{}, concurrency)
|
|
||||||
|
|
||||||
// First, create the file so subsequent operations will conflict
|
|
||||||
err := s.storage.LinkFromPool("", destPath, "test-package.deb", s.pool, s.srcPoolPath, s.testChecksums, false)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
// Launch concurrent operations that should mostly fail
|
|
||||||
for i := 0; i < concurrency; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(id int) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
err := s.storage.LinkFromPool(
|
|
||||||
"", // publishedPrefix
|
|
||||||
destPath, // publishedRelPath
|
|
||||||
"test-package.deb", // fileName
|
|
||||||
s.pool, // sourcePool
|
|
||||||
s.srcPoolPath, // sourcePath
|
|
||||||
s.testChecksums, // sourceChecksums
|
|
||||||
false, // force=false - should fail if file exists and is same
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errors <- err
|
|
||||||
} else {
|
|
||||||
successes <- struct{}{}
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for completion
|
|
||||||
wg.Wait()
|
|
||||||
duration := time.Since(start)
|
|
||||||
|
|
||||||
close(errors)
|
|
||||||
close(successes)
|
|
||||||
|
|
||||||
errorCount := 0
|
|
||||||
successCount := 0
|
|
||||||
|
|
||||||
for range errors {
|
|
||||||
errorCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
for range successes {
|
|
||||||
successCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Logf("Results with force=false: %d successes, %d errors, took %v", successCount, errorCount, duration)
|
|
||||||
|
|
||||||
// With force=false and identical files, operations should succeed (file already exists with same content)
|
|
||||||
// No race conditions should cause crashes or corruption
|
|
||||||
c.Assert(errorCount, Equals, 0, Commentf("With identical files and force=false, operations should succeed"))
|
|
||||||
|
|
||||||
// Verify the file still exists and has correct content
|
|
||||||
finalFile := filepath.Join(s.storage.rootPath, destPath, "test-package.deb")
|
|
||||||
content, err := os.ReadFile(finalFile)
|
|
||||||
c.Assert(err, IsNil)
|
|
||||||
c.Assert(content, DeepEquals, s.testContent, Commentf("File should not be corrupted by concurrent access"))
|
|
||||||
}
|
|
||||||
+7
-32
@@ -26,26 +26,6 @@ type PublishedStorage struct {
|
|||||||
verifyMethod uint
|
verifyMethod uint
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global mutex map to prevent concurrent access to the same destinationPath in LinkFromPool
|
|
||||||
var (
|
|
||||||
fileLockMutex sync.Mutex
|
|
||||||
fileLocks = make(map[string]*sync.Mutex)
|
|
||||||
)
|
|
||||||
|
|
||||||
// getFileLock returns a mutex for a specific file path to prevent concurrent modifications
|
|
||||||
func getFileLock(filePath string) *sync.Mutex {
|
|
||||||
fileLockMutex.Lock()
|
|
||||||
defer fileLockMutex.Unlock()
|
|
||||||
|
|
||||||
if mutex, exists := fileLocks[filePath]; exists {
|
|
||||||
return mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex := &sync.Mutex{}
|
|
||||||
fileLocks[filePath] = mutex
|
|
||||||
return mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check interfaces
|
// Check interfaces
|
||||||
var (
|
var (
|
||||||
_ aptly.PublishedStorage = (*PublishedStorage)(nil)
|
_ aptly.PublishedStorage = (*PublishedStorage)(nil)
|
||||||
@@ -172,11 +152,6 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
|||||||
poolPath := filepath.Join(storage.rootPath, publishedPrefix, publishedRelPath, filepath.Dir(fileName))
|
poolPath := filepath.Join(storage.rootPath, publishedPrefix, publishedRelPath, filepath.Dir(fileName))
|
||||||
destinationPath := filepath.Join(poolPath, baseName)
|
destinationPath := filepath.Join(poolPath, baseName)
|
||||||
|
|
||||||
// Acquire file-specific lock to prevent concurrent access to the same file
|
|
||||||
fileLock := getFileLock(destinationPath)
|
|
||||||
fileLock.Lock()
|
|
||||||
defer fileLock.Unlock()
|
|
||||||
|
|
||||||
var localSourcePool aptly.LocalPackagePool
|
var localSourcePool aptly.LocalPackagePool
|
||||||
if storage.linkMethod != LinkMethodCopy {
|
if storage.linkMethod != LinkMethodCopy {
|
||||||
pp, ok := sourcePool.(aptly.LocalPackagePool)
|
pp, ok := sourcePool.(aptly.LocalPackagePool)
|
||||||
@@ -194,7 +169,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
|||||||
|
|
||||||
var dstStat os.FileInfo
|
var dstStat os.FileInfo
|
||||||
|
|
||||||
dstStat, err = os.Stat(destinationPath)
|
dstStat, err = os.Stat(filepath.Join(poolPath, baseName))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// already exists, check source file
|
// already exists, check source file
|
||||||
|
|
||||||
@@ -213,7 +188,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
|||||||
} else {
|
} else {
|
||||||
// if source and destination have the same checksums, no need to copy
|
// if source and destination have the same checksums, no need to copy
|
||||||
var dstMD5 string
|
var dstMD5 string
|
||||||
dstMD5, err = utils.MD5ChecksumForFile(destinationPath)
|
dstMD5, err = utils.MD5ChecksumForFile(filepath.Join(poolPath, baseName))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -244,11 +219,11 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
|||||||
|
|
||||||
// source and destination have different inodes, if !forced, this is fatal error
|
// source and destination have different inodes, if !forced, this is fatal error
|
||||||
if !force {
|
if !force {
|
||||||
return fmt.Errorf("error linking file to %s: file already exists and is different", destinationPath)
|
return fmt.Errorf("error linking file to %s: file already exists and is different", filepath.Join(poolPath, baseName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// forced, so remove destination
|
// forced, so remove destination
|
||||||
err = os.Remove(destinationPath)
|
err = os.Remove(filepath.Join(poolPath, baseName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -263,7 +238,7 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dst *os.File
|
var dst *os.File
|
||||||
dst, err = os.Create(destinationPath)
|
dst, err = os.Create(filepath.Join(poolPath, baseName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = r.Close()
|
_ = r.Close()
|
||||||
return err
|
return err
|
||||||
@@ -291,9 +266,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath,
|
|||||||
|
|
||||||
err = dst.Close()
|
err = dst.Close()
|
||||||
} else if storage.linkMethod == LinkMethodSymLink {
|
} else if storage.linkMethod == LinkMethodSymLink {
|
||||||
err = localSourcePool.Symlink(sourcePath, destinationPath)
|
err = localSourcePool.Symlink(sourcePath, filepath.Join(poolPath, baseName))
|
||||||
} else {
|
} else {
|
||||||
err = localSourcePool.Link(sourcePath, destinationPath)
|
err = localSourcePool.Link(sourcePath, filepath.Join(poolPath, baseName))
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -632,16 +632,6 @@ func (s *DiskFullNoRootSuite) TestLinkFromPoolCopySyncErrorIsReturned(c *C) {
|
|||||||
c.Check(strings.Contains(err.Error(), "error syncing file"), Equals, true)
|
c.Check(strings.Contains(err.Error(), "error syncing file"), Equals, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DiskFullNoRootSuite) TestGetFileLockReusesMutex(c *C) {
|
|
||||||
a := getFileLock(filepath.Join(s.root, "a"))
|
|
||||||
b := getFileLock(filepath.Join(s.root, "a"))
|
|
||||||
c.Check(a == b, Equals, true)
|
|
||||||
|
|
||||||
c1 := getFileLock(filepath.Join(s.root, "c1"))
|
|
||||||
c2 := getFileLock(filepath.Join(s.root, "c2"))
|
|
||||||
c.Check(c1 == c2, Equals, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DiskFullNoRootSuite) TestPutFileFailsIfDestinationDirMissing(c *C) {
|
func (s *DiskFullNoRootSuite) TestPutFileFailsIfDestinationDirMissing(c *C) {
|
||||||
storage := NewPublishedStorage(s.root, "", "")
|
storage := NewPublishedStorage(s.root, "", "")
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ module github.com/aptly-dev/aptly
|
|||||||
|
|
||||||
go 1.24.0
|
go 1.24.0
|
||||||
|
|
||||||
|
toolchain go1.24.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlekSi/pointer v1.1.0
|
github.com/AlekSi/pointer v1.1.0
|
||||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55
|
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55
|
||||||
@@ -32,17 +34,16 @@ require (
|
|||||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
|
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
|
||||||
github.com/ugorji/go/codec v1.2.11
|
github.com/ugorji/go/codec v1.2.11
|
||||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
|
||||||
golang.org/x/crypto v0.46.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/sys v0.39.0
|
golang.org/x/sys v0.38.0
|
||||||
golang.org/x/term v0.38.0
|
golang.org/x/term v0.37.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.5.0
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/protobuf v1.34.2 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
@@ -88,6 +89,7 @@ require (
|
|||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/mailru/easyjson v0.7.6 // indirect
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
@@ -104,20 +106,19 @@ require (
|
|||||||
go.uber.org/multierr v1.10.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
go.uber.org/zap v1.26.0 // indirect
|
go.uber.org/zap v1.26.0 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/net v0.48.0 // indirect
|
golang.org/x/net v0.47.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.31.0 // indirect
|
||||||
golang.org/x/tools v0.39.0 // indirect
|
golang.org/x/tools v0.38.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||||
google.golang.org/grpc v1.79.3 // indirect
|
google.golang.org/grpc v1.64.1 // indirect
|
||||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
|
github.com/Azure/azure-storage-blob-go v0.15.0
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
|
||||||
github.com/ProtonMail/go-crypto v1.4.0
|
github.com/ProtonMail/go-crypto v1.4.0
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.5
|
github.com/aws/aws-sdk-go-v2 v1.41.5
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.28.5
|
github.com/aws/aws-sdk-go-v2/config v1.28.5
|
||||||
@@ -125,10 +126,7 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3
|
||||||
github.com/aws/smithy-go v1.24.2
|
github.com/aws/smithy-go v1.24.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/swaggo/files v1.0.1
|
|
||||||
github.com/swaggo/gin-swagger v1.6.0
|
|
||||||
github.com/swaggo/swag v1.16.3
|
github.com/swaggo/swag v1.16.3
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15
|
go.etcd.io/etcd/client/v3 v3.5.15
|
||||||
golang.org/x/oauth2 v0.34.0
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
|
||||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
|
||||||
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
|
github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI=
|
||||||
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
|
github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
|
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
|
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
|
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM=
|
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek=
|
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo=
|
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo=
|
||||||
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI=
|
github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI=
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
@@ -91,22 +92,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||||
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||||
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
|
||||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
@@ -131,8 +128,6 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
|||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
@@ -150,10 +145,11 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||||
@@ -201,6 +197,8 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
|
|||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
||||||
|
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
@@ -239,8 +237,6 @@ github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
|||||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@@ -288,10 +284,6 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
|||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
|
||||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
|
||||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
|
||||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
|
||||||
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
|
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
|
||||||
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
|
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
|
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
|
||||||
@@ -304,25 +296,12 @@ github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlV
|
|||||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
|
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
|
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM=
|
go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
|
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
|
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
|
||||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
|
||||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
|
||||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
|
||||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
|
||||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
@@ -335,44 +314,41 @@ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||||
|
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
|
||||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
|
||||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -388,24 +364,21 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -413,22 +386,19 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
|
||||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -437,8 +407,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
|
|||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ func NewDownloader(downLimit int64, maxTries int, progress aptly.Progress) aptly
|
|||||||
transport.DisableCompression = true
|
transport.DisableCompression = true
|
||||||
initTransport(&transport)
|
initTransport(&transport)
|
||||||
transport.RegisterProtocol("ftp", &protocol.FTPRoundTripper{})
|
transport.RegisterProtocol("ftp", &protocol.FTPRoundTripper{})
|
||||||
transport.RegisterProtocol("ar+https", NewGCPRoundTripper(&transport))
|
|
||||||
|
|
||||||
downloader := &downloaderImpl{
|
downloader := &downloaderImpl{
|
||||||
progress: progress,
|
progress: progress,
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"golang.org/x/oauth2/google"
|
|
||||||
)
|
|
||||||
|
|
||||||
// gcpRoundTripper wraps http.RoundTripper to add Google Cloud authentication.
|
|
||||||
// It delays GCP authentication initialization until the first actual request is made.
|
|
||||||
// This avoids unnecessary credential loading when ar+https protocol is not actually used.
|
|
||||||
//
|
|
||||||
// It uses Application Default Credentials (ADC) which checks:
|
|
||||||
// 1. GOOGLE_APPLICATION_CREDENTIALS environment variable
|
|
||||||
// 2. gcloud auth application-default credentials
|
|
||||||
// 3. GCE/GKE metadata server
|
|
||||||
// See https://cloud.google.com/docs/authentication/application-default-credentials for usage details.
|
|
||||||
type gcpRoundTripper struct {
|
|
||||||
base http.RoundTripper
|
|
||||||
initOnce sync.Once
|
|
||||||
tokenSrc oauth2.TokenSource
|
|
||||||
initErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *gcpRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
// Lazy initialization: only initialize GCP credentials on first request
|
|
||||||
t.initOnce.Do(func() {
|
|
||||||
creds, err := google.FindDefaultCredentials(context.Background(),
|
|
||||||
"https://www.googleapis.com/auth/cloud-platform")
|
|
||||||
if err != nil {
|
|
||||||
t.initErr = fmt.Errorf("failed to find default credentials: %w", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.tokenSrc = creds.TokenSource
|
|
||||||
})
|
|
||||||
|
|
||||||
reqCopy := req.Clone(req.Context())
|
|
||||||
reqCopy.URL.Scheme = strings.TrimPrefix(reqCopy.URL.Scheme, "ar+")
|
|
||||||
|
|
||||||
// Fall back to base transport if GCP auth initialization failed
|
|
||||||
if t.initErr != nil {
|
|
||||||
return t.base.RoundTrip(reqCopy)
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := t.tokenSrc.Token()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get OAuth2 token: %w", err)
|
|
||||||
}
|
|
||||||
token.SetAuthHeader(reqCopy)
|
|
||||||
|
|
||||||
return t.base.RoundTrip(reqCopy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGCPRoundTripper creates a new RoundTripper that handles GCP authentication for ar+https protocol.
|
|
||||||
func NewGCPRoundTripper(base http.RoundTripper) http.RoundTripper {
|
|
||||||
return &gcpRoundTripper{
|
|
||||||
base: base,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
package http
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGCPAuthTransport_RoundTrip(t *testing.T) {
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
auth := r.Header.Get("Authorization")
|
|
||||||
if auth == "" {
|
|
||||||
t.Error("Expected Authorization header, got none")
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
transport := NewGCPRoundTripper(http.DefaultTransport)
|
|
||||||
|
|
||||||
if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" {
|
|
||||||
t.Skip("Skipping test: GOOGLE_APPLICATION_CREDENTIALS not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{Transport: transport}
|
|
||||||
resp, err := client.Get(ts.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to make request: %v", err)
|
|
||||||
}
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("Expected status 200, got %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGCPAuthTransport_RoundTrip_with_dummy_tokenSource(t *testing.T) {
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
auth := r.Header.Get("Authorization")
|
|
||||||
if auth != "Bearer dummy-token" {
|
|
||||||
t.Errorf("Expected Authorization header 'Bearer dummy-token', got '%s'", auth)
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
// Use a dummy token source for testing
|
|
||||||
transport := &gcpRoundTripper{
|
|
||||||
base: http.DefaultTransport,
|
|
||||||
tokenSrc: oauth2.StaticTokenSource(&oauth2.Token{
|
|
||||||
AccessToken: "dummy-token",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
transport.initOnce.Do(func() {}) // Mark as initialized for testing
|
|
||||||
|
|
||||||
client := &http.Client{Transport: transport}
|
|
||||||
resp, err := client.Get(ts.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to make request: %v", err)
|
|
||||||
}
|
|
||||||
defer func(){ _ = resp.Body.Close() }()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
t.Errorf("Expected status 200, got %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGCPAuthTransport_RoundTrip_with_InvalidCredentials(t *testing.T) {
|
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
}))
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
// Create a temporary invalid credentials file
|
|
||||||
tmpFile, err := os.CreateTemp("", "invalid_credentials.json")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create temp file: %s", err)
|
|
||||||
}
|
|
||||||
defer func() { _ = os.Remove(tmpFile.Name()) }()
|
|
||||||
if _, err := tmpFile.WriteString(`{"invalid": "data"}`); err != nil {
|
|
||||||
t.Fatalf("Failed to write to temp file: %s", err)
|
|
||||||
}
|
|
||||||
_ = tmpFile.Close()
|
|
||||||
|
|
||||||
defaultEnv := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
|
||||||
_ = os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", tmpFile.Name())
|
|
||||||
defer func() { _ = os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", defaultEnv) }()
|
|
||||||
|
|
||||||
transport := &gcpRoundTripper{
|
|
||||||
base: http.DefaultTransport,
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{Transport: transport}
|
|
||||||
resp, err := client.Get(ts.URL)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to make request: %s", err)
|
|
||||||
}
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusForbidden {
|
|
||||||
t.Errorf("Expected status 403, got %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if transport.initErr == nil {
|
|
||||||
t.Error("Expected init error due to invalid credentials, got none")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-41
@@ -111,8 +111,9 @@ The legacy json configuration is still supported (and also supports comments):
|
|||||||
// Enable metrics for Prometheus client
|
// Enable metrics for Prometheus client
|
||||||
"enableMetricsEndpoint": false,
|
"enableMetricsEndpoint": false,
|
||||||
|
|
||||||
|
// Not implemented in this version\.
|
||||||
// Enable API documentation on /docs
|
// Enable API documentation on /docs
|
||||||
"enableSwaggerEndpoint": false,
|
//"enableSwaggerEndpoint": false,
|
||||||
|
|
||||||
// OBSOLETE: use via url param ?_async=true
|
// OBSOLETE: use via url param ?_async=true
|
||||||
"AsyncAPI": false,
|
"AsyncAPI": false,
|
||||||
@@ -615,10 +616,6 @@ gpg keyring to use when verifying Release file (could be specified multiple time
|
|||||||
max download tries till process fails with download error
|
max download tries till process fails with download error
|
||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
\-\fBwith\-appstream\fR
|
|
||||||
download AppStream (DEP\-11) metadata
|
|
||||||
.
|
|
||||||
.TP
|
|
||||||
\-\fBwith\-installer\fR
|
\-\fBwith\-installer\fR
|
||||||
download additional not packaged installer files
|
download additional not packaged installer files
|
||||||
.
|
.
|
||||||
@@ -742,10 +739,6 @@ max download tries till process fails with download error
|
|||||||
\-\fBskip\-existing\-packages\fR
|
\-\fBskip\-existing\-packages\fR
|
||||||
do not check file existence for packages listed in the internal database of the mirror
|
do not check file existence for packages listed in the internal database of the mirror
|
||||||
.
|
.
|
||||||
.TP
|
|
||||||
\-\fBlatest\fR
|
|
||||||
download only latest version of each package (per architecture)
|
|
||||||
.
|
|
||||||
.SH "RENAMES MIRROR"
|
.SH "RENAMES MIRROR"
|
||||||
\fBaptly\fR \fBmirror\fR \fBrename\fR \fIold\-name\fR \fInew\-name\fR
|
\fBaptly\fR \fBmirror\fR \fBrename\fR \fIold\-name\fR \fInew\-name\fR
|
||||||
.
|
.
|
||||||
@@ -794,10 +787,6 @@ disable verification of Release file signatures
|
|||||||
gpg keyring to use when verifying Release file (could be specified multiple times)
|
gpg keyring to use when verifying Release file (could be specified multiple times)
|
||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
\-\fBwith\-appstream\fR
|
|
||||||
download AppStream (DEP\-11) metadata
|
|
||||||
.
|
|
||||||
.TP
|
|
||||||
\-\fBwith\-installer\fR
|
\-\fBwith\-installer\fR
|
||||||
download additional not packaged installer files
|
download additional not packaged installer files
|
||||||
.
|
.
|
||||||
@@ -1573,14 +1562,6 @@ $ aptly publish repo testing
|
|||||||
Options:
|
Options:
|
||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
\-\fBsigned\-by\fR
|
|
||||||
set value for Signed-By field
|
|
||||||
.
|
|
||||||
.TP
|
|
||||||
\-\fBversion\fR
|
|
||||||
version of the release
|
|
||||||
.
|
|
||||||
.TP
|
|
||||||
\-\fBacquire\-by\-hash\fR
|
\-\fBacquire\-by\-hash\fR
|
||||||
provide index files by hash
|
provide index files by hash
|
||||||
.
|
.
|
||||||
@@ -1722,14 +1703,6 @@ $ aptly publish snapshot wheezy\-main
|
|||||||
Options:
|
Options:
|
||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
\-\fBsigned\-by\fR
|
|
||||||
set value for Signed-By field
|
|
||||||
.
|
|
||||||
.TP
|
|
||||||
\-\fBversion\fR
|
|
||||||
version of the release
|
|
||||||
.
|
|
||||||
.TP
|
|
||||||
\-\fBacquire\-by\-hash\fR
|
\-\fBacquire\-by\-hash\fR
|
||||||
provide index files by hash
|
provide index files by hash
|
||||||
.
|
.
|
||||||
@@ -2089,10 +2062,6 @@ This command would switch published repository (with one component) named ppa/wh
|
|||||||
Options:
|
Options:
|
||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
\-\fBsigned\-by\fR
|
|
||||||
set value for Signed-By field
|
|
||||||
.
|
|
||||||
.TP
|
|
||||||
\-\fBbatch\fR
|
\-\fBbatch\fR
|
||||||
run GPG with detached tty
|
run GPG with detached tty
|
||||||
.
|
.
|
||||||
@@ -2199,14 +2168,6 @@ $ aptly publish update wheezy ppa
|
|||||||
Options:
|
Options:
|
||||||
.
|
.
|
||||||
.TP
|
.TP
|
||||||
\-\fBsigned\-by\fR
|
|
||||||
set value for Signed-By field
|
|
||||||
.
|
|
||||||
.TP
|
|
||||||
\-\fBversion\fR
|
|
||||||
version of the release
|
|
||||||
.
|
|
||||||
.TP
|
|
||||||
\-\fBbatch\fR
|
\-\fBbatch\fR
|
||||||
run GPG with detached tty
|
run GPG with detached tty
|
||||||
.
|
.
|
||||||
|
|||||||
@@ -100,8 +100,9 @@ The legacy json configuration is still supported (and also supports comments):
|
|||||||
// Enable metrics for Prometheus client
|
// Enable metrics for Prometheus client
|
||||||
"enableMetricsEndpoint": false,
|
"enableMetricsEndpoint": false,
|
||||||
|
|
||||||
|
// Not implemented in this version.
|
||||||
// Enable API documentation on /docs
|
// Enable API documentation on /docs
|
||||||
"enableSwaggerEndpoint": false,
|
//"enableSwaggerEndpoint": false,
|
||||||
|
|
||||||
// OBSOLETE: use via url param ?_async=true
|
// OBSOLETE: use via url param ?_async=true
|
||||||
"AsyncAPI": false,
|
"AsyncAPI": false,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,12 +89,6 @@ func (g *GpgSigner) gpgArgs() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
|
|
||||||
if _, err := strconv.ParseInt(epoch, 10, 64); err == nil {
|
|
||||||
args = append(args, "--faked-system-time", epoch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -75,14 +74,6 @@ func (g *GoSigner) Init() error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
|
|
||||||
if sec, err := strconv.ParseInt(epoch, 10, 64); err == nil {
|
|
||||||
t := time.Unix(sec, 0).UTC()
|
|
||||||
g.signerConfig.Time = func() time.Time { return t }
|
|
||||||
g.signerConfig.NonDeterministicSignaturesViaNotation = packet.BoolPointer(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.passphraseFile != "" {
|
if g.passphraseFile != "" {
|
||||||
passF, err := os.Open(g.passphraseFile)
|
passF, err := os.Open(g.passphraseFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -83,8 +83,9 @@ serve_in_api_mode: false
|
|||||||
# Enable metrics for Prometheus client
|
# Enable metrics for Prometheus client
|
||||||
enable_metrics_endpoint: false
|
enable_metrics_endpoint: false
|
||||||
|
|
||||||
|
# Not implemented in this version.
|
||||||
# Enable API documentation on /docs
|
# Enable API documentation on /docs
|
||||||
enable_swagger_endpoint: false
|
#enable_swagger_endpoint: false
|
||||||
|
|
||||||
# OBSOLETE: use via url param ?_async=true
|
# OBSOLETE: use via url param ?_async=true
|
||||||
async_api: false
|
async_api: false
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ Options:
|
|||||||
-ignore-signatures: disable verification of Release file signatures
|
-ignore-signatures: disable verification of Release file signatures
|
||||||
-keyring=: gpg keyring to use when verifying Release file (could be specified multiple times)
|
-keyring=: gpg keyring to use when verifying Release file (could be specified multiple times)
|
||||||
-max-tries=1: max download tries till process fails with download error
|
-max-tries=1: max download tries till process fails with download error
|
||||||
-with-appstream: download AppStream (DEP-11) metadata
|
|
||||||
-with-installer: download additional not packaged installer files
|
-with-installer: download additional not packaged installer files
|
||||||
-with-sources: download source packages in addition to binary packages
|
-with-sources: download source packages in addition to binary packages
|
||||||
-with-udebs: download .udeb packages (Debian installer support)
|
-with-udebs: download .udeb packages (Debian installer support)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ Options:
|
|||||||
-ignore-signatures: disable verification of Release file signatures
|
-ignore-signatures: disable verification of Release file signatures
|
||||||
-keyring=: gpg keyring to use when verifying Release file (could be specified multiple times)
|
-keyring=: gpg keyring to use when verifying Release file (could be specified multiple times)
|
||||||
-max-tries=1: max download tries till process fails with download error
|
-max-tries=1: max download tries till process fails with download error
|
||||||
-with-appstream: download AppStream (DEP-11) metadata
|
|
||||||
-with-installer: download additional not packaged installer files
|
-with-installer: download additional not packaged installer files
|
||||||
-with-sources: download source packages in addition to binary packages
|
-with-sources: download source packages in addition to binary packages
|
||||||
-with-udebs: download .udeb packages (Debian installer support)
|
-with-udebs: download .udeb packages (Debian installer support)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ Options:
|
|||||||
-ignore-signatures: disable verification of Release file signatures
|
-ignore-signatures: disable verification of Release file signatures
|
||||||
-keyring=: gpg keyring to use when verifying Release file (could be specified multiple times)
|
-keyring=: gpg keyring to use when verifying Release file (could be specified multiple times)
|
||||||
-max-tries=1: max download tries till process fails with download error
|
-max-tries=1: max download tries till process fails with download error
|
||||||
-with-appstream: download AppStream (DEP-11) metadata
|
|
||||||
-with-installer: download additional not packaged installer files
|
-with-installer: download additional not packaged installer files
|
||||||
-with-sources: download source packages in addition to binary packages
|
-with-sources: download source packages in addition to binary packages
|
||||||
-with-udebs: download .udeb packages (Debian installer support)
|
-with-udebs: download .udeb packages (Debian installer support)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
|
|||||||
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
|
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
|
|||||||
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
|
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components:
|
|||||||
Architectures: all, amd64, i386
|
Architectures: all, amd64, i386
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
|
|||||||
Architectures: i386
|
Architectures: i386
|
||||||
Download Sources: yes
|
Download Sources: yes
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main
|
|||||||
Architectures: amd64, armel, i386, powerpc
|
Architectures: amd64, armel, i386, powerpc
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main
|
|||||||
Architectures: i386
|
Architectures: i386
|
||||||
Download Sources: yes
|
Download Sources: yes
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
|
|||||||
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
|
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components:
|
|||||||
Architectures: all
|
Architectures: all
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main
|
|||||||
Architectures: amd64, arm64, armel, armhf, i386
|
Architectures: amd64, arm64, armel, armhf, i386
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Filter: nginx | Priority (required)
|
Filter: nginx | Priority (required)
|
||||||
Filter With Deps: no
|
Filter With Deps: no
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
|
|||||||
Architectures: i386
|
Architectures: i386
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: yes
|
Download .udebs: yes
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: openmanage/740
|
|||||||
Architectures: amd64, i386
|
Architectures: amd64, i386
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main
|
|||||||
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
|
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main, contrib, non-free
|
|||||||
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
|
Architectures: amd64, arm64, armel, armhf, i386, mips, mips64el, mipsel, ppc64el, s390x
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components:
|
|||||||
Architectures: amd64
|
Architectures: amd64
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|
||||||
Information from release file:
|
Information from release file:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components:
|
|||||||
Architectures: amd64
|
Architectures: amd64
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Filter: cuda-12-6 (= 12.6.2-1)
|
Filter: cuda-12-6 (= 12.6.2-1)
|
||||||
Filter With Deps: yes
|
Filter With Deps: yes
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main
|
|||||||
Architectures: amd64, arm64, armel, armhf, i386
|
Architectures: amd64, arm64, armel, armhf, i386
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Filter: nginx | Priority (required)
|
Filter: nginx | Priority (required)
|
||||||
Filter With Deps: no
|
Filter With Deps: no
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Components: main
|
|||||||
Architectures: amd64, arm64, armel, armhf, i386
|
Architectures: amd64, arm64, armel, armhf, i386
|
||||||
Download Sources: no
|
Download Sources: no
|
||||||
Download .udebs: no
|
Download .udebs: no
|
||||||
Download AppStream: no
|
|
||||||
Filter: nginx | Priority (required)
|
Filter: nginx | Priority (required)
|
||||||
Filter With Deps: no
|
Filter With Deps: no
|
||||||
Last update: never
|
Last update: never
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
Downloading: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/dists/stretch/Release
|
|
||||||
|
|
||||||
Mirror [mirror38]: http://repo.aptly.info/system-tests/archive.debian.org/debian-archive/debian/ stretch [appstream] successfully added.
|
|
||||||
You can run 'aptly mirror update mirror38' to download repository contents.
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user