New deployment / build infrastructure

Esse commit está contido em:
Felix Geisendörfer
2013-12-21 11:41:15 +01:00
commit 29079cda83
16 arquivos alterados com 333 adições e 176 exclusões
+1 -1
Ver Arquivo
@@ -1,2 +1,2 @@
/bin
/http/fs/fs.go
/dist
+10
Ver Arquivo
@@ -0,0 +1,10 @@
version = $(shell git describe --tags --dirty)
dev:
./scripts/build.bash -version $(version) dist/dev
./dist/dev/deploy
dist:
./scripts/dist.bash $(version)
.PHONY: dev release
+146
Ver Arquivo
@@ -0,0 +1,146 @@
// Command install allows people to install and run GoDrone binaries on their
// ardrone.
package main
import (
"bitbucket.org/kardianos/osext"
"fmt"
ftp "github.com/jlaffaye/goftp"
ptelnet "github.com/ziutek/telnet"
"io"
"net"
"os"
"path"
"path/filepath"
"strings"
"time"
)
const cmdName = "install"
func Printf(format string, args ...interface{}) {
fmt.Fprintf(os.Stdout, format+"\n", args...)
}
func Fatalf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
}
func main() {
dir, err := osext.ExecutableFolder()
if err != nil {
Fatalf("Could not determine ExecutableFolder: %s", err)
}
task := DeployTask{
Host: "192.168.1.1",
FtpPort: "21",
FtpDir: "/data/video",
TelnetPort: "23",
TelnetPrompt: "# ",
Src: dir,
Dst: "godrone",
NetTimeout: 5 * time.Second,
}
if err := task.Run(); err != nil {
Fatalf("Failed to install godrone: %s", err)
}
}
type DeployTask struct {
// Host is the IP address of the drone.
Host string
// FtpPort is the ftp port.
FtpPort string
// FtpDir is the absolute path of the FTP directory on the drone file system.
FtpDir string
// TelnetPort is the telnet port.
TelnetPort string
// TelnetPrompt is the prompt string used by the drone's shell.
TelnetPrompt string
// Src is the absolute path to the godrone folder on the host.
Src string
// Dst is path relative to the FtpDir to deploy godrone on the drone.
Dst string
// NetTimeout is the timeout for all networking operations.
NetTimeout time.Duration
}
func (t DeployTask) Run() error {
dir, err := os.Open(t.Src)
if err != nil {
return err
}
defer dir.Close()
ftpAddr := net.JoinHostPort(t.Host, t.FtpPort)
Printf("Connecting to %s", ftpAddr)
// @TODO figure out how to set a connection timeout for ftp, might require
// sending a patch to goftp.
ftpConn, err := ftp.Connect(ftpAddr)
if err != nil {
return err
}
defer ftpConn.Quit()
Printf("Connected")
dstDir := t.Dst + ".next"
ftpConn.MakeDir(dstDir)
entries, err := dir.Readdir(-1)
if err != nil {
return err
}
for _, entry := range entries {
name := entry.Name()
baseName := filepath.Base(name)
if strings.HasPrefix(baseName, cmdName) {
// don't upload the installer itself
continue
}
srcName := filepath.Join(t.Src, name)
dstName := path.Join(dstDir, name)
file, err := os.Open(srcName)
if err != nil {
return err
}
Printf("Uploading %s", name)
if err := ftpConn.Stor(dstName, file); err != nil {
return err
}
}
telnetAddr := net.JoinHostPort(t.Host, t.TelnetPort)
Printf("Connecting to %s", telnetAddr)
telnetConn, err := net.DialTimeout("tcp", telnetAddr, t.NetTimeout)
if err != nil {
return err
}
defer telnetConn.Close()
Printf("Connected")
telnet, err := ptelnet.NewConn(telnetConn)
if err != nil {
return err
}
Printf("Running start.sh on drone")
if _, err := telnet.ReadUntil(t.TelnetPrompt); err != nil {
return err
}
if _, err := fmt.Fprintf(telnet, "cd %s/%s && sh start.sh\n", t.FtpDir, dstDir); err != nil {
return err
}
go io.Copy(telnet, os.Stdin)
if _, err := io.Copy(os.Stdout, telnet); err != nil {
return err
}
return nil
}
-14
Ver Arquivo
@@ -16,20 +16,6 @@ type Config struct {
HttpAddr string
}
var (
defaultRollPitchPID = []float64{0.04, 0, 0.002}
)
// DefaultConfig provides sensible defaults in absence of a config file.
var DefaultConfig = Config{
NavboardTTY: "/dev/ttyO1",
MotorboardTTY: "/dev/ttyO0",
RollPID: defaultRollPitchPID,
PitchPID: defaultRollPitchPID,
YawPID: []float64{0.04, 0, 0}, // disabled, needs magnotometer to work well
HttpAddr: ":80",
}
// LoadConfig loads the configuration from a toml file. Other file formats may
// be supported in the future as well.
func LoadConfig(file string, config *Config) error {
+9 -4
Ver Arquivo
@@ -1,8 +1,13 @@
NavboardTTY = "/dev/ttyO1"
MotorboardTTY = "/dev/ttyO0"
# PID controller settings
# feel free to play with these to adjust control behavior
RollPID = [ 0.04, 0.0, 0.002 ]
PitchPID = [ 0.04, 0.0, 0.002 ]
#disabled, needs magnotometer to work well
YawPID = [ 0.04, 0.0, 0.0 ]
AltitudePID = [ 0.3, 0.03, 0.03 ]
HttpAddr = ":80"
# http server settings
HttpAddr = ":80"
# do not change, otherwise nothing will work
NavboardTTY = "/dev/ttyO1"
MotorboardTTY = "/dev/ttyO0"
+14 -7
Ver Arquivo
@@ -17,23 +17,29 @@ import (
)
var (
c = flag.String("c", "", "Absolute or relative path to config file.")
// Convenience values to set the colors of all leds
green = motorboard.Leds(motorboard.LedGreen)
orange = motorboard.Leds(motorboard.LedOrange)
red = motorboard.Leds(motorboard.LedRed)
)
// Version is inserted during the build process.
// see http://stackoverflow.com/questions/11354518/golang-application-auto-build-versioning/11355611#11355611
var Version = "N/A"
func main() {
flag.Parse()
config := DefaultConfig
if *c != "" {
if err := LoadConfig(*c, &config); err != nil {
panic(err)
}
var config Config
configPath := flag.Arg(0)
if configPath == "" {
configPath = "config.toml"
}
if err := LoadConfig(configPath, &config); err != nil {
panic(err)
}
g, err := NewGoDrone(config)
if err != nil {
panic(err)
@@ -58,6 +64,7 @@ func NewGoDrone(c Config) (g GoDrone, err error) {
g.http = http.NewHandler(http.Config{
Control: g.control,
Log: g.log,
Version: Version,
})
g.httpAddr = c.HttpAddr
g.navCh = make(chan navboard.Data)
-12
Ver Arquivo
@@ -1,12 +0,0 @@
// Command version is a utility that prints the current godrone version and
// exits.
package main
import (
"fmt"
"github.com/felixge/godrone"
)
func main() {
fmt.Println(godrone.Version)
}
+3
Ver Arquivo
@@ -6,12 +6,15 @@
<link rel="stylesheet" type="text/css" href="/css/main.css" />
<!-- 3rd party -->
<script src="/js/vendor/jquery.js"></script>
<script src="/js/vendor/react-with-addons.js"></script>
<script src="/js/vendor/JSXTransformer.js"></script>
<!-- GoDrone -->
<script src="/js/client.js"></script>
<script src="/js/gamepad.js"></script>
<script src="/js/table.js" type="text/jsx"></script>
<script src="/js/config.js"></script><!-- dynamically generated -->
<script src="/js/main.js" type="text/jsx"></script>
</head>
<body>
+1 -1
Ver Arquivo
@@ -227,7 +227,7 @@
React.renderComponent(
<GoDrone
version="0.1"
version={Config.version}
wsUrl="ws://192.168.1.1/ws"
gamepadAxisMin={0.01}
maxAngle={6}
+22
Ver Arquivo
@@ -6,6 +6,8 @@ package http
import (
"code.google.com/p/go.net/websocket"
"encoding/json"
"fmt"
"github.com/felixge/godrone/attitude"
"github.com/felixge/godrone/control"
"github.com/felixge/godrone/drivers/navboard"
@@ -19,6 +21,7 @@ import (
type Config struct {
Control *control.Control
Log log.Interface
Version string
}
// Handler provides a http.Handler.
@@ -93,6 +96,21 @@ func (h *Handler) handleWebsocket(conn *websocket.Conn) {
}
}
func (h *Handler) handleConfig(w http.ResponseWriter, r *http.Request) {
config := map[string]interface{}{
"version": h.config.Version,
}
data, err := json.Marshal(config)
if err != nil {
err = h.config.Log.Error("Could marshal JSON config. err=%s", err)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "%s", err)
return
}
w.Header().Set("Content-Type", "text/javascript; charset=UTF-8")
fmt.Fprintf(w, "window.Config = %s;", data)
}
// ServeHTTP implements the http.Handler interface. It acts as a router
// dispatching websocket / asset requests.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -100,6 +118,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.websocketHandler.ServeHTTP(w, r)
return
}
if r.Method == "GET" && r.URL.Path == "/js/config.js" {
h.handleConfig(w, r)
return
}
h.fileHandler.ServeHTTP(w, r)
}
Arquivo executável
+61
Ver Arquivo
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -eu
main() {
local scripts_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
local root_dir="$( cd "${scripts_dir}"/.. && pwd )"
local base_pkg='github.com/felixge/godrone'
local arch="$(go env GOARCH)"
local os="$(go env GOOS)"
local ldflags=''
local deploy_name='deploy'
while [[ "$#" -gt 0 ]]; do
case "$1" in
'-arch')
arch="$2"
shift
;;
'-os')
os="$2"
shift
;;
'-version')
ldflags="-X main.Version $2"
shift
;;
*)
break
;;
esac
shift
done
local out_dir="$1"
if [[ "${os}" = "windows" ]]; then
deploy_name="${deploy_name}.exe"
fi
rm -rf "${out_dir}"
mkdir -p "${out_dir}"
makefs "${root_dir}/http/fs"
env \
GOOS="${os}" \
GOARCH="${arch}" \
go build -o "${out_dir}/${deploy_name}" "${base_pkg}/cmd/deploy"
env \
GOOS=linux \
GOARCH=arm \
GOARM=7 \
CGO_ENABLED=0 \
go build \
-o "${out_dir}/godrone" \
-ldflags "${ldflags}" \
"${base_pkg}/cmd/godrone"
cp "${scripts_dir}/start.sh" "${out_dir}"
cp "${root_dir}/LICENSE.txt" "${out_dir}"
cp "${root_dir}/cmd/godrone/config.toml" "${out_dir}"
}
main $@
-110
Ver Arquivo
@@ -1,110 +0,0 @@
#!/usr/bin/env bash
set -eu
usage() {
local exitcode="$1"
if [[ "$exitcode" = 1 ]]; then
exec 1>&2
fi
echo "Usage: $0 [-h] [--tracegc] [-i <ip>] [-e <envargs>] [<cmd>]"
exit "$exitcode"
}
log() {
echo "--> $@"
}
build_http_fs() {
log "Building http fs ..."
makefs "$1"
}
fetch_deps() {
log 'Fetching dependencies ...'
go get "$1"
}
build() {
local pkg="$1"
local path="$2"
log "Creating ${pkg} arm binary in ${path} ..."
mkdir -p "$(dirname "${path}")"
env \
GOOS=linux \
GOARCH=arm \
CGO_ENABLED=0 \
go build -o "${path}" "${pkg}"
}
upload() {
local ip="$1"
shift
local curlcmd="curl"
for file in $@; do
curlcmd=" ${curlcmd} -T '${file}' 'ftp://@${ip}/$(basename "${file}")'"
done
log "Uploading to ${ip}..."
bash -c "${curlcmd}"
}
main() {
local ip="192.168.1.1"
local envargs=''
while [[ "$#" -gt 0 ]]; do
case "$1" in
'-h')
usage 0
shift
;;
'-i')
ip="$2"
shift
;;
'--tracegc')
envargs="${envargs} GOGCTRACE=1"
;;
'-e')
envargs="${envargs} $2"
shift
;;
-*)
echo -e "unknown option: $1\n" 1>&2
usage 1
;;
*)
break
;;
esac
shift
done
readonly ip
readonly envargs
if [[ "$#" -gt 1 ]]; then
echo -e "unexpected argument: $2\n" 1>&2
usage 1
fi
local readonly cmd="${1:-godrone}"
local readonly scripts_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
local readonly dir="$( cd "${scripts_dir}"/.. && pwd )"
local readonly bin_path="${dir}/bin/${cmd}"
local readonly pkg_path="github.com/felixge/godrone/cmd/${cmd}"
local readonly startup_script='start.sh'
build_http_fs "${dir}/http/fs"
fetch_deps "${pkg_path}"
build "${pkg_path}" "${bin_path}"
upload "${ip}" "${scripts_dir}/${startup_script}" "${bin_path}"
log "Starting ${cmd} ..."
"${scripts_dir}/start.expect" \
"${ip}" \
"${startup_script}" \
"${cmd}" \
"${envargs}"
}
main $@
Arquivo executável
+55
Ver Arquivo
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -eu
main() {
local version="$1"
local scripts_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
local root_dir="$( cd "${scripts_dir}"/.. && pwd )"
local targets=(
'darwin 386 zip'
'darwin amd64 zip'
'windows 386 zip'
'windows amd64 zip'
'linux 386 tar'
'linux amd64 tar'
)
for target in "${targets[@]}"; do
build $target
done
}
build() {
local os="$1"
local arch="$2"
local archive="$3"
local name="godrone-${version}-${os}-${arch}"
local out_dir="${root_dir}/dist/${name}"
echo "Building ${name}"
"${scripts_dir}/build.bash" \
-os "${os}" \
-arch "${arch}" \
-version ${version} \
"${out_dir}"
cd "${out_dir}/.."
case "${archive}" in
'zip')
local archive_name="${out_dir}.zip"
rm -f "${archive_name}"
zip -q -r "${archive_name}" "${name}"
;;
'tar')
local archive_name="${out_dir}.tar.gz"
rm -f "${archive_name}"
tar -czf "${archive_name}" "${name}"
;;
*)
echo "unknown archive format: ${archive}"
exit 1
;;
esac
rm -rf "${out_dir}"
}
main $@
-11
Ver Arquivo
@@ -1,11 +0,0 @@
#!/usr/bin/env expect -f
set ip [lindex $argv 0]
set script [lindex $argv 1]
set cmd [lindex $argv 2]
set envargs [lindex $argv 3]
set prompt "# *$"
spawn telnet $ip
expect -re $prompt
send "sh /data/video/$script \"$cmd\" \"$envargs\"; exit\n"
interact
+11 -13
Ver Arquivo
@@ -1,23 +1,21 @@
#!/bin/sh
# This script kills any previously running control program (e.g. the original
# parrot firmware, godrone, or godrone diagnostic program) and then starts
# up the desired program.
set -eu
readonly cmd="$1"
readonly envargs="$2"
src_dir="$(pwd)"
target_dir="${src_dir/.next/}"
# avoid parrot firmware from restarting restarts
rm -rf "${target_dir}"
mv "${src_dir}" "${target_dir}"
cd "${target_dir}"
# avoid parrot firmware getting restarted after killing it
touch /tmp/.norespawn
# kill parrot firmware and the cmd we're deploying if it's running
killall -9 program.elf "${cmd}" 2> /dev/null || true
cd /data/video
chmod +x "${cmd}"
# kill parrot firmware and godrone
killall -9 program.elf godrone 2> /dev/null || true
# Taken from /bin/program.elf.respawner.sh. Not sure why/if it is needed, seems
# to turn the bottom LED red.
gpio 181 -d ho 1
exec env $envargs "./${cmd}"
chmod +x godrone
./godrone
-3
Ver Arquivo
@@ -1,3 +0,0 @@
package godrone
const Version = "0.1"