From fb308bdee3570fb7c4831daa3f3b3afe2e58adbc Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 30 Jul 2015 13:00:33 +0000 Subject: [PATCH 01/92] Add lint script from scope.yml --- lint | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100755 lint diff --git a/lint b/lint new file mode 100755 index 000000000..f3a2560f8 --- /dev/null +++ b/lint @@ -0,0 +1,133 @@ +#!/bin/bash +# This scipt lints go files for common errors. +# +# Its runs gofmt and go vet, and optionally golint and +# gocyclo, if they are installed. +# +# With no arguments, it lints the current files staged +# for git commit. Or you can pass it explicit filenames +# (or directories) and it will lint them. +# +# To use this script automatically, run: +# ln -s ../../bin/lint .git/hooks/pre-commit + +set -eu + +function spell_check { + filename="$1" + local lint_result=0 + + if grep -iH --color=always psueod "${filename}"; then + echo "${filename}: spelling mistake" + lint_result=1 + fi + + return $lint_result +} + +function test_mismatch { + filename="$1" + package=$(grep '^package ' $filename | awk '{print $2}') + local lint_result=0 + + if [[ $package == "main" ]]; then + continue # in package main, all bets are off + fi + + if [[ $filename == *"_internal_test.go" ]]; then + if [[ $package == *"_test" ]]; then + lint_result=1 + echo "${filename}: should not be part of a _test package" + fi + else + if [[ ! $package == *"_test" ]]; then + lint_result=1 + echo "${filename}: should be part of a _test package" + fi + fi + + return $lint_result +} + +function lint_go { + filename="$1" + local lint_result=0 + + if [ -n "$(gofmt -s -l "${filename}")" ]; then + lint_result=1 + echo "${filename}: run gofmt -s -w ${filename}!" + fi + + go tool vet "${filename}" || lint_result=$? + + # golint is completely optional. If you don't like it + # don't have it installed. + if type golint >/dev/null 2>&1; then + # golint doesn't set an exit code it seems + lintoutput=$(golint "${filename}") + if [ "$lintoutput" != "" ]; then + lint_result=1 + echo "$lintoutput" + fi + fi + + # gocyclo is completely optional. If you don't like it + # don't have it installed. Also never blocks a commit, + # it just warns. + if type gocyclo >/dev/null 2>&1; then + gocyclo -over 25 "${filename}" | while read line; do + echo "${filename}": higher than 25 cyclomatic complexity - "${line}" + done + fi + + return $lint_result +} + +function lint { + filename="$1" + ext="${filename##*\.}" + local lint_result=0 + + # Don't lint deleted files + if [ ! -f "$filename" ]; then + return + fi + + # Don't lint this script or static.go + case "${filename}" in + ./bin/lint) return;; + ./app/static.go) return;; + ./coverage.html) return;; + esac + + case "$ext" in + go) lint_go "${filename}" || lint_result=1 + ;; + esac + + if [[ $filename == *"_test.go" ]]; then + test_mismatch "${filename}" || lint_result=1 + fi + + spell_check "${filename}" || lint_result=1 + + return $lint_result +} + +function lint_files { + local lint_result=0 + while read filename; do + lint "${filename}" || lint_result=1 + done + exit $lint_result +} + +function list_files { + if [ $# -gt 0 ]; then + find "$@" -type f | grep -vE '^\./\.git/' + else + git diff --cached --name-only + fi +} + +list_files "$@" | lint_files From 696d3fae4b9cc39ea0914669eedd6e7c3021844d Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 30 Jul 2015 13:10:48 +0000 Subject: [PATCH 02/92] Add coverage merge tool from weave.git --- .gitignore | 1 + cover/cover.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 .gitignore create mode 100644 cover/cover.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..5cd748c0c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +cover/cover diff --git a/cover/cover.go b/cover/cover.go new file mode 100644 index 000000000..4c5fcfd7d --- /dev/null +++ b/cover/cover.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "os" + "sort" + + "golang.org/x/tools/cover" +) + +func merge(p1, p2 *cover.Profile) *cover.Profile { + output := cover.Profile{ + FileName: p1.FileName, + Mode: p1.Mode, + } + + i, j := 0, 0 + for i < len(p1.Blocks) && j < len(p2.Blocks) { + bi, bj := p1.Blocks[i], p2.Blocks[j] + if bi.StartLine == bj.StartLine && bi.StartCol == bj.StartCol { + + if bi.EndLine != bj.EndLine || + bi.EndCol != bj.EndCol || + bi.NumStmt != bj.NumStmt { + panic("Not run on same source!") + } + + output.Blocks = append(output.Blocks, cover.ProfileBlock{ + StartLine: bi.StartLine, + StartCol: bi.StartCol, + EndLine: bi.EndLine, + EndCol: bi.EndCol, + NumStmt: bi.NumStmt, + Count: bi.Count + bj.Count, + }) + i++ + j++ + } else if bi.StartLine < bj.StartLine || bi.StartLine == bj.StartLine && bi.StartCol < bj.StartCol { + output.Blocks = append(output.Blocks, bi) + i++ + } else { + output.Blocks = append(output.Blocks, bj) + j++ + } + } + + for ; i < len(p1.Blocks); i++ { + output.Blocks = append(output.Blocks, p1.Blocks[i]) + } + + for ; j < len(p2.Blocks); j++ { + output.Blocks = append(output.Blocks, p2.Blocks[j]) + } + + return &output +} + +func print(profiles []*cover.Profile) { + fmt.Println("mode: atomic") + for _, profile := range profiles { + for _, block := range profile.Blocks { + fmt.Printf("%s:%d.%d,%d.%d %d %d\n", profile.FileName, block.StartLine, block.StartCol, + block.EndLine, block.EndCol, block.NumStmt, block.Count) + } + } +} + +// Copied from https://github.com/golang/tools/blob/master/cover/profile.go +type byFileName []*cover.Profile + +func (p byFileName) Len() int { return len(p) } +func (p byFileName) Less(i, j int) bool { return p[i].FileName < p[j].FileName } +func (p byFileName) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +func main() { + outputProfiles := map[string]*cover.Profile{} + for _, input := range os.Args[1:] { + inputProfiles, err := cover.ParseProfiles(input) + if err != nil { + panic(fmt.Sprintf("Error parsing %s: %v", input, err)) + } + for _, ip := range inputProfiles { + op := outputProfiles[ip.FileName] + if op == nil { + outputProfiles[ip.FileName] = ip + } else { + outputProfiles[ip.FileName] = merge(op, ip) + } + } + } + profiles := make([]*cover.Profile, 0, len(outputProfiles)) + for _, profile := range outputProfiles { + profiles = append(profiles, profile) + } + sort.Sort(byFileName(profiles)) + print(profiles) +} From 4f611e832c838b394b3d3a8b2fb6caee4d9a5b03 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 30 Jul 2015 13:12:48 +0000 Subject: [PATCH 03/92] Add test script from weave --- test | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100755 test diff --git a/test b/test new file mode 100755 index 000000000..c5810607e --- /dev/null +++ b/test @@ -0,0 +1,54 @@ +#!/bin/bash + +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SLOW=${SLOW-} +GO_TEST_ARGS="-tags netgo -cpu 4 -timeout 8m" +if [ -n "$SLOW" -o "$1" = "-slow" ]; then + GO_TEST_ARGS="$GO_TEST_ARGS -race -covermode=atomic" + + if [ -n "$COVERDIR" ] ; then + coverdir="$COVERDIR" + else + coverdir=$(mktemp -d coverage.XXXXXXXXXX) + fi + + mkdir -p $coverdir +fi + +fail=0 + +TESTDIRS=$(find . -type f -name '*_test.go' | xargs -n1 dirname | grep -v prog | sort -u) + +for dir in $TESTDIRS; do + go get -t -tags netgo $dir + + GO_TEST_ARGS_RUN="$GO_TEST_ARGS" + if [ -n "$SLOW" ]; then + COVERPKGS=$((go list $dir; go list -f '{{join .Deps "\n"}}' $dir | grep "weaveworks") | paste -s -d,) + output=$(mktemp $coverdir/unit.XXXXXXXXXX) + GO_TEST_ARGS_RUN="$GO_TEST_ARGS -coverprofile=$output -coverpkg=$COVERPKGS" + fi + + START=$(date +%s) + if ! go test $GO_TEST_ARGS_RUN $dir ; then + fail=1 + fi + RUNTIME=$(( $(date +%s) - $START )) + + # Report test runtime when running on circle, to help scheduler + if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" ]; then + "$DIR/sched" time $dir $RUNTIME + fi +done + +if [ -n "$SLOW" -a -z "$COVERDIR" ] ; then + go get github.com/weaveworks/tools/cover + cover $coverdir/* >profile.cov + rm -rf $coverdir + go tool cover -html=profile.cov -o=coverage.html + go tool cover -func=profile.cov | tail -n1 +fi + +exit $fail From 76de35ea971c166317909fd636e0b733da59ef79 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 30 Jul 2015 13:16:49 +0000 Subject: [PATCH 04/92] Test script checks for scheduler. --- test | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test b/test index c5810607e..0730399df 100755 --- a/test +++ b/test @@ -21,6 +21,12 @@ fail=0 TESTDIRS=$(find . -type f -name '*_test.go' | xargs -n1 dirname | grep -v prog | sort -u) +# If running on circle, use the scheduler to work out what tests to run on what shard +if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" -a -x "$DIR/sched" ]; then + TESTDIRS=$(echo $TESTDIRS | "$DIR/sched" sched units-$CIRCLE_BUILD_NUM $CIRCLE_NODE_TOTAL $CIRCLE_NODE_INDEX) + echo $TESTDIRS +fi + for dir in $TESTDIRS; do go get -t -tags netgo $dir @@ -38,7 +44,7 @@ for dir in $TESTDIRS; do RUNTIME=$(( $(date +%s) - $START )) # Report test runtime when running on circle, to help scheduler - if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" ]; then + if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" -a -x "$DIR/sched" ]; then "$DIR/sched" time $dir $RUNTIME fi done From 704f5bd4e4db94b1852aebdc8544661388788961 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 30 Jul 2015 13:21:36 +0000 Subject: [PATCH 05/92] Automatically run tests with coverage etc on Circle. --- test | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test b/test index 0730399df..83dcb4efd 100755 --- a/test +++ b/test @@ -3,9 +3,13 @@ set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SLOW=${SLOW-} GO_TEST_ARGS="-tags netgo -cpu 4 -timeout 8m" -if [ -n "$SLOW" -o "$1" = "-slow" ]; then + +if [ -n "$SLOW" -o "$1" = "-slow" -o -n "$CIRCLECI" ]; then + SLOW=true +fi + +if [ -n "$SLOW" ]; then GO_TEST_ARGS="$GO_TEST_ARGS -race -covermode=atomic" if [ -n "$COVERDIR" ] ; then From 85b6d16ee7aefee194ada81b0af65749227a746c Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 30 Jul 2015 13:58:29 +0000 Subject: [PATCH 06/92] Add rebuild-image from scope.git --- rebuild-image | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100755 rebuild-image diff --git a/rebuild-image b/rebuild-image new file mode 100755 index 000000000..1842e572f --- /dev/null +++ b/rebuild-image @@ -0,0 +1,47 @@ +#!/bin/bash +# Rebuild a cached docker image if the input files have changed. +# Usage: ./rebuild-image + +set -eux + +IMAGENAME=$1 # no dashes in the name please! +IMAGEDIR=$2 +shift 2 + +INPUTFILES=$@ +CACHEDIR=$HOME/docker/ + +# Rebuild the image +rebuild() { + mkdir -p $CACHEDIR + rm $CACHEDIR/$IMAGENAME* || true + docker build -t $IMAGENAME $IMAGEDIR + docker save $IMAGENAME:latest > $CACHEDIR/image-$CIRCLE_SHA1 +} + +# Get the revision the cached image was build at +cached_image_rev() { + find $CACHEDIR -name '$IMAGENAME-*' -type f | sed 's/[^\-]*\-//' +} + +# Have there been any revision beween $1 and $2 +has_changes() { + local rev1=$1 + local rev2=$2 + local changes=$(git log --oneline $rev1..$rev2 -- $INPUTFILES | wc -l) + [ "$changes" -gt 0 ] +} + +cached_revision=$(cached_image_rev) +if [ -z "$cached_revision" ]; then + rebuild + exit 0 +fi + +if has_changes $cached_revision $CIRCLE_SHA1 ; then + rebuild + exit 0 +fi + +# we didn't rebuild; import cached version +docker load -i $CACHEDIR/$IMAGENAME-* From 1a03e4481792d0d39122a92ef7a3ad6eb8be741b Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 5 Aug 2015 13:16:25 +0000 Subject: [PATCH 07/92] Add some debugging to rebuild_image script --- rebuild-image | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rebuild-image b/rebuild-image index 1842e572f..0e6289803 100755 --- a/rebuild-image +++ b/rebuild-image @@ -34,14 +34,18 @@ has_changes() { cached_revision=$(cached_image_rev) if [ -z "$cached_revision" ]; then + echo ">>> No cached image found; rebuilding" rebuild exit 0 fi +echo ">>> Found cached image rev $cached_revision" if has_changes $cached_revision $CIRCLE_SHA1 ; then + echo ">>> Found changes, rebuilding" rebuild exit 0 fi # we didn't rebuild; import cached version +echo ">>> No changes found, importing cached image" docker load -i $CACHEDIR/$IMAGENAME-* From 756fc4198669f039c5d6836ed724e30097eaa986 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 5 Aug 2015 13:30:50 +0000 Subject: [PATCH 08/92] rebuild-image: save image to correctly named file. --- rebuild-image | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rebuild-image b/rebuild-image index 0e6289803..eef99d514 100755 --- a/rebuild-image +++ b/rebuild-image @@ -16,12 +16,12 @@ rebuild() { mkdir -p $CACHEDIR rm $CACHEDIR/$IMAGENAME* || true docker build -t $IMAGENAME $IMAGEDIR - docker save $IMAGENAME:latest > $CACHEDIR/image-$CIRCLE_SHA1 + docker save $IMAGENAME:latest > $CACHEDIR/$IMAGENAME-$CIRCLE_SHA1 } # Get the revision the cached image was build at cached_image_rev() { - find $CACHEDIR -name '$IMAGENAME-*' -type f | sed 's/[^\-]*\-//' + find $CACHEDIR -name "$IMAGENAME-*" -type f | sed 's/[^\-]*\-//' } # Have there been any revision beween $1 and $2 From 6aefe40b6dc86e76d77d86f5911a55e50c033254 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 12 Aug 2015 10:21:46 +0000 Subject: [PATCH 09/92] Add socks proxy. --- .gitignore | 2 ++ socks/Dockerfile | 7 ++++ socks/Makefile | 29 ++++++++++++++++ socks/connect.sh | 23 +++++++++++++ socks/main.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 148 insertions(+) create mode 100644 socks/Dockerfile create mode 100644 socks/Makefile create mode 100755 socks/connect.sh create mode 100644 socks/main.go diff --git a/.gitignore b/.gitignore index 5cd748c0c..78fe8261e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ cover/cover +socks/proxy +socks/image.tar diff --git a/socks/Dockerfile b/socks/Dockerfile new file mode 100644 index 000000000..867cd6bc5 --- /dev/null +++ b/socks/Dockerfile @@ -0,0 +1,7 @@ +FROM gliderlabs/alpine +MAINTAINER Weaveworks Inc +WORKDIR / +COPY proxy / +EXPOSE 8000 +EXPOSE 8080 +ENTRYPOINT ["/proxy"] diff --git a/socks/Makefile b/socks/Makefile new file mode 100644 index 000000000..2daeda643 --- /dev/null +++ b/socks/Makefile @@ -0,0 +1,29 @@ +.PHONY: all clean + +IMAGE_TAR=image.tar +IMAGE_NAME=weaveworks/socksproxy +PROXY_EXE=proxy +NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \ + rm $@; \ + echo "\nYour go standard library was built without the 'netgo' build tag."; \ + echo "To fix that, run"; \ + echo " sudo go clean -i net"; \ + echo " sudo go install -tags netgo std"; \ + false; \ +} + +all: $(IMAGE_TAR) + +$(IMAGE_TAR): Dockerfile $(PROXY_EXE) + docker build -t $(IMAGE_NAME) . + docker save $(IMAGE_NAME):latest > $@ + +$(PROXY_EXE): *.go + go get -tags netgo ./$(@D) + go build -ldflags "-extldflags \"-static\" -linkmode=external" -tags netgo -o $@ ./$(@D) + $(NETGO_CHECK) + +clean: + -docker rmi $(IMAGE_NAME) + rm -rf $(PROXY_EXE) $(IMAGE_TAR) + go clean ./... diff --git a/socks/connect.sh b/socks/connect.sh new file mode 100755 index 000000000..0d5ef84da --- /dev/null +++ b/socks/connect.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -eu + +if [ $# -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +HOST=$1 + +echo "Starting proxy container..." +PROXY_CONTAINER=$(ssh $HOST weave run -d weaveworks/socksproxy) + +function finish { + echo "Removing proxy container.." + ssh $HOST docker rm -f $PROXY_CONTAINER +} +trap finish EXIT + +PROXY_IP=$(ssh $HOST -- "docker inspect --format='{{.NetworkSettings.IPAddress}}' $PROXY_CONTAINER") +echo 'Please configure your browser for proxy http://localhost:8080/proxy.pac' +ssh -L8000:$PROXY_IP:8000 -L8080:$PROXY_IP:8080 $HOST docker attach $PROXY_CONTAINER diff --git a/socks/main.go b/socks/main.go new file mode 100644 index 000000000..c86ded74b --- /dev/null +++ b/socks/main.go @@ -0,0 +1,87 @@ +package main + +import ( + "fmt" + "net" + "net/http" + "strings" + "os" + "text/template" + + socks5 "github.com/armon/go-socks5" + "github.com/weaveworks/weave/common" + "github.com/docker/docker/pkg/mflag" +) + +const ( + pacfile = ` +function FindProxyForURL(url, host) { + if(shExpMatch(host, "*.weave.local")) { + return "SOCKS5 localhost:8000"; + } + {{range $key, $value := .}} + if (host == "{{$key}}") { + return "SOCKS5 localhost:8000"; + } + {{end}} + return "DIRECT"; +} +` +) + +func main() { + var as []string + common.ListVar(&as, []string{"a", "-alias"}, []string{}, "Specify hostname aliases in the form alias:hostname. Can be repeated.") + mflag.Parse() + + var aliases = map[string]string{} + for _, a := range as { + parts := strings.SplitN(a, ":", 2) + if len(parts) != 2 { + fmt.Printf("'%s' is not a valid alias.\n", a) + mflag.Usage() + os.Exit(1) + } + aliases[parts[0]] = parts[1] + } + + go socksProxy(aliases) + + t := template.Must(template.New("pacfile").Parse(pacfile)) + http.HandleFunc("/proxy.pac", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/x-ns-proxy-autoconfig") + t.Execute(w, aliases) + }) + + if err := http.ListenAndServe(":8080", nil); err != nil { + panic(err) + } +} + +type aliasingResolver struct { + aliases map[string]string + socks5.NameResolver +} + +func (r aliasingResolver) Resolve(name string) (net.IP, error) { + if alias, ok := r.aliases[name]; ok { + return r.NameResolver.Resolve(alias) + } + return r.NameResolver.Resolve(name) +} + +func socksProxy(aliases map[string]string) { + conf := &socks5.Config{ + Resolver: aliasingResolver{ + aliases: aliases, + NameResolver: socks5.DNSResolver{}, + }, + } + server, err := socks5.New(conf) + if err != nil { + panic(err) + } + if err := server.ListenAndServe("tcp", ":8000"); err != nil { + panic(err) + } +} From 7325e4504bef6aee3a9f2750fce8bf8bd3612eef Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 12 Aug 2015 11:56:55 +0000 Subject: [PATCH 10/92] Update import --- socks/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/socks/main.go b/socks/main.go index c86ded74b..45e69c6d1 100644 --- a/socks/main.go +++ b/socks/main.go @@ -9,7 +9,7 @@ import ( "text/template" socks5 "github.com/armon/go-socks5" - "github.com/weaveworks/weave/common" + "github.com/weaveworks/weave/common/mflagext" "github.com/docker/docker/pkg/mflag" ) @@ -31,7 +31,7 @@ function FindProxyForURL(url, host) { func main() { var as []string - common.ListVar(&as, []string{"a", "-alias"}, []string{}, "Specify hostname aliases in the form alias:hostname. Can be repeated.") + mflagext.ListVar(&as, []string{"a", "-alias"}, []string{}, "Specify hostname aliases in the form alias:hostname. Can be repeated.") mflag.Parse() var aliases = map[string]string{} From fab7da7a1a49bc067be42ae9317d9eff1eed4d8f Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 13 Aug 2015 11:25:07 +0000 Subject: [PATCH 11/92] Add circle.yml, lint etc --- circle.yml | 25 +++++++++++++++++++++++++ cover/Makefile | 11 +++++++++++ lint | 2 +- socks/main.go | 6 +++--- 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 circle.yml create mode 100644 cover/Makefile diff --git a/circle.yml b/circle.yml new file mode 100644 index 000000000..c1cb16237 --- /dev/null +++ b/circle.yml @@ -0,0 +1,25 @@ +machine: + services: + - docker + environment: + GOPATH: /home/ubuntu + SRCDIR: /home/ubuntu/src/github.com/weaveworks/tools + PATH: $PATH:$HOME/bin + +dependencies: + post: + - go clean -i net + - go install -tags netgo std + - mkdir -p $(dirname $SRCDIR) + - cp -r $(pwd)/ $SRCDIR + - go get \ + github.com/golang/lint/golint \ + github.com/fzipp/gocyclo \ + github.com/kisielk/errcheck + +test: + override: + - cd $SRDDIR; ./lint . + - cd $SRCDIR/cover; make + - cd $SRCDIR/socks; make + diff --git a/cover/Makefile b/cover/Makefile new file mode 100644 index 000000000..1453e63e3 --- /dev/null +++ b/cover/Makefile @@ -0,0 +1,11 @@ +.PHONY: all clean + +all: cover + +cover: *.go + go get -tags netgo ./$(@D) + go build -ldflags "-extldflags \"-static\" -linkmode=external" -tags netgo -o $@ ./$(@D) + +clean: + rm -rf cover + go clean ./... diff --git a/lint b/lint index f3a2560f8..aac233b84 100755 --- a/lint +++ b/lint @@ -95,7 +95,7 @@ function lint { # Don't lint this script or static.go case "${filename}" in - ./bin/lint) return;; + ./lint) return;; ./app/static.go) return;; ./coverage.html) return;; esac diff --git a/socks/main.go b/socks/main.go index 45e69c6d1..171b5fc13 100644 --- a/socks/main.go +++ b/socks/main.go @@ -4,13 +4,13 @@ import ( "fmt" "net" "net/http" - "strings" "os" + "strings" "text/template" socks5 "github.com/armon/go-socks5" - "github.com/weaveworks/weave/common/mflagext" "github.com/docker/docker/pkg/mflag" + "github.com/weaveworks/weave/common/mflagext" ) const ( @@ -73,7 +73,7 @@ func (r aliasingResolver) Resolve(name string) (net.IP, error) { func socksProxy(aliases map[string]string) { conf := &socks5.Config{ Resolver: aliasingResolver{ - aliases: aliases, + aliases: aliases, NameResolver: socks5.DNSResolver{}, }, } From 87b112b05c07bf05f2ef0437ffcf92a416933249 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 13 Aug 2015 11:27:09 +0000 Subject: [PATCH 12/92] Fix circle.yml --- circle.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index c1cb16237..4a52ad79f 100644 --- a/circle.yml +++ b/circle.yml @@ -12,10 +12,7 @@ dependencies: - go install -tags netgo std - mkdir -p $(dirname $SRCDIR) - cp -r $(pwd)/ $SRCDIR - - go get \ - github.com/golang/lint/golint \ - github.com/fzipp/gocyclo \ - github.com/kisielk/errcheck + - go get github.com/golang/lint/golint github.com/fzipp/gocyclo github.com/kisielk/errcheck test: override: From f2d9226c59245052c974babf3ba9bd4512cd5813 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 13 Aug 2015 11:28:44 +0000 Subject: [PATCH 13/92] Fix circle.yml --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 4a52ad79f..b79465799 100644 --- a/circle.yml +++ b/circle.yml @@ -16,7 +16,7 @@ dependencies: test: override: - - cd $SRDDIR; ./lint . + - cd $SRCDIR; ./lint . - cd $SRCDIR/cover; make - cd $SRCDIR/socks; make From 9b7bcf679d414f990c3aeca502a9a4ea9e8e37d5 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 13 Aug 2015 12:07:00 +0000 Subject: [PATCH 14/92] Don't test experimental stuff. --- test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test b/test index 83dcb4efd..49e2f9691 100755 --- a/test +++ b/test @@ -23,7 +23,7 @@ fi fail=0 -TESTDIRS=$(find . -type f -name '*_test.go' | xargs -n1 dirname | grep -v prog | sort -u) +TESTDIRS=$(find . -type f -name '*_test.go' | xargs -n1 dirname | grep -v prog | grep -v experimental | sort -u) # If running on circle, use the scheduler to work out what tests to run on what shard if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" -a -x "$DIR/sched" ]; then From d9522d85eff8b8817afbace46a733e01133652ae Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 13 Aug 2015 12:21:48 +0000 Subject: [PATCH 15/92] Replace / and - in image names with . --- rebuild-image | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebuild-image b/rebuild-image index eef99d514..c1d5c3444 100755 --- a/rebuild-image +++ b/rebuild-image @@ -4,7 +4,7 @@ set -eux -IMAGENAME=$1 # no dashes in the name please! +IMAGENAME=$(echo $1 | sed "s/[\/\-]/\./g") IMAGEDIR=$2 shift 2 From 6b74be05cd3b7b85678669db5e92ed886572aec8 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 13 Aug 2015 12:28:34 +0000 Subject: [PATCH 16/92] Fix rebuild-image --- rebuild-image | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rebuild-image b/rebuild-image index c1d5c3444..3fcc9a490 100755 --- a/rebuild-image +++ b/rebuild-image @@ -4,7 +4,8 @@ set -eux -IMAGENAME=$(echo $1 | sed "s/[\/\-]/\./g") +IMAGENAME=$1 +SAVEDNAME=$(echo $IMAGENAME | sed "s/[\/\-]/\./g") IMAGEDIR=$2 shift 2 @@ -14,14 +15,14 @@ CACHEDIR=$HOME/docker/ # Rebuild the image rebuild() { mkdir -p $CACHEDIR - rm $CACHEDIR/$IMAGENAME* || true + rm $CACHEDIR/$SAVEDNAME* || true docker build -t $IMAGENAME $IMAGEDIR - docker save $IMAGENAME:latest > $CACHEDIR/$IMAGENAME-$CIRCLE_SHA1 + docker save $IMAGENAME:latest > $CACHEDIR/$SAVEDNAME-$CIRCLE_SHA1 } # Get the revision the cached image was build at cached_image_rev() { - find $CACHEDIR -name "$IMAGENAME-*" -type f | sed 's/[^\-]*\-//' + find $CACHEDIR -name "$SAVEDNAME-*" -type f | sed 's/[^\-]*\-//' } # Have there been any revision beween $1 and $2 @@ -48,4 +49,4 @@ fi # we didn't rebuild; import cached version echo ">>> No changes found, importing cached image" -docker load -i $CACHEDIR/$IMAGENAME-* +docker load -i $CACHEDIR/$SAVEDNAME-$cached_revision From d6044b2c13b73613d72a3d4f769aa9d0be33336b Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 17 Aug 2015 15:21:33 +0000 Subject: [PATCH 17/92] Rebuild images every 24hrs --- rebuild-image | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rebuild-image b/rebuild-image index 3fcc9a490..0e582b0ba 100755 --- a/rebuild-image +++ b/rebuild-image @@ -33,6 +33,11 @@ has_changes() { [ "$changes" -gt 0 ] } +commit_timestamp() { + local rev=$1 + git show -s --format=%ct $rev +} + cached_revision=$(cached_image_rev) if [ -z "$cached_revision" ]; then echo ">>> No cached image found; rebuilding" @@ -47,6 +52,12 @@ if has_changes $cached_revision $CIRCLE_SHA1 ; then exit 0 fi +if [ "$(commit_timestamp $cached_revision)" -lt "$(( $(date +%s) - 86400 ))" ]; then + echo ">>> Image is more the 24hrs old; rebuilding" + rebuild + exit 0 +fi + # we didn't rebuild; import cached version echo ">>> No changes found, importing cached image" docker load -i $CACHEDIR/$SAVEDNAME-$cached_revision From a61c66f5c6d8ac353b35a768576d2867349151e2 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 17 Aug 2015 16:41:13 +0000 Subject: [PATCH 18/92] Add README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..bd8d4302e --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Weaveworks Tools + +Included in this repo are tools shared by weave.git and scope.git. They include + +- cover: a tool which merges overlapping coverage reports generated by go test +- lint: a script to lint Go project; runs various tools like golint, go vet, errcheck etc +- rebuild-image: a script to rebuild docker images when their input files change; useful + when you using docker images to build your software, but you don't want to build the + image every time. +- socks: a simple, dockerised SOCKS proxy for getting your laptop onto the Weave network +- test: a script to run all go unit tests in subdirectories, gather the coverage results, + and merge them into a single report. From 2d088c15a34c83e4d43e5b4d7f01dc236ab25672 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 17 Aug 2015 16:41:43 +0000 Subject: [PATCH 19/92] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bd8d4302e..ad801aa00 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ Included in this repo are tools shared by weave.git and scope.git. They include -- cover: a tool which merges overlapping coverage reports generated by go test -- lint: a script to lint Go project; runs various tools like golint, go vet, errcheck etc -- rebuild-image: a script to rebuild docker images when their input files change; useful +- ```cover```: a tool which merges overlapping coverage reports generated by go test +- ```lint```: a script to lint Go project; runs various tools like golint, go vet, errcheck etc +- ```rebuild-image```: a script to rebuild docker images when their input files change; useful when you using docker images to build your software, but you don't want to build the image every time. -- socks: a simple, dockerised SOCKS proxy for getting your laptop onto the Weave network -- test: a script to run all go unit tests in subdirectories, gather the coverage results, +- ```socks```: a simple, dockerised SOCKS proxy for getting your laptop onto the Weave network +- ```test```: a script to run all go unit tests in subdirectories, gather the coverage results, and merge them into a single report. From 435a2ceb5dfac6ef48eb1e91709ed08750904c47 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Fri, 25 Sep 2015 07:29:15 +0000 Subject: [PATCH 20/92] Add support for weave in lint script. --- lint | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lint b/lint index aac233b84..080713486 100755 --- a/lint +++ b/lint @@ -11,7 +11,25 @@ # To use this script automatically, run: # ln -s ../../bin/lint .git/hooks/pre-commit -set -eu +set -e + +IGNORE_LINT_COMMENT= +IGNORE_TEST_PACKAGES= +while true; do + case "$1" in + -nocomment) + IGNORE_LINT_COMMENT=1 + shift 1 + ;; + -notestpackage) + IGNORE_TEST_PACKAGES=1 + shift 1 + ;; + *) + break + esac +done + function spell_check { filename="$1" @@ -64,7 +82,11 @@ function lint_go { # don't have it installed. if type golint >/dev/null 2>&1; then # golint doesn't set an exit code it seems - lintoutput=$(golint "${filename}") + if [ "$IGNORE_LINT_COMMENT" = "1" ]; then + lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports') + else + lintoutput=$(golint "${filename}") + fi if [ "$lintoutput" != "" ]; then lint_result=1 echo "$lintoutput" @@ -105,8 +127,10 @@ function lint { ;; esac - if [[ $filename == *"_test.go" ]]; then - test_mismatch "${filename}" || lint_result=1 + if [ -z "$IGNORE_TEST_PACKAGES" ]; then + if [[ "$filename" == *"_test.go" ]]; then + test_mismatch "${filename}" || lint_result=1 + fi fi spell_check "${filename}" || lint_result=1 From 71fcad01fb4bde0000fdb22bbcb1cdfa019137b8 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Fri, 25 Sep 2015 09:37:08 +0000 Subject: [PATCH 21/92] Review feedback --- lint | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lint b/lint index 080713486..600a94f2c 100755 --- a/lint +++ b/lint @@ -82,12 +82,12 @@ function lint_go { # don't have it installed. if type golint >/dev/null 2>&1; then # golint doesn't set an exit code it seems - if [ "$IGNORE_LINT_COMMENT" = "1" ]; then - lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports') - else + if [ -z "$IGNORE_LINT_COMMENT" ]; then lintoutput=$(golint "${filename}") + else + lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports') fi - if [ "$lintoutput" != "" ]; then + if [ -n "$lintoutput" ]; then lint_result=1 echo "$lintoutput" fi From caecc98fa89c7115dd78b20912842ee592d7439d Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 28 Sep 2015 08:03:27 +0000 Subject: [PATCH 22/92] Make lint ignore certain files regardless of location: --- lint | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lint b/lint index 600a94f2c..8b24d5ba2 100755 --- a/lint +++ b/lint @@ -116,10 +116,10 @@ function lint { fi # Don't lint this script or static.go - case "${filename}" in - ./lint) return;; - ./app/static.go) return;; - ./coverage.html) return;; + case "$(basename "${filename}")" in + lint) return;; + static.go) return;; + coverage.html) return;; esac case "$ext" in From a286ddade02b98e429a8f65d52a825405a9efcf3 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 12 Oct 2015 17:35:00 +0100 Subject: [PATCH 23/92] only produce coverage for the module in the current dir and its sub-modules ...rather than all modules containing `weaveworks`. --- test | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test b/test index 49e2f9691..10f59aa64 100755 --- a/test +++ b/test @@ -31,12 +31,14 @@ if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" -a -x "$DIR/sched" ]; then echo $TESTDIRS fi +PACKAGE_BASE=$(go list -e ./) + for dir in $TESTDIRS; do go get -t -tags netgo $dir GO_TEST_ARGS_RUN="$GO_TEST_ARGS" if [ -n "$SLOW" ]; then - COVERPKGS=$((go list $dir; go list -f '{{join .Deps "\n"}}' $dir | grep "weaveworks") | paste -s -d,) + COVERPKGS=$((go list $dir; go list -f '{{join .Deps "\n"}}' $dir | grep "^$PACKAGE_BASE/") | paste -s -d,) output=$(mktemp $coverdir/unit.XXXXXXXXXX) GO_TEST_ARGS_RUN="$GO_TEST_ARGS -coverprofile=$output -coverpkg=$COVERPKGS" fi From 5e42a1d0aaa4b7b0340d172b26bb2adf751d2013 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Thu, 15 Oct 2015 10:31:17 +0100 Subject: [PATCH 24/92] added README for the socks proxy Based on unpublished blogpost. --- socks/README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 socks/README.md diff --git a/socks/README.md b/socks/README.md new file mode 100644 index 000000000..596c2b32e --- /dev/null +++ b/socks/README.md @@ -0,0 +1,53 @@ +# SOCKS Proxy + +The challenge: you’ve built and deployed your microservices based +application on a Weave network, running on a set of VMs on EC2. Many +of the services’ public API are reachable from the internet via an +Nginx-based reverse proxy, but some of the services also expose +private monitoring and manage endpoints via embedded HTTP servers. +How do I securely get access to these from my laptop, without exposing +them to the world? + +One method we’ve started using at Weaveworks is a 90’s technology - a +SOCKS proxy combined with a PAC script. It’s relatively +straight-forward: one ssh’s into any of the VMs participating in the +Weave network, starts the SOCKS proxy in a container on Weave the +network, and SSH port forwards a few local port to the proxy. All +that’s left is for the user to configure his browser to use the proxy, +and voila, you can now access your Docker containers, via the Weave +network (and with all the magic of weavedns), from your laptop’s +browser! + +It is perhaps worth noting there is nothing Weave-specific about this +approach - this should work with any SDN or private network. + +A quick example: + +``` +vm1$ weave launch +vm1$ eval $(weave env) +vm1$ docker run -d --name nginx nginx +``` + +And on your laptop + +``` +laptop$ git clone https://github.com/weaveworks/tools +laptop$ cd tools/socks +laptop$ ./connect.sh vm1 +Starting proxy container... +Please configure your browser for proxy +http://localhost:8080/proxy.pac +``` + +To configure your Mac to use the proxy: + +1. Open System Preferences +2. Select Network +3. Click the 'Advanced' button +4. Select the Proxies tab +5. Click the 'Automatic Proxy Configuration' check box +6. Enter 'http://localhost:8080/proxy.pac' in the URL box +7. Remove `*.local` from the 'Bypass proxy settings for these Hosts & Domains' + +Now point your browser at http://nginx.weave.local/ From 8262e7e18e01baa6a1ada77b1ecf2cfc5e8fd0b0 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 22 Oct 2015 18:02:37 +0000 Subject: [PATCH 25/92] Don't lint vendor/ --- lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint b/lint index 8b24d5ba2..7d8ef0a04 100755 --- a/lint +++ b/lint @@ -148,7 +148,7 @@ function lint_files { function list_files { if [ $# -gt 0 ]; then - find "$@" -type f | grep -vE '^\./\.git/' + find "$@" -type f | grep -vE '^\./\.git/' | grep -vE '^\./\vendor/' else git diff --cached --name-only fi From 785f9ebe7dc639fbfd7393c9cef1a9c4a1aac216 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Fri, 23 Oct 2015 08:54:11 +0000 Subject: [PATCH 26/92] Don't run tests in ./vendor --- lint | 2 +- test | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lint b/lint index 7d8ef0a04..818c05a25 100755 --- a/lint +++ b/lint @@ -148,7 +148,7 @@ function lint_files { function list_files { if [ $# -gt 0 ]; then - find "$@" -type f | grep -vE '^\./\.git/' | grep -vE '^\./\vendor/' + find "$@" -type f | grep -vE '^\./(\.git|vendor)/' else git diff --cached --name-only fi diff --git a/test b/test index 10f59aa64..017fdc80f 100755 --- a/test +++ b/test @@ -23,7 +23,7 @@ fi fail=0 -TESTDIRS=$(find . -type f -name '*_test.go' | xargs -n1 dirname | grep -v prog | grep -v experimental | sort -u) +TESTDIRS=$(find . -type f -name '*_test.go' | xargs -n1 dirname | grep -vE '^\./(\.git|vendor|prog|experimental)/' | sort -u) # If running on circle, use the scheduler to work out what tests to run on what shard if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" -a -x "$DIR/sched" ]; then @@ -38,7 +38,7 @@ for dir in $TESTDIRS; do GO_TEST_ARGS_RUN="$GO_TEST_ARGS" if [ -n "$SLOW" ]; then - COVERPKGS=$((go list $dir; go list -f '{{join .Deps "\n"}}' $dir | grep "^$PACKAGE_BASE/") | paste -s -d,) + COVERPKGS=$( (go list $dir; go list -f '{{join .Deps "\n"}}' $dir | grep "^$PACKAGE_BASE/") | paste -s -d,) output=$(mktemp $coverdir/unit.XXXXXXXXXX) GO_TEST_ARGS_RUN="$GO_TEST_ARGS -coverprofile=$output -coverpkg=$COVERPKGS" fi From f53a68b1b7ac6a726b43d210d600dab0c4c7e76e Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Fri, 23 Oct 2015 09:29:33 +0000 Subject: [PATCH 27/92] Copy test runner from weave repo, add some more details to README. --- .gitignore | 1 + README.md | 42 ++++++-- circle.yml | 1 + runner/Makefile | 11 ++ runner/runner.go | 273 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 319 insertions(+), 9 deletions(-) create mode 100644 runner/Makefile create mode 100644 runner/runner.go diff --git a/.gitignore b/.gitignore index 78fe8261e..f5c83426c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ cover/cover socks/proxy socks/image.tar +runner/runner diff --git a/README.md b/README.md index ad801aa00..154b8e75b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,36 @@ -# Weaveworks Tools +# Weaveworks Build Tools Included in this repo are tools shared by weave.git and scope.git. They include -- ```cover```: a tool which merges overlapping coverage reports generated by go test -- ```lint```: a script to lint Go project; runs various tools like golint, go vet, errcheck etc -- ```rebuild-image```: a script to rebuild docker images when their input files change; useful - when you using docker images to build your software, but you don't want to build the - image every time. -- ```socks```: a simple, dockerised SOCKS proxy for getting your laptop onto the Weave network -- ```test```: a script to run all go unit tests in subdirectories, gather the coverage results, - and merge them into a single report. +- ```cover```: a tool which merges overlapping coverage reports generated by go + test +- ```lint```: a script to lint Go project; runs various tools like golint, go + vet, errcheck etc +- ```rebuild-image```: a script to rebuild docker images when their input files + change; useful when you using docker images to build your software, but you + don't want to build the image every time. +- ```socks```: a simple, dockerised SOCKS proxy for getting your laptop onto + the Weave network +- ```test```: a script to run all go unit tests in subdirectories, gather the + coverage results, and merge them into a single report. +- ```runner```: a tools for running tests in parallel; given each test is + suffixes with the number of hosts it requires, and the host availible are + contained in the environment variable HOSTS, the tool will run tests in + parallel, on different hosts. + +## Using build-tools.git + +To allow you to tie your code to a specific version of build-tools.git, such +that future changes don't break you, we recommendation that you [`git subtree`]() +this repository into your own repository: + +[`git subtree`]: http://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/ + +``` +git subtree add --prefix tools https://github.com/weaveworks/build-tools.git master --squash +```` + +To update the code in build-tools.git, the process is therefore: +- PR into build-tools.git, go through normal review process etc. +- Do `git subtree pull --prefix tools https://github.com/weaveworks/build-tools.git master --squash` + in your repo, and PR that. diff --git a/circle.yml b/circle.yml index b79465799..0c1ebddb1 100644 --- a/circle.yml +++ b/circle.yml @@ -19,4 +19,5 @@ test: - cd $SRCDIR; ./lint . - cd $SRCDIR/cover; make - cd $SRCDIR/socks; make + - cd $SRCDIR/runner; make diff --git a/runner/Makefile b/runner/Makefile new file mode 100644 index 000000000..f19bcc7d8 --- /dev/null +++ b/runner/Makefile @@ -0,0 +1,11 @@ +.PHONY: all clean + +all: runner + +runner: *.go + go get -tags netgo ./$(@D) + go build -ldflags "-extldflags \"-static\" -linkmode=external" -tags netgo -o $@ ./$(@D) + +clean: + rm -rf runner + go clean ./... diff --git a/runner/runner.go b/runner/runner.go new file mode 100644 index 000000000..89b8c6ad1 --- /dev/null +++ b/runner/runner.go @@ -0,0 +1,273 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "os" + "os/exec" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/docker/docker/pkg/mflag" + "github.com/mgutz/ansi" +) + +const ( + schedulerHost = "positive-cocoa-90213.appspot.com" + jsonContentType = "application/json" +) + +var ( + start = ansi.ColorCode("black+ub") + fail = ansi.ColorCode("red+b") + succ = ansi.ColorCode("green+b") + reset = ansi.ColorCode("reset") + + useScheduler = false + runParallel = false + verbose = false + + consoleLock = sync.Mutex{} +) + +type test struct { + name string + hosts int +} + +type schedule struct { + Tests []string `json:"tests"` +} + +type result struct { + test + errored bool + hosts []string +} + +type tests []test + +func (ts tests) Len() int { return len(ts) } +func (ts tests) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] } +func (ts tests) Less(i, j int) bool { + if ts[i].hosts != ts[j].hosts { + return ts[i].hosts < ts[j].hosts + } + return ts[i].name < ts[j].name +} + +func (ts *tests) pick(availible int) (test, bool) { + // pick the first test that fits in the availible hosts + for i, test := range *ts { + if test.hosts <= availible { + *ts = append((*ts)[:i], (*ts)[i+1:]...) + return test, true + } + } + + return test{}, false +} + +func (t test) run(hosts []string) bool { + consoleLock.Lock() + fmt.Printf("%s>>> Running %s on %s%s\n", start, t.name, hosts, reset) + consoleLock.Unlock() + + var out bytes.Buffer + + cmd := exec.Command(t.name) + cmd.Env = os.Environ() + cmd.Stdout = &out + cmd.Stderr = &out + + // replace HOSTS in env + for i, env := range cmd.Env { + if strings.HasPrefix(env, "HOSTS") { + cmd.Env[i] = fmt.Sprintf("HOSTS=%s", strings.Join(hosts, " ")) + break + } + } + + start := time.Now() + err := cmd.Run() + duration := float64(time.Now().Sub(start)) / float64(time.Second) + + consoleLock.Lock() + if err != nil { + fmt.Printf("%s>>> Test %s finished after %0.1f secs with error: %v%s\n", fail, t.name, duration, err, reset) + } else { + fmt.Printf("%s>>> Test %s finished with success after %0.1f secs%s\n", succ, t.name, duration, reset) + } + if err != nil || verbose { + fmt.Print(out.String()) + fmt.Println() + } + consoleLock.Unlock() + + if err != nil && useScheduler { + updateScheduler(t.name, duration) + } + + return err != nil +} + +func updateScheduler(test string, duration float64) { + req := &http.Request{ + Method: "POST", + Host: schedulerHost, + URL: &url.URL{ + Opaque: fmt.Sprintf("/record/%s/%0.2f", url.QueryEscape(test), duration), + Scheme: "http", + Host: schedulerHost, + }, + Close: true, + } + if resp, err := http.DefaultClient.Do(req); err != nil { + fmt.Printf("Error updating scheduler: %v\n", err) + } else { + resp.Body.Close() + } +} + +func getSchedule(tests []string) ([]string, error) { + var ( + testRun = "integration-" + os.Getenv("CIRCLE_BUILD_NUM") + shardCount = os.Getenv("CIRCLE_NODE_TOTAL") + shardID = os.Getenv("CIRCLE_NODE_INDEX") + requestBody = &bytes.Buffer{} + ) + if err := json.NewEncoder(requestBody).Encode(schedule{tests}); err != nil { + return []string{}, err + } + url := fmt.Sprintf("http://%s/schedule/%s/%s/%s", schedulerHost, testRun, shardCount, shardID) + resp, err := http.Post(url, jsonContentType, requestBody) + if err != nil { + return []string{}, err + } + var sched schedule + if err := json.NewDecoder(resp.Body).Decode(&sched); err != nil { + return []string{}, err + } + return sched.Tests, nil +} + +func getTests(testNames []string) (tests, error) { + var err error + if useScheduler { + testNames, err = getSchedule(testNames) + if err != nil { + return tests{}, err + } + } + tests := tests{} + for _, name := range testNames { + parts := strings.Split(strings.TrimSuffix(name, "_test.sh"), "_") + numHosts, err := strconv.Atoi(parts[len(parts)-1]) + if err != nil { + numHosts = 1 + } + tests = append(tests, test{name, numHosts}) + fmt.Printf("Test %s needs %d hosts\n", name, numHosts) + } + return tests, nil +} + +func summary(tests, failed tests) { + if len(failed) > 0 { + fmt.Printf("%s>>> Ran %d tests, %d failed%s\n", fail, len(tests), len(failed), reset) + for _, test := range failed { + fmt.Printf("%s>>> Fail %s%s\n", fail, test.name, reset) + } + } else { + fmt.Printf("%s>>> Ran %d tests, all succeeded%s\n", succ, len(tests), reset) + } +} + +func parallel(ts tests, hosts []string) bool { + testsCopy := ts + sort.Sort(sort.Reverse(ts)) + resultsChan := make(chan result) + outstanding := 0 + failed := tests{} + for len(ts) > 0 || outstanding > 0 { + // While we have some free hosts, try and schedule + // a test on them + for len(hosts) > 0 { + test, ok := ts.pick(len(hosts)) + if !ok { + break + } + testHosts := hosts[:test.hosts] + hosts = hosts[test.hosts:] + + go func() { + errored := test.run(testHosts) + resultsChan <- result{test, errored, testHosts} + }() + outstanding++ + } + + // Otherwise, wait for the test to finish and return + // the hosts to the pool + result := <-resultsChan + hosts = append(hosts, result.hosts...) + outstanding-- + if result.errored { + failed = append(failed, result.test) + } + } + summary(testsCopy, failed) + return len(failed) > 0 +} + +func sequential(ts tests, hosts []string) bool { + failed := tests{} + for _, test := range ts { + if test.run(hosts) { + failed = append(failed, test) + } + } + summary(ts, failed) + return len(failed) > 0 +} + +func main() { + mflag.BoolVar(&useScheduler, []string{"scheduler"}, false, "Use scheduler to distribute tests across shards") + mflag.BoolVar(&runParallel, []string{"parallel"}, false, "Run tests in parallel on hosts where possible") + mflag.BoolVar(&verbose, []string{"v"}, false, "Print output from all tests (Also enabled via DEBUG=1)") + mflag.Parse() + + if len(os.Getenv("DEBUG")) > 0 { + verbose = true + } + + tests, err := getTests(mflag.Args()) + if err != nil { + fmt.Printf("Error parsing tests: %v\n", err) + os.Exit(1) + } + + hosts := strings.Fields(os.Getenv("HOSTS")) + maxHosts := len(hosts) + if maxHosts == 0 { + fmt.Print("No HOSTS specified.\n") + os.Exit(1) + } + + var errored bool + if runParallel { + errored = parallel(tests, hosts) + } else { + errored = sequential(tests, hosts) + } + + if errored { + os.Exit(1) + } +} From 23e4459d567cca2fbb13afbb3aa64636b4c1c102 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Fri, 23 Oct 2015 10:49:39 +0100 Subject: [PATCH 28/92] fixing up typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 154b8e75b..85fdfeb39 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ Included in this repo are tools shared by weave.git and scope.git. They include the Weave network - ```test```: a script to run all go unit tests in subdirectories, gather the coverage results, and merge them into a single report. -- ```runner```: a tools for running tests in parallel; given each test is - suffixes with the number of hosts it requires, and the host availible are +- ```runner```: a tool for running tests in parallel; given each test is + suffixed with the number of hosts it requires, and the hosts available are contained in the environment variable HOSTS, the tool will run tests in parallel, on different hosts. From beb4494499e4fa4c53c006ae299c65b59d81ab82 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Fri, 23 Oct 2015 10:00:45 +0000 Subject: [PATCH 29/92] Review feedback --- runner/runner.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/runner/runner.go b/runner/runner.go index 89b8c6ad1..bfac9c58b 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -19,8 +19,8 @@ import ( ) const ( - schedulerHost = "positive-cocoa-90213.appspot.com" - jsonContentType = "application/json" + defaultSchedulerHost = "positive-cocoa-90213.appspot.com" + jsonContentType = "application/json" ) var ( @@ -29,9 +29,10 @@ var ( succ = ansi.ColorCode("green+b") reset = ansi.ColorCode("reset") - useScheduler = false - runParallel = false - verbose = false + schedulerHost = defaultSchedulerHost + useScheduler = false + runParallel = false + verbose = false consoleLock = sync.Mutex{} ) @@ -241,6 +242,7 @@ func main() { mflag.BoolVar(&useScheduler, []string{"scheduler"}, false, "Use scheduler to distribute tests across shards") mflag.BoolVar(&runParallel, []string{"parallel"}, false, "Run tests in parallel on hosts where possible") mflag.BoolVar(&verbose, []string{"v"}, false, "Print output from all tests (Also enabled via DEBUG=1)") + mflag.StringVar(&schedulerHost, []string{"scheduler-host"}, defaultSchedulerHost, "Hostname of scheduler.") mflag.Parse() if len(os.Getenv("DEBUG")) > 0 { From 3e16fe8d0afb2f9a255930ec05da56e793d42696 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 26 Oct 2015 16:57:27 +0000 Subject: [PATCH 30/92] Add -no-go-get option to test. --- test | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/test b/test index 017fdc80f..6b0130fb1 100755 --- a/test +++ b/test @@ -4,8 +4,31 @@ set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" GO_TEST_ARGS="-tags netgo -cpu 4 -timeout 8m" +SLOW= +NO_GO_GET= -if [ -n "$SLOW" -o "$1" = "-slow" -o -n "$CIRCLECI" ]; then +usage() { + echo "$0 [-slow] [-in-container foo]" +} + +while [ $# -gt 0 ]; do + case "$1" in + "-slow") + SLOW=true + shift 1 + ;; + "-no-go-get") + NO_GO_GET=true + shift 1 + ;; + *) + usage + exit 2 + ;; + esac +done + +if [ -n "$SLOW" -o -n "$CIRCLECI" ]; then SLOW=true fi @@ -34,7 +57,9 @@ fi PACKAGE_BASE=$(go list -e ./) for dir in $TESTDIRS; do - go get -t -tags netgo $dir + if [ -z "$NO_GO_GET" ]; then + go get -t -tags netgo $dir + fi GO_TEST_ARGS_RUN="$GO_TEST_ARGS" if [ -n "$SLOW" ]; then @@ -44,7 +69,7 @@ for dir in $TESTDIRS; do fi START=$(date +%s) - if ! go test $GO_TEST_ARGS_RUN $dir ; then + if ! go test $GO_TEST_ARGS_RUN $dir; then fail=1 fi RUNTIME=$(( $(date +%s) - $START )) From 4fa0c68afa9c441d5ac2408e45c3a8657a568a94 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 26 Oct 2015 17:07:08 +0000 Subject: [PATCH 31/92] Don't collect coverage metrics for vendored packages. --- test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test b/test index 6b0130fb1..7037ee789 100755 --- a/test +++ b/test @@ -63,7 +63,7 @@ for dir in $TESTDIRS; do GO_TEST_ARGS_RUN="$GO_TEST_ARGS" if [ -n "$SLOW" ]; then - COVERPKGS=$( (go list $dir; go list -f '{{join .Deps "\n"}}' $dir | grep "^$PACKAGE_BASE/") | paste -s -d,) + COVERPKGS=$( (go list $dir; go list -f '{{join .Deps "\n"}}' $dir | grep -v "vendor" | grep "^$PACKAGE_BASE/") | paste -s -d,) output=$(mktemp $coverdir/unit.XXXXXXXXXX) GO_TEST_ARGS_RUN="$GO_TEST_ARGS -coverprofile=$output -coverpkg=$COVERPKGS" fi From d6ea3ad3799222fe91266e8ec0789e8284017ed6 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 27 Oct 2015 13:16:44 +0000 Subject: [PATCH 32/92] Use git diff to detect changes in rebuild-image --- rebuild-image | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebuild-image b/rebuild-image index 0e582b0ba..0eb3cff3c 100755 --- a/rebuild-image +++ b/rebuild-image @@ -29,7 +29,7 @@ cached_image_rev() { has_changes() { local rev1=$1 local rev2=$2 - local changes=$(git log --oneline $rev1..$rev2 -- $INPUTFILES | wc -l) + local changes=$(git diff --oneline $rev1..$rev2 -- $INPUTFILES | wc -l) [ "$changes" -gt 0 ] } From 6b3c7353f5b205d46fa65dae387b19f0e068b557 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 3 Nov 2015 14:37:47 +0000 Subject: [PATCH 33/92] Add scheduler from weave --- scheduler/.gitignore | 1 + scheduler/README.md | 6 ++ scheduler/app.yaml | 13 ++++ scheduler/appengine_config.py | 3 + scheduler/cron.yaml | 4 ++ scheduler/main.py | 112 ++++++++++++++++++++++++++++++++++ scheduler/requirements.txt | 2 + 7 files changed, 141 insertions(+) create mode 100644 scheduler/.gitignore create mode 100644 scheduler/README.md create mode 100644 scheduler/app.yaml create mode 100644 scheduler/appengine_config.py create mode 100644 scheduler/cron.yaml create mode 100644 scheduler/main.py create mode 100644 scheduler/requirements.txt diff --git a/scheduler/.gitignore b/scheduler/.gitignore new file mode 100644 index 000000000..a65b41774 --- /dev/null +++ b/scheduler/.gitignore @@ -0,0 +1 @@ +lib diff --git a/scheduler/README.md b/scheduler/README.md new file mode 100644 index 000000000..8489d7870 --- /dev/null +++ b/scheduler/README.md @@ -0,0 +1,6 @@ +To upload newer version: + +``` +pip install -r requirements.txt -t lib +appcfg.py update . +``` diff --git a/scheduler/app.yaml b/scheduler/app.yaml new file mode 100644 index 000000000..8bc59f004 --- /dev/null +++ b/scheduler/app.yaml @@ -0,0 +1,13 @@ +application: positive-cocoa-90213 +version: 1 +runtime: python27 +api_version: 1 +threadsafe: true + +handlers: +- url: .* + script: main.app + +libraries: +- name: webapp2 + version: latest diff --git a/scheduler/appengine_config.py b/scheduler/appengine_config.py new file mode 100644 index 000000000..f4489ff96 --- /dev/null +++ b/scheduler/appengine_config.py @@ -0,0 +1,3 @@ +from google.appengine.ext import vendor + +vendor.add('lib') diff --git a/scheduler/cron.yaml b/scheduler/cron.yaml new file mode 100644 index 000000000..652aed802 --- /dev/null +++ b/scheduler/cron.yaml @@ -0,0 +1,4 @@ +cron: +- description: periodic gc + url: /tasks/gc + schedule: every 5 minutes diff --git a/scheduler/main.py b/scheduler/main.py new file mode 100644 index 000000000..ed0c78e31 --- /dev/null +++ b/scheduler/main.py @@ -0,0 +1,112 @@ +import collections +import json +import logging +import operator +import re + +import flask +from oauth2client.client import GoogleCredentials +from googleapiclient import discovery + +from google.appengine.api import urlfetch +from google.appengine.ext import ndb + +app = flask.Flask('scheduler') +app.debug = True + +# We use exponential moving average to record +# test run times. Higher alpha discounts historic +# observations faster. +alpha = 0.3 + +PROJECT = 'positive-cocoa-90213' +ZONE = 'us-central1-a' + +class Test(ndb.Model): + total_run_time = ndb.FloatProperty(default=0.) # Not total, but a EWMA + total_runs = ndb.IntegerProperty(default=0) + +class Schedule(ndb.Model): + shards = ndb.JsonProperty() + +@app.route('/record//', methods=['POST']) +@ndb.transactional +def record(test_name, runtime): + test = Test.get_by_id(test_name) + if test is None: + test = Test(id=test_name) + test.total_run_time = (test.total_run_time * (1-alpha)) + (float(runtime) * alpha) + test.total_runs += 1 + test.put() + return ('', 204) + +@app.route('/schedule///', methods=['POST']) +def schedule(test_run, shard_count, shard): + # read tests from body + test_names = flask.request.get_json(force=True)['tests'] + + # first see if we have a scedule already + schedule_id = "%s-%d" % (test_run, shard_count) + schedule = Schedule.get_by_id(schedule_id) + if schedule is not None: + return flask.json.jsonify(tests=schedule.shards[str(shard)]) + + # if not, do simple greedy algorithm + test_times = ndb.get_multi(ndb.Key(Test, test_name) for test_name in test_names) + def avg(test): + if test is not None: + return test.total_run_time + return 1 + test_times = [(test_name, avg(test)) for test_name, test in zip(test_names, test_times)] + test_times_dict = dict(test_times) + test_times.sort(key=operator.itemgetter(1)) + + shards = {i: [] for i in xrange(shard_count)} + while test_times: + test_name, time = test_times.pop() + + # find shortest shard and put it in that + s, _ = min(((i, sum(test_times_dict[t] for t in shards[i])) + for i in xrange(shard_count)), key=operator.itemgetter(1)) + + shards[s].append(test_name) + + # atomically insert or retrieve existing schedule + schedule = Schedule.get_or_insert(schedule_id, shards=shards) + return flask.json.jsonify(tests=schedule.shards[str(shard)]) + +NAME_RE = re.compile(r'^host(?P\d+)-(?P\d+)-(?P\d+)$') + +@app.route('/tasks/gc') +def gc(): + # Get list of running VMs, pick build id out of VM name + credentials = GoogleCredentials.get_application_default() + compute = discovery.build('compute', 'v1', credentials=credentials) + instances = compute.instances().list(project=PROJECT, zone=ZONE).execute() + host_by_build = collections.defaultdict(list) + for instance in instances['items']: + matches = NAME_RE.match(instance['name']) + if matches is None: + continue + host_by_build[int(matches.group('build'))].append(instance['name']) + logging.info("Running VMs by build: %r", host_by_build) + + # Get list of builds, filter down to runnning builds + result = urlfetch.fetch('https://circleci.com/api/v1/project/weaveworks/weave', + headers={'Accept': 'application/json'}) + assert result.status_code == 200 + builds = json.loads(result.content) + running = {build['build_num'] for build in builds if build['status'] == 'running'} + logging.info("Runnings builds: %r", running) + + # Stop VMs for builds that aren't running + stopped = [] + for build, names in host_by_build.iteritems(): + if build in running: + continue + for name in names: + stopped.append(name) + logging.info("Stopping VM %s", name) + compute.instances().delete(project=PROJECT, zone=ZONE, instance=name).execute() + + return (flask.json.jsonify(running=list(running), stopped=stopped), 200) diff --git a/scheduler/requirements.txt b/scheduler/requirements.txt new file mode 100644 index 000000000..d4d47e6eb --- /dev/null +++ b/scheduler/requirements.txt @@ -0,0 +1,2 @@ +flask +google-api-python-client From f250a3b0e174aa1c8586f3dae2b871daeb04d4a8 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 3 Nov 2015 15:01:33 +0000 Subject: [PATCH 34/92] Add sched CLI and update readme. --- README.md | 2 ++ sched | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100755 sched diff --git a/README.md b/README.md index 85fdfeb39..32ddb57b1 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Included in this repo are tools shared by weave.git and scope.git. They include suffixed with the number of hosts it requires, and the hosts available are contained in the environment variable HOSTS, the tool will run tests in parallel, on different hosts. +- ```scheduler```: an appengine application that can be used to distribute + tests across different shards in CircleCI. ## Using build-tools.git diff --git a/sched b/sched new file mode 100755 index 000000000..e94e8af8f --- /dev/null +++ b/sched @@ -0,0 +1,38 @@ +#!/usr/bin/python +import sys, string, json, urllib +import requests + +BASE_URL="http://positive-cocoa-90213.appspot.com" + +def test_time(test_name, runtime): + r = requests.post(BASE_URL + "/record/%s/%f" % (urllib.quote(test_name, safe=""), runtime)) + print r.text + assert r.status_code == 204 + +def test_sched(test_run, shard_count, shard_id): + tests = json.dumps({'tests': string.split(sys.stdin.read())}) + r = requests.post(BASE_URL + "/schedule/%s/%d/%d" % (test_run, shard_count, shard_id), data=tests) + assert r.status_code == 200 + result = r.json() + for test in sorted(result['tests']): + print test + +def usage(): + print "%s " % sys.argv[0] + print " time " + print " sched " + +def main(): + if len(sys.argv) < 4: + usage() + sys.exit(1) + + if sys.argv[1] == "time": + test_time(sys.argv[2], float(sys.argv[3])) + elif sys.argv[1] == "sched": + test_sched(sys.argv[2], int(sys.argv[3]), int(sys.argv[4])) + else: + usage() + +if __name__ == '__main__': + main() From e22c576487bc3f33e2dc4fdcd9fb570e989c5258 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 3 Nov 2015 18:39:04 +0000 Subject: [PATCH 35/92] Compress cached images. --- rebuild-image | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rebuild-image b/rebuild-image index 0eb3cff3c..df3a371c4 100755 --- a/rebuild-image +++ b/rebuild-image @@ -17,12 +17,12 @@ rebuild() { mkdir -p $CACHEDIR rm $CACHEDIR/$SAVEDNAME* || true docker build -t $IMAGENAME $IMAGEDIR - docker save $IMAGENAME:latest > $CACHEDIR/$SAVEDNAME-$CIRCLE_SHA1 + docker save $IMAGENAME:latest | gzip - > $CACHEDIR/$SAVEDNAME-$CIRCLE_SHA1.gz } # Get the revision the cached image was build at cached_image_rev() { - find $CACHEDIR -name "$SAVEDNAME-*" -type f | sed 's/[^\-]*\-//' + find $CACHEDIR -name "$SAVEDNAME-*" -type f | sed -n 's/^[^\-]*\-\([a-z0-9]*\).gz$/\1/p' } # Have there been any revision beween $1 and $2 @@ -60,4 +60,4 @@ fi # we didn't rebuild; import cached version echo ">>> No changes found, importing cached image" -docker load -i $CACHEDIR/$SAVEDNAME-$cached_revision +zcat $CACHEDIR/$SAVEDNAME-$cached_revision.gz | docker load From 4be554132bdb23ed481300dedceb4d8f73ea9006 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 4 Nov 2015 12:12:35 +0000 Subject: [PATCH 36/92] Add gather_coverage.sh script. --- cover/gather_coverage.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100755 cover/gather_coverage.sh diff --git a/cover/gather_coverage.sh b/cover/gather_coverage.sh new file mode 100755 index 000000000..9026745a1 --- /dev/null +++ b/cover/gather_coverage.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# This scripts copies all the coverage reports from various circle shards, +# merges them and produces a complete report. + +set -ex +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DESTINATION=$1 +FROMDIR=$2 +mkdir -p $DESTINATION + +if [ -n "$CIRCLECI" ]; then + for i in $(seq 1 $(($CIRCLE_NODE_TOTAL - 1))); do + scp node$i:$FROMDIR/* $DESTINATION || true + done +fi + +go get github.com/weaveworks/build-tools/cover +cover $DESTINATION/* >profile.cov +go tool cover -html=profile.cov -o coverage.html +go tool cover -func=profile.cov -o coverage.txt +tar czf coverage.tar.gz $DESTINATION From 1fc4d66fac376fc85b98b3e16c31c46661d6db32 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 10 Nov 2015 11:57:31 +0000 Subject: [PATCH 37/92] Make test work on mac. --- test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test b/test index 7037ee789..9250f6280 100755 --- a/test +++ b/test @@ -63,7 +63,7 @@ for dir in $TESTDIRS; do GO_TEST_ARGS_RUN="$GO_TEST_ARGS" if [ -n "$SLOW" ]; then - COVERPKGS=$( (go list $dir; go list -f '{{join .Deps "\n"}}' $dir | grep -v "vendor" | grep "^$PACKAGE_BASE/") | paste -s -d,) + COVERPKGS=$( (go list $dir; go list -f '{{join .Deps "\n"}}' $dir | grep -v "vendor" | grep "^$PACKAGE_BASE/") | paste -s -d, -) output=$(mktemp $coverdir/unit.XXXXXXXXXX) GO_TEST_ARGS_RUN="$GO_TEST_ARGS -coverprofile=$output -coverpkg=$COVERPKGS" fi From c04c53476bb5d0b35b77a39f303063797fa3632a Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Wed, 11 Nov 2015 17:50:07 +0000 Subject: [PATCH 38/92] socks: Make main shExpMatch expression configurable --- socks/main.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/socks/main.go b/socks/main.go index 171b5fc13..6cde493d3 100644 --- a/socks/main.go +++ b/socks/main.go @@ -13,13 +13,18 @@ import ( "github.com/weaveworks/weave/common/mflagext" ) +type pacFileParameters struct { + HostMatch string + Aliases map[string]string +} + const ( pacfile = ` function FindProxyForURL(url, host) { - if(shExpMatch(host, "*.weave.local")) { + if(shExpMatch(host, "{{.HostMatch}}")) { return "SOCKS5 localhost:8000"; } - {{range $key, $value := .}} + {{range $key, $value := .Aliases}} if (host == "{{$key}}") { return "SOCKS5 localhost:8000"; } @@ -30,8 +35,12 @@ function FindProxyForURL(url, host) { ) func main() { - var as []string + var ( + as []string + hostMatch string + ) mflagext.ListVar(&as, []string{"a", "-alias"}, []string{}, "Specify hostname aliases in the form alias:hostname. Can be repeated.") + mflag.StringVar(&hostMatch, []string{"h", "-host-match"}, "*.weave.local", "Specify main host shExpMatch expression in pacfile") mflag.Parse() var aliases = map[string]string{} @@ -50,7 +59,7 @@ func main() { t := template.Must(template.New("pacfile").Parse(pacfile)) http.HandleFunc("/proxy.pac", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/x-ns-proxy-autoconfig") - t.Execute(w, aliases) + t.Execute(w, pacFileParameters{hostMatch, aliases}) }) if err := http.ListenAndServe(":8080", nil); err != nil { From 0bff9b4d25670b130907d212d87fb0d9189c0860 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 2 Dec 2015 13:16:32 +0000 Subject: [PATCH 39/92] Don't delete GCE nodes for running (but failed) builds --- scheduler/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scheduler/main.py b/scheduler/main.py index ed0c78e31..e7bd9d964 100644 --- a/scheduler/main.py +++ b/scheduler/main.py @@ -96,7 +96,7 @@ def gc(): headers={'Accept': 'application/json'}) assert result.status_code == 200 builds = json.loads(result.content) - running = {build['build_num'] for build in builds if build['status'] == 'running'} + running = {build['build_num'] for build in builds if not build.get('stop_time')} logging.info("Runnings builds: %r", running) # Stop VMs for builds that aren't running From 856790fe341b7fade6b3cd3cb9fd8fdb2b0664f8 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 7 Dec 2015 14:47:43 +0000 Subject: [PATCH 40/92] Use golang package name as a prefix on unit test schedules. --- test | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test b/test index 9250f6280..73a5952ef 100755 --- a/test +++ b/test @@ -50,7 +50,8 @@ TESTDIRS=$(find . -type f -name '*_test.go' | xargs -n1 dirname | grep -vE '^\./ # If running on circle, use the scheduler to work out what tests to run on what shard if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" -a -x "$DIR/sched" ]; then - TESTDIRS=$(echo $TESTDIRS | "$DIR/sched" sched units-$CIRCLE_BUILD_NUM $CIRCLE_NODE_TOTAL $CIRCLE_NODE_INDEX) + PREFIX=$(go list -e ./ | sed -e 's/\//-/g') + TESTDIRS=$(echo $TESTDIRS | "$DIR/sched" sched $PREFIX-$CIRCLE_BUILD_NUM $CIRCLE_NODE_TOTAL $CIRCLE_NODE_INDEX) echo $TESTDIRS fi From 0c4636a2b39a8547a8c51a2fc56da3c97d909b16 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Fri, 8 Jan 2016 19:30:01 +0000 Subject: [PATCH 41/92] Speed up tests by doing a go test -i before hand. --- test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test b/test index 73a5952ef..5b88d5270 100755 --- a/test +++ b/test @@ -57,6 +57,9 @@ fi PACKAGE_BASE=$(go list -e ./) +# Speed up the tests by compiling and installing their dependancies first. +go test -i $GO_TEST_ARGS $TESTDIRS + for dir in $TESTDIRS; do if [ -z "$NO_GO_GET" ]; then go get -t -tags netgo $dir From a4c722205c13f3ce35f90d017bf80bf0cb6f7134 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 12 Jan 2016 11:02:10 +0000 Subject: [PATCH 42/92] More gce scripts from weave into tools. --- integration/assert.sh | 186 ++++++++++++++++++++++++++++++++++++ integration/config.sh | 125 ++++++++++++++++++++++++ integration/gce.sh | 176 ++++++++++++++++++++++++++++++++++ integration/run_all.sh | 27 ++++++ integration/sanity_check.sh | 26 +++++ 5 files changed, 540 insertions(+) create mode 100644 integration/assert.sh create mode 100644 integration/config.sh create mode 100755 integration/gce.sh create mode 100755 integration/run_all.sh create mode 100755 integration/sanity_check.sh diff --git a/integration/assert.sh b/integration/assert.sh new file mode 100644 index 000000000..1a2f1f778 --- /dev/null +++ b/integration/assert.sh @@ -0,0 +1,186 @@ +#!/bin/bash +# assert.sh 1.1 - bash unit testing framework +# Copyright (C) 2009-2015 Robert Lehmann +# +# http://github.com/lehmannro/assert.sh +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +export DISCOVERONLY=${DISCOVERONLY:-} +export DEBUG=${DEBUG:-} +export STOP=${STOP:-} +export INVARIANT=${INVARIANT:-} +export CONTINUE=${CONTINUE:-} + +args="$(getopt -n "$0" -l \ + verbose,help,stop,discover,invariant,continue vhxdic $*)" \ +|| exit -1 +for arg in $args; do + case "$arg" in + -h) + echo "$0 [-vxidc]" \ + "[--verbose] [--stop] [--invariant] [--discover] [--continue]" + echo "`sed 's/./ /g' <<< "$0"` [-h] [--help]" + exit 0;; + --help) + cat < [stdin] + (( tests_ran++ )) || : + [[ -z "$DISCOVERONLY" ]] || return + expected=$(echo -ne "${2:-}") + result="$(eval 2>/dev/null $1 <<< ${3:-})" || true + if [[ "$result" == "$expected" ]]; then + [[ -z "$DEBUG" ]] || echo -n . + return + fi + result="$(sed -e :a -e '$!N;s/\n/\\n/;ta' <<< "$result")" + [[ -z "$result" ]] && result="nothing" || result="\"$result\"" + [[ -z "$2" ]] && expected="nothing" || expected="\"$2\"" + _assert_fail "expected $expected${_indent}got $result" "$1" "$3" +} + +assert_raises() { + # assert_raises [stdin] + (( tests_ran++ )) || : + [[ -z "$DISCOVERONLY" ]] || return + status=0 + (eval $1 <<< ${3:-}) > /dev/null 2>&1 || status=$? + expected=${2:-0} + if [[ "$status" -eq "$expected" ]]; then + [[ -z "$DEBUG" ]] || echo -n . + return + fi + _assert_fail "program terminated with code $status instead of $expected" "$1" "$3" +} + +_assert_fail() { + # _assert_fail + [[ -n "$DEBUG" ]] && echo -n X + report="test #$tests_ran \"$2${3:+ <<< $3}\" failed:${_indent}$1" + if [[ -n "$STOP" ]]; then + [[ -n "$DEBUG" ]] && echo + echo "$report" + exit 1 + fi + tests_errors[$tests_failed]="$report" + (( tests_failed++ )) || : +} + +skip_if() { + # skip_if + (eval $@) > /dev/null 2>&1 && status=0 || status=$? + [[ "$status" -eq 0 ]] || return + skip +} + +skip() { + # skip (no arguments) + shopt -q extdebug && tests_extdebug=0 || tests_extdebug=1 + shopt -q -o errexit && tests_errexit=0 || tests_errexit=1 + # enable extdebug so returning 1 in a DEBUG trap handler skips next command + shopt -s extdebug + # disable errexit (set -e) so we can safely return 1 without causing exit + set +o errexit + tests_trapped=0 + trap _skip DEBUG +} +_skip() { + if [[ $tests_trapped -eq 0 ]]; then + # DEBUG trap for command we want to skip. Do not remove the handler + # yet because *after* the command we need to reset extdebug/errexit (in + # another DEBUG trap.) + tests_trapped=1 + [[ -z "$DEBUG" ]] || echo -n s + return 1 + else + trap - DEBUG + [[ $tests_extdebug -eq 0 ]] || shopt -u extdebug + [[ $tests_errexit -eq 1 ]] || set -o errexit + return 0 + fi +} + + +_assert_reset +: ${tests_suite_status:=0} # remember if any of the tests failed so far +_assert_cleanup() { + local status=$? + # modify exit code if it's not already non-zero + [[ $status -eq 0 && -z $CONTINUE ]] && exit $tests_suite_status +} +trap _assert_cleanup EXIT diff --git a/integration/config.sh b/integration/config.sh new file mode 100644 index 000000000..3d7a8dafe --- /dev/null +++ b/integration/config.sh @@ -0,0 +1,125 @@ +# NB only to be sourced + +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Protect against being sourced multiple times to prevent +# overwriting assert.sh global state +if ! [ -z "$SOURCED_CONFIG_SH" ]; then + return +fi +SOURCED_CONFIG_SH=true + +# these ought to match what is in Vagrantfile +N_MACHINES=${N_MACHINES:-3} +IP_PREFIX=${IP_PREFIX:-192.168.48} +IP_SUFFIX_BASE=${IP_SUFFIX_BASE:-10} + +if [ -z "$HOSTS" ] ; then + for i in $(seq 1 $N_MACHINES); do + IP="${IP_PREFIX}.$((${IP_SUFFIX_BASE}+$i))" + HOSTS="$HOSTS $IP" + done +fi + +# these are used by the tests +HOST1=$(echo $HOSTS | cut -f 1 -d ' ') +HOST2=$(echo $HOSTS | cut -f 2 -d ' ') +HOST3=$(echo $HOSTS | cut -f 3 -d ' ') + +. "$DIR/assert.sh" + +SSH_DIR=${SSH_DIR:-$DIR} +SSH=${SSH:-ssh -l vagrant -i "$SSH_DIR/insecure_private_key" -o "UserKnownHostsFile=$SSH_DIR/.ssh_known_hosts" -o CheckHostIP=no -o StrictHostKeyChecking=no} + +SMALL_IMAGE="alpine" +TEST_IMAGES="$SMALL_IMAGE" + +PING="ping -nq -W 1 -c 1" +DOCKER_PORT=2375 + +remote() { + rem=$1 + shift 1 + "$@" > >(while read line; do echo -e $'\e[0;34m'"$rem>"$'\e[0m'" $line"; done) +} + +colourise() { + [ -t 0 ] && echo -ne $'\e['$1'm' || true + shift + # It's important that we don't do this in a subshell, as some + # commands we execute need to modify global state + "$@" + [ -t 0 ] && echo -ne $'\e[0m' || true +} + +whitely() { + colourise '1;37' "$@" +} + +greyly () { + colourise '0;37' "$@" +} + +redly() { + colourise '1;31' "$@" +} + +greenly() { + colourise '1;32' "$@" +} + +run_on() { + host=$1 + shift 1 + [ -z "$DEBUG" ] || greyly echo "Running on $host: $@" >&2 + remote $host $SSH $host "$@" +} + +docker_on() { + host=$1 + shift 1 + [ -z "$DEBUG" ] || greyly echo "Docker on $host:$DOCKER_PORT: $@" >&2 + docker -H tcp://$host:$DOCKER_PORT "$@" +} + +weave_on() { + host=$1 + shift 1 + [ -z "$DEBUG" ] || greyly echo "Weave on $host:$DOCKER_PORT: $@" >&2 + DOCKER_HOST=tcp://$host:$DOCKER_PORT $WEAVE "$@" +} + +exec_on() { + host=$1 + container=$2 + shift 2 + docker -H tcp://$host:$DOCKER_PORT exec $container "$@" +} + +rm_containers() { + host=$1 + shift + [ $# -eq 0 ] || docker_on $host rm -f "$@" >/dev/null +} + +start_suite() { + for host in $HOSTS; do + [ -z "$DEBUG" ] || echo "Cleaning up on $host: removing all containers and resetting weave" + PLUGIN_ID=$(docker_on $host ps -aq --filter=name=weaveplugin) + PLUGIN_FILTER="cat" + [ -n "$PLUGIN_ID" ] && PLUGIN_FILTER="grep -v $PLUGIN_ID" + rm_containers $host $(docker_on $host ps -aq 2>/dev/null | $PLUGIN_FILTER) + run_on $host "docker network ls | grep -q ' weave ' && docker network rm weave" || true + weave_on $host reset 2>/dev/null + done + whitely echo "$@" +} + +end_suite() { + whitely assert_end +} + +WEAVE=$DIR/../weave + diff --git a/integration/gce.sh b/integration/gce.sh new file mode 100755 index 000000000..bb6f95efa --- /dev/null +++ b/integration/gce.sh @@ -0,0 +1,176 @@ +#!/bin/bash +# This script has a bunch of GCE-related functions: +# ./gce.sh setup - starts two VMs on GCE and configures them to run our integration tests +# . ./gce.sh; ./run_all.sh - set a bunch of environment variables for the tests +# ./gce.sh destroy - tear down the VMs +# ./gce.sh make_template - make a fresh VM template; update TEMPLATE_NAME first! + +set -e + +: ${KEY_FILE:=/tmp/gce_private_key.json} +: ${SSH_KEY_FILE:=$HOME/.ssh/gce_ssh_key} +: ${IMAGE:=ubuntu-14-04} +: ${ZONE:=us-central1-a} +: ${PROJECT:=} +: ${TEMPLATE_NAME:=} +: ${NUM_HOSTS:=} + +if [ -z "${PROJECT}" -o -z "${NUM_HOSTS}" -o -z "${TEMPLATE_NAME}" ]; then + echo "Must specify PROJECT, NUM_HOSTS and TEMPLATE_NAME" + exit 1 +fi + +SUFFIX="" +if [ -n "$CIRCLECI" ]; then + SUFFIX="-${CIRCLE_BUILD_NUM}-$CIRCLE_NODE_INDEX" +fi + +# Setup authentication +gcloud auth activate-service-account --key-file $KEY_FILE 1>/dev/null +gcloud config set project $PROJECT + +function vm_names { + local names= + for i in $(seq 1 $NUM_HOSTS); do + names="host$i$SUFFIX $names" + done + echo "$names" +} + +# Delete all vms in this account +function destroy { + names="$(vm_names)" + if [ $(gcloud compute instances list --zone $ZONE -q $names | wc -l) -le 1 ] ; then + return 0 + fi + for i in {0..10}; do + # gcloud instances delete can sometimes hang. + case $(set +e; timeout 60s /bin/bash -c "gcloud compute instances delete --zone $ZONE -q $names >/dev/null 2>&1"; echo $?) in + 0) + return 0 + ;; + 124) + # 124 means it timed out + break + ;; + *) + return 1 + esac + done +} + +function internal_ip { + jq -r ".[] | select(.name == \"$2\") | .networkInterfaces[0].networkIP" $1 +} + +function external_ip { + jq -r ".[] | select(.name == \"$2\") | .networkInterfaces[0].accessConfigs[0].natIP" $1 +} + +function try_connect { + for i in {0..10}; do + ssh -t $1 true && return + sleep 2 + done +} + +function install_docker_on { + name=$1 + ssh -t $name sudo bash -x -s <> /etc/default/docker; +service docker restart +EOF + # It seems we need a short delay for docker to start up, so I put this in + # a separate ssh connection. This installs nsenter. + ssh -t $name sudo docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter +} + +function copy_hosts { + hostname=$1 + hosts=$2 + cat $hosts | ssh -t "$hostname" "sudo -- sh -c \"cat >>/etc/hosts\"" +} + +# Create new set of VMs +function setup { + destroy + + names="$(vm_names)" + gcloud compute instances create $names --image $TEMPLATE_NAME --zone $ZONE + gcloud compute config-ssh --ssh-key-file $SSH_KEY_FILE + sed -i '/UserKnownHostsFile=\/dev\/null/d' ~/.ssh/config + + # build an /etc/hosts file for these vms + hosts=$(mktemp hosts.XXXXXXXXXX) + json=$(mktemp json.XXXXXXXXXX) + gcloud compute instances list --format=json >$json + for name in $names; do + echo "$(internal_ip $json $name) $name.$ZONE.$PROJECT" >>$hosts + done + + for name in $names; do + hostname="$name.$ZONE.$PROJECT" + + # Add the remote ip to the local /etc/hosts + sudo sed -i "/$hostname/d" /etc/hosts + sudo sh -c "echo \"$(external_ip $json $name) $hostname\" >>/etc/hosts" + try_connect $hostname + + copy_hosts $hostname $hosts & + done + + wait + + rm $hosts $json +} + +function make_template { + gcloud compute instances create $TEMPLATE_NAME --image $IMAGE --zone $ZONE + gcloud compute config-ssh --ssh-key-file $SSH_KEY_FILE + name="$TEMPLATE_NAME.$ZONE.$PROJECT" + try_connect $name + install_docker_on $name + gcloud -q compute instances delete $TEMPLATE_NAME --keep-disks boot --zone $ZONE + gcloud compute images create $TEMPLATE_NAME --source-disk $TEMPLATE_NAME --source-disk-zone $ZONE +} + +function hosts { + hosts= + args= + json=$(mktemp json.XXXXXXXXXX) + gcloud compute instances list --format=json >$json + for name in $(vm_names); do + hostname="$name.$ZONE.$PROJECT" + hosts="$hostname $hosts" + args="--add-host=$hostname:$(internal_ip $json $name) $args" + done + echo export SSH=\"ssh -l vagrant\" + echo export HOSTS=\"$hosts\" + echo export ADD_HOST_ARGS=\"$args\" + rm $json +} + +case "$1" in +setup) + setup + ;; + +hosts) + hosts + ;; + +destroy) + destroy + ;; + +make_template) + # see if template exists + if ! gcloud compute images list | grep $PROJECT | grep $TEMPLATE_NAME; then + make_template + fi +esac diff --git a/integration/run_all.sh b/integration/run_all.sh new file mode 100755 index 000000000..13cb82c41 --- /dev/null +++ b/integration/run_all.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +. "$DIR/config.sh" + +whitely echo Sanity checks +if ! bash "$DIR/sanity_check.sh"; then + whitely echo ...failed + exit 1 +fi +whitely echo ...ok + +TESTS="${@:-$(find . -name '*_test.sh')}" +RUNNER_ARGS="" + +# If running on circle, use the scheduler to work out what tests to run +if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" ]; then + RUNNER_ARGS="$RUNNER_ARGS -scheduler" +fi + +# If running on circle or PARALLEL is not empty, run tests in parallel +if [ -n "$CIRCLECI" -o -n "$PARALLEL" ]; then + RUNNER_ARGS="$RUNNER_ARGS -parallel" +fi + +make -C ${DIR}/../runner +HOSTS="$HOSTS" "${DIR}/../runner/runner" $RUNNER_ARGS $TESTS diff --git a/integration/sanity_check.sh b/integration/sanity_check.sh new file mode 100755 index 000000000..592a6b77c --- /dev/null +++ b/integration/sanity_check.sh @@ -0,0 +1,26 @@ +#! /bin/bash + +. ./config.sh + +set -e + +whitely echo Ping each host from the other +for host in $HOSTS; do + for other in $HOSTS; do + [ $host = $other ] || run_on $host $PING $other + done +done + +whitely echo Check we can reach docker + +for host in $HOSTS; do + echo + echo Host Version Info: $host + echo ===================================== + echo "# docker version" + docker_on $host version + echo "# docker info" + docker_on $host info + echo "# weave version" + weave_on $host version +done From c5800d0af1efeeba332b5f22d7286c86ca9db137 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 12 Jan 2016 13:28:33 +0000 Subject: [PATCH 43/92] Increase rebuild-image image lifetime to 3 days. --- rebuild-image | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rebuild-image b/rebuild-image index df3a371c4..ea54f7403 100755 --- a/rebuild-image +++ b/rebuild-image @@ -52,7 +52,8 @@ if has_changes $cached_revision $CIRCLE_SHA1 ; then exit 0 fi -if [ "$(commit_timestamp $cached_revision)" -lt "$(( $(date +%s) - 86400 ))" ]; then +IMAGE_TIMEOUT="$(( 3 * 24 * 60 * 60 ))" +if [ "$(commit_timestamp $cached_revision)" -lt "${IMAGE_TIMEOUT}" ]; then echo ">>> Image is more the 24hrs old; rebuilding" rebuild exit 0 From f7f137037275c8b7d1cd6c38722f2ac1187b7192 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Tue, 12 Jan 2016 21:11:53 +0000 Subject: [PATCH 44/92] Use CIRCLE_PROJECT_REPONAME to distinguih schedules for different repos. --- runner/runner.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/runner/runner.go b/runner/runner.go index bfac9c58b..819b0ab4e 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -138,7 +138,9 @@ func updateScheduler(test string, duration float64) { func getSchedule(tests []string) ([]string, error) { var ( - testRun = "integration-" + os.Getenv("CIRCLE_BUILD_NUM") + project = os.Getenv("CIRCLE_PROJECT_REPONAME") + buildNum = os.Getenv("CIRCLE_BUILD_NUM") + testRun = project + "-integration-" + buildNum shardCount = os.Getenv("CIRCLE_NODE_TOTAL") shardID = os.Getenv("CIRCLE_NODE_INDEX") requestBody = &bytes.Buffer{} From c7d833c2b1fc8447022d3b8b41930c2c8430d122 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Sat, 16 Jan 2016 12:10:02 -0800 Subject: [PATCH 45/92] Allow vendor dirs in sub directories. --- lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint b/lint index 818c05a25..2adc9213e 100755 --- a/lint +++ b/lint @@ -148,7 +148,7 @@ function lint_files { function list_files { if [ $# -gt 0 ]; then - find "$@" -type f | grep -vE '^\./(\.git|vendor)/' + find "$@" -type f | grep -vE '(^\./\.git|^\./\.pkg|/vendor/)' else git diff --cached --name-only fi From 7732be6356bbb95de5f65ac17f3088b33fcd5d5e Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 4 Feb 2016 15:33:07 +0000 Subject: [PATCH 46/92] Add availible to the spell checker. --- lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint b/lint index 2adc9213e..a9a8b5440 100755 --- a/lint +++ b/lint @@ -35,7 +35,7 @@ function spell_check { filename="$1" local lint_result=0 - if grep -iH --color=always psueod "${filename}"; then + if grep -iH --color=always psueod availible "${filename}"; then echo "${filename}: spelling mistake" lint_result=1 fi From 954b02339236c796fb86da33ec43f246a4c1a561 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 4 Feb 2016 15:38:09 +0000 Subject: [PATCH 47/92] Correct last commit. --- lint | 2 +- runner/runner.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lint b/lint index a9a8b5440..89d534ba8 100755 --- a/lint +++ b/lint @@ -35,7 +35,7 @@ function spell_check { filename="$1" local lint_result=0 - if grep -iH --color=always psueod availible "${filename}"; then + if grep -iH --color=always 'psueod\|availible' "${filename}"; then echo "${filename}: spelling mistake" lint_result=1 fi diff --git a/runner/runner.go b/runner/runner.go index 819b0ab4e..dee4ba638 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -63,10 +63,10 @@ func (ts tests) Less(i, j int) bool { return ts[i].name < ts[j].name } -func (ts *tests) pick(availible int) (test, bool) { - // pick the first test that fits in the availible hosts +func (ts *tests) pick(available int) (test, bool) { + // pick the first test that fits in the available hosts for i, test := range *ts { - if test.hosts <= availible { + if test.hosts <= available { *ts = append((*ts)[:i], (*ts)[i+1:]...) return test, true } From cbb0567cd424aed8560fb6c4de322e6c3b157b9a Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Wed, 10 Feb 2016 10:33:16 +0000 Subject: [PATCH 48/92] Exclude ALL_CAPS warnings from linter --- lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint b/lint index 89d534ba8..22deea8b2 100755 --- a/lint +++ b/lint @@ -85,7 +85,7 @@ function lint_go { if [ -z "$IGNORE_LINT_COMMENT" ]; then lintoutput=$(golint "${filename}") else - lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports') + lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports|ALL_CAPS') fi if [ -n "$lintoutput" ]; then lint_result=1 From d76a90a9993910e24444b7a197f9d451b68834ae Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 22 Feb 2016 13:56:42 +0000 Subject: [PATCH 49/92] Don't lint codecgen'd code. --- lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint b/lint index 22deea8b2..05adb45c6 100755 --- a/lint +++ b/lint @@ -148,7 +148,7 @@ function lint_files { function list_files { if [ $# -gt 0 ]; then - find "$@" -type f | grep -vE '(^\./\.git|^\./\.pkg|/vendor/)' + find "$@" -type f | grep -vE '(^\./\.git|^\./\.pkg|/vendor/|codecgen.go$)' else git diff --cached --name-only fi From 07ebcff87e0afd78d74fe222e8035b86ad62c6ba Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 22 Feb 2016 14:10:04 +0000 Subject: [PATCH 50/92] Update to ignore node_modules and *.generated.go --- lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint b/lint index 05adb45c6..22db070c0 100755 --- a/lint +++ b/lint @@ -148,7 +148,7 @@ function lint_files { function list_files { if [ $# -gt 0 ]; then - find "$@" -type f | grep -vE '(^\./\.git|^\./\.pkg|/vendor/|codecgen.go$)' + find "$@" -type f | grep -vE '(^\./\.git|^\./\.pkg|/vendor/|/node_modules/|\.codecgen\.go$|\.generated\.go$)' else git diff --cached --name-only fi From 495e875a599b955691de9c071f28bd9a1f2ff942 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 2 Mar 2016 11:51:21 +0000 Subject: [PATCH 51/92] Add reciept to spellchecker --- lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint b/lint index 22db070c0..db4cd00be 100755 --- a/lint +++ b/lint @@ -35,7 +35,7 @@ function spell_check { filename="$1" local lint_result=0 - if grep -iH --color=always 'psueod\|availible' "${filename}"; then + if grep -iH --color=always 'psueod\|availible\|reciept' "${filename}"; then echo "${filename}: spelling mistake" lint_result=1 fi From f9042035f89b49399e765243120440a2dc4c5e6a Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 23 Mar 2016 10:56:02 +0000 Subject: [PATCH 52/92] Update socks proxy for upstream changes. --- socks/main.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/socks/main.go b/socks/main.go index 6cde493d3..520df27d9 100644 --- a/socks/main.go +++ b/socks/main.go @@ -11,6 +11,7 @@ import ( socks5 "github.com/armon/go-socks5" "github.com/docker/docker/pkg/mflag" "github.com/weaveworks/weave/common/mflagext" + "golang.org/x/net/context" ) type pacFileParameters struct { @@ -72,11 +73,11 @@ type aliasingResolver struct { socks5.NameResolver } -func (r aliasingResolver) Resolve(name string) (net.IP, error) { +func (r aliasingResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) { if alias, ok := r.aliases[name]; ok { - return r.NameResolver.Resolve(alias) + return r.NameResolver.Resolve(ctx, alias) } - return r.NameResolver.Resolve(name) + return r.NameResolver.Resolve(ctx, name) } func socksProxy(aliases map[string]string) { From b377e46d29bd3a4ab4f247384011763f2757108a Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 23 Mar 2016 10:02:03 +0000 Subject: [PATCH 53/92] Add misspell to the lint script. --- lint | 22 ++++++++++++++++++++-- rebuild-image | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lint b/lint index db4cd00be..01ad47085 100755 --- a/lint +++ b/lint @@ -15,6 +15,7 @@ set -e IGNORE_LINT_COMMENT= IGNORE_TEST_PACKAGES= +IGNORE_SPELLINGS= while true; do case "$1" in -nocomment) @@ -25,6 +26,10 @@ while true; do IGNORE_TEST_PACKAGES=1 shift 1 ;; + -ignorespelling) + IGNORE_SPELLINGS="$2,$IGNORE_SPELLINGS" + shift 2 + ;; *) break esac @@ -35,8 +40,21 @@ function spell_check { filename="$1" local lint_result=0 - if grep -iH --color=always 'psueod\|availible\|reciept' "${filename}"; then - echo "${filename}: spelling mistake" + # we don't want to spell check tar balls or binaries + if file $filename | grep executable >/dev/null 2>&1; then + return $lint_result + fi + if [[ $filename == *".tar" ]]; then + return $lint_result + fi + + # misspell is completely optional. If you don't like it + # don't have it installed. + if ! type misspell >/dev/null 2>&1; then + return $lint_result + fi + + if misspell -i "$IGNORE_SPELLINGS" "${filename}"; then lint_result=1 fi diff --git a/rebuild-image b/rebuild-image index ea54f7403..1e00cbbe0 100755 --- a/rebuild-image +++ b/rebuild-image @@ -25,7 +25,7 @@ cached_image_rev() { find $CACHEDIR -name "$SAVEDNAME-*" -type f | sed -n 's/^[^\-]*\-\([a-z0-9]*\).gz$/\1/p' } -# Have there been any revision beween $1 and $2 +# Have there been any revision between $1 and $2 has_changes() { local rev1=$1 local rev2=$2 From f9aa081666dcf6a8a6c8a3e061fd47a72370595d Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 23 Mar 2016 11:15:40 +0000 Subject: [PATCH 54/92] Correctly fail when spelling mistakes are found. --- lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint b/lint index 01ad47085..7d539f5da 100755 --- a/lint +++ b/lint @@ -54,7 +54,7 @@ function spell_check { return $lint_result fi - if misspell -i "$IGNORE_SPELLINGS" "${filename}"; then + if ! misspell -error -i "$IGNORE_SPELLINGS" "${filename}"; then lint_result=1 fi From e5af286c4ccff298c2ec7e9a9bbba9098f55ccf1 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 16 May 2016 12:20:48 +0100 Subject: [PATCH 55/92] GC scope integration test VMs --- scheduler/app.yaml | 2 ++ scheduler/main.py | 29 +++++++++++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/scheduler/app.yaml b/scheduler/app.yaml index 8bc59f004..21f5f0527 100644 --- a/scheduler/app.yaml +++ b/scheduler/app.yaml @@ -11,3 +11,5 @@ handlers: libraries: - name: webapp2 version: latest +- name: ssl + version: latest diff --git a/scheduler/main.py b/scheduler/main.py index e7bd9d964..f509f0e1f 100644 --- a/scheduler/main.py +++ b/scheduler/main.py @@ -15,13 +15,10 @@ app = flask.Flask('scheduler') app.debug = True # We use exponential moving average to record -# test run times. Higher alpha discounts historic +# test run times. Higher alpha discounts historic # observations faster. alpha = 0.3 -PROJECT = 'positive-cocoa-90213' -ZONE = 'us-central1-a' - class Test(ndb.Model): total_run_time = ndb.FloatProperty(default=0.) # Not total, but a EWMA total_runs = ndb.IntegerProperty(default=0) @@ -77,12 +74,28 @@ def schedule(test_run, shard_count, shard): NAME_RE = re.compile(r'^host(?P\d+)-(?P\d+)-(?P\d+)$') +PROJECTS = [ + ('weaveworks/weave', 'positive-cocoa-90213', 'us-central1-a'), + ('weaveworks/scope', 'scope-integration-tests', 'us-central1-a'), +] + @app.route('/tasks/gc') def gc(): # Get list of running VMs, pick build id out of VM name credentials = GoogleCredentials.get_application_default() compute = discovery.build('compute', 'v1', credentials=credentials) - instances = compute.instances().list(project=PROJECT, zone=ZONE).execute() + + for repo, project, zone in PROJECTS: + gc_project(compute, repo, project, zone) + + return "Done" + +def gc_project(compute, repo, project, zone): + logging.info("GCing %s, %s, %s", repo, project, zone) + instances = compute.instances().list(project=project, zone=zone).execute() + if 'items' not in instances: + return + host_by_build = collections.defaultdict(list) for instance in instances['items']: matches = NAME_RE.match(instance['name']) @@ -92,7 +105,7 @@ def gc(): logging.info("Running VMs by build: %r", host_by_build) # Get list of builds, filter down to runnning builds - result = urlfetch.fetch('https://circleci.com/api/v1/project/weaveworks/weave', + result = urlfetch.fetch('https://circleci.com/api/v1/project/%s' % repo, headers={'Accept': 'application/json'}) assert result.status_code == 200 builds = json.loads(result.content) @@ -107,6 +120,6 @@ def gc(): for name in names: stopped.append(name) logging.info("Stopping VM %s", name) - compute.instances().delete(project=PROJECT, zone=ZONE, instance=name).execute() + compute.instances().delete(project=project, zone=zone, instance=name).execute() - return (flask.json.jsonify(running=list(running), stopped=stopped), 200) + return From 7a660904a67c9ba664850388192506506b88e4ce Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 18 May 2016 12:50:33 +0100 Subject: [PATCH 56/92] Add publish-site from weave --- publish-site | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100755 publish-site diff --git a/publish-site b/publish-site new file mode 100755 index 000000000..4d0984b92 --- /dev/null +++ b/publish-site @@ -0,0 +1,50 @@ +#!/bin/bash + +set -e +set -o pipefail + +: ${PRODUCT:=} + +fatal() { + echo "$@" >&2 + exit 1 +} + +if [ ! -d .git ] ; then + fatal "Current directory is not a git clone" +fi + +if [ -z "${PRODUCT}" ]; then + fatal "Must specify PRODUCT" +fi + +if ! BRANCH=$(git symbolic-ref --short HEAD) || [ -z "$BRANCH" ] ; then + fatal "Could not determine branch" +fi + +case "$BRANCH" in + issues/*) + VERSION="${BRANCH#issues/}" + TAGS="$VERSION" + ;; + *) + if echo "$BRANCH" | grep -qE '^[0-9]+\.[0-9]+' ; then + DESCRIBE=$(git describe --match 'v*') + if ! VERSION=$(echo "$DESCRIBE" | grep -oP '(?<=^v)[0-9]+\.[0-9]+\.[0-9]+') ; then + fatal "Could not infer latest $BRANCH version from $DESCRIBE" + fi + TAGS="$VERSION latest" + else + VERSION="$BRANCH" + TAGS="$VERSION" + fi + ;; +esac + +for TAG in $TAGS ; do + echo ">>> Publishing $PRODUCT $VERSION to $1/docs/$PRODUCT/$TAG" + wordepress \ + --url "$1" --user "$2" --password "$3" \ + --product "$PRODUCT" --version "$VERSION" --tag "$TAG" \ + publish site +done From e9749a5383582b87d8140f2f44afcecdfab95b89 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 18 May 2016 14:08:05 +0100 Subject: [PATCH 57/92] Make scheduler aware of test parallelisation --- .gitignore | 2 ++ sched | 26 ++++++++++++++------------ scheduler/main.py | 15 ++++++++++++++- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index f5c83426c..b6ea60f8f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ cover/cover socks/proxy socks/image.tar runner/runner +*.pyc +*~ diff --git a/sched b/sched index e94e8af8f..cf47773e5 100755 --- a/sched +++ b/sched @@ -1,36 +1,38 @@ #!/usr/bin/python import sys, string, json, urllib import requests +import optparse -BASE_URL="http://positive-cocoa-90213.appspot.com" - -def test_time(test_name, runtime): - r = requests.post(BASE_URL + "/record/%s/%f" % (urllib.quote(test_name, safe=""), runtime)) +def test_time(target, test_name, runtime): + r = requests.post(target + "/record/%s/%f" % (urllib.quote(test_name, safe=""), runtime)) print r.text assert r.status_code == 204 -def test_sched(test_run, shard_count, shard_id): +def test_sched(target, test_run, shard_count, shard_id): tests = json.dumps({'tests': string.split(sys.stdin.read())}) - r = requests.post(BASE_URL + "/schedule/%s/%d/%d" % (test_run, shard_count, shard_id), data=tests) + r = requests.post(target + "/schedule/%s/%d/%d" % (test_run, shard_count, shard_id), data=tests) assert r.status_code == 200 result = r.json() for test in sorted(result['tests']): print test def usage(): - print "%s " % sys.argv[0] + print "%s (--target=...) " % sys.argv[0] print " time " print " sched " def main(): - if len(sys.argv) < 4: + parser = optparse.OptionParser() + parser.add_option('--target', default="http://positive-cocoa-90213.appspot.com") + options, args = parser.parse_args() + if len(args) < 3: usage() sys.exit(1) - if sys.argv[1] == "time": - test_time(sys.argv[2], float(sys.argv[3])) - elif sys.argv[1] == "sched": - test_sched(sys.argv[2], int(sys.argv[3]), int(sys.argv[4])) + if args[0] == "time": + test_time(options.target, args[1], float(args[2])) + elif args[0] == "sched": + test_sched(options.target, args[1], int(args[2]), int(args[3])) else: usage() diff --git a/scheduler/main.py b/scheduler/main.py index f509f0e1f..8907e202d 100644 --- a/scheduler/main.py +++ b/scheduler/main.py @@ -23,6 +23,19 @@ class Test(ndb.Model): total_run_time = ndb.FloatProperty(default=0.) # Not total, but a EWMA total_runs = ndb.IntegerProperty(default=0) + def parallelism(self): + name = self.key.string_id() + m = re.search('(\d+)_test.sh$', name) + if m is None: + return 1 + else: + return int(m.group(1)) + + def cost(self): + p = self.parallelism() + logging.info("Test %s has parallelism %d and avg run time %s", self.key.string_id(), p, self.total_run_time) + return self.parallelism() * self.total_run_time + class Schedule(ndb.Model): shards = ndb.JsonProperty() @@ -52,7 +65,7 @@ def schedule(test_run, shard_count, shard): test_times = ndb.get_multi(ndb.Key(Test, test_name) for test_name in test_names) def avg(test): if test is not None: - return test.total_run_time + return test.cost() return 1 test_times = [(test_name, avg(test)) for test_name, test in zip(test_names, test_times)] test_times_dict = dict(test_times) From 2da55ceef2d033bb84062b575fd306bba59d638d Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sat, 4 Jun 2016 14:28:52 +0000 Subject: [PATCH 58/92] Don't spell-check compressed files --- lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint b/lint index 7d539f5da..771ea4a60 100755 --- a/lint +++ b/lint @@ -44,7 +44,7 @@ function spell_check { if file $filename | grep executable >/dev/null 2>&1; then return $lint_result fi - if [[ $filename == *".tar" ]]; then + if [[ $filename == *".tar" || $filename == *".gz" ]]; then return $lint_result fi From f2e40b45c83b47177a33c9fc53137ecc33e5510e Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 10 Jun 2016 17:53:36 +0100 Subject: [PATCH 59/92] Time out commands after three minutes This means we get to see their output up to the point they timed out, and also means that we avoid CircleCI killing the entire job after five minutes of inactivity. --- runner/runner.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/runner/runner.go b/runner/runner.go index dee4ba638..54b45feb3 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -33,6 +33,7 @@ var ( useScheduler = false runParallel = false verbose = false + timeout = 180 // In seconds. Three minutes ought to be enough for any test consoleLock = sync.Mutex{} ) @@ -96,7 +97,16 @@ func (t test) run(hosts []string) bool { } start := time.Now() - err := cmd.Run() + var err error + + c := make(chan error, 1) + go func() { c <- cmd.Run() }() + select { + case err = <-c: + case <-time.After(time.Duration(timeout) * time.Second): + err = fmt.Errorf("timed out") + } + duration := float64(time.Now().Sub(start)) / float64(time.Second) consoleLock.Lock() @@ -245,6 +255,7 @@ func main() { mflag.BoolVar(&runParallel, []string{"parallel"}, false, "Run tests in parallel on hosts where possible") mflag.BoolVar(&verbose, []string{"v"}, false, "Print output from all tests (Also enabled via DEBUG=1)") mflag.StringVar(&schedulerHost, []string{"scheduler-host"}, defaultSchedulerHost, "Hostname of scheduler.") + mflag.IntVar(&timeout, []string{"timeout"}, 180, "Max time to run one test for, in seconds") mflag.Parse() if len(os.Getenv("DEBUG")) > 0 { From 1b64e461c2dffb77a289f73d52e28d3917bf21e7 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 7 Jul 2016 16:46:31 +0100 Subject: [PATCH 60/92] Add Weave Cloud client --- cmd/wcloud/cli.go | 178 +++++++++++++++++++++++++++++++++++++++++++ cmd/wcloud/client.go | 131 +++++++++++++++++++++++++++++++ cmd/wcloud/types.go | 24 ++++++ 3 files changed, 333 insertions(+) create mode 100644 cmd/wcloud/cli.go create mode 100644 cmd/wcloud/client.go create mode 100644 cmd/wcloud/types.go diff --git a/cmd/wcloud/cli.go b/cmd/wcloud/cli.go new file mode 100644 index 000000000..e9dd1aa39 --- /dev/null +++ b/cmd/wcloud/cli.go @@ -0,0 +1,178 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/olekukonko/tablewriter" + "gopkg.in/yaml.v2" +) + +func env(key, def string) string { + if val, ok := os.LookupEnv(key); ok { + return val + } + return def +} + +var ( + token = env("SERVICE_TOKEN", "") + baseURL = env("BASE_URL", "https://cloud.weave.works") +) + +func usage() { + fmt.Println(`Usage: + deploy : Deploy image to your configured env + list List recent deployments + config () Get (or set) the configured env + logs Show lots for the given deployment`) +} + +func main() { + if len(os.Args) <= 1 { + usage() + os.Exit(1) + } + + c := NewClient(token, baseURL) + + switch os.Args[1] { + case "deploy": + deploy(c, os.Args[2:]) + case "list": + list(c, os.Args[2:]) + case "config": + config(c, os.Args[2:]) + case "logs": + logs(c, os.Args[2:]) + case "help": + usage() + default: + usage() + } +} + +func deploy(c Client, args []string) { + if len(args) != 1 { + usage() + return + } + parts := strings.SplitN(args[0], ":", 2) + if len(parts) < 2 { + usage() + return + } + deployment := Deployment{ + ImageName: parts[0], + Version: parts[1], + } + if err := c.Deploy(deployment); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } +} + +func list(c Client, args []string) { + flags := flag.NewFlagSet("list", flag.ContinueOnError) + page := flags.Int("page", 0, "Zero based index of page to list.") + pagesize := flags.Int("page-size", 10, "Number of results per page") + if err := flags.Parse(args); err != nil { + usage() + return + } + deployments, err := c.GetDeployments(*page, *pagesize) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Created", "ID", "Image", "Version", "State"}) + table.SetBorder(false) + table.SetColumnSeparator(" ") + for _, deployment := range deployments { + table.Append([]string{ + deployment.CreatedAt.Format(time.RFC822), + deployment.ID, + deployment.ImageName, + deployment.Version, + deployment.State, + }) + } + table.Render() +} + +func loadConfig(filename string) (*Config, error) { + extension := filepath.Ext(filename) + var config Config + buf, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + if extension == ".yaml" || extension == ".yml" { + if err := yaml.Unmarshal(buf, &config); err != nil { + return nil, err + } + } else { + if err := json.NewDecoder(bytes.NewReader(buf)).Decode(&config); err != nil { + return nil, err + } + } + return &config, nil +} + +func config(c Client, args []string) { + if len(args) > 1 { + usage() + return + } + + if len(args) == 1 { + config, err := loadConfig(args[0]) + if err != nil { + fmt.Println("Error reading config:", err) + os.Exit(1) + } + + if err := c.SetConfig(config); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + } else { + config, err := c.GetConfig() + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + buf, err := yaml.Marshal(config) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + fmt.Println(string(buf)) + } +} + +func logs(c Client, args []string) { + if len(args) != 1 { + usage() + return + } + + output, err := c.GetLogs(args[0]) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + fmt.Println(string(output)) +} diff --git a/cmd/wcloud/client.go b/cmd/wcloud/client.go new file mode 100644 index 000000000..7f21b817e --- /dev/null +++ b/cmd/wcloud/client.go @@ -0,0 +1,131 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" +) + +// Client for the deployment service +type Client struct { + token string + baseURL string +} + +// NewClient makes a new Client +func NewClient(token, baseURL string) Client { + return Client{ + token: token, + baseURL: baseURL, + } +} + +func (c Client) newRequest(method, path string, body io.Reader) (*http.Request, error) { + req, err := http.NewRequest(method, c.baseURL+path, body) + if err != nil { + return nil, err + } + req.Header.Add("Authorization", fmt.Sprintf("Scope-Probe token=%s", c.token)) + return req, nil +} + +// Deploy notifies the deployment service about a new deployment +func (c Client) Deploy(deployment Deployment) error { + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(deployment); err != nil { + return err + } + req, err := c.newRequest("POST", "/api/deploy", &buf) + if err != nil { + return err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + if res.StatusCode != 204 { + return fmt.Errorf("Error making request: %s", res.Status) + } + return nil +} + +// GetDeployments returns a list of deployments +func (c Client) GetDeployments(page, pagesize int) ([]Deployment, error) { + req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy?page=%d&pagesize=%d", page, pagesize), nil) + if err != nil { + return nil, err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("Error making request: %s", res.Status) + } + var response struct { + Deployments []Deployment `json:"deployments"` + } + if err := json.NewDecoder(res.Body).Decode(&response); err != nil { + return nil, err + } + return response.Deployments, nil +} + +// GetConfig returns the current Config +func (c Client) GetConfig() (*Config, error) { + req, err := c.newRequest("GET", "/api/config/deploy", nil) + if err != nil { + return nil, err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("Error making request: %s", res.Status) + } + var config Config + if err := json.NewDecoder(res.Body).Decode(&config); err != nil { + return nil, err + } + return &config, nil +} + +// SetConfig sets the current Config +func (c Client) SetConfig(config *Config) error { + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(config); err != nil { + return err + } + req, err := c.newRequest("POST", "/api/config/deploy", &buf) + if err != nil { + return err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + if res.StatusCode != 204 { + return fmt.Errorf("Error making request: %s", res.Status) + } + return nil +} + +// GetLogs returns the logs for a given deployment. +func (c Client) GetLogs(deployID string) ([]byte, error) { + req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy/%s/log", deployID), nil) + if err != nil { + return nil, err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("Error making request: %s", res.Status) + } + return ioutil.ReadAll(res.Body) +} diff --git a/cmd/wcloud/types.go b/cmd/wcloud/types.go new file mode 100644 index 000000000..16e6b2622 --- /dev/null +++ b/cmd/wcloud/types.go @@ -0,0 +1,24 @@ +package main + +import ( + "time" +) + +// Deployment describes a deployment +type Deployment struct { + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + ImageName string `json:"image_name"` + Version string `json:"version"` + Priority int `json:"priority"` + State string `json:"status"` + LogKey string `json:"-"` +} + +// Config for the deployment system for a user. +type Config struct { + RepoURL string `json:"repo_url" yaml:"repo_url"` + RepoPath string `json:"repo_path" yaml:"repo_path"` + RepoKey string `json:"repo_key" yaml:"repo_key"` + KubeconfigPath string `json:"kubeconfig_path" yaml:"kubeconfig_path"` +} From d9ab133a8507e59d171ce8d43d642083883108e5 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 8 Jul 2016 14:08:20 +0100 Subject: [PATCH 61/92] Script for finding files with a given type --- files-with-type | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100755 files-with-type diff --git a/files-with-type b/files-with-type new file mode 100755 index 000000000..d969f4405 --- /dev/null +++ b/files-with-type @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# +# Find all files with a given MIME type. +# +# e.g. +# $ files-with-type text/x-shellscript k8s infra +# +# Assumes `find`, `xargs`, and `file` are all installed. + +mime_type=$1 +shift + +find "$@" -print0 -type f |xargs -0 file --mime-type | grep "${mime_type}" | sed -e 's/:.*$//' From 239935c063acefee3b3ced37cb82d4e9f46d041c Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 8 Jul 2016 14:19:51 +0100 Subject: [PATCH 62/92] shell-lint tool --- README.md | 4 ++++ shell-lint | 13 +++++++++++++ 2 files changed, 17 insertions(+) create mode 100755 shell-lint diff --git a/README.md b/README.md index 32ddb57b1..e570ef717 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,15 @@ Included in this repo are tools shared by weave.git and scope.git. They include - ```cover```: a tool which merges overlapping coverage reports generated by go test +- ```files-with-type```: a tool to search directories for files of a given + MIME type - ```lint```: a script to lint Go project; runs various tools like golint, go vet, errcheck etc - ```rebuild-image```: a script to rebuild docker images when their input files change; useful when you using docker images to build your software, but you don't want to build the image every time. +- ```shell-lint```: a script to lint multiple shell files with + [shellcheck](http://www.shellcheck.net/) - ```socks```: a simple, dockerised SOCKS proxy for getting your laptop onto the Weave network - ```test```: a script to run all go unit tests in subdirectories, gather the diff --git a/shell-lint b/shell-lint new file mode 100755 index 000000000..5cc77cb2a --- /dev/null +++ b/shell-lint @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# +# Lint all shell files in given directories with `shellcheck`. +# +# e.g. +# $ shell-lint infra k8s +# +# Depends on: +# - shellcheck +# - files-with-type +# - file >= 5.22 + +"$(dirname "${BASH_SOURCE[0]}")/files-with-type" text/x-shellscript "$@" | xargs shellcheck From 8c6170d292d34923d23d5eef6b6ac5e4af6b7188 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 8 Jul 2016 14:54:40 +0100 Subject: [PATCH 63/92] Fix lint in all the build-tools scripts --- cover/gather_coverage.sh | 11 +++-- integration/assert.sh | 20 ++++----- integration/gce.sh | 88 ++++++++++++++++++------------------- integration/run_all.sh | 20 +++++---- integration/sanity_check.sh | 14 +++--- lint | 12 ++--- publish-site | 2 +- rebuild-image | 26 ++++++----- runner/runner.go | 8 +++- socks/connect.sh | 11 +++-- test | 49 +++++++++++---------- 11 files changed, 137 insertions(+), 124 deletions(-) diff --git a/cover/gather_coverage.sh b/cover/gather_coverage.sh index 9026745a1..271ac7d40 100755 --- a/cover/gather_coverage.sh +++ b/cover/gather_coverage.sh @@ -3,19 +3,18 @@ # merges them and produces a complete report. set -ex -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DESTINATION=$1 FROMDIR=$2 -mkdir -p $DESTINATION +mkdir -p "$DESTINATION" if [ -n "$CIRCLECI" ]; then - for i in $(seq 1 $(($CIRCLE_NODE_TOTAL - 1))); do - scp node$i:$FROMDIR/* $DESTINATION || true + for i in $(seq 1 $((CIRCLE_NODE_TOTAL - 1))); do + scp "node$i:$FROMDIR"/* "$DESTINATION" || true done fi go get github.com/weaveworks/build-tools/cover -cover $DESTINATION/* >profile.cov +cover "$DESTINATION"/* >profile.cov go tool cover -html=profile.cov -o coverage.html go tool cover -func=profile.cov -o coverage.txt -tar czf coverage.tar.gz $DESTINATION +tar czf coverage.tar.gz "$DESTINATION" diff --git a/integration/assert.sh b/integration/assert.sh index 1a2f1f778..da689b3eb 100644 --- a/integration/assert.sh +++ b/integration/assert.sh @@ -24,14 +24,14 @@ export INVARIANT=${INVARIANT:-} export CONTINUE=${CONTINUE:-} args="$(getopt -n "$0" -l \ - verbose,help,stop,discover,invariant,continue vhxdic $*)" \ + verbose,help,stop,discover,invariant,continue vhxdic "$@")" \ || exit -1 for arg in $args; do case "$arg" in -h) echo "$0 [-vxidc]" \ "[--verbose] [--stop] [--invariant] [--discover] [--continue]" - echo "`sed 's/./ /g' <<< "$0"` [-h] [--help]" + echo "$(sed 's/./ /g' <<< "$0") [-h] [--help]" exit 0;; --help) cat < /dev/null 2>&1 || status=$? + (eval "$1" <<< "${3:-}") > /dev/null 2>&1 || status=$? expected=${2:-0} if [[ "$status" -eq "$expected" ]]; then [[ -z "$DEBUG" ]] || echo -n . @@ -143,7 +143,7 @@ _assert_fail() { skip_if() { # skip_if - (eval $@) > /dev/null 2>&1 && status=0 || status=$? + (eval "$@") > /dev/null 2>&1 && status=0 || status=$? [[ "$status" -eq 0 ]] || return skip } diff --git a/integration/gce.sh b/integration/gce.sh index bb6f95efa..1f1019fce 100755 --- a/integration/gce.sh +++ b/integration/gce.sh @@ -7,15 +7,15 @@ set -e -: ${KEY_FILE:=/tmp/gce_private_key.json} -: ${SSH_KEY_FILE:=$HOME/.ssh/gce_ssh_key} -: ${IMAGE:=ubuntu-14-04} -: ${ZONE:=us-central1-a} -: ${PROJECT:=} -: ${TEMPLATE_NAME:=} -: ${NUM_HOSTS:=} +: "${KEY_FILE:=/tmp/gce_private_key.json}" +: "${SSH_KEY_FILE:=$HOME/.ssh/gce_ssh_key}" +: "${IMAGE:=ubuntu-14-04}" +: "${ZONE:=us-central1-a}" +: "${PROJECT:=}" +: "${TEMPLATE_NAME:=}" +: "${NUM_HOSTS:=}" -if [ -z "${PROJECT}" -o -z "${NUM_HOSTS}" -o -z "${TEMPLATE_NAME}" ]; then +if [ -z "${PROJECT}" ] || [ -z "${NUM_HOSTS}" ] || [ -z "${TEMPLATE_NAME}" ]; then echo "Must specify PROJECT, NUM_HOSTS and TEMPLATE_NAME" exit 1 fi @@ -26,21 +26,21 @@ if [ -n "$CIRCLECI" ]; then fi # Setup authentication -gcloud auth activate-service-account --key-file $KEY_FILE 1>/dev/null -gcloud config set project $PROJECT +gcloud auth activate-service-account --key-file "$KEY_FILE" 1>/dev/null +gcloud config set project "$PROJECT" function vm_names { local names= - for i in $(seq 1 $NUM_HOSTS); do - names="host$i$SUFFIX $names" + for i in $(seq 1 "$NUM_HOSTS"); do + names=( "host$i$SUFFIX" "${names[@]}" ) done - echo "$names" + echo "${names[@]}" } # Delete all vms in this account function destroy { names="$(vm_names)" - if [ $(gcloud compute instances list --zone $ZONE -q $names | wc -l) -le 1 ] ; then + if [ "$(gcloud compute instances list --zone "$ZONE" -q "$names" | wc -l)" -le 1 ] ; then return 0 fi for i in {0..10}; do @@ -60,23 +60,23 @@ function destroy { } function internal_ip { - jq -r ".[] | select(.name == \"$2\") | .networkInterfaces[0].networkIP" $1 + jq -r ".[] | select(.name == \"$2\") | .networkInterfaces[0].networkIP" "$1" } function external_ip { - jq -r ".[] | select(.name == \"$2\") | .networkInterfaces[0].accessConfigs[0].natIP" $1 + jq -r ".[] | select(.name == \"$2\") | .networkInterfaces[0].accessConfigs[0].natIP" "$1" } function try_connect { for i in {0..10}; do - ssh -t $1 true && return + ssh -t "$1" true && return sleep 2 done } function install_docker_on { name=$1 - ssh -t $name sudo bash -x -s <>/etc/hosts\"" + ssh -t "$hostname" "sudo -- sh -c \"cat >>/etc/hosts\"" < "$hosts" } # Create new set of VMs function setup { destroy - names="$(vm_names)" - gcloud compute instances create $names --image $TEMPLATE_NAME --zone $ZONE - gcloud compute config-ssh --ssh-key-file $SSH_KEY_FILE + names=( $(vm_names) ) + gcloud compute instances create "${names[@]}" --image "$TEMPLATE_NAME" --zone "$ZONE" + gcloud compute config-ssh --ssh-key-file "$SSH_KEY_FILE" sed -i '/UserKnownHostsFile=\/dev\/null/d' ~/.ssh/config # build an /etc/hosts file for these vms hosts=$(mktemp hosts.XXXXXXXXXX) json=$(mktemp json.XXXXXXXXXX) - gcloud compute instances list --format=json >$json - for name in $names; do - echo "$(internal_ip $json $name) $name.$ZONE.$PROJECT" >>$hosts + gcloud compute instances list --format=json > "$json" + for name in "${names[@]}"; do + echo "$(internal_ip "$json" "$name") $name.$ZONE.$PROJECT" >> "$hosts" done - for name in $names; do + for name in "${names[@]}"; do hostname="$name.$ZONE.$PROJECT" # Add the remote ip to the local /etc/hosts sudo sed -i "/$hostname/d" /etc/hosts - sudo sh -c "echo \"$(external_ip $json $name) $hostname\" >>/etc/hosts" - try_connect $hostname + sudo sh -c "echo \"$(external_ip "$json" "$name") $hostname\" >>/etc/hosts" + try_connect "$hostname" - copy_hosts $hostname $hosts & + copy_hosts "$hostname" "$hosts" & done wait - rm $hosts $json + rm "$hosts" "$json" } function make_template { - gcloud compute instances create $TEMPLATE_NAME --image $IMAGE --zone $ZONE - gcloud compute config-ssh --ssh-key-file $SSH_KEY_FILE + gcloud compute instances create "$TEMPLATE_NAME" --image "$IMAGE" --zone "$ZONE" + gcloud compute config-ssh --ssh-key-file "$SSH_KEY_FILE" name="$TEMPLATE_NAME.$ZONE.$PROJECT" - try_connect $name - install_docker_on $name - gcloud -q compute instances delete $TEMPLATE_NAME --keep-disks boot --zone $ZONE - gcloud compute images create $TEMPLATE_NAME --source-disk $TEMPLATE_NAME --source-disk-zone $ZONE + try_connect "$name" + install_docker_on "$name" + gcloud -q compute instances delete "$TEMPLATE_NAME" --keep-disks boot --zone "$ZONE" + gcloud compute images create "$TEMPLATE_NAME" --source-disk "$TEMPLATE_NAME" --source-disk-zone "$ZONE" } function hosts { hosts= args= json=$(mktemp json.XXXXXXXXXX) - gcloud compute instances list --format=json >$json + gcloud compute instances list --format=json > "$json" for name in $(vm_names); do hostname="$name.$ZONE.$PROJECT" - hosts="$hostname $hosts" - args="--add-host=$hostname:$(internal_ip $json $name) $args" + hosts=( $hostname "${hosts[@]}" ) + args=( "--add-host=$hostname:$(internal_ip "$json" "$name")" "${args[@]}" ) done echo export SSH=\"ssh -l vagrant\" - echo export HOSTS=\"$hosts\" - echo export ADD_HOST_ARGS=\"$args\" - rm $json + echo "export HOSTS=\"${hosts[*]}\"" + echo "export ADD_HOST_ARGS=\"${args[*]}\"" + rm "$json" } case "$1" in @@ -170,7 +170,7 @@ destroy) make_template) # see if template exists - if ! gcloud compute images list | grep $PROJECT | grep $TEMPLATE_NAME; then + if ! gcloud compute images list | grep "$PROJECT" | grep "$TEMPLATE_NAME"; then make_template fi esac diff --git a/integration/run_all.sh b/integration/run_all.sh index 13cb82c41..cb6d93d2c 100755 --- a/integration/run_all.sh +++ b/integration/run_all.sh @@ -1,6 +1,9 @@ #!/bin/bash +set -ex + DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck disable=SC1090 . "$DIR/config.sh" whitely echo Sanity checks @@ -10,18 +13,19 @@ if ! bash "$DIR/sanity_check.sh"; then fi whitely echo ...ok -TESTS="${@:-$(find . -name '*_test.sh')}" -RUNNER_ARGS="" +# shellcheck disable=SC2068 +TESTS=( ${@:-$(find . -name '*_test.sh')} ) +RUNNER_ARGS=( ) # If running on circle, use the scheduler to work out what tests to run -if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" ]; then - RUNNER_ARGS="$RUNNER_ARGS -scheduler" +if [ -n "$CIRCLECI" ] && [ -z "$NO_SCHEDULER" ]; then + RUNNER_ARGS=( "${RUNNER_ARGS[@]}" -scheduler ) fi # If running on circle or PARALLEL is not empty, run tests in parallel -if [ -n "$CIRCLECI" -o -n "$PARALLEL" ]; then - RUNNER_ARGS="$RUNNER_ARGS -parallel" +if [ -n "$CIRCLECI" ] || [ -n "$PARALLEL" ]; then + RUNNER_ARGS=( "${RUNNER_ARGS[@]}" -parallel ) fi -make -C ${DIR}/../runner -HOSTS="$HOSTS" "${DIR}/../runner/runner" $RUNNER_ARGS $TESTS +make -C "${DIR}/../runner" +HOSTS="$HOSTS" "${DIR}/../runner/runner" "${RUNNER_ARGS[@]}" "${TESTS[@]}" diff --git a/integration/sanity_check.sh b/integration/sanity_check.sh index 592a6b77c..c88337fe8 100755 --- a/integration/sanity_check.sh +++ b/integration/sanity_check.sh @@ -1,5 +1,5 @@ #! /bin/bash - +# shellcheck disable=SC1091 . ./config.sh set -e @@ -7,7 +7,7 @@ set -e whitely echo Ping each host from the other for host in $HOSTS; do for other in $HOSTS; do - [ $host = $other ] || run_on $host $PING $other + [ "$host" = "$other" ] || run_on "$host" "$PING" "$other" done done @@ -15,12 +15,12 @@ whitely echo Check we can reach docker for host in $HOSTS; do echo - echo Host Version Info: $host - echo ===================================== + echo "Host Version Info: $host" + echo "=====================================" echo "# docker version" - docker_on $host version + docker_on "$host" version echo "# docker info" - docker_on $host info + docker_on "$host" info echo "# weave version" - weave_on $host version + weave_on "$host" version done diff --git a/lint b/lint index 771ea4a60..e807f8824 100755 --- a/lint +++ b/lint @@ -1,7 +1,7 @@ #!/bin/bash # This scipt lints go files for common errors. # -# Its runs gofmt and go vet, and optionally golint and +# It runs gofmt and go vet, and optionally golint and # gocyclo, if they are installed. # # With no arguments, it lints the current files staged @@ -41,7 +41,7 @@ function spell_check { local lint_result=0 # we don't want to spell check tar balls or binaries - if file $filename | grep executable >/dev/null 2>&1; then + if file "$filename" | grep executable >/dev/null 2>&1; then return $lint_result fi if [[ $filename == *".tar" || $filename == *".gz" ]]; then @@ -63,11 +63,11 @@ function spell_check { function test_mismatch { filename="$1" - package=$(grep '^package ' $filename | awk '{print $2}') + package=$(grep '^package ' "$filename" | awk '{print $2}') local lint_result=0 if [[ $package == "main" ]]; then - continue # in package main, all bets are off + return # in package main, all bets are off fi if [[ $filename == *"_internal_test.go" ]]; then @@ -115,7 +115,7 @@ function lint_go { # don't have it installed. Also never blocks a commit, # it just warns. if type gocyclo >/dev/null 2>&1; then - gocyclo -over 25 "${filename}" | while read line; do + gocyclo -over 25 "${filename}" | while read -r line; do echo "${filename}": higher than 25 cyclomatic complexity - "${line}" done fi @@ -158,7 +158,7 @@ function lint { function lint_files { local lint_result=0 - while read filename; do + while read -r filename; do lint "${filename}" || lint_result=1 done exit $lint_result diff --git a/publish-site b/publish-site index 4d0984b92..900eb7cb6 100755 --- a/publish-site +++ b/publish-site @@ -3,7 +3,7 @@ set -e set -o pipefail -: ${PRODUCT:=} +: "${PRODUCT:=}" fatal() { echo "$@" >&2 diff --git a/rebuild-image b/rebuild-image index 1e00cbbe0..772ecb934 100755 --- a/rebuild-image +++ b/rebuild-image @@ -5,37 +5,39 @@ set -eux IMAGENAME=$1 -SAVEDNAME=$(echo $IMAGENAME | sed "s/[\/\-]/\./g") +# shellcheck disable=SC2001 +SAVEDNAME=$(echo "$IMAGENAME" | sed "s/[\/\-]/\./g") IMAGEDIR=$2 shift 2 -INPUTFILES=$@ +INPUTFILES=( "$@" ) CACHEDIR=$HOME/docker/ # Rebuild the image rebuild() { - mkdir -p $CACHEDIR - rm $CACHEDIR/$SAVEDNAME* || true - docker build -t $IMAGENAME $IMAGEDIR - docker save $IMAGENAME:latest | gzip - > $CACHEDIR/$SAVEDNAME-$CIRCLE_SHA1.gz + mkdir -p "$CACHEDIR" + rm "$CACHEDIR/$SAVEDNAME"* || true + docker build -t "$IMAGENAME" "$IMAGEDIR" + docker save "$IMAGENAME:latest" | gzip - > "$CACHEDIR/$SAVEDNAME-$CIRCLE_SHA1.gz" } # Get the revision the cached image was build at cached_image_rev() { - find $CACHEDIR -name "$SAVEDNAME-*" -type f | sed -n 's/^[^\-]*\-\([a-z0-9]*\).gz$/\1/p' + find "$CACHEDIR" -name "$SAVEDNAME-*" -type f | sed -n 's/^[^\-]*\-\([a-z0-9]*\).gz$/\1/p' } # Have there been any revision between $1 and $2 has_changes() { local rev1=$1 local rev2=$2 - local changes=$(git diff --oneline $rev1..$rev2 -- $INPUTFILES | wc -l) + local changes + changes=$(git diff --oneline "$rev1..$rev2" -- "${INPUTFILES[@]}" | wc -l) [ "$changes" -gt 0 ] } commit_timestamp() { local rev=$1 - git show -s --format=%ct $rev + git show -s --format=%ct "$rev" } cached_revision=$(cached_image_rev) @@ -46,14 +48,14 @@ if [ -z "$cached_revision" ]; then fi echo ">>> Found cached image rev $cached_revision" -if has_changes $cached_revision $CIRCLE_SHA1 ; then +if has_changes "$cached_revision" "$CIRCLE_SHA1" ; then echo ">>> Found changes, rebuilding" rebuild exit 0 fi IMAGE_TIMEOUT="$(( 3 * 24 * 60 * 60 ))" -if [ "$(commit_timestamp $cached_revision)" -lt "${IMAGE_TIMEOUT}" ]; then +if [ "$(commit_timestamp "$cached_revision")" -lt "${IMAGE_TIMEOUT}" ]; then echo ">>> Image is more the 24hrs old; rebuilding" rebuild exit 0 @@ -61,4 +63,4 @@ fi # we didn't rebuild; import cached version echo ">>> No changes found, importing cached image" -zcat $CACHEDIR/$SAVEDNAME-$cached_revision.gz | docker load +zcat "$CACHEDIR/$SAVEDNAME-$cached_revision.gz" | docker load diff --git a/runner/runner.go b/runner/runner.go index 54b45feb3..57769d3d0 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -155,15 +155,18 @@ func getSchedule(tests []string) ([]string, error) { shardID = os.Getenv("CIRCLE_NODE_INDEX") requestBody = &bytes.Buffer{} ) + fmt.Printf("getSchedule: %v", tests) if err := json.NewEncoder(requestBody).Encode(schedule{tests}); err != nil { return []string{}, err } url := fmt.Sprintf("http://%s/schedule/%s/%s/%s", schedulerHost, testRun, shardCount, shardID) + fmt.Printf("POSTing to %v: %v", url, requestBody) resp, err := http.Post(url, jsonContentType, requestBody) if err != nil { return []string{}, err } var sched schedule + fmt.Printf("Got response: %v", resp.Body) if err := json.NewDecoder(resp.Body).Decode(&sched); err != nil { return []string{}, err } @@ -262,9 +265,10 @@ func main() { verbose = true } - tests, err := getTests(mflag.Args()) + testArgs := mflag.Args() + tests, err := getTests(testArgs) if err != nil { - fmt.Printf("Error parsing tests: %v\n", err) + fmt.Printf("Error parsing tests: %v (%v)\n", err, testArgs) os.Exit(1) } diff --git a/socks/connect.sh b/socks/connect.sh index 0d5ef84da..b6af8a6c4 100755 --- a/socks/connect.sh +++ b/socks/connect.sh @@ -10,14 +10,17 @@ fi HOST=$1 echo "Starting proxy container..." -PROXY_CONTAINER=$(ssh $HOST weave run -d weaveworks/socksproxy) +PROXY_CONTAINER=$(ssh "$HOST" weave run -d weaveworks/socksproxy) function finish { echo "Removing proxy container.." - ssh $HOST docker rm -f $PROXY_CONTAINER + # shellcheck disable=SC2029 + ssh "$HOST" docker rm -f "$PROXY_CONTAINER" } trap finish EXIT -PROXY_IP=$(ssh $HOST -- "docker inspect --format='{{.NetworkSettings.IPAddress}}' $PROXY_CONTAINER") +# shellcheck disable=SC2029 +PROXY_IP=$(ssh "$HOST" -- "docker inspect --format='{{.NetworkSettings.IPAddress}}' $PROXY_CONTAINER") echo 'Please configure your browser for proxy http://localhost:8080/proxy.pac' -ssh -L8000:$PROXY_IP:8000 -L8080:$PROXY_IP:8080 $HOST docker attach $PROXY_CONTAINER +# shellcheck disable=SC2029 +ssh "-L8000:$PROXY_IP:8000" "-L8080:$PROXY_IP:8080" "$HOST" docker attach "$PROXY_CONTAINER" diff --git a/test b/test index 5b88d5270..61791772b 100755 --- a/test +++ b/test @@ -1,9 +1,9 @@ #!/bin/bash -set -e +set -ex DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -GO_TEST_ARGS="-tags netgo -cpu 4 -timeout 8m" +GO_TEST_ARGS=( -tags netgo -cpu 4 -timeout 8m ) SLOW= NO_GO_GET= @@ -28,66 +28,67 @@ while [ $# -gt 0 ]; do esac done -if [ -n "$SLOW" -o -n "$CIRCLECI" ]; then +if [ -n "$SLOW" ] || [ -n "$CIRCLECI" ]; then SLOW=true fi if [ -n "$SLOW" ]; then - GO_TEST_ARGS="$GO_TEST_ARGS -race -covermode=atomic" + GO_TEST_ARGS=( "${GO_TEST_ARGS[@]}" -race -covermode=atomic ) + # shellcheck disable=SC2153 if [ -n "$COVERDIR" ] ; then coverdir="$COVERDIR" else coverdir=$(mktemp -d coverage.XXXXXXXXXX) fi - mkdir -p $coverdir + mkdir -p "$coverdir" fi fail=0 -TESTDIRS=$(find . -type f -name '*_test.go' | xargs -n1 dirname | grep -vE '^\./(\.git|vendor|prog|experimental)/' | sort -u) +TESTDIRS=( $(find . -type f -name '*_test.go' -print0 | xargs -0 -n1 dirname | grep -vE '^\./(\.git|vendor|prog|experimental)/' | sort -u) ) # If running on circle, use the scheduler to work out what tests to run on what shard -if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" -a -x "$DIR/sched" ]; then +if [ -n "$CIRCLECI" ] && [ -z "$NO_SCHEDULER" ] && [ -x "$DIR/sched" ]; then PREFIX=$(go list -e ./ | sed -e 's/\//-/g') - TESTDIRS=$(echo $TESTDIRS | "$DIR/sched" sched $PREFIX-$CIRCLE_BUILD_NUM $CIRCLE_NODE_TOTAL $CIRCLE_NODE_INDEX) - echo $TESTDIRS + TESTDIRS=( $(echo "${TESTDIRS[@]}" | "$DIR/sched" sched "$PREFIX-$CIRCLE_BUILD_NUM" "$CIRCLE_NODE_TOTAL" "$CIRCLE_NODE_INDEX") ) + echo "${TESTDIRS[@]}" fi PACKAGE_BASE=$(go list -e ./) -# Speed up the tests by compiling and installing their dependancies first. -go test -i $GO_TEST_ARGS $TESTDIRS +# Speed up the tests by compiling and installing their dependencies first. +go test -i "${GO_TEST_ARGS[@]}" "${TESTDIRS[@]}" -for dir in $TESTDIRS; do +for dir in "${TESTDIRS[@]}"; do if [ -z "$NO_GO_GET" ]; then - go get -t -tags netgo $dir + go get -t -tags netgo "$dir" fi - GO_TEST_ARGS_RUN="$GO_TEST_ARGS" + GO_TEST_ARGS_RUN=( "${GO_TEST_ARGS[@]}" ) if [ -n "$SLOW" ]; then - COVERPKGS=$( (go list $dir; go list -f '{{join .Deps "\n"}}' $dir | grep -v "vendor" | grep "^$PACKAGE_BASE/") | paste -s -d, -) - output=$(mktemp $coverdir/unit.XXXXXXXXXX) - GO_TEST_ARGS_RUN="$GO_TEST_ARGS -coverprofile=$output -coverpkg=$COVERPKGS" + COVERPKGS=$( (go list "$dir"; go list -f '{{join .Deps "\n"}}' "$dir" | grep -v "vendor" | grep "^$PACKAGE_BASE/") | paste -s -d, -) + output=$(mktemp "$coverdir/unit.XXXXXXXXXX") + GO_TEST_ARGS_RUN=( "${GO_TEST_ARGS[@]}" -coverprofile=$output -coverpkg=$COVERPKGS ) fi START=$(date +%s) - if ! go test $GO_TEST_ARGS_RUN $dir; then + if ! go test "${GO_TEST_ARGS_RUN[@]}" "$dir"; then fail=1 fi - RUNTIME=$(( $(date +%s) - $START )) + RUNTIME=$(( $(date +%s) - START )) # Report test runtime when running on circle, to help scheduler - if [ -n "$CIRCLECI" -a -z "$NO_SCHEDULER" -a -x "$DIR/sched" ]; then - "$DIR/sched" time $dir $RUNTIME + if [ -n "$CIRCLECI" ] && [ -z "$NO_SCHEDULER" ] && [ -x "$DIR/sched" ]; then + "$DIR/sched" time "$dir" $RUNTIME fi done -if [ -n "$SLOW" -a -z "$COVERDIR" ] ; then +if [ -n "$SLOW" ] && [ -z "$COVERDIR" ] ; then go get github.com/weaveworks/tools/cover - cover $coverdir/* >profile.cov - rm -rf $coverdir + cover "$coverdir"/* >profile.cov + rm -rf "$coverdir" go tool cover -html=profile.cov -o=coverage.html go tool cover -func=profile.cov | tail -n1 fi From 0620e589fa0bf14f17790e8b9e81c2076b68c899 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Tue, 12 Jul 2016 15:51:42 +0100 Subject: [PATCH 64/92] Review tweaks --- rebuild-image | 2 +- runner/runner.go | 3 --- socks/connect.sh | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/rebuild-image b/rebuild-image index 772ecb934..a579a47de 100755 --- a/rebuild-image +++ b/rebuild-image @@ -31,7 +31,7 @@ has_changes() { local rev1=$1 local rev2=$2 local changes - changes=$(git diff --oneline "$rev1..$rev2" -- "${INPUTFILES[@]}" | wc -l) + changes=$(git diff --oneline "$rev1..$rev2" -- "${INPUTFILES[@]}" | wc -l) [ "$changes" -gt 0 ] } diff --git a/runner/runner.go b/runner/runner.go index 57769d3d0..707369d81 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -155,18 +155,15 @@ func getSchedule(tests []string) ([]string, error) { shardID = os.Getenv("CIRCLE_NODE_INDEX") requestBody = &bytes.Buffer{} ) - fmt.Printf("getSchedule: %v", tests) if err := json.NewEncoder(requestBody).Encode(schedule{tests}); err != nil { return []string{}, err } url := fmt.Sprintf("http://%s/schedule/%s/%s/%s", schedulerHost, testRun, shardCount, shardID) - fmt.Printf("POSTing to %v: %v", url, requestBody) resp, err := http.Post(url, jsonContentType, requestBody) if err != nil { return []string{}, err } var sched schedule - fmt.Printf("Got response: %v", resp.Body) if err := json.NewDecoder(resp.Body).Decode(&sched); err != nil { return []string{}, err } diff --git a/socks/connect.sh b/socks/connect.sh index b6af8a6c4..25dcf8221 100755 --- a/socks/connect.sh +++ b/socks/connect.sh @@ -14,7 +14,7 @@ PROXY_CONTAINER=$(ssh "$HOST" weave run -d weaveworks/socksproxy) function finish { echo "Removing proxy container.." - # shellcheck disable=SC2029 + # shellcheck disable=SC2029 ssh "$HOST" docker rm -f "$PROXY_CONTAINER" } trap finish EXIT From 47a01522c62c88b893ca3d113d0e1e7353519472 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Wed, 13 Jul 2016 15:09:24 +0100 Subject: [PATCH 65/92] Only lint git files Means we don't get spurious warnings from generated Javascript files. --- lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint b/lint index e807f8824..eeca6653f 100755 --- a/lint +++ b/lint @@ -166,7 +166,7 @@ function lint_files { function list_files { if [ $# -gt 0 ]; then - find "$@" -type f | grep -vE '(^\./\.git|^\./\.pkg|/vendor/|/node_modules/|\.codecgen\.go$|\.generated\.go$)' + git ls-files --exclude-standard | grep -v '^vendor/' else git diff --cached --name-only fi From c86fd3d5bd376918ba05760a19858b8692ca8d34 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 14 Jul 2016 14:45:06 +0100 Subject: [PATCH 66/92] Add notification config for wcloud --- cmd/wcloud/types.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmd/wcloud/types.go b/cmd/wcloud/types.go index 16e6b2622..d2ad5e70c 100644 --- a/cmd/wcloud/types.go +++ b/cmd/wcloud/types.go @@ -21,4 +21,12 @@ type Config struct { RepoPath string `json:"repo_path" yaml:"repo_path"` RepoKey string `json:"repo_key" yaml:"repo_key"` KubeconfigPath string `json:"kubeconfig_path" yaml:"kubeconfig_path"` + + Notifications []NotificationConfig `json:"notification" yaml:"notification"` +} + +// NotificationConfig describes how to send notifications +type NotificationConfig struct { + SlackWebhookURL string `json:"slack_webhook_url" yaml:"slack_webhook_url"` + SlackUsername string `json:"slack_username" yaml:"slack_username"` } From b2ab3803917a34a441f27edd65f150aab6474ad4 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 14 Jul 2016 15:18:28 +0100 Subject: [PATCH 67/92] Fix field name --- cmd/wcloud/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/wcloud/types.go b/cmd/wcloud/types.go index d2ad5e70c..bb0e18660 100644 --- a/cmd/wcloud/types.go +++ b/cmd/wcloud/types.go @@ -22,7 +22,7 @@ type Config struct { RepoKey string `json:"repo_key" yaml:"repo_key"` KubeconfigPath string `json:"kubeconfig_path" yaml:"kubeconfig_path"` - Notifications []NotificationConfig `json:"notification" yaml:"notification"` + Notifications []NotificationConfig `json:"notifications" yaml:"notifications"` } // NotificationConfig describes how to send notifications From cf53dc1f19564fc3fe5fb6cf81b5eaf0282bcb77 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 14 Jul 2016 16:42:23 +0100 Subject: [PATCH 68/92] Exclude vendor from shell linting --- files-with-type | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files-with-type b/files-with-type index d969f4405..0c7dcbb09 100755 --- a/files-with-type +++ b/files-with-type @@ -10,4 +10,4 @@ mime_type=$1 shift -find "$@" -print0 -type f |xargs -0 file --mime-type | grep "${mime_type}" | sed -e 's/:.*$//' +git ls-files | grep -vE '^vendor/' | xargs file --mime-type | grep "${mime_type}" | sed -e 's/:.*$//' From 8b7ec6ebbca8d9ae751cdd79b2554087a1a1c5d4 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 14 Jul 2016 16:42:38 +0100 Subject: [PATCH 69/92] Speed up test by using git ls-files --- test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test b/test index 61791772b..55da67e3e 100755 --- a/test +++ b/test @@ -47,7 +47,7 @@ fi fail=0 -TESTDIRS=( $(find . -type f -name '*_test.go' -print0 | xargs -0 -n1 dirname | grep -vE '^\./(\.git|vendor|prog|experimental)/' | sort -u) ) +TESTDIRS=( $(git ls-files -- '*_test.go' | grep -vE '^(vendor|prog|experimental)/' | xargs -n1 dirname | sort -u) ) # If running on circle, use the scheduler to work out what tests to run on what shard if [ -n "$CIRCLECI" ] && [ -z "$NO_SCHEDULER" ] && [ -x "$DIR/sched" ]; then From 87864270eee441f25ef6d25ebe36652ddc5b4396 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 14 Jul 2016 16:42:51 +0100 Subject: [PATCH 70/92] Remove spurious debugging code from test --- test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test b/test index 55da67e3e..a59c922e2 100755 --- a/test +++ b/test @@ -1,6 +1,6 @@ #!/bin/bash -set -ex +set -e DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" GO_TEST_ARGS=( -tags netgo -cpu 4 -timeout 8m ) From bfb174766fe5d213ad2b7c9857f9680f8019b43d Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 14 Jul 2016 17:44:19 +0100 Subject: [PATCH 71/92] Test directories need ./ prefixes, obviously. --- test | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test b/test index a59c922e2..f8d76c56b 100755 --- a/test +++ b/test @@ -47,7 +47,8 @@ fi fail=0 -TESTDIRS=( $(git ls-files -- '*_test.go' | grep -vE '^(vendor|prog|experimental)/' | xargs -n1 dirname | sort -u) ) +# NB: Relies on paths being prefixed with './'. +TESTDIRS=( $(git ls-files -- '*_test.go' | grep -vE '^(vendor|prog|experimental)/' | xargs -n1 dirname | sort -u | sed -e 's|^|./|') ) # If running on circle, use the scheduler to work out what tests to run on what shard if [ -n "$CIRCLECI" ] && [ -z "$NO_SCHEDULER" ] && [ -x "$DIR/sched" ]; then From 2cfcf087a3e27f6fca008a02088e1f43385a060b Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Fri, 15 Jul 2016 11:14:11 +0100 Subject: [PATCH 72/92] Add blacklist to wcloud client --- cmd/wcloud/types.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/wcloud/types.go b/cmd/wcloud/types.go index bb0e18660..b02ee4c14 100644 --- a/cmd/wcloud/types.go +++ b/cmd/wcloud/types.go @@ -23,6 +23,9 @@ type Config struct { KubeconfigPath string `json:"kubeconfig_path" yaml:"kubeconfig_path"` Notifications []NotificationConfig `json:"notifications" yaml:"notifications"` + + // Globs of files not to change, relative to the route of the repo + ConfigFileBlackList []string `json:"config_file_black_list" yaml:"config_file_black_list"` } // NotificationConfig describes how to send notifications From c045d165bc09d67a98e50d7e06223cd29aa35e76 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 15 Jul 2016 11:19:09 +0100 Subject: [PATCH 73/92] Properly exclude vendor from lint --- files-with-type | 2 +- lint | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/files-with-type b/files-with-type index 0c7dcbb09..771380ada 100755 --- a/files-with-type +++ b/files-with-type @@ -10,4 +10,4 @@ mime_type=$1 shift -git ls-files | grep -vE '^vendor/' | xargs file --mime-type | grep "${mime_type}" | sed -e 's/:.*$//' +git ls-files "$@" | grep -vE '^vendor/' | xargs file --mime-type | grep "${mime_type}" | sed -e 's/:.*$//' diff --git a/lint b/lint index eeca6653f..994d4c321 100755 --- a/lint +++ b/lint @@ -166,7 +166,7 @@ function lint_files { function list_files { if [ $# -gt 0 ]; then - git ls-files --exclude-standard | grep -v '^vendor/' + git ls-files --exclude-standard | grep -vE '(^|/)vendor/' else git diff --cached --name-only fi From df494d6a2dc19891370200358da47843fb4ed555 Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Fri, 15 Jul 2016 11:31:58 +0100 Subject: [PATCH 74/92] Remove dependencies --- files-with-type | 2 -- 1 file changed, 2 deletions(-) diff --git a/files-with-type b/files-with-type index 771380ada..8238980c6 100755 --- a/files-with-type +++ b/files-with-type @@ -4,8 +4,6 @@ # # e.g. # $ files-with-type text/x-shellscript k8s infra -# -# Assumes `find`, `xargs`, and `file` are all installed. mime_type=$1 shift From d9a1c6cfa34dc9f24fd30e2c3c0ec978abd242c9 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 18 Jul 2016 12:54:41 +0100 Subject: [PATCH 75/92] Add wcloud events, update flags and error nicely when there is no config --- cmd/wcloud/cli.go | 33 +++++++++++++++++++++++++++++---- cmd/wcloud/client.go | 23 +++++++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/cmd/wcloud/cli.go b/cmd/wcloud/cli.go index e9dd1aa39..377d5ced3 100644 --- a/cmd/wcloud/cli.go +++ b/cmd/wcloud/cli.go @@ -52,6 +52,8 @@ func main() { config(c, os.Args[2:]) case "logs": logs(c, os.Args[2:]) + case "events": + events(c, os.Args[2:]) case "help": usage() default: @@ -80,14 +82,17 @@ func deploy(c Client, args []string) { } func list(c Client, args []string) { - flags := flag.NewFlagSet("list", flag.ContinueOnError) - page := flags.Int("page", 0, "Zero based index of page to list.") - pagesize := flags.Int("page-size", 10, "Number of results per page") + var ( + flags = flag.NewFlagSet("", flag.ContinueOnError) + since = flags.Duration("since", 7*24*time.Hour, "How far back to fetch results") + ) if err := flags.Parse(args); err != nil { usage() return } - deployments, err := c.GetDeployments(*page, *pagesize) + through := time.Now() + from := through.Add(-*since) + deployments, err := c.GetDeployments(through.Unix(), from.Unix()) if err != nil { fmt.Println(err.Error()) os.Exit(1) @@ -109,6 +114,26 @@ func list(c Client, args []string) { table.Render() } +func events(c Client, args []string) { + var ( + flags = flag.NewFlagSet("", flag.ContinueOnError) + since = flags.Duration("since", 7*24*time.Hour, "How far back to fetch results") + ) + if err := flags.Parse(args); err != nil { + usage() + return + } + through := time.Now() + from := through.Add(-*since) + events, err := c.GetEvents(through.Unix(), from.Unix()) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + fmt.Println("events: ", string(events)) +} + func loadConfig(filename string) (*Config, error) { extension := filepath.Ext(filename) var config Config diff --git a/cmd/wcloud/client.go b/cmd/wcloud/client.go index 7f21b817e..9c0df87d4 100644 --- a/cmd/wcloud/client.go +++ b/cmd/wcloud/client.go @@ -53,8 +53,8 @@ func (c Client) Deploy(deployment Deployment) error { } // GetDeployments returns a list of deployments -func (c Client) GetDeployments(page, pagesize int) ([]Deployment, error) { - req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy?page=%d&pagesize=%d", page, pagesize), nil) +func (c Client) GetDeployments(from, through int64) ([]Deployment, error) { + req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy?from=%d&through=%d", from, through), nil) if err != nil { return nil, err } @@ -74,6 +74,22 @@ func (c Client) GetDeployments(page, pagesize int) ([]Deployment, error) { return response.Deployments, nil } +// GetDeployments returns a list of deployments +func (c Client) GetEvents(from, through int64) ([]byte, error) { + req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy/from=%d&through=%d", from, through), nil) + if err != nil { + return nil, err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("Error making request: %s", res.Status) + } + return ioutil.ReadAll(res.Body) +} + // GetConfig returns the current Config func (c Client) GetConfig() (*Config, error) { req, err := c.newRequest("GET", "/api/config/deploy", nil) @@ -84,6 +100,9 @@ func (c Client) GetConfig() (*Config, error) { if err != nil { return nil, err } + if res.StatusCode == 404 { + return nil, fmt.Errorf("No configuration uploaded yet.") + } if res.StatusCode != 200 { return nil, fmt.Errorf("Error making request: %s", res.Status) } From 77355b9f62a9b25f19aac217d906002f073b08c6 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 18 Jul 2016 13:04:42 +0100 Subject: [PATCH 76/92] Lint --- cmd/wcloud/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/wcloud/client.go b/cmd/wcloud/client.go index 9c0df87d4..b8562425c 100644 --- a/cmd/wcloud/client.go +++ b/cmd/wcloud/client.go @@ -74,7 +74,7 @@ func (c Client) GetDeployments(from, through int64) ([]Deployment, error) { return response.Deployments, nil } -// GetDeployments returns a list of deployments +// GetEvents returns the raw events. func (c Client) GetEvents(from, through int64) ([]byte, error) { req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy/from=%d&through=%d", from, through), nil) if err != nil { From f2f4e5bff7e0ebc8326ba0e4fb5fd516e23c635f Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 18 Jul 2016 15:43:43 +0100 Subject: [PATCH 77/92] Fix the wcloud client --- cmd/wcloud/cli.go | 4 ++-- cmd/wcloud/client.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/wcloud/cli.go b/cmd/wcloud/cli.go index 377d5ced3..cd11bd30f 100644 --- a/cmd/wcloud/cli.go +++ b/cmd/wcloud/cli.go @@ -92,7 +92,7 @@ func list(c Client, args []string) { } through := time.Now() from := through.Add(-*since) - deployments, err := c.GetDeployments(through.Unix(), from.Unix()) + deployments, err := c.GetDeployments(from.Unix(), through.Unix()) if err != nil { fmt.Println(err.Error()) os.Exit(1) @@ -125,7 +125,7 @@ func events(c Client, args []string) { } through := time.Now() from := through.Add(-*since) - events, err := c.GetEvents(through.Unix(), from.Unix()) + events, err := c.GetEvents(from.Unix(), through.Unix()) if err != nil { fmt.Println(err.Error()) os.Exit(1) diff --git a/cmd/wcloud/client.go b/cmd/wcloud/client.go index b8562425c..d53b15589 100644 --- a/cmd/wcloud/client.go +++ b/cmd/wcloud/client.go @@ -76,7 +76,7 @@ func (c Client) GetDeployments(from, through int64) ([]Deployment, error) { // GetEvents returns the raw events. func (c Client) GetEvents(from, through int64) ([]byte, error) { - req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy/from=%d&through=%d", from, through), nil) + req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy/events?from=%d&through=%d", from, through), nil) if err != nil { return nil, err } From dda97857587e2c70bf4f8097153971e92570d335 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 20 Jul 2016 17:40:55 +0100 Subject: [PATCH 78/92] Update deploy api --- cmd/wcloud/client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/wcloud/client.go b/cmd/wcloud/client.go index d53b15589..e081aaae0 100644 --- a/cmd/wcloud/client.go +++ b/cmd/wcloud/client.go @@ -38,7 +38,7 @@ func (c Client) Deploy(deployment Deployment) error { if err := json.NewEncoder(&buf).Encode(deployment); err != nil { return err } - req, err := c.newRequest("POST", "/api/deploy", &buf) + req, err := c.newRequest("POST", "/api/deploy/deploy", &buf) if err != nil { return err } @@ -54,7 +54,7 @@ func (c Client) Deploy(deployment Deployment) error { // GetDeployments returns a list of deployments func (c Client) GetDeployments(from, through int64) ([]Deployment, error) { - req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy?from=%d&through=%d", from, through), nil) + req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy/deploy?from=%d&through=%d", from, through), nil) if err != nil { return nil, err } @@ -76,7 +76,7 @@ func (c Client) GetDeployments(from, through int64) ([]Deployment, error) { // GetEvents returns the raw events. func (c Client) GetEvents(from, through int64) ([]byte, error) { - req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy/events?from=%d&through=%d", from, through), nil) + req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy/event?from=%d&through=%d", from, through), nil) if err != nil { return nil, err } From 7e850f87dfaab65f14b5d641ce5d50d8e6a8a540 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 21 Jul 2016 17:10:01 +0100 Subject: [PATCH 79/92] Fix logs path --- cmd/wcloud/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/wcloud/client.go b/cmd/wcloud/client.go index e081aaae0..57836cd9a 100644 --- a/cmd/wcloud/client.go +++ b/cmd/wcloud/client.go @@ -135,7 +135,7 @@ func (c Client) SetConfig(config *Config) error { // GetLogs returns the logs for a given deployment. func (c Client) GetLogs(deployID string) ([]byte, error) { - req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy/%s/log", deployID), nil) + req, err := c.newRequest("GET", fmt.Sprintf("/api/deploy/deploy/%s/log", deployID), nil) if err != nil { return nil, err } From 5312c40075c731a7fe74e13f5763acb57a61b712 Mon Sep 17 00:00:00 2001 From: Mike Lang Date: Fri, 12 Aug 2016 14:30:39 +0100 Subject: [PATCH 80/92] Import image-tag script into build tools so it can be shared We have divergent versions of this script across repos. This particular version comes from 6334836@scope. We in particular want this version's changes to support branches with a '/' character in them. --- image-tag | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100755 image-tag diff --git a/image-tag b/image-tag new file mode 100755 index 000000000..edee35930 --- /dev/null +++ b/image-tag @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +WORKING_SUFFIX=$(if ! git diff --exit-code --quiet HEAD >&2; \ + then echo "-WIP"; \ + else echo ""; \ + fi) +BRANCH_PREFIX=$(git rev-parse --abbrev-ref HEAD) +echo "${BRANCH_PREFIX//\//-}-$(git rev-parse --short HEAD)$WORKING_SUFFIX" From a781575611cdda5952e15e9d708c7b5f1e57b331 Mon Sep 17 00:00:00 2001 From: Mike Lang Date: Wed, 17 Aug 2016 14:20:24 +0100 Subject: [PATCH 81/92] shell-lint: Don't fail if no shell scripts found This fixes the case where the provided paths do not contain any shell scripts, ie. xargs is passed no values. In this case by default, xargs invokes the command once with no args. This causes shellcheck to print usage and exit failure. The GNU-specific --no-run-if-empty changes the behaviour so that in this case, shellcheck is simply never run. This allows us to use this script as a general lint even in contexts where no shell scripts are present, or none of the files passed are shell scripts. --- shell-lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell-lint b/shell-lint index 5cc77cb2a..fffbb8b11 100755 --- a/shell-lint +++ b/shell-lint @@ -10,4 +10,4 @@ # - files-with-type # - file >= 5.22 -"$(dirname "${BASH_SOURCE[0]}")/files-with-type" text/x-shellscript "$@" | xargs shellcheck +"$(dirname "${BASH_SOURCE[0]}")/files-with-type" text/x-shellscript "$@" | xargs --no-run-if-empty shellcheck From e8e2b698e5c34ae08137c7b3d484c387a5e87d03 Mon Sep 17 00:00:00 2001 From: Mike Lang Date: Wed, 17 Aug 2016 14:28:28 +0100 Subject: [PATCH 82/92] integrations: Fix a shellcheck linter error --- integration/gce.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/integration/gce.sh b/integration/gce.sh index 1f1019fce..dd0e92770 100755 --- a/integration/gce.sh +++ b/integration/gce.sh @@ -39,6 +39,7 @@ function vm_names { # Delete all vms in this account function destroy { + local names names="$(vm_names)" if [ "$(gcloud compute instances list --zone "$ZONE" -q "$names" | wc -l)" -le 1 ] ; then return 0 From e6876d1cec554d2a7e9238725555d694b534eeee Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Aug 2016 15:05:44 +0100 Subject: [PATCH 83/92] Add template fields to wcloud config. --- cmd/wcloud/types.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/wcloud/types.go b/cmd/wcloud/types.go index b02ee4c14..74fec5eeb 100644 --- a/cmd/wcloud/types.go +++ b/cmd/wcloud/types.go @@ -12,7 +12,6 @@ type Deployment struct { Version string `json:"version"` Priority int `json:"priority"` State string `json:"status"` - LogKey string `json:"-"` } // Config for the deployment system for a user. @@ -26,10 +25,13 @@ type Config struct { // Globs of files not to change, relative to the route of the repo ConfigFileBlackList []string `json:"config_file_black_list" yaml:"config_file_black_list"` + + CommitMessageTemplate string `json:"commit_message_template" yaml:"commit_message_template"` // See https://golang.org/pkg/text/template/ } // NotificationConfig describes how to send notifications type NotificationConfig struct { SlackWebhookURL string `json:"slack_webhook_url" yaml:"slack_webhook_url"` SlackUsername string `json:"slack_username" yaml:"slack_username"` + MessageTemplate string `json:"message_template" yaml:"message_template"` } From 7acfbd7034bb340eaf59a3ad6399c435696db733 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 17 Aug 2016 15:11:04 +0100 Subject: [PATCH 84/92] Propagate the local username --- cmd/wcloud/cli.go | 11 +++++++++-- cmd/wcloud/types.go | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cmd/wcloud/cli.go b/cmd/wcloud/cli.go index cd11bd30f..9cf3080dd 100644 --- a/cmd/wcloud/cli.go +++ b/cmd/wcloud/cli.go @@ -7,6 +7,7 @@ import ( "fmt" "io/ioutil" "os" + "os/user" "path/filepath" "strings" "time" @@ -71,9 +72,15 @@ func deploy(c Client, args []string) { usage() return } + user, err := user.Current() + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } deployment := Deployment{ - ImageName: parts[0], - Version: parts[1], + ImageName: parts[0], + Version: parts[1], + TriggeringUser: user.Username, } if err := c.Deploy(deployment); err != nil { fmt.Println(err.Error()) diff --git a/cmd/wcloud/types.go b/cmd/wcloud/types.go index 74fec5eeb..2b30f25df 100644 --- a/cmd/wcloud/types.go +++ b/cmd/wcloud/types.go @@ -12,6 +12,8 @@ type Deployment struct { Version string `json:"version"` Priority int `json:"priority"` State string `json:"status"` + + TriggeringUser string `json:"triggering_user"` } // Config for the deployment system for a user. From 9f760ab1fbf1867d582f5b5d968ecfdd3fa28a07 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 18 Aug 2016 11:31:22 +0100 Subject: [PATCH 85/92] Allow wcloud users to override username --- cmd/wcloud/cli.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/cmd/wcloud/cli.go b/cmd/wcloud/cli.go index 9cf3080dd..824d4ff00 100644 --- a/cmd/wcloud/cli.go +++ b/cmd/wcloud/cli.go @@ -63,6 +63,15 @@ func main() { } func deploy(c Client, args []string) { + var ( + flags = flag.NewFlagSet("", flag.ContinueOnError) + username = flags.String("u", "", "Username to report to deploy service (default with be current user)") + ) + if err := flags.Parse(args); err != nil { + usage() + return + } + args = flags.Args() if len(args) != 1 { usage() return @@ -72,15 +81,18 @@ func deploy(c Client, args []string) { usage() return } - user, err := user.Current() - if err != nil { - fmt.Println(err.Error()) - os.Exit(1) + if *username == "" { + user, err := user.Current() + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + *username = user.Username } deployment := Deployment{ ImageName: parts[0], Version: parts[1], - TriggeringUser: user.Username, + TriggeringUser: *username, } if err := c.Deploy(deployment); err != nil { fmt.Println(err.Error()) From 92cd0b84adebeb94cc8818a8858e02b114f2da0a Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Wed, 24 Aug 2016 10:16:44 +0100 Subject: [PATCH 86/92] [wcloud] Add support for repo_branch option --- cmd/wcloud/types.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/wcloud/types.go b/cmd/wcloud/types.go index 2b30f25df..317ad81de 100644 --- a/cmd/wcloud/types.go +++ b/cmd/wcloud/types.go @@ -20,6 +20,7 @@ type Deployment struct { type Config struct { RepoURL string `json:"repo_url" yaml:"repo_url"` RepoPath string `json:"repo_path" yaml:"repo_path"` + RepoBranch string `json:"repo_branch" yaml:"repo_branch"` RepoKey string `json:"repo_key" yaml:"repo_key"` KubeconfigPath string `json:"kubeconfig_path" yaml:"kubeconfig_path"` From 3fe92f503c960222b2111c7940a6328906074b2f Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 25 Aug 2016 09:51:58 +0200 Subject: [PATCH 87/92] Add wcloud deploy --service flag --- cmd/wcloud/cli.go | 22 +++++++++++++++++++--- cmd/wcloud/types.go | 3 ++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/cmd/wcloud/cli.go b/cmd/wcloud/cli.go index 824d4ff00..2806d69fa 100644 --- a/cmd/wcloud/cli.go +++ b/cmd/wcloud/cli.go @@ -16,6 +16,19 @@ import ( "gopkg.in/yaml.v2" ) +// ArrayFlags allows you to collect repeated flags +type ArrayFlags []string + +func (a *ArrayFlags) String() string { + return strings.Join(*a, ",") +} + +// Set implements flags.Value +func (a *ArrayFlags) Set(value string) error { + *a = append(*a, value) + return nil +} + func env(key, def string) string { if val, ok := os.LookupEnv(key); ok { return val @@ -66,7 +79,9 @@ func deploy(c Client, args []string) { var ( flags = flag.NewFlagSet("", flag.ContinueOnError) username = flags.String("u", "", "Username to report to deploy service (default with be current user)") + services ArrayFlags ) + flag.Var(&services, "service", "Service to update (can be repeated)") if err := flags.Parse(args); err != nil { usage() return @@ -90,9 +105,10 @@ func deploy(c Client, args []string) { *username = user.Username } deployment := Deployment{ - ImageName: parts[0], - Version: parts[1], - TriggeringUser: *username, + ImageName: parts[0], + Version: parts[1], + TriggeringUser: *username, + IntendedServices: service, } if err := c.Deploy(deployment); err != nil { fmt.Println(err.Error()) diff --git a/cmd/wcloud/types.go b/cmd/wcloud/types.go index 317ad81de..178655c28 100644 --- a/cmd/wcloud/types.go +++ b/cmd/wcloud/types.go @@ -13,7 +13,8 @@ type Deployment struct { Priority int `json:"priority"` State string `json:"status"` - TriggeringUser string `json:"triggering_user"` + TriggeringUser string `json:"triggering_user"` + IntendedServices []string `json:"intended_services"` } // Config for the deployment system for a user. From ef559010df0df16d840e894955a77f6a84867253 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 25 Aug 2016 10:46:53 +0200 Subject: [PATCH 88/92] Review feedback, and build wcloud in circle. --- circle.yml | 1 + cmd/wcloud/cli.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 0c1ebddb1..fb3c7ce8e 100644 --- a/circle.yml +++ b/circle.yml @@ -20,4 +20,5 @@ test: - cd $SRCDIR/cover; make - cd $SRCDIR/socks; make - cd $SRCDIR/runner; make + - go build ./cmd/wcloud diff --git a/cmd/wcloud/cli.go b/cmd/wcloud/cli.go index 2806d69fa..663119ed5 100644 --- a/cmd/wcloud/cli.go +++ b/cmd/wcloud/cli.go @@ -108,7 +108,7 @@ func deploy(c Client, args []string) { ImageName: parts[0], Version: parts[1], TriggeringUser: *username, - IntendedServices: service, + IntendedServices: services, } if err := c.Deploy(deployment); err != nil { fmt.Println(err.Error()) From 9cbab402fb5605b5c037281e36e45c66d1f29b84 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 25 Aug 2016 10:49:29 +0200 Subject: [PATCH 89/92] Add wcloud Makefile --- .gitignore | 1 + circle.yml | 2 +- cmd/wcloud/Makefile | 11 +++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 cmd/wcloud/Makefile diff --git a/.gitignore b/.gitignore index b6ea60f8f..635dff1f1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ cover/cover socks/proxy socks/image.tar runner/runner +cmd/wcloud/wcloud *.pyc *~ diff --git a/circle.yml b/circle.yml index fb3c7ce8e..74cf3d40a 100644 --- a/circle.yml +++ b/circle.yml @@ -20,5 +20,5 @@ test: - cd $SRCDIR/cover; make - cd $SRCDIR/socks; make - cd $SRCDIR/runner; make - - go build ./cmd/wcloud + - cd $SRCDIR/cmd/wcloud; make diff --git a/cmd/wcloud/Makefile b/cmd/wcloud/Makefile new file mode 100644 index 000000000..86b0df380 --- /dev/null +++ b/cmd/wcloud/Makefile @@ -0,0 +1,11 @@ +.PHONY: all clean + +all: wcloud + +wcloud: *.go + go get ./$(@D) + go build -o $@ ./$(@D) + +clean: + rm -rf wcloud + go clean ./... From b895344d2ba07c033bf366cb55dddde64e3cbac7 Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Fri, 26 Aug 2016 11:00:03 +0000 Subject: [PATCH 90/92] Use mflag package from weaveworks fork until we find a better solution --- runner/runner.go | 2 +- socks/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/runner/runner.go b/runner/runner.go index 707369d81..c92ac6b5b 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -14,8 +14,8 @@ import ( "sync" "time" - "github.com/docker/docker/pkg/mflag" "github.com/mgutz/ansi" + "github.com/weaveworks/docker/pkg/mflag" ) const ( diff --git a/socks/main.go b/socks/main.go index 520df27d9..83a214980 100644 --- a/socks/main.go +++ b/socks/main.go @@ -9,7 +9,7 @@ import ( "text/template" socks5 "github.com/armon/go-socks5" - "github.com/docker/docker/pkg/mflag" + "github.com/weaveworks/docker/pkg/mflag" "github.com/weaveworks/weave/common/mflagext" "golang.org/x/net/context" ) From 48beb60bbebb6511b8c41fb339f48dc97505966e Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Fri, 26 Aug 2016 11:55:12 +0000 Subject: [PATCH 91/92] Sync changes done directly in scope/tools Applies tools changes from: commit 7f46b90e272f00275ce47a29e0cef0c62bbc4e38 Author: Krzesimir Nowak Date: Thu Jul 21 11:55:08 2016 +0200 Make LatestMap "generic" This commit makes the LatestMap type a sort of a base class, that should not be used directly. This also adds a generator for LatestMap "concrete" types with a specific data type in the LatestEntry. commit 0f1cb82084435622b2b1c78bd36884cf90f1ab25 Author: Krzesimir Nowak Date: Thu Jul 28 14:26:08 2016 +0200 Allow testing only a subset of directories This can be done by calling TESTDIRS="./report ./probe" make tests commit 97eb8d033dc07dee338a43c170e809731ca3c9e4 Author: Krzesimir Nowak Date: Thu Aug 4 11:44:21 2016 +0200 Do not spell check Makefiles and JSON files Spell check does not handle JSON files very well. It finds misspelled words in hexadecimal hashes (like misspelled "dead" in "123daed456"). Ignore Makefiles too - there is not so much free text to spell check and the spell check complains about words it was told by Makefile to --- generate_latest_map | 176 ++++++++++++++++++++++++++++++++++++++++++++ lint | 4 +- test | 11 ++- 3 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 generate_latest_map diff --git a/generate_latest_map b/generate_latest_map new file mode 100644 index 000000000..366a718d4 --- /dev/null +++ b/generate_latest_map @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +# +# Generate concrete implementations of LatestMap. +# +# e.g. +# $ generate_latest_map ./report/out.go string NodeControlData ... +# +# Depends on: +# - gofmt + +function generate_header { + local out_file="${1}" + local cmd="${2}" + + cat << EOF >"${out_file}" + // Generated file, do not edit. + // To regenerate, run ${cmd} + + package report + + import ( + "time" + + "github.com/ugorji/go/codec" + ) +EOF +} + +function generate_latest_map { + local out_file="$1" + local data_type="$2" + local uppercase_data_type="${data_type^}" + local lowercase_data_type="${data_type,}" + local wire_entry_type="wire${uppercase_data_type}LatestEntry" + local decoder_type="${lowercase_data_type}LatestEntryDecoder" + local iface_decoder_variable="${uppercase_data_type}LatestEntryDecoder" + local latest_map_type="${uppercase_data_type}LatestMap" + local empty_latest_map_variable="Empty${latest_map_type}" + local make_function="Make${latest_map_type}" + + local json_timestamp='`json:"timestamp"`' + local json_value='`json:"value"`' + + cat << EOF >>"${out_file}" + type ${wire_entry_type} struct { + Timestamp time.Time ${json_timestamp} + Value ${data_type} ${json_value} + } + + type ${decoder_type} struct {} + + func (d *${decoder_type}) Decode(decoder *codec.Decoder, entry *LatestEntry) { + wire := ${wire_entry_type}{} + decoder.Decode(&wire) + entry.Timestamp = wire.Timestamp + entry.Value = wire.Value + } + + // ${iface_decoder_variable} is an implementation of LatestEntryDecoder + // that decodes the LatestEntry instances having a ${data_type} value. + var ${iface_decoder_variable} LatestEntryDecoder = &${decoder_type}{} + + // ${latest_map_type} holds latest ${data_type} instances. + type ${latest_map_type} LatestMap + + // ${empty_latest_map_variable} is an empty ${latest_map_type}. Start with this. + var ${empty_latest_map_variable} = (${latest_map_type})(MakeLatestMapWithDecoder(${iface_decoder_variable})) + + // ${make_function} makes an empty ${latest_map_type}. + func ${make_function}() ${latest_map_type} { + return ${empty_latest_map_variable} + } + + // Copy is a noop, as ${latest_map_type}s are immutable. + func (m ${latest_map_type}) Copy() ${latest_map_type} { + return (${latest_map_type})((LatestMap)(m).Copy()) + } + + // Size returns the number of elements. + func (m ${latest_map_type}) Size() int { + return (LatestMap)(m).Size() + } + + // Merge produces a fresh ${latest_map_type} containing the keys from both inputs. + // When both inputs contain the same key, the newer value is used. + func (m ${latest_map_type}) Merge(other ${latest_map_type}) ${latest_map_type} { + return (${latest_map_type})((LatestMap)(m).Merge((LatestMap)(other))) + } + + // Lookup the value for the given key. + func (m ${latest_map_type}) Lookup(key string) (${data_type}, bool) { + v, ok := (LatestMap)(m).Lookup(key) + if !ok { + var zero ${data_type} + return zero, false + } + return v.(${data_type}), true + } + + // LookupEntry returns the raw entry for the given key. + func (m ${latest_map_type}) LookupEntry(key string) (${data_type}, time.Time, bool) { + v, timestamp, ok := (LatestMap)(m).LookupEntry(key) + if !ok { + var zero ${data_type} + return zero, timestamp, false + } + return v.(${data_type}), timestamp, true + } + + // Set the value for the given key. + func (m ${latest_map_type}) Set(key string, timestamp time.Time, value ${data_type}) ${latest_map_type} { + return (${latest_map_type})((LatestMap)(m).Set(key, timestamp, value)) + } + + // Delete the value for the given key. + func (m ${latest_map_type}) Delete(key string) ${latest_map_type} { + return (${latest_map_type})((LatestMap)(m).Delete(key)) + } + + // ForEach executes fn on each key value pair in the map. + func (m ${latest_map_type}) ForEach(fn func(k string, timestamp time.Time, v ${data_type})) { + (LatestMap)(m).ForEach(func(key string, ts time.Time, value interface{}) { + fn(key, ts, value.(${data_type})) + }) + } + + // String returns the ${latest_map_type}'s string representation. + func (m ${latest_map_type}) String() string { + return (LatestMap)(m).String() + } + + // DeepEqual tests equality with other ${latest_map_type}. + func (m ${latest_map_type}) DeepEqual(n ${latest_map_type}) bool { + return (LatestMap)(m).DeepEqual((LatestMap)(n)) + } + + // CodecEncodeSelf implements codec.Selfer. + func (m *${latest_map_type}) CodecEncodeSelf(encoder *codec.Encoder) { + (*LatestMap)(m).CodecEncodeSelf(encoder) + } + + // CodecDecodeSelf implements codec.Selfer. + func (m *${latest_map_type}) CodecDecodeSelf(decoder *codec.Decoder) { + bm := (*LatestMap)(m) + bm.decoder = ${iface_decoder_variable} + bm.CodecDecodeSelf(decoder) + } + + // MarshalJSON shouldn't be used, use CodecEncodeSelf instead. + func (${latest_map_type}) MarshalJSON() ([]byte, error) { + panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead") + } + + // UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead. + func (*${latest_map_type}) UnmarshalJSON(b []byte) error { + panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead") + } +EOF +} + +if [ -z "${1}" ]; then + echo "No output file given" + exit 1 +fi + +out="${1}" +outtmp="${out}.tmp" + +generate_header "${outtmp}" "${0} ${*}" +shift +for t in ${*}; do + generate_latest_map "${outtmp}" "${t}" +done + +gofmt -s -w "${outtmp}" +mv "${outtmp}" "${out}" diff --git a/lint b/lint index 994d4c321..30e8f904e 100755 --- a/lint +++ b/lint @@ -40,11 +40,11 @@ function spell_check { filename="$1" local lint_result=0 - # we don't want to spell check tar balls or binaries + # we don't want to spell check tar balls, binaries, Makefile and json files if file "$filename" | grep executable >/dev/null 2>&1; then return $lint_result fi - if [[ $filename == *".tar" || $filename == *".gz" ]]; then + if [[ $filename == *".tar" || $filename == *".gz" || $filename == *".json" || $(basename "$filename") == "Makefile" ]]; then return $lint_result fi diff --git a/test b/test index f8d76c56b..4ee4ca1a4 100755 --- a/test +++ b/test @@ -47,8 +47,15 @@ fi fail=0 -# NB: Relies on paths being prefixed with './'. -TESTDIRS=( $(git ls-files -- '*_test.go' | grep -vE '^(vendor|prog|experimental)/' | xargs -n1 dirname | sort -u | sed -e 's|^|./|') ) +if [ -z "$TESTDIRS" ]; then + # NB: Relies on paths being prefixed with './'. + TESTDIRS=( $(git ls-files -- '*_test.go' | grep -vE '^(vendor|prog|experimental)/' | xargs -n1 dirname | sort -u | sed -e 's|^|./|') ) +else + # TESTDIRS on the right side is not really an array variable, it + # is just a string with spaces, but it is written like that to + # shut up the shellcheck tool. + TESTDIRS=( $(for d in ${TESTDIRS[*]}; do echo "$d"; done) ) +fi # If running on circle, use the scheduler to work out what tests to run on what shard if [ -n "$CIRCLECI" ] && [ -z "$NO_SCHEDULER" ] && [ -x "$DIR/sched" ]; then From 3f4934d941a6d88e154f366906f66d533fb098ad Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Fri, 26 Aug 2016 12:29:46 +0000 Subject: [PATCH 92/92] Remove generate_latest_map --- generate_latest_map | 176 -------------------------------------------- 1 file changed, 176 deletions(-) delete mode 100644 generate_latest_map diff --git a/generate_latest_map b/generate_latest_map deleted file mode 100644 index 366a718d4..000000000 --- a/generate_latest_map +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env bash -# -# Generate concrete implementations of LatestMap. -# -# e.g. -# $ generate_latest_map ./report/out.go string NodeControlData ... -# -# Depends on: -# - gofmt - -function generate_header { - local out_file="${1}" - local cmd="${2}" - - cat << EOF >"${out_file}" - // Generated file, do not edit. - // To regenerate, run ${cmd} - - package report - - import ( - "time" - - "github.com/ugorji/go/codec" - ) -EOF -} - -function generate_latest_map { - local out_file="$1" - local data_type="$2" - local uppercase_data_type="${data_type^}" - local lowercase_data_type="${data_type,}" - local wire_entry_type="wire${uppercase_data_type}LatestEntry" - local decoder_type="${lowercase_data_type}LatestEntryDecoder" - local iface_decoder_variable="${uppercase_data_type}LatestEntryDecoder" - local latest_map_type="${uppercase_data_type}LatestMap" - local empty_latest_map_variable="Empty${latest_map_type}" - local make_function="Make${latest_map_type}" - - local json_timestamp='`json:"timestamp"`' - local json_value='`json:"value"`' - - cat << EOF >>"${out_file}" - type ${wire_entry_type} struct { - Timestamp time.Time ${json_timestamp} - Value ${data_type} ${json_value} - } - - type ${decoder_type} struct {} - - func (d *${decoder_type}) Decode(decoder *codec.Decoder, entry *LatestEntry) { - wire := ${wire_entry_type}{} - decoder.Decode(&wire) - entry.Timestamp = wire.Timestamp - entry.Value = wire.Value - } - - // ${iface_decoder_variable} is an implementation of LatestEntryDecoder - // that decodes the LatestEntry instances having a ${data_type} value. - var ${iface_decoder_variable} LatestEntryDecoder = &${decoder_type}{} - - // ${latest_map_type} holds latest ${data_type} instances. - type ${latest_map_type} LatestMap - - // ${empty_latest_map_variable} is an empty ${latest_map_type}. Start with this. - var ${empty_latest_map_variable} = (${latest_map_type})(MakeLatestMapWithDecoder(${iface_decoder_variable})) - - // ${make_function} makes an empty ${latest_map_type}. - func ${make_function}() ${latest_map_type} { - return ${empty_latest_map_variable} - } - - // Copy is a noop, as ${latest_map_type}s are immutable. - func (m ${latest_map_type}) Copy() ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Copy()) - } - - // Size returns the number of elements. - func (m ${latest_map_type}) Size() int { - return (LatestMap)(m).Size() - } - - // Merge produces a fresh ${latest_map_type} containing the keys from both inputs. - // When both inputs contain the same key, the newer value is used. - func (m ${latest_map_type}) Merge(other ${latest_map_type}) ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Merge((LatestMap)(other))) - } - - // Lookup the value for the given key. - func (m ${latest_map_type}) Lookup(key string) (${data_type}, bool) { - v, ok := (LatestMap)(m).Lookup(key) - if !ok { - var zero ${data_type} - return zero, false - } - return v.(${data_type}), true - } - - // LookupEntry returns the raw entry for the given key. - func (m ${latest_map_type}) LookupEntry(key string) (${data_type}, time.Time, bool) { - v, timestamp, ok := (LatestMap)(m).LookupEntry(key) - if !ok { - var zero ${data_type} - return zero, timestamp, false - } - return v.(${data_type}), timestamp, true - } - - // Set the value for the given key. - func (m ${latest_map_type}) Set(key string, timestamp time.Time, value ${data_type}) ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Set(key, timestamp, value)) - } - - // Delete the value for the given key. - func (m ${latest_map_type}) Delete(key string) ${latest_map_type} { - return (${latest_map_type})((LatestMap)(m).Delete(key)) - } - - // ForEach executes fn on each key value pair in the map. - func (m ${latest_map_type}) ForEach(fn func(k string, timestamp time.Time, v ${data_type})) { - (LatestMap)(m).ForEach(func(key string, ts time.Time, value interface{}) { - fn(key, ts, value.(${data_type})) - }) - } - - // String returns the ${latest_map_type}'s string representation. - func (m ${latest_map_type}) String() string { - return (LatestMap)(m).String() - } - - // DeepEqual tests equality with other ${latest_map_type}. - func (m ${latest_map_type}) DeepEqual(n ${latest_map_type}) bool { - return (LatestMap)(m).DeepEqual((LatestMap)(n)) - } - - // CodecEncodeSelf implements codec.Selfer. - func (m *${latest_map_type}) CodecEncodeSelf(encoder *codec.Encoder) { - (*LatestMap)(m).CodecEncodeSelf(encoder) - } - - // CodecDecodeSelf implements codec.Selfer. - func (m *${latest_map_type}) CodecDecodeSelf(decoder *codec.Decoder) { - bm := (*LatestMap)(m) - bm.decoder = ${iface_decoder_variable} - bm.CodecDecodeSelf(decoder) - } - - // MarshalJSON shouldn't be used, use CodecEncodeSelf instead. - func (${latest_map_type}) MarshalJSON() ([]byte, error) { - panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead") - } - - // UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead. - func (*${latest_map_type}) UnmarshalJSON(b []byte) error { - panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead") - } -EOF -} - -if [ -z "${1}" ]; then - echo "No output file given" - exit 1 -fi - -out="${1}" -outtmp="${out}.tmp" - -generate_header "${outtmp}" "${0} ${*}" -shift -for t in ${*}; do - generate_latest_map "${outtmp}" "${t}" -done - -gofmt -s -w "${outtmp}" -mv "${outtmp}" "${out}"