Merge pull request #728 from weaveworks/make-make-build-ui

Make make build ui
This commit is contained in:
Paul Bellamy
2015-12-07 15:18:59 +00:00
22 changed files with 150 additions and 19032 deletions

6
.gitignore vendored
View File

@@ -34,8 +34,8 @@ coverage.html
.*.uptodate
scope.tar
scope_ui_build.tar
app/app
app/scope-app
prog/app/app
prog/app/scope-app
prog/probe/probe
prog/probe/scope-probe
docker/scope-app
@@ -54,3 +54,5 @@ experimental/_integration/_integration
*sublime-project
*sublime-workspace
*npm-debug.log
app/static.go
prog/app/static.go

View File

@@ -3,7 +3,7 @@
# If you can use Docker without being root, you can `make SUDO= <target>`
SUDO=sudo -E
DOCKERHUB_USER=weaveworks
APP_EXE=app/scope-app
APP_EXE=prog/app/scope-app
PROBE_EXE=prog/probe/scope-probe
FIXPROBE_EXE=experimental/fixprobe/fixprobe
SCOPE_IMAGE=$(DOCKERHUB_USER)/scope
@@ -38,7 +38,7 @@ $(SCOPE_EXPORT): $(APP_EXE) $(PROBE_EXE) $(DOCKER_DISTRIB) docker/weave $(RUNSVI
$(RUNSVINIT): vendor/runsvinit/*.go
$(APP_EXE): app/*.go render/*.go report/*.go xfer/*.go common/sanitize/*.go
$(APP_EXE): app/*.go render/*.go report/*.go xfer/*.go common/sanitize/*.go prog/app/*.go prog/app/static.go
$(PROBE_EXE): prog/probe/*.go $(shell find probe/ -type f -name *.go) report/*.go xfer/*.go common/sanitize/*.go common/exec/*.go
@@ -62,30 +62,35 @@ $(RUNSVINIT):
go build -ldflags "-extldflags \"-static\"" -o $@ ./$(@D)
endif
static: client/build/app.js
esc -o app/static.go -prefix client/build client/build
static: prog/app/static.go
prog/app/static.go: client/build/app.js
esc -o $@ -prefix client/build client/build
ifeq ($(BUILD_IN_CONTAINER),true)
client/build/app.js: $(shell find client/app/scripts -type f)
client/build/app.js: $(shell find client/app/scripts -type f) $(SCOPE_UI_BUILD_UPTODATE)
mkdir -p client/build
$(SUDO) docker run $(RM) $(RUN_FLAGS) -v $(shell pwd)/client/app:/home/weave/app \
-v $(shell pwd)/client/build:/home/weave/build \
$(SCOPE_UI_BUILD_IMAGE) npm run build
client-test: $(shell find client/app/scripts -type f)
client-test: $(shell find client/app/scripts -type f) $(SCOPE_UI_BUILD_UPTODATE)
$(SUDO) docker run $(RM) $(RUN_FLAGS) -v $(shell pwd)/client/app:/home/weave/app \
-v $(shell pwd)/client/test:/home/weave/test \
$(SCOPE_UI_BUILD_IMAGE) npm test
client-lint:
client-lint: $(SCOPE_UI_BUILD_UPTODATE)
$(SUDO) docker run $(RM) $(RUN_FLAGS) -v $(shell pwd)/client/app:/home/weave/app \
-v $(shell pwd)/client/test:/home/weave/test \
$(SCOPE_UI_BUILD_IMAGE) npm run lint
client-start:
client-start: $(SCOPE_UI_BUILD_UPTODATE)
$(SUDO) docker run $(RM) $(RUN_FLAGS) --net=host -v $(shell pwd)/client/app:/home/weave/app \
-v $(shell pwd)/client/build:/home/weave/build \
$(SCOPE_UI_BUILD_IMAGE) npm start
else
client/build/app.js:
cd client && npm run build
endif
$(SCOPE_UI_BUILD_UPTODATE): client/Dockerfile client/package.json client/webpack.local.config.js client/webpack.production.config.js client/server.js client/.eslintrc
@@ -96,8 +101,6 @@ $(SCOPE_BACKEND_BUILD_UPTODATE): backend/*
$(SUDO) docker build -t $(SCOPE_BACKEND_BUILD_IMAGE) backend
touch $@
frontend: $(SCOPE_UI_BUILD_UPTODATE)
clean:
go clean ./...
$(SUDO) docker rmi $(SCOPE_UI_BUILD_IMAGE) $(SCOPE_BACKEND_BUILD_IMAGE) >/dev/null 2>&1 || true

View File

@@ -192,16 +192,14 @@ Kubernetes-specific views "Pods", and "Pods by Service".
## <a name="developing"></a>Developing
The build is in five stages. `make deps` installs some tools we use later in
the build. `make frontend` builds a UI build image with all NPM dependencies.
`make static` compiles the UI into `static.go` which is part of the repository
for convenience. The final `make` builds the app and probe, in a container,
and pushes the lot into a Docker image called **weaveworks/scope**.
The build is in two stages. `make deps` installs some tools we use later in
the build. `make` builds the UI build container, builds the UI in said
container, builds the backend build container, builds the app and probe in a
said container, and finally pushes the lot into a Docker image called
**weaveworks/scope**.
```
make deps
make frontend
make static
make
```

View File

@@ -1,4 +1,4 @@
package main
package app
import (
"net/http"

View File

@@ -1,15 +1,24 @@
package main
package app_test
import (
"encoding/json"
"net/http/httptest"
"testing"
"github.com/gorilla/mux"
"github.com/weaveworks/scope/app"
"github.com/weaveworks/scope/report"
)
func topologyServer() *httptest.Server {
router := mux.NewRouter()
app.RegisterTopologyRoutes(StaticReport{}, router)
return httptest.NewServer(router)
}
func TestAPIReport(t *testing.T) {
ts := httptest.NewServer(Router(StaticReport{}))
ts := topologyServer()
defer ts.Close()
is404(t, ts, "/api/report/foobar")

View File

@@ -1,4 +1,4 @@
package main
package app
import (
"net/http"

View File

@@ -1,4 +1,4 @@
package main
package app_test
import (
"bytes"
@@ -6,21 +6,24 @@ import (
"encoding/json"
"net/http/httptest"
"testing"
"time"
"github.com/gorilla/mux"
"k8s.io/kubernetes/pkg/api"
"github.com/weaveworks/scope/app"
"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test/fixture"
)
func TestAPITopology(t *testing.T) {
ts := httptest.NewServer(Router(StaticReport{}))
ts := topologyServer()
defer ts.Close()
body := getRawJSON(t, ts, "/api/topology")
var topologies []APITopologyDesc
var topologies []app.APITopologyDesc
if err := json.Unmarshal(body, &topologies); err != nil {
t.Fatalf("JSON parse error: %s", err)
}
@@ -48,12 +51,16 @@ func TestAPITopology(t *testing.T) {
}
func TestAPITopologyAddsKubernetes(t *testing.T) {
ts := httptest.NewServer(Router(StaticReport{}))
router := mux.NewRouter()
c := app.NewCollector(1 * time.Minute)
app.RegisterTopologyRoutes(c, router)
app.RegisterReportPostHandler(c, router)
ts := httptest.NewServer(router)
defer ts.Close()
body := getRawJSON(t, ts, "/api/topology")
var topologies []APITopologyDesc
var topologies []app.APITopologyDesc
if err := json.Unmarshal(body, &topologies); err != nil {
t.Fatalf("JSON parse error: %s", err)
}

View File

@@ -1,4 +1,4 @@
package main
package app
import (
"net/http"

View File

@@ -1,15 +1,15 @@
package main
package app_test
import (
"encoding/json"
"fmt"
"net/http/httptest"
"net/url"
"reflect"
"testing"
"github.com/gorilla/websocket"
"github.com/weaveworks/scope/app"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/render/expected"
"github.com/weaveworks/scope/report"
@@ -18,25 +18,25 @@ import (
)
func TestAll(t *testing.T) {
ts := httptest.NewServer(Router(StaticReport{}))
ts := topologyServer()
defer ts.Close()
body := getRawJSON(t, ts, "/api/topology")
var topologies []APITopologyDesc
var topologies []app.APITopologyDesc
if err := json.Unmarshal(body, &topologies); err != nil {
t.Fatalf("JSON parse error: %s", err)
}
getTopology := func(topologyURL string) {
body := getRawJSON(t, ts, topologyURL)
var topology APITopology
var topology app.APITopology
if err := json.Unmarshal(body, &topology); err != nil {
t.Fatalf("JSON parse error: %s", err)
}
for _, node := range topology.Nodes {
body := getRawJSON(t, ts, fmt.Sprintf("%s/%s", topologyURL, url.QueryEscape(node.ID)))
var node APINode
var node app.APINode
if err := json.Unmarshal(body, &node); err != nil {
t.Fatalf("JSON parse error: %s", err)
}
@@ -53,10 +53,10 @@ func TestAll(t *testing.T) {
}
func TestAPITopologyContainers(t *testing.T) {
ts := httptest.NewServer(Router(StaticReport{}))
ts := topologyServer()
{
body := getRawJSON(t, ts, "/api/topology/containers")
var topo APITopology
var topo app.APITopology
if err := json.Unmarshal(body, &topo); err != nil {
t.Fatal(err)
}
@@ -73,12 +73,12 @@ func TestAPITopologyContainers(t *testing.T) {
}
func TestAPITopologyApplications(t *testing.T) {
ts := httptest.NewServer(Router(StaticReport{}))
ts := topologyServer()
defer ts.Close()
is404(t, ts, "/api/topology/applications/foobar")
{
body := getRawJSON(t, ts, "/api/topology/applications/"+expected.ServerProcessID)
var node APINode
var node app.APINode
if err := json.Unmarshal(body, &node); err != nil {
t.Fatal(err)
}
@@ -90,7 +90,7 @@ func TestAPITopologyApplications(t *testing.T) {
}
{
body := getRawJSON(t, ts, fmt.Sprintf("/api/topology/applications/%s/%s", expected.ClientProcess1ID, expected.ServerProcessID))
var edge APIEdge
var edge app.APIEdge
if err := json.Unmarshal(body, &edge); err != nil {
t.Fatalf("JSON parse error: %s", err)
}
@@ -104,12 +104,12 @@ func TestAPITopologyApplications(t *testing.T) {
}
func TestAPITopologyHosts(t *testing.T) {
ts := httptest.NewServer(Router(StaticReport{}))
ts := topologyServer()
defer ts.Close()
is404(t, ts, "/api/topology/hosts/foobar")
{
body := getRawJSON(t, ts, "/api/topology/hosts")
var topo APITopology
var topo app.APITopology
if err := json.Unmarshal(body, &topo); err != nil {
t.Fatal(err)
}
@@ -120,7 +120,7 @@ func TestAPITopologyHosts(t *testing.T) {
}
{
body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostRenderedID)
var node APINode
var node app.APINode
if err := json.Unmarshal(body, &node); err != nil {
t.Fatal(err)
}
@@ -132,7 +132,7 @@ func TestAPITopologyHosts(t *testing.T) {
}
{
body := getRawJSON(t, ts, fmt.Sprintf("/api/topology/hosts/%s/%s", expected.ClientHostRenderedID, expected.ServerHostRenderedID))
var edge APIEdge
var edge app.APIEdge
if err := json.Unmarshal(body, &edge); err != nil {
t.Fatalf("JSON parse error: %s", err)
}
@@ -146,7 +146,7 @@ func TestAPITopologyHosts(t *testing.T) {
// Basic websocket test
func TestAPITopologyWebsocket(t *testing.T) {
ts := httptest.NewServer(Router(StaticReport{}))
ts := topologyServer()
defer ts.Close()
url := "/api/topology/applications/ws"

View File

@@ -1,4 +1,4 @@
package main
package app
import (
"sync"
@@ -21,9 +21,15 @@ type Adder interface {
Add(report.Report)
}
// A Collector is a Reporter and an Adder
type Collector interface {
Reporter
Adder
}
// Collector receives published reports from multiple producers. It yields a
// single merged report, representing all collected reports.
type Collector struct {
type collector struct {
mtx sync.Mutex
reports []timestampReport
window time.Duration
@@ -60,8 +66,8 @@ func (wc *waitableCondition) Broadcast() {
}
// NewCollector returns a collector ready for use.
func NewCollector(window time.Duration) *Collector {
return &Collector{
func NewCollector(window time.Duration) Collector {
return &collector{
window: window,
waitableCondition: waitableCondition{
waiters: map[chan struct{}]struct{}{},
@@ -72,7 +78,7 @@ func NewCollector(window time.Duration) *Collector {
var now = time.Now
// Add adds a report to the collector's internal state. It implements Adder.
func (c *Collector) Add(rpt report.Report) {
func (c *collector) Add(rpt report.Report) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.reports = append(c.reports, timestampReport{now(), rpt})
@@ -84,7 +90,7 @@ func (c *Collector) Add(rpt report.Report) {
// Report returns a merged report over all added reports. It implements
// Reporter.
func (c *Collector) Report() report.Report {
func (c *collector) Report() report.Report {
c.mtx.Lock()
defer c.mtx.Unlock()

View File

@@ -1,17 +1,18 @@
package main
package app_test
import (
"reflect"
"testing"
"time"
"github.com/weaveworks/scope/app"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
)
func TestCollector(t *testing.T) {
window := time.Millisecond
c := NewCollector(window)
c := app.NewCollector(window)
r1 := report.MakeReport()
r1.Endpoint.AddNode("foo", report.MakeNode())

View File

@@ -1,4 +1,4 @@
package main
package app
import (
"log"
@@ -12,7 +12,8 @@ import (
"github.com/weaveworks/scope/xfer"
)
func registerControlRoutes(router *mux.Router) {
// RegisterControlRoutes registers the various control routes with a http mux.
func RegisterControlRoutes(router *mux.Router) {
controlRouter := &controlRouter{
probes: map[string]controlHandler{},
}

View File

@@ -1,4 +1,4 @@
package main
package app_test
import (
"encoding/json"
@@ -9,14 +9,15 @@ import (
"testing"
"time"
"github.com/weaveworks/scope/xfer"
"github.com/gorilla/mux"
"github.com/weaveworks/scope/app"
"github.com/weaveworks/scope/xfer"
)
func TestControl(t *testing.T) {
router := mux.NewRouter()
registerControlRoutes(router)
app.RegisterControlRoutes(router)
server := httptest.NewServer(router)
defer server.Close()

View File

@@ -1,4 +1,4 @@
package main
package app_test
import (
"github.com/weaveworks/scope/report"

View File

@@ -1,4 +1,4 @@
package main
package app
import (
"compress/gzip"
@@ -16,6 +16,14 @@ import (
"github.com/weaveworks/scope/xfer"
)
var (
// Version - set at buildtime.
Version = "dev"
// UniqueID - set at runtime.
UniqueID = "0"
)
// URLMatcher uses request.RequestURI (the raw, unparsed request) to attempt
// to match pattern. It does this as go's URL.Parse method is broken, and
// mistakenly unescapes the Path before parsing it. This breaks %2F (encoded
@@ -47,16 +55,12 @@ func URLMatcher(pattern string) mux.MatcherFunc {
}
}
type collector interface {
Reporter
Adder
}
func gzipHandler(h http.HandlerFunc) http.HandlerFunc {
return handlers.GZIPHandlerFunc(h, nil)
}
func registerTopologyRoutes(c collector, router *mux.Router) {
// RegisterTopologyRoutes registers the various topology routes with a http mux.
func RegisterTopologyRoutes(c Reporter, router *mux.Router) {
get := router.Methods("GET").Subrouter()
get.HandleFunc("/api", gzipHandler(apiHandler))
get.HandleFunc("/api/topology", gzipHandler(topologyRegistry.makeTopologyList(c)))
@@ -69,13 +73,12 @@ func registerTopologyRoutes(c collector, router *mux.Router) {
get.MatcherFunc(URLMatcher("/api/topology/{topology}/{local}/{remote}")).HandlerFunc(
gzipHandler(topologyRegistry.captureRenderer(c, handleEdge)))
get.HandleFunc("/api/report", gzipHandler(makeRawReportHandler(c)))
post := router.Methods("POST").Subrouter()
post.HandleFunc("/api/report", makeReportPostHandler(c)).Methods("POST")
}
func makeReportPostHandler(a Adder) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// RegisterReportPostHandler registers the handler for report submission
func RegisterReportPostHandler(a Adder, router *mux.Router) {
post := router.Methods("POST").Subrouter()
post.HandleFunc("/api/report", func(w http.ResponseWriter, r *http.Request) {
var (
rpt report.Report
reader = r.Body
@@ -102,13 +105,13 @@ func makeReportPostHandler(a Adder) http.HandlerFunc {
topologyRegistry.enableKubernetesTopologies()
}
w.WriteHeader(http.StatusOK)
}
})
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
respondWith(w, http.StatusOK, xfer.Details{
ID: uniqueID,
Version: version,
ID: UniqueID,
Version: Version,
Hostname: hostname.Get(),
})
}

View File

@@ -1,9 +1,10 @@
package main
package app_test
import (
"bytes"
"encoding/gob"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
@@ -12,6 +13,7 @@ import (
"github.com/gorilla/mux"
"github.com/weaveworks/scope/app"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/fixture"
)
@@ -21,7 +23,7 @@ type v map[string]string
func TestURLMatcher(t *testing.T) {
test := func(pattern, path string, match bool, vars v) {
routeMatch := &mux.RouteMatch{}
if URLMatcher(pattern)(&http.Request{RequestURI: path}, routeMatch) != match {
if app.URLMatcher(pattern)(&http.Request{RequestURI: path}, routeMatch) != match {
t.Fatalf("'%s' '%s'", pattern, path)
}
if match && !reflect.DeepEqual(v(routeMatch.Vars), vars) {
@@ -39,21 +41,38 @@ func TestURLMatcher(t *testing.T) {
func TestReportPostHandler(t *testing.T) {
test := func(contentType string, encoder func(interface{}) ([]byte, error)) {
router := mux.NewRouter()
c := app.NewCollector(1 * time.Minute)
app.RegisterReportPostHandler(c, router)
ts := httptest.NewServer(router)
defer ts.Close()
b, err := encoder(fixture.Report)
if err != nil {
t.Fatalf("Content-Type %s: %s", contentType, err)
}
r, _ := http.NewRequest("POST", "/api/report", bytes.NewReader(b))
r.Header.Set("Content-Type", contentType)
w := httptest.NewRecorder()
c := NewCollector(1 * time.Minute)
makeReportPostHandler(c).ServeHTTP(w, r)
if w.Code != http.StatusOK {
t.Fatalf("Content-Type %s: http status: %d\nbody: %s", contentType, w.Code, w.Body.String())
req, err := http.NewRequest("POST", ts.URL+"/api/report", bytes.NewReader(b))
if err != nil {
t.Fatalf("Error posting report: %v", err)
}
// Just check a few items, to confirm it parsed. Otherwise
// reflect.DeepEqual chokes on nil vs empty arrays.
req.Header.Set("Content-Type", contentType)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Error posting report %v", err)
}
_, err = ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
t.Fatalf("Error posting report: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("Error posting report: %d", resp.StatusCode)
}
if want, have := fixture.Report.Endpoint.Nodes, c.Report().Endpoint.Nodes; len(have) == 0 || len(want) != len(have) {
t.Fatalf("Content-Type %s: %v", contentType, test.Diff(have, want))
}

View File

@@ -1,4 +1,4 @@
package main
package app_test
import (
"bytes"

View File

@@ -1,4 +1,4 @@
package main
package app
import (
"encoding/json"

View File

@@ -1,21 +0,0 @@
// Basic site layout tests.
package main
import (
"net/http/httptest"
"testing"
"github.com/gorilla/mux"
)
// Test site
func TestSite(t *testing.T) {
router := mux.NewRouter()
registerStatic(router)
ts := httptest.NewServer(router)
defer ts.Close()
is200(t, ts, "/")
is200(t, ts, "/index.html")
is404(t, ts, "/index.html/foobar")
}

File diff suppressed because it is too large Load Diff

View File

@@ -39,9 +39,9 @@ test:
parallel: true
- cd $SRCDIR; make RM= static:
parallel: true
- cd $SRCDIR; rm -f app/scope-app prog/probe/scope-probe; if [ "$CIRCLE_NODE_INDEX" = "0" ]; then GOARCH=arm make RM= app/scope-app prog/probe/scope-probe; else GOOS=darwin make RM= app/scope-app prog/probe/scope-probe; fi:
- cd $SRCDIR; rm -f prog/app/scope-app prog/probe/scope-probe; if [ "$CIRCLE_NODE_INDEX" = "0" ]; then GOARCH=arm make RM= prog/app/scope-app prog/probe/scope-probe; else GOOS=darwin make RM= prog/app/scope-app prog/probe/scope-probe; fi:
parallel: true
- cd $SRCDIR; rm -f app/scope-app prog/probe/scope-probe; make RM=:
- cd $SRCDIR; rm -f prog/app/scope-app prog/probe/scope-probe; make RM=:
parallel: true
- cd $SRCDIR/experimental; ./build_on_circle.sh:
parallel: true

View File

@@ -14,27 +14,17 @@ import (
"github.com/gorilla/mux"
"github.com/weaveworks/weave/common"
"github.com/weaveworks/scope/app"
"github.com/weaveworks/scope/xfer"
)
var (
// Set at buildtime.
version = "dev"
// Set at runtime.
uniqueID = "0"
)
func registerStatic(router *mux.Router) {
router.Methods("GET").PathPrefix("/").Handler(http.FileServer(FS(false)))
}
// Router creates the mux for all the various app components.
func Router(c collector) *mux.Router {
func Router(c app.Collector) *mux.Router {
router := mux.NewRouter()
registerTopologyRoutes(c, router)
registerControlRoutes(router)
registerStatic(router)
app.RegisterTopologyRoutes(c, router)
app.RegisterReportPostHandler(c, router)
app.RegisterControlRoutes(router)
router.Methods("GET").PathPrefix("/").Handler(http.FileServer(FS(false)))
return router
}
@@ -48,7 +38,7 @@ func main() {
flag.Parse()
if *printVersion {
fmt.Println(version)
fmt.Println(app.Version)
return
}
@@ -60,11 +50,9 @@ func main() {
defer log.Print("app exiting")
rand.Seed(time.Now().UnixNano())
uniqueID = strconv.FormatInt(rand.Int63(), 16)
log.Printf("app starting, version %s, ID %s", version, uniqueID)
c := NewCollector(*window)
http.Handle("/", Router(c))
app.UniqueID = strconv.FormatInt(rand.Int63(), 16)
log.Printf("app starting, version %s, ID %s", app.Version, app.UniqueID)
http.Handle("/", Router(app.NewCollector(*window)))
go func() {
log.Printf("listening on %s", *listen)
log.Print(http.ListenAndServe(*listen, nil))