mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-06-07 05:42:42 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fa1922477 | |||
| 836137f15d |
@@ -68,3 +68,4 @@ List of contributors, in chronological order:
|
||||
* Blake Kostner (https://github.com/btkostner)
|
||||
* Leigh London (https://github.com/leighlondon)
|
||||
* Gordian Schoenherr (https://github.com/schoenherrg)
|
||||
* Brett Hawn (https://github.com/bpiraeus)
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
func Authorize(username string, password string) (ok bool) {
|
||||
config := context.Config()
|
||||
|
||||
if config.Auth.Type != "" {
|
||||
switch strings.ToLower(config.Auth.Type) {
|
||||
case "ldap":
|
||||
ok = doLdapAuth(username, password)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func doLdapAuth(username string, password string) bool {
|
||||
config := context.Config()
|
||||
attributes := []string{"DN", "CN"}
|
||||
|
||||
server := config.Auth.Server
|
||||
dn := config.Auth.LdapDN
|
||||
filter := fmt.Sprintf(config.Auth.LdapFilter, username)
|
||||
|
||||
// connect to ldap server
|
||||
conn, err := ldap.Dial("tcp", server)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// reconnect via tls
|
||||
err = conn.StartTLS(&tls.Config{InsecureSkipVerify: config.Auth.SecureTLS})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// format our request and then fire it off
|
||||
request := ldap.NewSearchRequest(dn, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, attributes, nil)
|
||||
search, err := conn.Search(request)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// get our modified dn and then check our user for auth
|
||||
udn := search.Entries[0].DN
|
||||
err = conn.Bind(udn, password)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getGroups(c *gin.Context, username string) {
|
||||
|
||||
var groups []string
|
||||
config := context.Config()
|
||||
dn := config.Auth.LdapDN
|
||||
session := sessions.Default(c)
|
||||
// connect to ldap server
|
||||
server := fmt.Sprintf("%s", config.Auth.Server)
|
||||
conn, err := ldap.Dial("tcp", server)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// reconnect via tls
|
||||
err = conn.StartTLS(&tls.Config{InsecureSkipVerify: true})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filter := fmt.Sprintf("(|(member=uid=%s,ou=people,dc=llnw,dc=com)(member=uid=%s,ou=people,dc=llnw,dc=com))", username, username)
|
||||
request := ldap.NewSearchRequest(dn, ldap.ScopeWholeSubtree, 0, 0, 0, false, filter, []string{"dn", "cn"}, nil)
|
||||
search, err := conn.Search(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(search.Entries) < 1 {
|
||||
return
|
||||
}
|
||||
for _, v := range search.Entries {
|
||||
value := strings.Split(strings.TrimLeft(v.DN, "cn="), ",")[0]
|
||||
groups = append(groups, fmt.Sprintf("%s,", value))
|
||||
}
|
||||
session.Set("Groups", groups)
|
||||
}
|
||||
|
||||
func checkGroup(c *gin.Context, ldgroup string) bool {
|
||||
session := sessions.Default(c)
|
||||
groups := session.Get("Groups")
|
||||
if ldgroup == "" {
|
||||
return true
|
||||
}
|
||||
for _, v := range groups.([]string) {
|
||||
if strings.Contains(v, ldgroup) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func CheckGroup(c *gin.Context, ldgroup string) (err error) {
|
||||
if !checkGroup(c, ldgroup) {
|
||||
err = fmt.Errorf("Authorisation Failred")
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -267,7 +267,13 @@ func apiPublishRepoOrSnapshot(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = CheckGroup(c, localRepo.LdapGroup)
|
||||
if err != nil {
|
||||
c.AbortWithError(403, err)
|
||||
}
|
||||
|
||||
resources = append(resources, string(localRepo.Key()))
|
||||
|
||||
sources = append(sources, localRepo)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -95,6 +95,8 @@ type repoCreateParams struct {
|
||||
DefaultComponent string ` json:"DefaultComponent" example:"main"`
|
||||
// Snapshot name to create repoitory from (optional)
|
||||
FromSnapshot string ` json:"FromSnapshot" example:""`
|
||||
//
|
||||
LdapGroup string
|
||||
}
|
||||
|
||||
// @Summary Create Repository
|
||||
@@ -125,6 +127,7 @@ func apiReposCreate(c *gin.Context) {
|
||||
repo := deb.NewLocalRepo(b.Name, b.Comment)
|
||||
repo.DefaultComponent = b.DefaultComponent
|
||||
repo.DefaultDistribution = b.DefaultDistribution
|
||||
repo.LdapGroup = b.LdapGroup
|
||||
|
||||
collectionFactory := context.NewCollectionFactory()
|
||||
|
||||
@@ -173,6 +176,8 @@ type reposEditParams struct {
|
||||
DefaultDistribution *string ` json:"DefaultDistribution" example:""`
|
||||
// Change Devault Component for publishing
|
||||
DefaultComponent *string ` json:"DefaultComponent" example:""`
|
||||
//
|
||||
LdapGroup *string
|
||||
}
|
||||
|
||||
// @Summary Update Repository
|
||||
@@ -199,6 +204,12 @@ func apiReposEdit(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = CheckGroup(c, repo.LdapGroup)
|
||||
if err != nil {
|
||||
c.AbortWithError(403, err)
|
||||
return
|
||||
}
|
||||
|
||||
if b.Name != nil {
|
||||
_, err := collection.ByName(*b.Name)
|
||||
if err == nil {
|
||||
@@ -217,6 +228,9 @@ func apiReposEdit(c *gin.Context) {
|
||||
if b.DefaultComponent != nil {
|
||||
repo.DefaultComponent = *b.DefaultComponent
|
||||
}
|
||||
if b.LdapGroup != nil {
|
||||
repo.LdapGroup = *b.LdapGroup
|
||||
}
|
||||
|
||||
err = collection.Update(repo)
|
||||
if err != nil {
|
||||
@@ -276,6 +290,12 @@ func apiReposDrop(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = CheckGroup(c, repo.LdapGroup)
|
||||
if err != nil {
|
||||
c.AbortWithError(403, err)
|
||||
return
|
||||
}
|
||||
|
||||
resources := []string{string(repo.Key())}
|
||||
taskName := fmt.Sprintf("Delete repo %s", name)
|
||||
maybeRunTaskInBackground(c, taskName, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
|
||||
@@ -365,6 +385,11 @@ func apiReposPackagesAddDelete(c *gin.Context, taskNamePrefix string, cb func(li
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
err = CheckGroup(c, repo.LdapGroup)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: 403, Value: nil}, err
|
||||
}
|
||||
|
||||
out.Printf("Loading packages...\n")
|
||||
list, err := deb.NewPackageListFromRefList(repo.RefList(), collectionFactory.PackageCollection(), nil)
|
||||
if err != nil {
|
||||
@@ -522,6 +547,11 @@ func apiReposPackageFromDir(c *gin.Context) {
|
||||
return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err
|
||||
}
|
||||
|
||||
err = CheckGroup(c, repo.LdapGroup)
|
||||
if err != nil {
|
||||
return &task.ProcessReturnValue{Code: 403, Value: nil}, err
|
||||
}
|
||||
|
||||
verifier := context.GetVerifier()
|
||||
|
||||
var (
|
||||
@@ -845,6 +875,11 @@ func apiReposIncludePackageFromDir(c *gin.Context) {
|
||||
AbortWithJSONError(c, 404, err)
|
||||
return
|
||||
}
|
||||
err = CheckGroup(c, repo.LdapGroup)
|
||||
if err != nil {
|
||||
c.AbortWithError(403, err)
|
||||
return
|
||||
}
|
||||
|
||||
resources = append(resources, string(repo.Key()))
|
||||
}
|
||||
|
||||
+132
-63
@@ -1,9 +1,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/aptly-dev/aptly/aptly"
|
||||
ctx "github.com/aptly-dev/aptly/context"
|
||||
@@ -15,6 +18,10 @@ import (
|
||||
"github.com/aptly-dev/aptly/docs"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
uuid "github.com/nu7hatch/gouuid"
|
||||
)
|
||||
|
||||
var context *ctx.AptlyContext
|
||||
@@ -133,105 +140,167 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
||||
api.GET("/healthy", apiHealthy)
|
||||
}
|
||||
|
||||
// set up cookies and sessions
|
||||
token, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
store := cookie.NewStore([]byte(token.String()))
|
||||
router.Use(sessions.Sessions(token.String(), store))
|
||||
// prep our config fetcher ahead of need
|
||||
config := context.Config()
|
||||
|
||||
// prep a logfile if we've set one
|
||||
if config.LogFile != "" {
|
||||
file, err := os.OpenFile(config.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
log.SetOutput(file)
|
||||
}
|
||||
|
||||
router.GET("/version", apiVersion)
|
||||
|
||||
var username string
|
||||
var password string
|
||||
router.POST("/login", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
session.Options(sessions.Options{MaxAge: 30})
|
||||
if config.UseAuth {
|
||||
log.Printf("UseAuth is enabled\n")
|
||||
username = c.PostForm("username")
|
||||
password = c.PostForm("password")
|
||||
if !Authorize(username, password) {
|
||||
c.AbortWithError(403, fmt.Errorf("Authorization Failure"))
|
||||
}
|
||||
log.Printf("%s authorized from %s\n", username, c.ClientIP())
|
||||
}
|
||||
session.Set(token.String(), time.Now().Unix())
|
||||
session.Save()
|
||||
getGroups(c, username)
|
||||
c.String(200, "Authorized!")
|
||||
})
|
||||
|
||||
router.POST("/logout", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
session.Options(sessions.Options{MaxAge: -1})
|
||||
session.Save()
|
||||
c.String(200, "Deauthorized")
|
||||
})
|
||||
|
||||
authorize := router.Group("/api", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
if config.UseAuth {
|
||||
if session.Get(token.String()) == nil {
|
||||
c.AbortWithError(403, fmt.Errorf("not authorized"))
|
||||
}
|
||||
session.Options(sessions.Options{MaxAge: 30})
|
||||
session.Set(token.String(), time.Now().Unix())
|
||||
session.Save()
|
||||
}
|
||||
})
|
||||
|
||||
{
|
||||
api.GET("/repos", apiReposList)
|
||||
api.POST("/repos", apiReposCreate)
|
||||
api.GET("/repos/:name", apiReposShow)
|
||||
api.PUT("/repos/:name", apiReposEdit)
|
||||
api.DELETE("/repos/:name", apiReposDrop)
|
||||
authorize.GET("/repos", apiReposList)
|
||||
authorize.POST("/repos", apiReposCreate)
|
||||
authorize.GET("/repos/:name", apiReposShow)
|
||||
authorize.PUT("/repos/:name", apiReposEdit)
|
||||
authorize.DELETE("/repos/:name", apiReposDrop)
|
||||
|
||||
api.GET("/repos/:name/packages", apiReposPackagesShow)
|
||||
api.POST("/repos/:name/packages", apiReposPackagesAdd)
|
||||
api.DELETE("/repos/:name/packages", apiReposPackagesDelete)
|
||||
authorize.GET("/repos/:name/packages", apiReposPackagesShow)
|
||||
authorize.POST("/repos/:name/packages", apiReposPackagesAdd)
|
||||
authorize.DELETE("/repos/:name/packages", apiReposPackagesDelete)
|
||||
|
||||
api.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile)
|
||||
api.POST("/repos/:name/file/:dir", apiReposPackageFromDir)
|
||||
api.POST("/repos/:name/copy/:src/:file", apiReposCopyPackage)
|
||||
authorize.POST("/repos/:name/file/:dir/:file", apiReposPackageFromFile)
|
||||
authorize.POST("/repos/:name/file/:dir", apiReposPackageFromDir)
|
||||
authorize.POST("/repos/:name/copy/:src/:file", apiReposCopyPackage)
|
||||
|
||||
api.POST("/repos/:name/include/:dir/:file", apiReposIncludePackageFromFile)
|
||||
api.POST("/repos/:name/include/:dir", apiReposIncludePackageFromDir)
|
||||
authorize.POST("/repos/:name/include/:dir/:file", apiReposIncludePackageFromFile)
|
||||
authorize.POST("/repos/:name/include/:dir", apiReposIncludePackageFromDir)
|
||||
|
||||
api.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository)
|
||||
authorize.POST("/repos/:name/snapshots", apiSnapshotsCreateFromRepository)
|
||||
}
|
||||
|
||||
{
|
||||
api.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror)
|
||||
authorize.POST("/mirrors/:name/snapshots", apiSnapshotsCreateFromMirror)
|
||||
}
|
||||
|
||||
{
|
||||
api.GET("/mirrors", apiMirrorsList)
|
||||
api.GET("/mirrors/:name", apiMirrorsShow)
|
||||
api.GET("/mirrors/:name/packages", apiMirrorsPackages)
|
||||
api.POST("/mirrors", apiMirrorsCreate)
|
||||
api.PUT("/mirrors/:name", apiMirrorsUpdate)
|
||||
api.DELETE("/mirrors/:name", apiMirrorsDrop)
|
||||
authorize.GET("/mirrors", apiMirrorsList)
|
||||
authorize.GET("/mirrors/:name", apiMirrorsShow)
|
||||
authorize.GET("/mirrors/:name/packages", apiMirrorsPackages)
|
||||
authorize.POST("/mirrors", apiMirrorsCreate)
|
||||
authorize.PUT("/mirrors/:name", apiMirrorsUpdate)
|
||||
authorize.DELETE("/mirrors/:name", apiMirrorsDrop)
|
||||
}
|
||||
|
||||
{
|
||||
api.POST("/gpg/key", apiGPGAddKey)
|
||||
authorize.POST("/gpg/key", apiGPGAddKey)
|
||||
}
|
||||
|
||||
{
|
||||
api.GET("/s3", apiS3List)
|
||||
authorize.GET("/s3", apiS3List)
|
||||
}
|
||||
|
||||
{
|
||||
api.GET("/files", apiFilesListDirs)
|
||||
api.POST("/files/:dir", apiFilesUpload)
|
||||
api.GET("/files/:dir", apiFilesListFiles)
|
||||
api.DELETE("/files/:dir", apiFilesDeleteDir)
|
||||
api.DELETE("/files/:dir/:name", apiFilesDeleteFile)
|
||||
authorize.GET("/files", apiFilesListDirs)
|
||||
authorize.POST("/files/:dir", apiFilesUpload)
|
||||
authorize.GET("/files/:dir", apiFilesListFiles)
|
||||
authorize.DELETE("/files/:dir", apiFilesDeleteDir)
|
||||
authorize.DELETE("/files/:dir/:name", apiFilesDeleteFile)
|
||||
}
|
||||
|
||||
{
|
||||
api.GET("/publish", apiPublishList)
|
||||
api.GET("/publish/:prefix/:distribution", apiPublishShow)
|
||||
api.POST("/publish", apiPublishRepoOrSnapshot)
|
||||
api.POST("/publish/:prefix", apiPublishRepoOrSnapshot)
|
||||
api.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch)
|
||||
api.DELETE("/publish/:prefix/:distribution", apiPublishDrop)
|
||||
api.POST("/publish/:prefix/:distribution/sources", apiPublishAddSource)
|
||||
api.GET("/publish/:prefix/:distribution/sources", apiPublishListChanges)
|
||||
api.PUT("/publish/:prefix/:distribution/sources", apiPublishSetSources)
|
||||
api.DELETE("/publish/:prefix/:distribution/sources", apiPublishDropChanges)
|
||||
api.PUT("/publish/:prefix/:distribution/sources/:component", apiPublishUpdateSource)
|
||||
api.DELETE("/publish/:prefix/:distribution/sources/:component", apiPublishRemoveSource)
|
||||
api.POST("/publish/:prefix/:distribution/update", apiPublishUpdate)
|
||||
authorize.GET("/publish", apiPublishList)
|
||||
authorize.GET("/publish/:prefix/:distribution", apiPublishShow)
|
||||
authorize.POST("/publish", apiPublishRepoOrSnapshot)
|
||||
authorize.POST("/publish/:prefix", apiPublishRepoOrSnapshot)
|
||||
authorize.PUT("/publish/:prefix/:distribution", apiPublishUpdateSwitch)
|
||||
authorize.DELETE("/publish/:prefix/:distribution", apiPublishDrop)
|
||||
authorize.POST("/publish/:prefix/:distribution/sources", apiPublishAddSource)
|
||||
authorize.GET("/publish/:prefix/:distribution/sources", apiPublishListChanges)
|
||||
authorize.PUT("/publish/:prefix/:distribution/sources", apiPublishSetSources)
|
||||
authorize.DELETE("/publish/:prefix/:distribution/sources", apiPublishDropChanges)
|
||||
authorize.PUT("/publish/:prefix/:distribution/sources/:component", apiPublishUpdateSource)
|
||||
authorize.DELETE("/publish/:prefix/:distribution/sources/:component", apiPublishRemoveSource)
|
||||
authorize.POST("/publish/:prefix/:distribution/update", apiPublishUpdate)
|
||||
}
|
||||
|
||||
{
|
||||
api.GET("/snapshots", apiSnapshotsList)
|
||||
api.POST("/snapshots", apiSnapshotsCreate)
|
||||
api.PUT("/snapshots/:name", apiSnapshotsUpdate)
|
||||
api.GET("/snapshots/:name", apiSnapshotsShow)
|
||||
api.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages)
|
||||
api.DELETE("/snapshots/:name", apiSnapshotsDrop)
|
||||
api.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff)
|
||||
api.POST("/snapshots/:name/merge", apiSnapshotsMerge)
|
||||
api.POST("/snapshots/:name/pull", apiSnapshotsPull)
|
||||
authorize.GET("/snapshots", apiSnapshotsList)
|
||||
authorize.POST("/snapshots", apiSnapshotsCreate)
|
||||
authorize.PUT("/snapshots/:name", apiSnapshotsUpdate)
|
||||
authorize.GET("/snapshots/:name", apiSnapshotsShow)
|
||||
authorize.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages)
|
||||
authorize.DELETE("/snapshots/:name", apiSnapshotsDrop)
|
||||
authorize.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff)
|
||||
authorize.POST("/snapshots/:name/merge", apiSnapshotsMerge)
|
||||
authorize.POST("/snapshots/:name/pull", apiSnapshotsPull)
|
||||
}
|
||||
|
||||
{
|
||||
api.GET("/packages/:key", apiPackagesShow)
|
||||
api.GET("/packages", apiPackages)
|
||||
authorize.GET("/packages/:key", apiPackagesShow)
|
||||
authorize.GET("/packages", apiPackages)
|
||||
}
|
||||
|
||||
{
|
||||
api.GET("/graph.:ext", apiGraph)
|
||||
authorize.GET("/graph.:ext", apiGraph)
|
||||
}
|
||||
{
|
||||
api.POST("/db/cleanup", apiDbCleanup)
|
||||
authorize.POST("/db/cleanup", apiDbCleanup)
|
||||
}
|
||||
{
|
||||
api.GET("/tasks", apiTasksList)
|
||||
api.POST("/tasks-clear", apiTasksClear)
|
||||
api.GET("/tasks-wait", apiTasksWait)
|
||||
api.GET("/tasks/:id/wait", apiTasksWaitForTaskByID)
|
||||
api.GET("/tasks/:id/output", apiTasksOutputShow)
|
||||
api.GET("/tasks/:id/detail", apiTasksDetailShow)
|
||||
api.GET("/tasks/:id/return_value", apiTasksReturnValueShow)
|
||||
api.GET("/tasks/:id", apiTasksShow)
|
||||
api.DELETE("/tasks/:id", apiTasksDelete)
|
||||
authorize.GET("/tasks", apiTasksList)
|
||||
authorize.POST("/tasks-clear", apiTasksClear)
|
||||
authorize.GET("/tasks-wait", apiTasksWait)
|
||||
authorize.GET("/tasks/:id/wait", apiTasksWaitForTaskByID)
|
||||
authorize.GET("/tasks/:id/output", apiTasksOutputShow)
|
||||
authorize.GET("/tasks/:id/detail", apiTasksDetailShow)
|
||||
authorize.GET("/tasks/:id/return_value", apiTasksReturnValueShow)
|
||||
authorize.GET("/tasks/:id", apiTasksShow)
|
||||
authorize.DELETE("/tasks/:id", apiTasksDelete)
|
||||
}
|
||||
|
||||
return router
|
||||
|
||||
@@ -251,6 +251,12 @@ func apiSnapshotsCreateFromRepository(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = CheckGroup(c, repo.LdapGroup)
|
||||
if err != nil {
|
||||
c.AbortWithError(403, err)
|
||||
return
|
||||
}
|
||||
|
||||
// including snapshot resource key
|
||||
resources := []string{string(repo.Key()), "S" + b.Name}
|
||||
taskName := fmt.Sprintf("Create snapshot of repo %s", name)
|
||||
|
||||
@@ -18,6 +18,7 @@ func aptlyRepoCreate(cmd *commander.Command, args []string) error {
|
||||
repo := deb.NewLocalRepo(args[0], context.Flags().Lookup("comment").Value.String())
|
||||
repo.DefaultDistribution = context.Flags().Lookup("distribution").Value.String()
|
||||
repo.DefaultComponent = context.Flags().Lookup("component").Value.String()
|
||||
repo.LdapGroup = context.Flags().Lookup("ldap-group").Value.String()
|
||||
|
||||
uploadersFile := context.Flags().Lookup("uploaders-file").Value.Get().(string)
|
||||
if uploadersFile != "" {
|
||||
@@ -79,6 +80,7 @@ Example:
|
||||
cmd.Flag.String("distribution", "", "default distribution when publishing")
|
||||
cmd.Flag.String("component", "main", "default component when publishing")
|
||||
cmd.Flag.String("uploaders-file", "", "uploaders.json to be used when including .changes into this repository")
|
||||
cmd.Flag.String("ldap-group", "", "ldap group that owns the repo, leave empty to allow ALL")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ func aptlyRepoEdit(cmd *commander.Command, args []string) error {
|
||||
repo.DefaultComponent = flag.Value.String()
|
||||
case "uploaders-file":
|
||||
uploadersFile = pointer.ToString(flag.Value.String())
|
||||
case "ldap-group":
|
||||
repo.LdapGroup = flag.Value.String()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -82,6 +84,7 @@ Example:
|
||||
cmd.Flag.String("distribution", "", "default distribution when publishing")
|
||||
cmd.Flag.String("component", "", "default component when publishing")
|
||||
cmd.Flag.String("uploaders-file", "", "uploaders.json to be used when including .changes into this repository")
|
||||
cmd.Flag.String("ldap-group", "", "ldap group that owns the repo, leave empty to allow ALL")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ func aptlyRepoShowTxt(_ *commander.Command, args []string) error {
|
||||
fmt.Printf("Comment: %s\n", repo.Comment)
|
||||
fmt.Printf("Default Distribution: %s\n", repo.DefaultDistribution)
|
||||
fmt.Printf("Default Component: %s\n", repo.DefaultComponent)
|
||||
fmt.Printf("Ldap Group: %s\n", repo.LdapGroup)
|
||||
if repo.Uploaders != nil {
|
||||
fmt.Printf("Uploaders: %s\n", repo.Uploaders)
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ local keyring="*-keyring=[gpg keyring to use when verifying Release file (could
|
||||
local create_edit=("-comment=[any text that would be used to described local repository]:comment: "
|
||||
"-component=[default component when publishing]:component:($components)"
|
||||
"-distribution=[default distribution when publishing]:distribution:($dists)"
|
||||
"-ldap-group=[ldap group for repo actions, empty by default]:ldap-group"
|
||||
$aptly_uploaders
|
||||
)
|
||||
|
||||
|
||||
@@ -5,13 +5,11 @@ import (
|
||||
gocontext "context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
@@ -23,7 +21,6 @@ import (
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
"github.com/aptly-dev/aptly/database/etcddb"
|
||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||
"github.com/aptly-dev/aptly/database/ssdb"
|
||||
"github.com/aptly-dev/aptly/deb"
|
||||
"github.com/aptly-dev/aptly/files"
|
||||
"github.com/aptly-dev/aptly/http"
|
||||
@@ -32,7 +29,6 @@ import (
|
||||
"github.com/aptly-dev/aptly/swift"
|
||||
"github.com/aptly-dev/aptly/task"
|
||||
"github.com/aptly-dev/aptly/utils"
|
||||
"github.com/seefan/gossdb/v2/conf"
|
||||
"github.com/smira/commander"
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
@@ -305,21 +301,6 @@ func (context *AptlyContext) _database() (database.Storage, error) {
|
||||
context.database, err = goleveldb.NewDB(dbPath)
|
||||
case "etcd":
|
||||
context.database, err = etcddb.NewDB(context.config().DatabaseBackend.URL)
|
||||
case "ssdb":
|
||||
var cfg conf.Config
|
||||
u, e := url.Parse(context.config().DatabaseBackend.URL)
|
||||
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
cfg.Port, e = strconv.Atoi(u.Port())
|
||||
cfg.Host = strings.Split(u.Host, ":")[0]
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
password, _ := u.User.Password()
|
||||
cfg.Password = password
|
||||
context.database, err = ssdb.NewOpenDB(&cfg)
|
||||
default:
|
||||
context.database, err = goleveldb.NewDB(context.dbPath())
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
package ssdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
"github.com/seefan/gossdb/v2/conf"
|
||||
"github.com/seefan/gossdb/v2/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
delOpt = "del"
|
||||
)
|
||||
|
||||
type bWriteData struct {
|
||||
key []byte
|
||||
value []byte
|
||||
opts string
|
||||
err error
|
||||
}
|
||||
|
||||
type Batch struct {
|
||||
cfg *conf.Config
|
||||
// key-value chan
|
||||
w chan bWriteData
|
||||
p map[string]interface{}
|
||||
d []string
|
||||
db *pool.Client
|
||||
}
|
||||
|
||||
// func internalOpenBatch...
|
||||
func internalOpenBatch(_ database.Storage) *Batch {
|
||||
b := &Batch{
|
||||
w: make(chan bWriteData),
|
||||
p: make(map[string]interface{}),
|
||||
}
|
||||
b.run()
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Batch) run() {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case w, ok := <-b.w:
|
||||
{
|
||||
if !ok {
|
||||
ssdbLog("ssdb batch write chan closed")
|
||||
return
|
||||
}
|
||||
|
||||
if w.opts == "write" {
|
||||
ssdbLog("ssdb batch write")
|
||||
var err error
|
||||
if len(b.p) > 0 && len(b.d) == 0 {
|
||||
err = b.db.MultiSet(b.p)
|
||||
ssdbLog("ssdb batch set errinfo: ", err)
|
||||
} else if len(b.d) > 0 && len(b.p) == 0 {
|
||||
err = b.db.MultiDel(b.d...)
|
||||
ssdbLog("ssdb batch del errinfo: ", err)
|
||||
} else if len(b.p) == 0 && len(b.d) == 0 {
|
||||
err = nil
|
||||
} else {
|
||||
err = fmt.Errorf("ssdb batch does not support both put and delete operations")
|
||||
}
|
||||
ssdbLog("ssdb batch write errinfo: ", err)
|
||||
b.w <- bWriteData{
|
||||
err: err,
|
||||
}
|
||||
ssdbLog("ssdb batch write end")
|
||||
} else {
|
||||
ssdbLog("ssdb batch", w.opts)
|
||||
if w.opts == "put" {
|
||||
b.p[string(w.key)] = w.value
|
||||
} else if w.opts == delOpt {
|
||||
b.d = append(b.d, string(w.key))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (b *Batch) stop() {
|
||||
ssdbLog("ssdb batch stop")
|
||||
close(b.w)
|
||||
}
|
||||
|
||||
func (b *Batch) Put(key, value []byte) (err error) {
|
||||
// err = b.db.Set(string(key), string(value))
|
||||
w := bWriteData{
|
||||
key: key,
|
||||
value: value,
|
||||
opts: "put",
|
||||
}
|
||||
|
||||
b.w <- w
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Batch) Delete(key []byte) (err error) {
|
||||
/* err = b.db.Del(string(key))
|
||||
return */
|
||||
w := bWriteData{
|
||||
key: key,
|
||||
opts: delOpt,
|
||||
}
|
||||
|
||||
b.w <- w
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Batch) Write() (err error) {
|
||||
defer b.stop()
|
||||
w := bWriteData{
|
||||
opts: "write",
|
||||
}
|
||||
|
||||
b.w <- w
|
||||
result := <-b.w
|
||||
return result.err
|
||||
}
|
||||
|
||||
// batch should implement database.Batch
|
||||
var (
|
||||
_ database.Batch = &Batch{}
|
||||
)
|
||||
@@ -1,62 +0,0 @@
|
||||
package ssdb
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
"github.com/seefan/gossdb/v2"
|
||||
"github.com/seefan/gossdb/v2/conf"
|
||||
"github.com/seefan/gossdb/v2/pool"
|
||||
)
|
||||
|
||||
var defaultBufSize = 102400
|
||||
var defaultPoolSize = 1
|
||||
|
||||
func internalOpen(cfg *conf.Config) (*pool.Client, error) {
|
||||
ssdbLog("internalOpen")
|
||||
|
||||
cfg.ReadBufferSize = defaultBufSize
|
||||
cfg.WriteBufferSize = defaultBufSize
|
||||
cfg.MaxPoolSize = defaultPoolSize
|
||||
cfg.PoolSize = defaultPoolSize
|
||||
cfg.MinPoolSize = defaultPoolSize
|
||||
cfg.MaxWaitSize = 100 * defaultPoolSize
|
||||
cfg.RetryEnabled = true
|
||||
|
||||
//override by env
|
||||
if os.Getenv("SSDB_READBUFFERSIZE") != "" {
|
||||
readBufSize, err := strconv.Atoi(os.Getenv("SSDB_READBUFFERSIZE"))
|
||||
if err != nil {
|
||||
cfg.ReadBufferSize = readBufSize
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("SSDB_WRITEBUFFERSIZE") != "" {
|
||||
writeBufSize, err := strconv.Atoi(os.Getenv("SSDB_WRITEBUFFERSIZE"))
|
||||
if err != nil {
|
||||
cfg.WriteBufferSize = writeBufSize
|
||||
}
|
||||
}
|
||||
|
||||
var cfgs = []*conf.Config{cfg}
|
||||
err := gossdb.Start(cfgs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gossdb.NewClient()
|
||||
}
|
||||
|
||||
func NewDB(cfg *conf.Config) (database.Storage, error) {
|
||||
return &Storage{cfg: cfg}, nil
|
||||
}
|
||||
|
||||
func NewOpenDB(cfg *conf.Config) (database.Storage, error) {
|
||||
db, err := NewDB(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, db.Open()
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
package ssdb_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
"github.com/aptly-dev/aptly/database/ssdb"
|
||||
"github.com/seefan/gossdb/v2/conf"
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
// Launch gocheck tests
|
||||
func Test(t *testing.T) {
|
||||
TestingT(t)
|
||||
}
|
||||
|
||||
func setUpSsdb() error {
|
||||
setUpStr := `
|
||||
#!/bin/bash
|
||||
if [ ! -e /tmp/ssdb-master/ssdb-master ]; then
|
||||
mkdir -p /tmp/ssdb-master
|
||||
wget --no-check-certificate https://github.com/ideawu/ssdb/archive/master.zip -O /tmp/ssdb-master/master.zip
|
||||
cd /tmp/ssdb-master && unzip master && cd ssdb-master && make all
|
||||
fi
|
||||
cd /tmp/ssdb-master/ssdb-master && ./ssdb-server -d ssdb.conf -s restart
|
||||
sleep 2`
|
||||
|
||||
tmpShell, err := ioutil.TempFile("/tmp", "ssdbSetup")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmpShell.Name())
|
||||
|
||||
_, err = tmpShell.WriteString(setUpStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command("/bin/bash", tmpShell.Name())
|
||||
fmt.Println(cmd.String())
|
||||
output, err := cmd.Output()
|
||||
fmt.Println(string(output))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setUpSsdb()
|
||||
m.Run()
|
||||
}
|
||||
|
||||
type SSDBSuite struct {
|
||||
cfg *conf.Config
|
||||
db database.Storage
|
||||
}
|
||||
|
||||
var _ = Suite(&SSDBSuite{cfg: &conf.Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: 8888,
|
||||
}})
|
||||
|
||||
func (s *SSDBSuite) SetUpTest(c *C) {
|
||||
var err error
|
||||
s.db, err = ssdb.NewOpenDB(s.cfg)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *SSDBSuite) TestSetUpTest(c *C) {
|
||||
var err error
|
||||
s.db, err = ssdb.NewOpenDB(s.cfg)
|
||||
c.Assert(err, IsNil)
|
||||
}
|
||||
|
||||
func (s *SSDBSuite) TestGetPut(c *C) {
|
||||
var (
|
||||
key = []byte("key")
|
||||
value = []byte("value")
|
||||
)
|
||||
var err error
|
||||
|
||||
err = s.db.Put(key, value)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
result, err := s.db.Get(key)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(result, DeepEquals, value)
|
||||
}
|
||||
|
||||
func (s *SSDBSuite) TestTemporaryDelete(c *C) {
|
||||
fmt.Println("TestTemporaryDelete")
|
||||
var (
|
||||
key = []byte("key")
|
||||
value = []byte("value")
|
||||
)
|
||||
|
||||
temp, err := s.db.CreateTemporary()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
c.Check(temp.HasPrefix([]byte(nil)), Equals, false)
|
||||
|
||||
err = temp.Put(key, value)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(temp.HasPrefix([]byte(nil)), Equals, true)
|
||||
|
||||
c.Assert(temp.Close(), IsNil)
|
||||
c.Assert(temp.Drop(), IsNil)
|
||||
}
|
||||
|
||||
func (s *SSDBSuite) TestDelete(c *C) {
|
||||
var (
|
||||
key = []byte("key")
|
||||
value = []byte("value")
|
||||
)
|
||||
|
||||
err := s.db.Put(key, value)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
_, err = s.db.Get(key)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
err = s.db.Delete(key)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
}
|
||||
|
||||
func (s *SSDBSuite) TestByPrefix(c *C) {
|
||||
//c.Check(s.db.FetchByPrefix([]byte{0x80}), DeepEquals, [][]byte{})
|
||||
|
||||
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||
s.db.Put([]byte{0x80, 0x03}, []byte{0x03})
|
||||
s.db.Put([]byte{0x80, 0x02}, []byte{0x02})
|
||||
c.Check(len(s.db.FetchByPrefix([]byte{0x80})), DeepEquals, len([][]byte{{0x01}, {0x02}, {0x03}}))
|
||||
c.Check(len(s.db.KeysByPrefix([]byte{0x80})), DeepEquals, len([][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}}))
|
||||
|
||||
s.db.Put([]byte{0x90, 0x01}, []byte{0x04})
|
||||
c.Check(len(s.db.FetchByPrefix([]byte{0x80})), DeepEquals, len([][]byte{{0x01}, {0x02}, {0x03}}))
|
||||
c.Check(len(s.db.KeysByPrefix([]byte{0x80})), DeepEquals, len([][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}}))
|
||||
|
||||
s.db.Put([]byte{0x00, 0x01}, []byte{0x05})
|
||||
c.Check(len(s.db.FetchByPrefix([]byte{0x80})), DeepEquals, len([][]byte{{0x01}, {0x02}, {0x03}}))
|
||||
c.Check(len(s.db.KeysByPrefix([]byte{0x80})), DeepEquals, len([][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}}))
|
||||
|
||||
keys := [][]byte{}
|
||||
values := [][]byte{}
|
||||
|
||||
c.Check(s.db.ProcessByPrefix([]byte{0x80}, func(k, v []byte) error {
|
||||
keys = append(keys, append([]byte(nil), k...))
|
||||
values = append(values, append([]byte(nil), v...))
|
||||
return nil
|
||||
}), IsNil)
|
||||
|
||||
c.Check(len(values), DeepEquals, len([][]byte{{0x01}, {0x02}, {0x03}}))
|
||||
c.Check(len(keys), DeepEquals, len([][]byte{{0x80, 0x01}, {0x80, 0x02}, {0x80, 0x03}}))
|
||||
|
||||
c.Check(s.db.ProcessByPrefix([]byte{0x80}, func(k, v []byte) error {
|
||||
return database.ErrNotFound
|
||||
}), Equals, database.ErrNotFound)
|
||||
|
||||
c.Check(s.db.ProcessByPrefix([]byte{0xa0}, func(k, v []byte) error {
|
||||
return database.ErrNotFound
|
||||
}), IsNil)
|
||||
|
||||
c.Check(s.db.FetchByPrefix([]byte{0xa0}), DeepEquals, [][]byte{})
|
||||
c.Check(s.db.KeysByPrefix([]byte{0xa0}), DeepEquals, [][]byte{})
|
||||
}
|
||||
|
||||
func (s *SSDBSuite) TestHasPrefix(c *C) {
|
||||
s.db.Put([]byte{0x80, 0x01}, []byte{0x01})
|
||||
|
||||
//c.Check(s.db.HasPrefix([]byte("")), Equals, true)
|
||||
c.Check(s.db.HasPrefix([]byte{0x80}), Equals, true)
|
||||
c.Check(s.db.HasPrefix([]byte{0x79}), Equals, false)
|
||||
}
|
||||
|
||||
func (s *SSDBSuite) TestTransactionCommit(c *C) {
|
||||
var (
|
||||
key = []byte("key")
|
||||
key2 = []byte("key2")
|
||||
value = []byte("value")
|
||||
value2 = []byte("value2")
|
||||
)
|
||||
s.db.Delete(key)
|
||||
s.db.Delete(key2)
|
||||
transaction, err := s.db.OpenTransaction()
|
||||
c.Assert(err, IsNil)
|
||||
defer transaction.Discard()
|
||||
|
||||
err = s.db.Put(key, value)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
v, err := s.db.Get(key)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(v, DeepEquals, value)
|
||||
|
||||
err = transaction.Put(key2, value2)
|
||||
c.Assert(err, IsNil)
|
||||
v, err = transaction.Get(key2)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(v, DeepEquals, value2)
|
||||
_, err = s.db.Get(key2)
|
||||
c.Assert(err, ErrorMatches, "key not found")
|
||||
|
||||
err = transaction.Delete(key)
|
||||
c.Assert(err, IsNil)
|
||||
_, err = transaction.Get(key)
|
||||
c.Assert(err, ErrorMatches, "key not found")
|
||||
v, err = s.db.Get(key)
|
||||
c.Assert(err, IsNil)
|
||||
c.Check(v, DeepEquals, value)
|
||||
|
||||
err = transaction.Commit()
|
||||
c.Check(err, IsNil)
|
||||
|
||||
v, err = s.db.Get(key2)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(v, DeepEquals, value2)
|
||||
|
||||
_, err = s.db.Get(key)
|
||||
c.Assert(err, ErrorMatches, "key not found")
|
||||
}
|
||||
|
||||
func (s *SSDBSuite) TestBatch(c *C) {
|
||||
var (
|
||||
key = []byte("bkey")
|
||||
key2 = []byte("bkey2")
|
||||
value = []byte("bvalue")
|
||||
value2 = []byte("bvalue2")
|
||||
)
|
||||
|
||||
err := s.db.Put(key, value)
|
||||
c.Check(err, IsNil)
|
||||
|
||||
batch := s.db.CreateBatch()
|
||||
batch.Put(key2, value2)
|
||||
v, err := s.db.Get(key)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(v, DeepEquals, value)
|
||||
_, err = s.db.Get(key2)
|
||||
c.Check(err, ErrorMatches, "key not found")
|
||||
|
||||
err = batch.Write()
|
||||
c.Check(err, IsNil)
|
||||
|
||||
v, err = s.db.Get(key2)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(v, DeepEquals, value2)
|
||||
|
||||
batch = s.db.CreateBatch()
|
||||
batch.Delete(key)
|
||||
batch.Delete(key2)
|
||||
c.Check(err, IsNil)
|
||||
v, err = s.db.Get(key)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(v, DeepEquals, value)
|
||||
c.Check(err, IsNil)
|
||||
v, err = s.db.Get(key2)
|
||||
c.Check(err, IsNil)
|
||||
c.Check(v, DeepEquals, value2)
|
||||
|
||||
err = batch.Write()
|
||||
c.Check(err, IsNil)
|
||||
|
||||
_, err = s.db.Get(key2)
|
||||
c.Check(err, ErrorMatches, "key not found")
|
||||
_, err = s.db.Get(key)
|
||||
c.Check(err, ErrorMatches, "key not found")
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package ssdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func ssdbLog(a ...interface{}) {
|
||||
if os.Getenv("SSDB_DEBUG") != "" {
|
||||
fmt.Println(a...)
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
package ssdb
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
"github.com/aptly-dev/aptly/database/goleveldb"
|
||||
"github.com/seefan/gossdb/v2"
|
||||
"github.com/seefan/gossdb/v2/conf"
|
||||
"github.com/seefan/gossdb/v2/pool"
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
cfg *conf.Config
|
||||
db *pool.Client
|
||||
}
|
||||
|
||||
// CreateTemporary creates new DB of the same type in temp dir
|
||||
func (s *Storage) CreateTemporary() (database.Storage, error) {
|
||||
// use leveldb as temp db
|
||||
tmpPath := os.Getenv("SSDB_TMPDB_PATH")
|
||||
if tmpPath == "" {
|
||||
tmpPath = "/tmp/ssdb_tmpdb_path"
|
||||
}
|
||||
gdb, err := goleveldb.NewDB(tmpPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gdb.CreateTemporary()
|
||||
}
|
||||
|
||||
// Get key value from ssdb
|
||||
func (s *Storage) Get(key []byte) (value []byte, err error) {
|
||||
// ssdbLog("ssdb origin db get key:", string(key))
|
||||
getResp, err := s.db.Get(string(key))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
value = getResp.Bytes()
|
||||
|
||||
if len(value) == 0 {
|
||||
err = database.ErrNotFound
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Put saves key to ssdb, if key has the same value in DB already, it is not saved
|
||||
func (s *Storage) Put(key []byte, value []byte) (err error) {
|
||||
//ssdbLog("ssdb origin db put key:", string(key), " value: ", string(value))
|
||||
err = s.db.Set(string(key), value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes key from ssdb
|
||||
func (s *Storage) Delete(key []byte) (err error) {
|
||||
//ssdbLog("ssdb origin db del key:", string(key))
|
||||
err = s.db.Del(string(key))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// KeysByPrefix returns all keys that start with prefix
|
||||
func (s *Storage) KeysByPrefix(prefix []byte) [][]byte {
|
||||
result := make([][]byte, 0)
|
||||
getResp, err := s.db.Keys(string(prefix), string(prefix)+"}", -1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, ev := range getResp {
|
||||
key := []byte(ev)
|
||||
keyc := make([]byte, len(key))
|
||||
copy(keyc, key)
|
||||
result = append(result, key)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FetchByPrefix returns all values with keys that start with prefix
|
||||
func (s *Storage) FetchByPrefix(prefix []byte) [][]byte {
|
||||
result := make([][]byte, 0)
|
||||
getResp, err := s.db.Scan(string(prefix), string(prefix)+"}", -1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, ev := range getResp {
|
||||
value := ev.Bytes()
|
||||
valuec := make([]byte, len(value))
|
||||
copy(valuec, value)
|
||||
result = append(result, valuec)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// HasPrefix checks whether it can find any key with given prefix and returns true if one exists
|
||||
func (s *Storage) HasPrefix(prefix []byte) bool {
|
||||
//ssdbLog("HasPrefix", string(prefix), string(prefix)+"}")
|
||||
getResp, err := s.db.Keys(string(prefix), string(prefix)+"}", -1)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
//ssdbLog("HasPrefix", len(getResp))
|
||||
if len(getResp) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ProcessByPrefix iterates through all entries where key starts with prefix and calls
|
||||
// StorageProcessor on key value pair
|
||||
func (s *Storage) ProcessByPrefix(prefix []byte, proc database.StorageProcessor) error {
|
||||
getResp, err := s.db.Scan(string(prefix), string(prefix)+"}", -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range getResp {
|
||||
err := proc([]byte(k), v.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close finishes ssdb connect
|
||||
func (s *Storage) Close() error {
|
||||
ssdbLog("ssdb close")
|
||||
if s.db != nil {
|
||||
s.db.Close()
|
||||
s.db = nil
|
||||
}
|
||||
gossdb.Shutdown()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reopen tries to open (re-open) the database
|
||||
func (s *Storage) Open() error {
|
||||
ssdbLog("ssdb open")
|
||||
if s.db != nil && s.db.IsOpen() {
|
||||
ssdbLog("ssdb opened")
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
s.db, err = internalOpen(s.cfg)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateBatch creates a Batch object
|
||||
func (s *Storage) CreateBatch() database.Batch {
|
||||
Batch := internalOpenBatch(s)
|
||||
Batch.cfg = s.cfg
|
||||
Batch.db = s.db
|
||||
return Batch
|
||||
}
|
||||
|
||||
// OpenTransaction creates new transaction.
|
||||
func (s *Storage) OpenTransaction() (database.Transaction, error) {
|
||||
return internalOpenTransaction(s)
|
||||
}
|
||||
|
||||
// CompactDB compacts database by merging layers
|
||||
func (s *Storage) CompactDB() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Drop removes all the ssdb files (DANGEROUS!)
|
||||
func (s *Storage) Drop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check interface
|
||||
var (
|
||||
_ database.Storage = &Storage{}
|
||||
)
|
||||
@@ -1,188 +0,0 @@
|
||||
package ssdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aptly-dev/aptly/database"
|
||||
)
|
||||
|
||||
type trWriteData struct {
|
||||
key []byte
|
||||
value []byte
|
||||
opts string
|
||||
err error
|
||||
}
|
||||
|
||||
type trReadData struct {
|
||||
kv []byte
|
||||
err error
|
||||
}
|
||||
|
||||
type transaction struct {
|
||||
// for key-value-operation chan
|
||||
w chan trWriteData
|
||||
// key read chan
|
||||
r chan trReadData
|
||||
q map[string]trWriteData
|
||||
t database.Storage
|
||||
}
|
||||
|
||||
// func internalOpenTransaction...
|
||||
func internalOpenTransaction(t database.Storage) (*transaction, error) {
|
||||
tr := &transaction{
|
||||
w: make(chan trWriteData),
|
||||
r: make(chan trReadData),
|
||||
q: make(map[string]trWriteData),
|
||||
t: t,
|
||||
}
|
||||
|
||||
return tr, tr.run()
|
||||
}
|
||||
|
||||
// func run...
|
||||
func (t *transaction) run() error {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case w, ok := <-t.w:
|
||||
{
|
||||
if !ok {
|
||||
ssdbLog("ssdb transaction write chan closed")
|
||||
return
|
||||
}
|
||||
|
||||
if w.opts == "commit" {
|
||||
ssdbLog("ssdb transaction commit")
|
||||
var errs []error
|
||||
for _, vo := range t.q {
|
||||
if vo.opts == "put" {
|
||||
err := t.t.Put(vo.key, vo.value)
|
||||
if err != nil {
|
||||
//ssdbLog(err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if vo.opts == delOpt {
|
||||
err := t.t.Delete(vo.key)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
t.w <- trWriteData{
|
||||
err: nil,
|
||||
}
|
||||
} else {
|
||||
t.w <- trWriteData{
|
||||
err: fmt.Errorf("ssdb transaction write errs: %v", errs),
|
||||
}
|
||||
}
|
||||
ssdbLog("ssdb transaction commit end")
|
||||
} else {
|
||||
ssdbLog("ssdb transaction", w.opts)
|
||||
//ssdbLog("ssdb r transaction", w.opts, "key: ", string(w.key), "value: ", string(w.value))
|
||||
t.q[string(w.key)] = w
|
||||
}
|
||||
}
|
||||
case r, ok := <-t.r:
|
||||
{
|
||||
if !ok {
|
||||
ssdbLog("ssdb transaction read chan closed")
|
||||
return
|
||||
}
|
||||
|
||||
if rData, ok := t.q[string(r.kv)]; ok {
|
||||
if rData.opts == delOpt {
|
||||
// del return not found error
|
||||
t.r <- trReadData{
|
||||
kv: nil,
|
||||
err: database.ErrNotFound,
|
||||
}
|
||||
} else {
|
||||
t.r <- trReadData{
|
||||
kv: rData.value,
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
v, err := t.t.Get(r.kv)
|
||||
t.r <- trReadData{
|
||||
kv: v,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get implements database.Reader interface.
|
||||
func (t *transaction) Get(key []byte) ([]byte, error) {
|
||||
keyc := make([]byte, len(key))
|
||||
copy(keyc, key)
|
||||
r := trReadData{
|
||||
kv: keyc,
|
||||
err: nil,
|
||||
}
|
||||
t.r <- r
|
||||
result := <-t.r
|
||||
return result.kv, result.err
|
||||
}
|
||||
|
||||
// Put implements database.Writer interface.
|
||||
func (t *transaction) Put(key, value []byte) error {
|
||||
//ssdbLog("golf*********************ssdb put")
|
||||
//ssdbLog("ssdb transaction db put key:", string(key), " value: ", string(value))
|
||||
keyc := make([]byte, len(key))
|
||||
copy(keyc, key)
|
||||
valuec := make([]byte, len(value))
|
||||
copy(valuec, value)
|
||||
w := trWriteData{
|
||||
key: keyc,
|
||||
value: valuec,
|
||||
opts: "put",
|
||||
}
|
||||
|
||||
t.w <- w
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete implements database.Writer interface.
|
||||
func (t *transaction) Delete(key []byte) error {
|
||||
//return t.t.Delete(key)
|
||||
//ssdbLog("golf*********************ssdb del")
|
||||
keyc := make([]byte, len(key))
|
||||
copy(keyc, key)
|
||||
w := trWriteData{
|
||||
key: keyc,
|
||||
opts: delOpt,
|
||||
}
|
||||
|
||||
t.w <- w
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *transaction) Commit() error {
|
||||
w := trWriteData{
|
||||
opts: "commit",
|
||||
}
|
||||
|
||||
t.w <- w
|
||||
result := <-t.w
|
||||
return result.err
|
||||
}
|
||||
|
||||
// Discard is safe to call after Commit(), it would be no-op
|
||||
func (t *transaction) Discard() {
|
||||
ssdbLog("ssdb transaction stop")
|
||||
close(t.r)
|
||||
close(t.w)
|
||||
}
|
||||
|
||||
// transaction should implement database.Transaction
|
||||
var _ database.Transaction = &transaction{}
|
||||
@@ -27,6 +27,8 @@ type LocalRepo struct {
|
||||
Uploaders *Uploaders `codec:"Uploaders,omitempty" json:"-"`
|
||||
// "Snapshot" of current list of packages
|
||||
packageRefs *PackageRefList
|
||||
// ldap group for repos
|
||||
LdapGroup string `codec:",ldap-group"`
|
||||
}
|
||||
|
||||
// NewLocalRepo creates new instance of Debian local repository
|
||||
@@ -54,6 +56,14 @@ func (repo *LocalRepo) NumPackages() int {
|
||||
return repo.packageRefs.Len()
|
||||
}
|
||||
|
||||
// LdapGroup returns the ldapgroup if any for the repo
|
||||
func (repo *LocalRepo) GetLDGroup() string {
|
||||
if repo.LdapGroup != "" {
|
||||
return fmt.Sprintf("[%s]", repo.LdapGroup)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// RefList returns package list for repo
|
||||
func (repo *LocalRepo) RefList() *PackageRefList {
|
||||
return repo.packageRefs
|
||||
|
||||
+17
-2
@@ -341,6 +341,7 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// Storage. First, publishing endpoints should be described in the aptly
|
||||
// configuration file. Each endpoint has its name and associated settings.
|
||||
"AzurePublishEndpoints": {
|
||||
<<<<<<< HEAD
|
||||
// // Endpoint Name
|
||||
// "test": {
|
||||
|
||||
@@ -392,12 +393,26 @@ The legacy json configuration is still supported (and also supports comments):
|
||||
// // See: Azure documentation https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
|
||||
// // defaults to "https://<accountName>.blob.core.windows.net"
|
||||
// "endpoint": ""
|
||||
}
|
||||
},
|
||||
|
||||
// Authorization for repos may be configured for ldap groups (and is extensible for others),
|
||||
// default is no authorization.
|
||||
"Auth": {
|
||||
// // auth type, only supports ldap currently
|
||||
// "authType: "",
|
||||
// // auth server to use (eg. ldaps://ldap.example.com)
|
||||
// "server\": "",
|
||||
// // DN for ldap searches
|
||||
// "ldapDN\": "",
|
||||
// // ldap filter
|
||||
// "ldapFilter": "",
|
||||
// // enable secureTLS, default is off
|
||||
// "secureTLS": false
|
||||
}
|
||||
|
||||
// End of config
|
||||
}
|
||||
|
||||
|
||||
## PACKAGE QUERY
|
||||
|
||||
Some commands accept package queries to identify list of packages to process.
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
// ConfigStructure is structure of main configuration
|
||||
type ConfigStructure struct { // nolint: maligned
|
||||
<<<<<<< HEAD
|
||||
// General
|
||||
RootDir string `json:"rootDir" yaml:"root_dir"`
|
||||
LogLevel string `json:"logLevel" yaml:"log_level"`
|
||||
@@ -63,6 +64,10 @@ type ConfigStructure struct { // nolint: maligned
|
||||
SwiftPublishRoots map[string]SwiftPublishRoot `json:"SwiftPublishEndpoints" yaml:"swift_publish_endpoints"`
|
||||
AzurePublishRoots map[string]AzureEndpoint `json:"AzurePublishEndpoints" yaml:"azure_publish_endpoints"`
|
||||
PackagePoolStorage PackagePoolStorage `json:"packagePoolStorage" yaml:"packagepool_storage"`
|
||||
|
||||
// Authentication
|
||||
UseAuth bool `json:"useAuth"`
|
||||
Auth AAuth `json:"Auth"`
|
||||
}
|
||||
|
||||
// DBConfig
|
||||
@@ -211,9 +216,19 @@ type AzureEndpoint struct {
|
||||
Endpoint string `json:"endpoint" yaml:"endpoint"`
|
||||
}
|
||||
|
||||
type AAuth struct {
|
||||
Type string `json:"authType"`
|
||||
Server string `json:"server"`
|
||||
LdapDN string `json:"ldapDN"`
|
||||
LdapFilter string `json:"ldapFilter"`
|
||||
SecureTLS bool `json:"secureTLS"`
|
||||
}
|
||||
|
||||
// Config is configuration for aptly, shared by all modules
|
||||
var Config = ConfigStructure{
|
||||
RootDir: filepath.Join(os.Getenv("HOME"), ".aptly"),
|
||||
LogFile: "",
|
||||
UseAuth: false, // should we enable auth
|
||||
DownloadConcurrency: 4,
|
||||
DownloadLimit: 0,
|
||||
Downloader: "default",
|
||||
@@ -243,6 +258,7 @@ var Config = ConfigStructure{
|
||||
LogFormat: "default",
|
||||
ServeInAPIMode: false,
|
||||
EnableSwaggerEndpoint: false,
|
||||
Auth: AAuth{},
|
||||
}
|
||||
|
||||
// LoadConfig loads configuration from json file
|
||||
|
||||
@@ -155,7 +155,15 @@ func (s *ConfigSuite) TestSaveConfig(c *C) {
|
||||
" \"packagePoolStorage\": {\n" +
|
||||
" \"type\": \"local\",\n" +
|
||||
" \"path\": \"/tmp/aptly-pool\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"useAuth\": false,\n"+
|
||||
" \"Auth\": {\n"+
|
||||
" \"authType\": \"\",\n"+
|
||||
" \"server\": \"\",\n"+
|
||||
" \"ldapDN\": \"\",\n"+
|
||||
" \"ldapFilter\": \"\",\n"+
|
||||
" \"secureTLS\": false\n"+
|
||||
" }\n"+
|
||||
"}")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user