mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-05-06 22:18:28 +00:00
Merge branch 'lebauce-graph-api'
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/smira/aptly/deb"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GET /api/graph.:ext
|
||||||
|
func apiGraph(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
output []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
ext := c.Params.ByName("ext")
|
||||||
|
|
||||||
|
factory := context.CollectionFactory()
|
||||||
|
|
||||||
|
factory.RemoteRepoCollection().RLock()
|
||||||
|
defer factory.RemoteRepoCollection().RUnlock()
|
||||||
|
factory.LocalRepoCollection().RLock()
|
||||||
|
defer factory.LocalRepoCollection().RUnlock()
|
||||||
|
factory.SnapshotCollection().RLock()
|
||||||
|
defer factory.SnapshotCollection().RUnlock()
|
||||||
|
factory.PublishedRepoCollection().RLock()
|
||||||
|
defer factory.PublishedRepoCollection().RUnlock()
|
||||||
|
|
||||||
|
graph, err := deb.BuildGraph(factory)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBufferString(graph.String())
|
||||||
|
|
||||||
|
command := exec.Command("dot", "-T"+ext)
|
||||||
|
command.Stderr = os.Stderr
|
||||||
|
|
||||||
|
stdin, err := command.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
c.Fail(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(stdin, buf)
|
||||||
|
if err != nil {
|
||||||
|
c.Fail(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stdin.Close()
|
||||||
|
if err != nil {
|
||||||
|
c.Fail(500, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err = command.Output()
|
||||||
|
if err != nil {
|
||||||
|
c.Fail(500, fmt.Errorf("unable to execute dot: %s (is graphviz package installed?)", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mimeType := mime.TypeByExtension("." + ext)
|
||||||
|
if mimeType == "" {
|
||||||
|
mimeType = "application/octet-stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data(200, mimeType, output)
|
||||||
|
}
|
||||||
@@ -52,5 +52,9 @@ func Router(c *ctx.AptlyContext) http.Handler {
|
|||||||
root.DELETE("/publish/:prefix/:distribution", apiPublishDrop)
|
root.DELETE("/publish/:prefix/:distribution", apiPublishDrop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
root.GET("/graph.:ext", apiGraph)
|
||||||
|
}
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-116
@@ -2,7 +2,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"code.google.com/p/gographviz"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/smira/aptly/deb"
|
"github.com/smira/aptly/deb"
|
||||||
"github.com/smira/commander"
|
"github.com/smira/commander"
|
||||||
@@ -10,7 +9,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func aptlyGraph(cmd *commander.Command, args []string) error {
|
func aptlyGraph(cmd *commander.Command, args []string) error {
|
||||||
@@ -21,121 +19,11 @@ func aptlyGraph(cmd *commander.Command, args []string) error {
|
|||||||
return commander.ErrCommandError
|
return commander.ErrCommandError
|
||||||
}
|
}
|
||||||
|
|
||||||
graph := gographviz.NewEscape()
|
|
||||||
graph.SetDir(true)
|
|
||||||
graph.SetName("aptly")
|
|
||||||
|
|
||||||
existingNodes := map[string]bool{}
|
|
||||||
|
|
||||||
fmt.Printf("Loading mirrors...\n")
|
|
||||||
|
|
||||||
err = context.CollectionFactory().RemoteRepoCollection().ForEach(func(repo *deb.RemoteRepo) error {
|
|
||||||
err := context.CollectionFactory().RemoteRepoCollection().LoadComplete(repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
graph.AddNode("aptly", repo.UUID, map[string]string{
|
|
||||||
"shape": "Mrecord",
|
|
||||||
"style": "filled",
|
|
||||||
"fillcolor": "darkgoldenrod1",
|
|
||||||
"label": fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}",
|
|
||||||
repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "),
|
|
||||||
strings.Join(repo.Architectures, ", "), repo.NumPackages()),
|
|
||||||
})
|
|
||||||
existingNodes[repo.UUID] = true
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Loading local repos...\n")
|
|
||||||
|
|
||||||
err = context.CollectionFactory().LocalRepoCollection().ForEach(func(repo *deb.LocalRepo) error {
|
|
||||||
err := context.CollectionFactory().LocalRepoCollection().LoadComplete(repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
graph.AddNode("aptly", repo.UUID, map[string]string{
|
|
||||||
"shape": "Mrecord",
|
|
||||||
"style": "filled",
|
|
||||||
"fillcolor": "mediumseagreen",
|
|
||||||
"label": fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}",
|
|
||||||
repo.Name, repo.Comment, repo.NumPackages()),
|
|
||||||
})
|
|
||||||
existingNodes[repo.UUID] = true
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Loading snapshots...\n")
|
|
||||||
|
|
||||||
context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
|
||||||
existingNodes[snapshot.UUID] = true
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
err = context.CollectionFactory().SnapshotCollection().ForEach(func(snapshot *deb.Snapshot) error {
|
|
||||||
err := context.CollectionFactory().SnapshotCollection().LoadComplete(snapshot)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
description := snapshot.Description
|
|
||||||
if snapshot.SourceKind == "repo" {
|
|
||||||
description = "Snapshot from repo"
|
|
||||||
}
|
|
||||||
|
|
||||||
graph.AddNode("aptly", snapshot.UUID, map[string]string{
|
|
||||||
"shape": "Mrecord",
|
|
||||||
"style": "filled",
|
|
||||||
"fillcolor": "cadetblue1",
|
|
||||||
"label": fmt.Sprintf("{Snapshot %s|%s|pkgs: %d}", snapshot.Name, description, snapshot.NumPackages()),
|
|
||||||
})
|
|
||||||
|
|
||||||
if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" {
|
|
||||||
for _, uuid := range snapshot.SourceIDs {
|
|
||||||
_, exists := existingNodes[uuid]
|
|
||||||
if exists {
|
|
||||||
graph.AddEdge(uuid, snapshot.UUID, true, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Loading published repos...\n")
|
|
||||||
|
|
||||||
context.CollectionFactory().PublishedRepoCollection().ForEach(func(repo *deb.PublishedRepo) error {
|
|
||||||
graph.AddNode("aptly", repo.UUID, map[string]string{
|
|
||||||
"shape": "Mrecord",
|
|
||||||
"style": "filled",
|
|
||||||
"fillcolor": "darkolivegreen1",
|
|
||||||
"label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution,
|
|
||||||
strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")),
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, uuid := range repo.Sources {
|
|
||||||
_, exists := existingNodes[uuid]
|
|
||||||
if exists {
|
|
||||||
graph.AddEdge(uuid, repo.UUID, true, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
fmt.Printf("Generating graph...\n")
|
fmt.Printf("Generating graph...\n")
|
||||||
|
graph, err := deb.BuildGraph(context.CollectionFactory())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
buf := bytes.NewBufferString(graph.String())
|
buf := bytes.NewBufferString(graph.String())
|
||||||
|
|
||||||
|
|||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
package deb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.google.com/p/gographviz"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildGraph(collectionFactory *CollectionFactory) (gographviz.Interface, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
graph := gographviz.NewEscape()
|
||||||
|
graph.SetDir(true)
|
||||||
|
graph.SetName("aptly")
|
||||||
|
|
||||||
|
existingNodes := map[string]bool{}
|
||||||
|
|
||||||
|
err = collectionFactory.RemoteRepoCollection().ForEach(func(repo *RemoteRepo) error {
|
||||||
|
err := collectionFactory.RemoteRepoCollection().LoadComplete(repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.AddNode("aptly", repo.UUID, map[string]string{
|
||||||
|
"shape": "Mrecord",
|
||||||
|
"style": "filled",
|
||||||
|
"fillcolor": "darkgoldenrod1",
|
||||||
|
"label": fmt.Sprintf("{Mirror %s|url: %s|dist: %s|comp: %s|arch: %s|pkgs: %d}",
|
||||||
|
repo.Name, repo.ArchiveRoot, repo.Distribution, strings.Join(repo.Components, ", "),
|
||||||
|
strings.Join(repo.Architectures, ", "), repo.NumPackages()),
|
||||||
|
})
|
||||||
|
existingNodes[repo.UUID] = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = collectionFactory.LocalRepoCollection().ForEach(func(repo *LocalRepo) error {
|
||||||
|
err := collectionFactory.LocalRepoCollection().LoadComplete(repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.AddNode("aptly", repo.UUID, map[string]string{
|
||||||
|
"shape": "Mrecord",
|
||||||
|
"style": "filled",
|
||||||
|
"fillcolor": "mediumseagreen",
|
||||||
|
"label": fmt.Sprintf("{Repo %s|comment: %s|pkgs: %d}",
|
||||||
|
repo.Name, repo.Comment, repo.NumPackages()),
|
||||||
|
})
|
||||||
|
existingNodes[repo.UUID] = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionFactory.SnapshotCollection().ForEach(func(snapshot *Snapshot) error {
|
||||||
|
existingNodes[snapshot.UUID] = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
err = collectionFactory.SnapshotCollection().ForEach(func(snapshot *Snapshot) error {
|
||||||
|
err := collectionFactory.SnapshotCollection().LoadComplete(snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
description := snapshot.Description
|
||||||
|
if snapshot.SourceKind == "repo" {
|
||||||
|
description = "Snapshot from repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.AddNode("aptly", snapshot.UUID, map[string]string{
|
||||||
|
"shape": "Mrecord",
|
||||||
|
"style": "filled",
|
||||||
|
"fillcolor": "cadetblue1",
|
||||||
|
"label": fmt.Sprintf("{Snapshot %s|%s|pkgs: %d}", snapshot.Name, description, snapshot.NumPackages()),
|
||||||
|
})
|
||||||
|
|
||||||
|
if snapshot.SourceKind == "repo" || snapshot.SourceKind == "local" || snapshot.SourceKind == "snapshot" {
|
||||||
|
for _, uuid := range snapshot.SourceIDs {
|
||||||
|
_, exists := existingNodes[uuid]
|
||||||
|
if exists {
|
||||||
|
graph.AddEdge(uuid, snapshot.UUID, true, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionFactory.PublishedRepoCollection().ForEach(func(repo *PublishedRepo) error {
|
||||||
|
graph.AddNode("aptly", repo.UUID, map[string]string{
|
||||||
|
"shape": "Mrecord",
|
||||||
|
"style": "filled",
|
||||||
|
"fillcolor": "darkolivegreen1",
|
||||||
|
"label": fmt.Sprintf("{Published %s/%s|comp: %s|arch: %s}", repo.Prefix, repo.Distribution,
|
||||||
|
strings.Join(repo.Components(), " "), strings.Join(repo.Architectures, ", ")),
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, uuid := range repo.Sources {
|
||||||
|
_, exists := existingNodes[uuid]
|
||||||
|
if exists {
|
||||||
|
graph.AddEdge(uuid, repo.UUID, true, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return graph, nil
|
||||||
|
}
|
||||||
@@ -6,3 +6,4 @@ from .repos import *
|
|||||||
from .files import *
|
from .files import *
|
||||||
from .publish import *
|
from .publish import *
|
||||||
from .version import *
|
from .version import *
|
||||||
|
from .graph import *
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from api_lib import APITest
|
||||||
|
|
||||||
|
|
||||||
|
class GraphAPITest(APITest):
|
||||||
|
"""
|
||||||
|
GET /graph.:ext
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
resp = self.get("/api/graph.png")
|
||||||
|
self.check_equal(resp.headers["Content-Type"], "image/png")
|
||||||
|
self.check_equal(resp.content[:4], '\x89PNG')
|
||||||
|
|
||||||
|
self.check_equal(self.post("/api/repos", json={"Name": "xyz", "Comment": "fun repo"}).status_code, 201)
|
||||||
|
resp = self.get("/api/graph.svg")
|
||||||
|
self.check_equal(resp.headers["Content-Type"], "image/svg+xml")
|
||||||
|
self.check_equal(resp.content[:4], '<?xm')
|
||||||
@@ -45,7 +45,7 @@ class PublishAPITestRepo(APITest):
|
|||||||
'Storage': ''})
|
'Storage': ''})
|
||||||
|
|
||||||
|
|
||||||
class PublishSnapshotAPITestRepo(APITest):
|
class PublishSnapshotAPITest(APITest):
|
||||||
"""
|
"""
|
||||||
POST /publish/:prefix/snapshot
|
POST /publish/:prefix/snapshot
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user