<commit_before>// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// +build !noupgrade

package upgrade

import (
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"sort"
	"strings"

	"github.com/syncthing/syncthing/lib/dialer"
	"github.com/syncthing/syncthing/lib/signature"
)

const DisabledByCompilation = false

// This is an HTTP/HTTPS client that does *not* perform certificate
// validation. We do this because some systems where Syncthing runs have
// issues with old or missing CA roots. It doesn't actually matter that we
// load the upgrade insecurely as we verify an ECDSA signature of the actual
// binary contents before accepting the upgrade.
var insecureHTTP = &http.Client{
	Transport: &http.Transport{
		Dial:  dialer.Dial,
		Proxy: http.ProxyFromEnvironment,
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
	},
}

// LatestGithubReleases returns the latest releases, including prereleases or
// not depending on the argument
func LatestGithubReleases(releasesURL, version string) ([]Release, error) {
	resp, err := insecureHTTP.Get(releasesURL)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode > 299 {
		return nil, fmt.Errorf("API call returned HTTP error: %s", resp.Status)
	}

	var rels []Release
	json.NewDecoder(resp.Body).Decode(&rels)
	resp.Body.Close()

	return rels, nil
}

type SortByRelease []Release

func (s SortByRelease) Len() int {
	return len(s)
}
func (s SortByRelease) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s SortByRelease) Less(i, j int) bool {
	return CompareVersions(s[i].Tag, s[j].Tag) > 0
}

func LatestRelease(releasesURL, version string) (Release, error) {
	rels, _ := LatestGithubReleases(releasesURL, version)
	return SelectLatestRelease(version, rels)
}

func SelectLatestRelease(version string, rels []Release) (Release, error) {
	if len(rels) == 0 {
		return Release{}, ErrVersionUnknown
	}

	sort.Sort(SortByRelease(rels))
	// Check for a beta build
	beta := strings.Contains(version, "-beta")

	for _, rel := range rels {
		if rel.Prerelease && !beta {
			continue
		}
		for _, asset := range rel.Assets {
			assetName := path.Base(asset.Name)
			// Check for the architecture
			expectedRelease := releaseName(rel.Tag)
			l.Debugf("expected release asset %q", expectedRelease)
			l.Debugln("considering release", assetName)
			if strings.HasPrefix(assetName, expectedRelease) {
				return rel, nil
			}
		}
	}
	return Release{}, ErrVersionUnknown
}

// Upgrade to the given release, saving the previous binary with a ".old" extension.
func upgradeTo(binary string, rel Release) error {
	expectedRelease := releaseName(rel.Tag)
	l.Debugf("expected release asset %q", expectedRelease)
	for _, asset := range rel.Assets {
		assetName := path.Base(asset.Name)
		l.Debugln("considering release", assetName)

		if strings.HasPrefix(assetName, expectedRelease) {
			return upgradeToURL(binary, asset.URL)
		}
	}

	return ErrVersionUnknown
}

// Upgrade to the given release, saving the previous binary with a ".old" extension.
func upgradeToURL(binary string, url string) error {
	fname, err := readRelease(filepath.Dir(binary), url)
	if err != nil {
		return err
	}

	old := binary + ".old"
	os.Remove(old)
	err = os.Rename(binary, old)
	if err != nil {
		return err
	}
	err = os.Rename(fname, binary)
	if err != nil {
		return err
	}
	return nil
}

func readRelease(dir, url string) (string, error) {
	l.Debugf("loading %q", url)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	req.Header.Add("Accept", "application/octet-stream")
	resp, err := insecureHTTP.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	switch runtime.GOOS {
	case "windows":
		return readZip(dir, resp.Body)
	default:
		return readTarGz(dir, resp.Body)
	}
}

func readTarGz(dir string, r io.Reader) (string, error) {
	gr, err := gzip.NewReader(r)
	if err != nil {
		return "", err
	}

	tr := tar.NewReader(gr)

	var tempName string
	var sig []byte

	// Iterate through the files in the archive.
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// end of tar archive
			break
		}
		if err != nil {
			return "", err
		}

		shortName := path.Base(hdr.Name)

		l.Debugf("considering file %q", shortName)

		err = archiveFileVisitor(dir, &tempName, &sig, shortName, tr)
		if err != nil {
			return "", err
		}

		if tempName != "" && sig != nil {
			break
		}
	}

	if err := verifyUpgrade(tempName, sig); err != nil {
		return "", err
	}

	return tempName, nil
}

func readZip(dir string, r io.Reader) (string, error) {
	body, err := ioutil.ReadAll(r)
	if err != nil {
		return "", err
	}

	archive, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
	if err != nil {
		return "", err
	}

	var tempName string
	var sig []byte

	// Iterate through the files in the archive.
	for _, file := range archive.File {
		shortName := path.Base(file.Name)

		l.Debugf("considering file %q", shortName)

		inFile, err := file.Open()
		if err != nil {
			return "", err
		}

		err = archiveFileVisitor(dir, &tempName, &sig, shortName, inFile)
		inFile.Close()
		if err != nil {
			return "", err
		}

		if tempName != "" && sig != nil {
			break
		}
	}

	if err := verifyUpgrade(tempName, sig); err != nil {
		return "", err
	}

	return tempName, nil
}

// archiveFileVisitor is called for each file in an archive. It may set
// tempFile and signature.
func archiveFileVisitor(dir string, tempFile *string, signature *[]byte, filename string, filedata io.Reader) error {
	var err error
	switch filename {
	case "syncthing", "syncthing.exe":
		l.Debugln("reading binary")
		*tempFile, err = writeBinary(dir, filedata)
		if err != nil {
			return err
		}

	case "syncthing.sig", "syncthing.exe.sig":
		l.Debugln("reading signature")
		*signature, err = ioutil.ReadAll(filedata)
		if err != nil {
			return err
		}
	}

	return nil
}

func verifyUpgrade(tempName string, sig []byte) error {
	if tempName == "" {
		return fmt.Errorf("no upgrade found")
	}
	if sig == nil {
		return fmt.Errorf("no signature found")
	}

	l.Debugf("checking signature\n%s", sig)

	fd, err := os.Open(tempName)
	if err != nil {
		return err
	}
	err = signature.Verify(SigningKey, sig, fd)
	fd.Close()

	if err != nil {
		os.Remove(tempName)
		return err
	}

	return nil
}

func writeBinary(dir string, inFile io.Reader) (filename string, err error) {
	// Write the binary to a temporary file.

	outFile, err := ioutil.TempFile(dir, "syncthing")
	if err != nil {
		return "", err
	}

	_, err = io.Copy(outFile, inFile)
	if err != nil {
		os.Remove(outFile.Name())
		return "", err
	}

	err = outFile.Close()
	if err != nil {
		os.Remove(outFile.Name())
		return "", err
	}

	err = os.Chmod(outFile.Name(), os.FileMode(0755))
	if err != nil {
		os.Remove(outFile.Name())
		return "", err
	}

	return outFile.Name(), nil
}
<commit_msg>Upgrade lib should use same criteria for beta check as main<commit_after>// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

// +build !noupgrade

package upgrade

import (
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"sort"
	"strings"

	"github.com/syncthing/syncthing/lib/dialer"
	"github.com/syncthing/syncthing/lib/signature"
)

const DisabledByCompilation = false

// This is an HTTP/HTTPS client that does *not* perform certificate
// validation. We do this because some systems where Syncthing runs have
// issues with old or missing CA roots. It doesn't actually matter that we
// load the upgrade insecurely as we verify an ECDSA signature of the actual
// binary contents before accepting the upgrade.
var insecureHTTP = &http.Client{
	Transport: &http.Transport{
		Dial:  dialer.Dial,
		Proxy: http.ProxyFromEnvironment,
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
	},
}

// LatestGithubReleases returns the latest releases, including prereleases or
// not depending on the argument
func LatestGithubReleases(releasesURL, version string) ([]Release, error) {
	resp, err := insecureHTTP.Get(releasesURL)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode > 299 {
		return nil, fmt.Errorf("API call returned HTTP error: %s", resp.Status)
	}

	var rels []Release
	json.NewDecoder(resp.Body).Decode(&rels)
	resp.Body.Close()

	return rels, nil
}

type SortByRelease []Release

func (s SortByRelease) Len() int {
	return len(s)
}
func (s SortByRelease) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s SortByRelease) Less(i, j int) bool {
	return CompareVersions(s[i].Tag, s[j].Tag) > 0
}

func LatestRelease(releasesURL, version string) (Release, error) {
	rels, _ := LatestGithubReleases(releasesURL, version)
	return SelectLatestRelease(version, rels)
}

func SelectLatestRelease(version string, rels []Release) (Release, error) {
	if len(rels) == 0 {
		return Release{}, ErrVersionUnknown
	}

	sort.Sort(SortByRelease(rels))
	// Check for a beta build
	beta := strings.Contains(version, "-")

	for _, rel := range rels {
		if rel.Prerelease && !beta {
			continue
		}
		for _, asset := range rel.Assets {
			assetName := path.Base(asset.Name)
			// Check for the architecture
			expectedRelease := releaseName(rel.Tag)
			l.Debugf("expected release asset %q", expectedRelease)
			l.Debugln("considering release", assetName)
			if strings.HasPrefix(assetName, expectedRelease) {
				return rel, nil
			}
		}
	}
	return Release{}, ErrVersionUnknown
}

// Upgrade to the given release, saving the previous binary with a ".old" extension.
func upgradeTo(binary string, rel Release) error {
	expectedRelease := releaseName(rel.Tag)
	l.Debugf("expected release asset %q", expectedRelease)
	for _, asset := range rel.Assets {
		assetName := path.Base(asset.Name)
		l.Debugln("considering release", assetName)

		if strings.HasPrefix(assetName, expectedRelease) {
			return upgradeToURL(binary, asset.URL)
		}
	}

	return ErrVersionUnknown
}

// Upgrade to the given release, saving the previous binary with a ".old" extension.
func upgradeToURL(binary string, url string) error {
	fname, err := readRelease(filepath.Dir(binary), url)
	if err != nil {
		return err
	}

	old := binary + ".old"
	os.Remove(old)
	err = os.Rename(binary, old)
	if err != nil {
		return err
	}
	err = os.Rename(fname, binary)
	if err != nil {
		return err
	}
	return nil
}

func readRelease(dir, url string) (string, error) {
	l.Debugf("loading %q", url)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	req.Header.Add("Accept", "application/octet-stream")
	resp, err := insecureHTTP.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	switch runtime.GOOS {
	case "windows":
		return readZip(dir, resp.Body)
	default:
		return readTarGz(dir, resp.Body)
	}
}

func readTarGz(dir string, r io.Reader) (string, error) {
	gr, err := gzip.NewReader(r)
	if err != nil {
		return "", err
	}

	tr := tar.NewReader(gr)

	var tempName string
	var sig []byte

	// Iterate through the files in the archive.
	for {
		hdr, err := tr.Next()
		if err == io.EOF {
			// end of tar archive
			break
		}
		if err != nil {
			return "", err
		}

		shortName := path.Base(hdr.Name)

		l.Debugf("considering file %q", shortName)

		err = archiveFileVisitor(dir, &tempName, &sig, shortName, tr)
		if err != nil {
			return "", err
		}

		if tempName != "" && sig != nil {
			break
		}
	}

	if err := verifyUpgrade(tempName, sig); err != nil {
		return "", err
	}

	return tempName, nil
}

func readZip(dir string, r io.Reader) (string, error) {
	body, err := ioutil.ReadAll(r)
	if err != nil {
		return "", err
	}

	archive, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
	if err != nil {
		return "", err
	}

	var tempName string
	var sig []byte

	// Iterate through the files in the archive.
	for _, file := range archive.File {
		shortName := path.Base(file.Name)

		l.Debugf("considering file %q", shortName)

		inFile, err := file.Open()
		if err != nil {
			return "", err
		}

		err = archiveFileVisitor(dir, &tempName, &sig, shortName, inFile)
		inFile.Close()
		if err != nil {
			return "", err
		}

		if tempName != "" && sig != nil {
			break
		}
	}

	if err := verifyUpgrade(tempName, sig); err != nil {
		return "", err
	}

	return tempName, nil
}

// archiveFileVisitor is called for each file in an archive. It may set
// tempFile and signature.
func archiveFileVisitor(dir string, tempFile *string, signature *[]byte, filename string, filedata io.Reader) error {
	var err error
	switch filename {
	case "syncthing", "syncthing.exe":
		l.Debugln("reading binary")
		*tempFile, err = writeBinary(dir, filedata)
		if err != nil {
			return err
		}

	case "syncthing.sig", "syncthing.exe.sig":
		l.Debugln("reading signature")
		*signature, err = ioutil.ReadAll(filedata)
		if err != nil {
			return err
		}
	}

	return nil
}

func verifyUpgrade(tempName string, sig []byte) error {
	if tempName == "" {
		return fmt.Errorf("no upgrade found")
	}
	if sig == nil {
		return fmt.Errorf("no signature found")
	}

	l.Debugf("checking signature\n%s", sig)

	fd, err := os.Open(tempName)
	if err != nil {
		return err
	}
	err = signature.Verify(SigningKey, sig, fd)
	fd.Close()

	if err != nil {
		os.Remove(tempName)
		return err
	}

	return nil
}

func writeBinary(dir string, inFile io.Reader) (filename string, err error) {
	// Write the binary to a temporary file.

	outFile, err := ioutil.TempFile(dir, "syncthing")
	if err != nil {
		return "", err
	}

	_, err = io.Copy(outFile, inFile)
	if err != nil {
		os.Remove(outFile.Name())
		return "", err
	}

	err = outFile.Close()
	if err != nil {
		os.Remove(outFile.Name())
		return "", err
	}

	err = os.Chmod(outFile.Name(), os.FileMode(0755))
	if err != nil {
		os.Remove(outFile.Name())
		return "", err
	}

	return outFile.Name(), nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"os"

	"github.com/TNG/gpg-validation-server/gpg"
	"github.com/TNG/gpg-validation-server/storage"
	"github.com/TNG/gpg-validation-server/validator"
	"github.com/codegangsta/cli"
)

const (
	okExitCode    = 0
	errorExitCode = 1
)

var store storage.GetSetDeleter

func appAction(c *cli.Context) error {
	store = storage.NewMemoryStore()
	smtpHost := fmt.Sprintf("%v:%v", c.String("host"), c.Int("smtp-port"))
	httpHost := fmt.Sprintf("%v:%v", c.String("host"), c.Int("http-port"))

	privateKeyPath := c.String("private-key")
	if privateKeyPath == "" {
		return fmt.Errorf("Invalid private key file path: %s", privateKeyPath)
	}
	privateKeyInput, err := os.Open(privateKeyPath)
	if err != nil {
		return fmt.Errorf("Cannot open private key file '%s': %s", privateKeyPath, err)
	}
	defer func() { _ = privateKeyInput.Close() }()

	gpgUtil, err := gpg.NewGPG(privateKeyInput, c.String("passphrase"))
	if err != nil {
		return fmt.Errorf("Cannot initialize GPG: %s", err)
	}

	log.Println("Setting up SMTP server listening at: ", smtpHost)
	go serveSMTPRequestReceiver(fmt.Sprintf("%v:%v", c.String("host"), c.Int("smtp-port")), *gpgUtil)

	log.Println("Setting up HTTP server listening at: ", httpHost)
	log.Fatal(serveNonceConfirmer(c.String("host") + ":8080"))
	return nil
}

func processMailAction(c *cli.Context) error {
	var err error
	var inputMail *os.File

	inputFilePath := c.String("file")

	if inputFilePath == "" {
		inputMail = os.Stdin
	} else {
		inputMail, err = os.Open(inputFilePath)
		if err != nil {
			return fmt.Errorf("Cannot open mail file '%s': %s", inputFilePath, err)
		}
		defer func() { _ = inputMail.Close() }()
	}

	privateKeyPath := c.String("private-key")
	if privateKeyPath == "" {
		return fmt.Errorf("Invalid private key file path: %s", privateKeyPath)
	}
	privateKeyInput, err := os.Open(privateKeyPath)
	if err != nil {
		return fmt.Errorf("Cannot open private key file '%s': %s", privateKeyPath, err)
	}
	defer func() { _ = privateKeyInput.Close() }()

	gpgUtil, err := gpg.NewGPG(privateKeyInput, c.String("passphrase"))
	if err != nil {
		return fmt.Errorf("Cannot initialize GPG: %s", err)
	}

	result, err := validator.HandleMail(inputMail, gpgUtil)
	if err != nil {
		return fmt.Errorf("Cannot handle mail: %s", err)
	}

	log.Printf("Mail has valid signature: %v.\n", result.IsSigned())

	return nil
}

func confirmNonceAction(c *cli.Context) error {
	var nonce [32]byte

	nonceSlice, err := hex.DecodeString(c.String("nonce"))
	if err != nil {
		return err
	}
	if len(nonceSlice) != 32 {
		return errors.New(fmt.Sprint("Nonce has invalid length: ", len(nonceSlice)))
	}
	copy(nonce[:], nonceSlice)

	return ConfirmNonce(nonce)
}

func cliErrorHandler(action func(*cli.Context) error) func(*cli.Context) cli.ExitCoder {
	return func(c *cli.Context) cli.ExitCoder {
		if err := action(c); err != nil {
			return cli.NewExitError(fmt.Sprint("Error: ", err), errorExitCode)
		}
		return nil
	}
}

// RunApp starts the server with the provided arguments.
func RunApp(args []string) {
	app := cli.NewApp()
	app.Name = "GPG Validation Service"
	app.Usage = "Run a server that manages email verification and signs verified keys with the servers GPG key."

	app.Commands = []cli.Command{
		{
			Name:   "process-mail",
			Usage:  "process an incoming email",
			Action: cliErrorHandler(processMailAction),
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "file",
					Value: "./test/mails/signed_request_enigmail.eml",
					// TODO Handle missing value, use better default
					Usage: "`FILE_PATH` of the mail file, omit to read from stdin",
				},
				cli.StringFlag{
					Name:  "private-key",
					Value: "./test/keys/test-gpg-validation@server.local (0x87144E5E) sec.asc.gpg",
					// TODO Handle missing value, use better default
					Usage: "`PRIVATE_KEY_PATH` to the private gpg key of the server",
				},
				cli.StringFlag{
					Name:  "passphrase",
					Value: "validation",
					// TODO Handle missing value, use better default.
					Usage: "`PASSPHRASE` of the private key",
				},
			},
		},
		{
			Name:   "confirm-nonce",
			Usage:  "process an nonce that has been confirmed",
			Action: cliErrorHandler(confirmNonceAction),
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "nonce",
					Value: "",
					Usage: "String value of the Nonce",
				},
			},
		},
	}

	app.Action = cliErrorHandler(appAction)
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "host",
			Value: "localhost",
			Usage: "`HOST` of the mail and http servers. Set to the blank value to bind to all interfaces.",
		},
		cli.IntFlag{
			Name:  "http-port",
			Value: 8080,
			Usage: "`PORT` for the HTTP nonce receiver",
		},
		cli.IntFlag{
			Name:  "smtp-port",
			Value: 2525,
			Usage: "`PORT` for the SMTP server",
		},
		cli.StringFlag{
			Name:  "private-key",
			Value: "./test/keys/test-gpg-validation@server.local (0x87144E5E) sec.asc.gpg",
			// TODO Handle missing value, use better default
			Usage: "`PRIVATE_KEY_PATH` to the private gpg key of the server",
		},
		cli.StringFlag{
			Name:  "passphrase",
			Value: "validation",
			// TODO Handle missing value, use better default.
			Usage: "`PASSPHRASE` of the private key",
		},
	}

	err := app.Run(args)
	if err != nil {
		cli.OsExiter(errorExitCode)
	} else {
		cli.OsExiter(okExitCode)
	}
}

func main() {
	RunApp(os.Args)
}
<commit_msg>Refactor main.go for readability and less duplication.<commit_after>package main

import (
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"os"

	"github.com/TNG/gpg-validation-server/gpg"
	"github.com/TNG/gpg-validation-server/storage"
	"github.com/TNG/gpg-validation-server/validator"
	"github.com/codegangsta/cli"
)

const (
	okExitCode    = 0
	errorExitCode = 1
)

var store storage.GetSetDeleter

func appAction(c *cli.Context) error {
	store = storage.NewMemoryStore()
	smtpHost := fmt.Sprintf("%v:%v", c.String("host"), c.Int("smtp-port"))
	httpHost := fmt.Sprintf("%v:%v", c.String("host"), c.Int("http-port"))

	privateKeyPath := c.String("private-key")
	if privateKeyPath == "" {
		return fmt.Errorf("Invalid private key file path: %s", privateKeyPath)
	}
	privateKeyInput, err := os.Open(privateKeyPath)
	if err != nil {
		return fmt.Errorf("Cannot open private key file '%s': %s", privateKeyPath, err)
	}
	defer func() { _ = privateKeyInput.Close() }()

	gpgUtil, err := gpg.NewGPG(privateKeyInput, c.String("passphrase"))
	if err != nil {
		return fmt.Errorf("Cannot initialize GPG: %s", err)
	}

	log.Println("Setting up SMTP server listening at: ", smtpHost)
	go serveSMTPRequestReceiver(fmt.Sprintf("%v:%v", c.String("host"), c.Int("smtp-port")), *gpgUtil)

	log.Println("Setting up HTTP server listening at: ", httpHost)
	log.Fatal(serveNonceConfirmer(c.String("host") + ":8080"))
	return nil
}

func processMailAction(c *cli.Context) error {
	var err error
	var inputMail *os.File

	inputFilePath := c.String("file")

	if inputFilePath == "" {
		inputMail = os.Stdin
	} else {
		inputMail, err = os.Open(inputFilePath)
		if err != nil {
			return fmt.Errorf("Cannot open mail file '%s': %s", inputFilePath, err)
		}
		defer func() { _ = inputMail.Close() }()
	}

	privateKeyPath := c.String("private-key")
	if privateKeyPath == "" {
		return fmt.Errorf("Invalid private key file path: %s", privateKeyPath)
	}
	privateKeyInput, err := os.Open(privateKeyPath)
	if err != nil {
		return fmt.Errorf("Cannot open private key file '%s': %s", privateKeyPath, err)
	}
	defer func() { _ = privateKeyInput.Close() }()

	gpgUtil, err := gpg.NewGPG(privateKeyInput, c.String("passphrase"))
	if err != nil {
		return fmt.Errorf("Cannot initialize GPG: %s", err)
	}

	result, err := validator.HandleMail(inputMail, gpgUtil)
	if err != nil {
		return fmt.Errorf("Cannot handle mail: %s", err)
	}

	log.Printf("Mail has valid signature: %v.\n", result.IsSigned())

	return nil
}

func confirmNonceAction(c *cli.Context) error {
	var nonce [32]byte

	nonceSlice, err := hex.DecodeString(c.String("nonce"))
	if err != nil {
		return err
	}
	if len(nonceSlice) != 32 {
		return errors.New(fmt.Sprint("Nonce has invalid length: ", len(nonceSlice)))
	}
	copy(nonce[:], nonceSlice)

	return ConfirmNonce(nonce)
}

func cliErrorHandler(action func(*cli.Context) error) func(*cli.Context) cli.ExitCoder {
	return func(c *cli.Context) cli.ExitCoder {
		if err := action(c); err != nil {
			return cli.NewExitError(fmt.Sprint("Error: ", err), errorExitCode)
		}
		return nil
	}
}

// subCommands to execute single aspects of the key validation process without requiring the full server startup.
var subCommands = []cli.Command{
	{
		Name:   "process-mail",
		Usage:  "process an incoming email",
		Action: cliErrorHandler(processMailAction),
		Flags: append(
			[]cli.Flag{
				cli.StringFlag{
					Name:  "file",
					Value: "./test/mails/signed_request_enigmail.eml",
					// TODO Handle missing value, use better default
					Usage: "`FILE_PATH` of the mail file, omit to read from stdin",
				},
			},
			privateKeyFlags...,
		),
	},
	{
		Name:   "confirm-nonce",
		Usage:  "process an nonce that has been confirmed",
		Action: cliErrorHandler(confirmNonceAction),
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  "nonce",
				Value: "",
				Usage: "String value of the Nonce",
			},
		},
	},
}

var privateKeyFlags = []cli.Flag{
	cli.StringFlag{
		Name:  "private-key",
		Value: "./test/keys/test-gpg-validation@server.local (0x87144E5E) sec.asc.gpg",
		// TODO Handle missing value, use better default
		Usage: "`PRIVATE_KEY_PATH` to the private gpg key of the server",
	},
	cli.StringFlag{
		Name:  "passphrase",
		Value: "validation",
		// TODO Handle missing value, use better default.
		Usage: "`PASSPHRASE` of the private key",
	},
}

// RunApp starts the server with the provided arguments.
func RunApp(args []string) {
	app := cli.NewApp()
	app.Name = "GPG Validation Service"
	app.Usage = "Run a server that manages email verification and signs verified keys with the servers GPG key."
	app.Commands = subCommands
	app.Action = cliErrorHandler(appAction)
	app.Flags = append(
		[]cli.Flag{
			cli.StringFlag{
				Name:  "host",
				Value: "localhost",
				Usage: "`HOST` of the mail and http servers. Set to the blank value to bind to all interfaces.",
			},
			cli.IntFlag{
				Name:  "http-port",
				Value: 8080,
				Usage: "`PORT` for the HTTP nonce receiver",
			},
			cli.IntFlag{
				Name:  "smtp-port",
				Value: 2525,
				Usage: "`PORT` for the SMTP server",
			},
		},
		privateKeyFlags...,
	)

	if err := app.Run(args); err != nil {
		cli.OsExiter(errorExitCode)
	} else {
		cli.OsExiter(okExitCode)
	}
}

func main() {
	RunApp(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/boombuler/barcode"
	"github.com/boombuler/barcode/qr"
	"github.com/ianschenck/envflag"
	"image/png"
	"log"
	"net"
	"net/http"
	"net/http/fcgi"
)

var (
	addr string
	size int
)

type httpServer struct{}

func (s httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	str := r.FormValue("c")
	if str == "" {
		log.Print("empty content")
		return
	}

	w.Header().Set("Content-Type", "image/png")

	qrcode, err := qr.Encode(str, qr.Q, qr.Auto) // L,M,Q,H
	if err != nil {
		log.Println(err)
	} else {
		qrcode, err = barcode.Scale(qrcode, size, size)
		if err != nil {
			log.Println(err)
		} else {
			log.Printf("generated barcode: %s", qrcode.Content())
			png.Encode(w, qrcode)
		}
	}
}

func init() {
	envflag.StringVar(&addr, "QRD_LISTEN", "127.0.0.1:9001", "listen address")
	envflag.IntVar(&size, "QRD_SIZE", 160, "barcode dimension")
}

func main() {
	var (
		l   net.Listener
		err error
	)
	envflag.Parse()

	if size < 100 {
		size = 100
	} else if size > 720 {
		size = 720
	}

	if addr[0] == '/' {
		l, err = net.Listen("unix", addr)
	} else {
		l, err = net.Listen("tcp", addr)
	}

	if err != nil {
		log.Println(err)
	}

	log.Printf("Start fcgi service at addr %s", addr)
	srv := new(httpServer)
	fcgi.Serve(l, srv)
}
<commit_msg>添加尺寸参数<commit_after>package main

import (
	"github.com/boombuler/barcode"
	"github.com/boombuler/barcode/qr"
	"github.com/ianschenck/envflag"
	"image/png"
	"log"
	"net"
	"net/http"
	"net/http/fcgi"
	"strconv"
)

var (
	addr      string
	dimension int
)

type httpServer struct{}

func (s httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	str := r.FormValue("c")
	if str == "" {
		log.Print("empty content")
		return
	}

	size := validSize(dimension)
	if s := r.FormValue("s"); s != "" {
		if i, err := strconv.ParseInt(s, 10, 64); err == nil {
			size = validSize(int(i))
		}
	}

	w.Header().Set("Content-Type", "image/png")

	qrcode, err := qr.Encode(str, qr.Q, qr.Auto) // L,M,Q,H
	if err != nil {
		log.Println(err)
	} else {
		qrcode, err = barcode.Scale(qrcode, size, size)
		if err != nil {
			log.Println(err)
		} else {
			log.Printf("generated barcode: %s", qrcode.Content())
			png.Encode(w, qrcode)
		}
	}
}

func init() {
	envflag.StringVar(&addr, "QRD_LISTEN", "127.0.0.1:9001", "listen address")
	envflag.IntVar(&dimension, "QRD_DIMENSION", 160, "barcode dimension")
}

func main() {
	var (
		l   net.Listener
		err error
	)
	envflag.Parse()

	if addr[0] == '/' {
		l, err = net.Listen("unix", addr)
	} else {
		l, err = net.Listen("tcp", addr)
	}

	if err != nil {
		log.Println(err)
	}

	log.Printf("Start fcgi service at addr %s", addr)
	srv := new(httpServer)
	fcgi.Serve(l, srv)
}

func validSize(dimension int) int {
	if dimension < 60 {
		return 60
	} else if dimension > 720 {
		return 720
	}
	return dimension
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"math/rand"
	"net/http"
	"time"

	"github.com/gorilla/mux"
	"github.com/sirupsen/logrus"
	"github.com/urfave/negroni"

	"github.com/ivan1993spb/snake-server/connections"
	"github.com/ivan1993spb/snake-server/handlers"
	"github.com/ivan1993spb/snake-server/middlewares"
)

const (
	defaultAddress     = ":8080"
	defaultGroupsLimit = 100
	defaultConnsLimit  = 1000
)

var (
	address     string
	groupsLimit int
	connsLimit  int
	seed        int64
	flagJSONLog bool
	logLevel    string
)

func init() {
	flag.StringVar(&address, "address", defaultAddress, "address to serve")
	flag.IntVar(&groupsLimit, "groups-limit", defaultGroupsLimit, "groups limit")
	flag.IntVar(&connsLimit, "conns-limit", defaultConnsLimit, "web-socket connections limit")
	flag.Int64Var(&seed, "seed", time.Now().UnixNano(), "random seed")
	flag.BoolVar(&flagJSONLog, "log-json", false, "use json format for logger")
	flag.StringVar(&logLevel, "log-level", "info", "set log level: panic, fatal, error, warning (warn), info or debug")
	flag.Parse()
}

func logger() *logrus.Logger {
	logger := logrus.New()
	if flagJSONLog {
		logger.Formatter = &logrus.JSONFormatter{}
	}
	if level, err := logrus.ParseLevel(logLevel); err != nil {
		logger.SetLevel(logrus.InfoLevel)
	} else {
		logger.SetLevel(level)
	}
	return logger
}

func main() {
	logger := logger()

	logger.WithFields(logrus.Fields{
		"conns_limit":  connsLimit,
		"groups_limit": groupsLimit,
		"seed":         seed,
		"log_level":    logLevel,
	}).Info("preparing to start server")

	rand.Seed(seed)

	groupManager, err := connections.NewConnectionGroupManager(logger, groupsLimit, connsLimit)
	if err != nil {
		logger.Fatalln("cannot create connections group manager:", err)
	}

	rootRouter := mux.NewRouter()

	// Web-Socket route
	rootRouter.Path(handlers.URLRouteGameWebSocketByID).Methods(handlers.MethodGame).Handler(handlers.NewGameWebSocketHandler(logger, groupManager))

	// API routes
	apiRouter := mux.NewRouter().StrictSlash(true)
	apiRouter.Path(handlers.URLRouteCreateGame).Methods(handlers.MethodCreateGame).Handler(handlers.NewCreateGameHandler(logger, groupManager))
	apiRouter.Path(handlers.URLRouteGetGameByID).Methods(handlers.MethodGetGame).Handler(handlers.NewGetGameHandler(logger, groupManager))
	apiRouter.Path(handlers.URLRouteDeleteGameByID).Methods(handlers.MethodDeleteGame).Handler(handlers.NewDeleteGameHandler(logger, groupManager))
	apiRouter.Path(handlers.URLRouteGetGames).Methods(handlers.MethodGetGames).Handler(handlers.NewGetGamesHandler(logger, groupManager))
	// Use middlewares for API routes
	rootRouter.NewRoute().Handler(negroni.New(
		middlewares.NewRecovery(logger),
		middlewares.NewLogger(logger, "api"),
		middlewares.NewCORS(),
		negroni.Wrap(apiRouter),
	))

	n := negroni.New()
	n.UseHandler(rootRouter)

	logger.WithField("address", address).Info("starting server")

	if err := http.ListenAndServe(address, n); err != nil {
		logger.Fatalf("server error: %s", err)
	}
}
<commit_msg>Create routes handlers in main.go<commit_after>package main

import (
	"flag"
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"time"

	"github.com/gorilla/mux"
	"github.com/sirupsen/logrus"
	"github.com/urfave/negroni"

	"github.com/ivan1993spb/snake-server/connections"
	"github.com/ivan1993spb/snake-server/handlers"
	"github.com/ivan1993spb/snake-server/middlewares"
)

const ServerName = "Snake-Server"

const (
	defaultAddress     = ":8080"
	defaultGroupsLimit = 100
	defaultConnsLimit  = 1000
)

var (
	Version = "dev"
	Build   = "dev"
)

var (
	address     string
	groupsLimit int
	connsLimit  int
	seed        int64
	flagJSONLog bool
	logLevel    string
)

func usage() {
	fmt.Fprint(os.Stderr, "Wellcome to snake-server!\n\n")
	fmt.Fprintf(os.Stderr, "Server version %s, build %s\n\n", Version, Build)
	fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\n", os.Args[0])
	flag.PrintDefaults()
}

func init() {
	flag.StringVar(&address, "address", defaultAddress, "address to serve")
	flag.IntVar(&groupsLimit, "groups-limit", defaultGroupsLimit, "groups limit")
	flag.IntVar(&connsLimit, "conns-limit", defaultConnsLimit, "web-socket connections limit")
	flag.Int64Var(&seed, "seed", time.Now().UnixNano(), "random seed")
	flag.BoolVar(&flagJSONLog, "log-json", false, "use json format for logger")
	flag.StringVar(&logLevel, "log-level", "info", "set log level: panic, fatal, error, warning (warn), info or debug")
	flag.Usage = usage
	flag.Parse()
}

func logger() *logrus.Logger {
	logger := logrus.New()
	if flagJSONLog {
		logger.Formatter = &logrus.JSONFormatter{}
	} else {
		// TODO: Set up formatter with colors for windows ?
		// https://github.com/sirupsen/logrus/issues/172
	}
	if level, err := logrus.ParseLevel(logLevel); err != nil {
		logger.SetLevel(logrus.InfoLevel)
	} else {
		logger.SetLevel(level)
	}
	return logger
}

func main() {
	logger := logger()

	logger.WithFields(logrus.Fields{
		"version": Version,
		"build":   Build,
	}).Info("wellcome to snake server!")

	logger.WithFields(logrus.Fields{
		"conns_limit":  connsLimit,
		"groups_limit": groupsLimit,
		"seed":         seed,
		"log_level":    logLevel,
	}).Info("preparing to start server")

	rand.Seed(seed)

	groupManager, err := connections.NewConnectionGroupManager(logger, groupsLimit, connsLimit)
	if err != nil {
		logger.Fatalln("cannot create connections group manager:", err)
	}

	rootRouter := mux.NewRouter()

	// Web-Socket route
	rootRouter.Path(handlers.URLRouteGameWebSocketByID).Methods(handlers.MethodGame).Handler(handlers.NewGameWebSocketHandler(logger, groupManager))

	// API routes
	apiRouter := mux.NewRouter().StrictSlash(true)
	apiRouter.Path(handlers.URLRouteGetInfo).Methods(handlers.MethodGetInfo).Handler(handlers.NewGetInfoHandler(logger))
	apiRouter.Path(handlers.URLRouteGetCapacity).Methods(handlers.MethodGetCapacity).Handler(handlers.NewGetCapacityHandler(logger, groupManager))
	apiRouter.Path(handlers.URLRouteCreateGame).Methods(handlers.MethodCreateGame).Handler(handlers.NewCreateGameHandler(logger, groupManager))
	apiRouter.Path(handlers.URLRouteGetGameByID).Methods(handlers.MethodGetGame).Handler(handlers.NewGetGameHandler(logger, groupManager))
	apiRouter.Path(handlers.URLRouteDeleteGameByID).Methods(handlers.MethodDeleteGame).Handler(handlers.NewDeleteGameHandler(logger, groupManager))
	apiRouter.Path(handlers.URLRouteGetGames).Methods(handlers.MethodGetGames).Handler(handlers.NewGetGamesHandler(logger, groupManager))
	// Use middlewares for API routes
	rootRouter.NewRoute().Handler(negroni.New(
		middlewares.NewRecovery(logger),
		middlewares.NewLogger(logger, "api"),
		middlewares.NewCORS(),
		negroni.Wrap(apiRouter),
	))

	n := negroni.New()
	n.Use(middlewares.NewServerInfo(ServerName, Version, Build))
	n.UseHandler(rootRouter)

	logger.WithField("address", address).Info("starting server")

	if err := http.ListenAndServe(address, n); err != nil {
		logger.Fatalf("server error: %s", err)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 The AUTHORS
//
// This file is part of paprika-github-status.
//
// paprika-github-status is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// paprika-github-status 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with paprika-github-status.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	// Stdlib
	"os"
	"os/signal"
	"strings"
	"syscall"

	// Cider
	"github.com/cider/go-cider/cider/services/logging"
	"github.com/cider/go-cider/cider/services/pubsub"
	zlogging "github.com/cider/go-cider/cider/transports/zmq3/logging"
	zpubsub "github.com/cider/go-cider/cider/transports/zmq3/pubsub"

	// Others
	"code.google.com/p/goauth2/oauth"
	"github.com/google/go-github/github"
	zmq "github.com/pebbe/zmq3"
)

func main() {
	// Initialise the Logging service.
	logger, err := logging.NewService(func() (logging.Transport, error) {
		factory := zlogging.NewTransportFactory()
		factory.MustReadConfigFromEnv("CIDER_ZMQ3_LOGGING_").MustBeFullyConfigured()
		return factory.NewTransport(os.Getenv("CIDER_ALIAS"))
	})
	if err != nil {
		panic(err)
	}
	defer zmq.Term()
	defer logger.Flush()

	if err := innerMain(logger); err != nil {
		panic(err)
	}
}

func innerMain(logger *logging.Service) error {
	// Read the required variables from the environment.
	token := os.Getenv("GITHUB_TOKEN")
	if token == "" {
		return logger.Critical("GITHUB_TOKEN is not set")
	}

	// Initialise the GitHub client.
	transport := oauth.Transport{
		Token: &oauth.Token{AccessToken: token},
	}
	client := github.NewClient(transport.Client())

	// Initialise the PubSub service.
	eventBus, err := pubsub.NewService(func() (pubsub.Transport, error) {
		factory := zpubsub.NewTransportFactory()
		factory.MustReadConfigFromEnv("CIDER_ZMQ3_PUBSUB_").MustBeFullyConfigured()
		return factory.NewTransport(os.Getenv("CIDER_ALIAS"))
	})
	if err != nil {
		return logger.Critical(err)
	}
	defer func() {
		select {
		case <-eventBus.Closed():
			goto Wait
		default:
		}
		if err := eventBus.Close(); err != nil {
			logger.Critical(err)
		}
	Wait:
		if err := eventBus.Wait(); err != nil {
			logger.Critical(err)
		}
	}()

	// Start catching signals.
	signalCh := make(chan os.Signal, 1)
	signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)

	// Subscribe to all significant build events.
	if _, err := eventBus.Subscribe("paprika.build.enqueued", func(event pubsub.Event) {
		// Unmarshal the event object.
		var body BuildEnqueuedEvent
		if err := event.Unmarshal(&body); err != nil {
			logger.Warn(err)
			return
		}

		// Do nothing if the build source is not a pull request.
		if body.PullRequest == nil {
			logger.Info("ENQUEUED event received, but not a pull request, skipping...")
			return
		}

		// Set the pull request status to pending.
		logger.Infof("Setting status for %v to PENDING", body.PullRequest.HTMLURL)
		if err := postStatus(client, body.PullRequest.StatusesURL, &github.RepoStatus{
			State:       github.String("pending"),
			Description: github.String("The build is enqueued or running"),
			Context:     github.String("Paprika CI"),
		}); err != nil {
			logger.Error(err)
			return
		}
	}); err != nil {
		return logger.Critical(err)
	}

	if _, err := eventBus.Subscribe("paprika.build.finished", func(event pubsub.Event) {
		// Unmarshal the event object.
		var body BuildFinishedEvent
		if err := event.Unmarshal(&body); err != nil {
			logger.Warn(err)
			return
		}

		// Do nothing if the build source is not a pull request.
		if body.PullRequest == nil {
			logger.Info("FINISHED event received, but not a pull request, skipping...")
			return
		}

		// Set the final pull request status.
		var desc string
		switch body.Result {
		case "success":
			desc = "The build succeeded!"
		case "failure":
			desc = "The build failed!"
		case "error":
			if body.Error != "" {
				desc = body.Error
			} else {
				desc = "Paprika exploded, oops"
			}
		}

		logger.Infof("Setting status for %v to %v",
			body.PullRequest.HTMLURL, strings.ToUpper(body.Result))
		if err := postStatus(client, body.PullRequest.StatusesURL, &github.RepoStatus{
			State:       &body.Result,
			TargetURL:   body.OutputURL,
			Description: &desc,
			Context:     github.String("Paprika CI"),
		}); err != nil {
			logger.Error(err)
			return
		}
	}); err != nil {
		return logger.Critical(err)
	}

	// Start processing signals, block until crashed or terminated.
	select {
	case <-eventBus.Closed():
		return eventBus.Wait()
	case <-signalCh:
		return nil
	}
}

func postStatus(client *github.Client, statusesURL string, status *github.RepoStatus) error {
	req, err := client.NewRequest("POST", statusesURL, status)
	if err != nil {
		return err
	}

	req.Header.Set("Accept", "application/vnd.github.she-hulk-preview+json")

	_, err = client.Do(req, nil)
	return err
}
<commit_msg>bugfix: Close the Logging service properly<commit_after>// Copyright (c) 2014 The AUTHORS
//
// This file is part of paprika-github-status.
//
// paprika-github-status is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// paprika-github-status 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with paprika-github-status.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	// Stdlib
	"os"
	"os/signal"
	"strings"
	"syscall"

	// Cider
	"github.com/cider/go-cider/cider/services/logging"
	"github.com/cider/go-cider/cider/services/pubsub"
	zlogging "github.com/cider/go-cider/cider/transports/zmq3/logging"
	zpubsub "github.com/cider/go-cider/cider/transports/zmq3/pubsub"

	// Others
	"code.google.com/p/goauth2/oauth"
	"github.com/google/go-github/github"
	zmq "github.com/pebbe/zmq3"
)

func main() {
	// Initialise the Logging service.
	logger, err := logging.NewService(func() (logging.Transport, error) {
		factory := zlogging.NewTransportFactory()
		factory.MustReadConfigFromEnv("CIDER_ZMQ3_LOGGING_").MustBeFullyConfigured()
		return factory.NewTransport(os.Getenv("CIDER_ALIAS"))
	})
	if err != nil {
		panic(err)
	}
	defer zmq.Term()
	defer logger.Close()

	if err := innerMain(logger); err != nil {
		panic(err)
	}
}

func innerMain(logger *logging.Service) error {
	// Read the required variables from the environment.
	token := os.Getenv("GITHUB_TOKEN")
	if token == "" {
		return logger.Critical("GITHUB_TOKEN is not set")
	}

	// Initialise the GitHub client.
	transport := oauth.Transport{
		Token: &oauth.Token{AccessToken: token},
	}
	client := github.NewClient(transport.Client())

	// Initialise the PubSub service.
	eventBus, err := pubsub.NewService(func() (pubsub.Transport, error) {
		factory := zpubsub.NewTransportFactory()
		factory.MustReadConfigFromEnv("CIDER_ZMQ3_PUBSUB_").MustBeFullyConfigured()
		return factory.NewTransport(os.Getenv("CIDER_ALIAS"))
	})
	if err != nil {
		return logger.Critical(err)
	}
	defer func() {
		select {
		case <-eventBus.Closed():
			goto Wait
		default:
		}
		if err := eventBus.Close(); err != nil {
			logger.Critical(err)
		}
	Wait:
		if err := eventBus.Wait(); err != nil {
			logger.Critical(err)
		}
	}()

	// Start catching signals.
	signalCh := make(chan os.Signal, 1)
	signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)

	// Subscribe to all significant build events.
	if _, err := eventBus.Subscribe("paprika.build.enqueued", func(event pubsub.Event) {
		// Unmarshal the event object.
		var body BuildEnqueuedEvent
		if err := event.Unmarshal(&body); err != nil {
			logger.Warn(err)
			return
		}

		// Do nothing if the build source is not a pull request.
		if body.PullRequest == nil {
			logger.Info("ENQUEUED event received, but not a pull request, skipping...")
			return
		}

		// Set the pull request status to pending.
		logger.Infof("Setting status for %v to PENDING", body.PullRequest.HTMLURL)
		if err := postStatus(client, body.PullRequest.StatusesURL, &github.RepoStatus{
			State:       github.String("pending"),
			Description: github.String("The build is enqueued or running"),
			Context:     github.String("Paprika CI"),
		}); err != nil {
			logger.Error(err)
			return
		}
	}); err != nil {
		return logger.Critical(err)
	}

	if _, err := eventBus.Subscribe("paprika.build.finished", func(event pubsub.Event) {
		// Unmarshal the event object.
		var body BuildFinishedEvent
		if err := event.Unmarshal(&body); err != nil {
			logger.Warn(err)
			return
		}

		// Do nothing if the build source is not a pull request.
		if body.PullRequest == nil {
			logger.Info("FINISHED event received, but not a pull request, skipping...")
			return
		}

		// Set the final pull request status.
		var desc string
		switch body.Result {
		case "success":
			desc = "The build succeeded!"
		case "failure":
			desc = "The build failed!"
		case "error":
			if body.Error != "" {
				desc = body.Error
			} else {
				desc = "Paprika exploded, oops"
			}
		}

		logger.Infof("Setting status for %v to %v",
			body.PullRequest.HTMLURL, strings.ToUpper(body.Result))
		if err := postStatus(client, body.PullRequest.StatusesURL, &github.RepoStatus{
			State:       &body.Result,
			TargetURL:   body.OutputURL,
			Description: &desc,
			Context:     github.String("Paprika CI"),
		}); err != nil {
			logger.Error(err)
			return
		}
	}); err != nil {
		return logger.Critical(err)
	}

	// Start processing signals, block until crashed or terminated.
	select {
	case <-eventBus.Closed():
		return eventBus.Wait()
	case <-signalCh:
		return nil
	}
}

func postStatus(client *github.Client, statusesURL string, status *github.RepoStatus) error {
	req, err := client.NewRequest("POST", statusesURL, status)
	if err != nil {
		return err
	}

	req.Header.Set("Accept", "application/vnd.github.she-hulk-preview+json")

	_, err = client.Do(req, nil)
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/jessevdk/go-flags"
	"github.com/yuya-takeyama/argf"
)

func usage() {
	os.Stderr.WriteString(`
Usage: cspv OPTION... [FILE]...
Print selected parts of CSV from each FILE to standard output.

Options:
  -i, --indexes=LIST       select only these indexes
  -h, --headers=LIST       select only these headers
  -d, --delimiter=STRING   use STRING as the output delimiter (default: \t)
      --help               display this help text and exit
      --version            display version information and exit
`[1:])
}

func version() {
	os.Stderr.WriteString(`
v0.3.0
`[1:])
}

type Option struct {
	IndexesList string `short:"i" long:"indexes"`
	HeadersList string `short:"h" long:"headers"`
	Delimiter   string `short:"d" long:"delimiter" default:"\t"`
	IsHelp      bool   `          long:"help"`
	IsVersion   bool   `          long:"version"`
	Files       []string
}

func parseOption(args []string) (opt *Option, err error) {
	opt = &Option{}
	flag := flags.NewParser(opt, flags.PassDoubleDash)

	opt.Files, err = flag.ParseArgs(args)
	if err != nil && !opt.IsHelp && !opt.IsVersion {
		return nil, err
	}
	return opt, nil
}

func newCSVScannerFromOption(opt *Option) (c *CSVScanner, err error) {
	var selector Selector
	switch {
	case opt.IndexesList != "" && opt.HeadersList != "":
		return nil, fmt.Errorf("only one type of list may be specified")
	case opt.IndexesList != "":
		indexes, err := parseIndexesList(opt.IndexesList)
		if err != nil {
			return nil, err
		}
		selector = NewIndexes(indexes)
	case opt.HeadersList != "":
		headers, err := parseHeadersList(opt.HeadersList)
		if err != nil {
			return nil, err
		}
		selector = NewHeaders(headers)
	default:
		selector = NewAll()
	}

	reader, err := argf.From(opt.Files)
	if err != nil {
		return nil, err
	}

	c = NewCSVScanner(selector, reader)
	c.Delimiter = opt.Delimiter
	return c, nil
}

func do(c *CSVScanner) error {
	for c.Scan() {
		fmt.Println(c.Text())
	}
	return c.Err()
}

func printErr(err error) {
	fmt.Fprintln(os.Stderr, "cspv:", err)
}

func guideToHelp() {
	os.Stderr.WriteString(`
Try 'cspv --help' for more information.
`[1:])
}

func _main() int {
	opt, err := parseOption(os.Args[1:])
	if err != nil {
		printErr(err)
		guideToHelp()
		return 2
	}
	switch {
	case opt.IsHelp:
		usage()
		return 0
	case opt.IsVersion:
		version()
		return 0
	}

	c, err := newCSVScannerFromOption(opt)
	if err != nil {
		printErr(err)
		guideToHelp()
		return 2
	}
	if err = do(c); err != nil {
		printErr(err)
		return 1
	}
	return 0
}

func main() {
	e := _main()
	os.Exit(e)
}
<commit_msg>Rewrite usage line to allow default selector<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/jessevdk/go-flags"
	"github.com/yuya-takeyama/argf"
)

func usage() {
	os.Stderr.WriteString(`
Usage: csvp [OPTION]... [FILE]...
Print selected parts of CSV from each FILE to standard output.

Options:
  -i, --indexes=LIST       select only these indexes
  -h, --headers=LIST       select only these headers
  -d, --delimiter=STRING   use STRING as the output delimiter (default: \t)
      --help               display this help text and exit
      --version            display version information and exit
`[1:])
}

func version() {
	os.Stderr.WriteString(`
v0.3.0
`[1:])
}

type Option struct {
	IndexesList string `short:"i" long:"indexes"`
	HeadersList string `short:"h" long:"headers"`
	Delimiter   string `short:"d" long:"delimiter" default:"\t"`
	IsHelp      bool   `          long:"help"`
	IsVersion   bool   `          long:"version"`
	Files       []string
}

func parseOption(args []string) (opt *Option, err error) {
	opt = &Option{}
	flag := flags.NewParser(opt, flags.PassDoubleDash)

	opt.Files, err = flag.ParseArgs(args)
	if err != nil && !opt.IsHelp && !opt.IsVersion {
		return nil, err
	}
	return opt, nil
}

func newCSVScannerFromOption(opt *Option) (c *CSVScanner, err error) {
	var selector Selector
	switch {
	case opt.IndexesList != "" && opt.HeadersList != "":
		return nil, fmt.Errorf("only one type of list may be specified")
	case opt.IndexesList != "":
		indexes, err := parseIndexesList(opt.IndexesList)
		if err != nil {
			return nil, err
		}
		selector = NewIndexes(indexes)
	case opt.HeadersList != "":
		headers, err := parseHeadersList(opt.HeadersList)
		if err != nil {
			return nil, err
		}
		selector = NewHeaders(headers)
	default:
		selector = NewAll()
	}

	reader, err := argf.From(opt.Files)
	if err != nil {
		return nil, err
	}

	c = NewCSVScanner(selector, reader)
	c.Delimiter = opt.Delimiter
	return c, nil
}

func do(c *CSVScanner) error {
	for c.Scan() {
		fmt.Println(c.Text())
	}
	return c.Err()
}

func printErr(err error) {
	fmt.Fprintln(os.Stderr, "cspv:", err)
}

func guideToHelp() {
	os.Stderr.WriteString(`
Try 'cspv --help' for more information.
`[1:])
}

func _main() int {
	opt, err := parseOption(os.Args[1:])
	if err != nil {
		printErr(err)
		guideToHelp()
		return 2
	}
	switch {
	case opt.IsHelp:
		usage()
		return 0
	case opt.IsVersion:
		version()
		return 0
	}

	c, err := newCSVScannerFromOption(opt)
	if err != nil {
		printErr(err)
		guideToHelp()
		return 2
	}
	if err = do(c); err != nil {
		printErr(err)
		return 1
	}
	return 0
}

func main() {
	e := _main()
	os.Exit(e)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"runtime"
	"runtime/pprof"
	"time"
)

const (
	name    string = "godedupe"
	version string = "1.1.1"
)

var (
	cpuprofile         string
	currentDir         string
	excludeEmptyDir    bool
	excludeEmptyFiles  bool
	excludeHiddenFiles bool
	showCurrentValues  bool
	enableRecursion    bool
	ignoreSymLinks     bool
	showSummary        bool
	quiet              bool
	showNotification   bool
	fileExt            string
	maxDepth           int
)

// Options for start the program
type Options struct {
	currentDir         string
	excludeEmptyFiles  bool
	excludeHiddenFiles bool
	enableRecursion    bool
	ignoreSymLinks     bool
	showSummary        bool
	quiet              bool
	showNotification   bool
	fileExt            string
	maxDepth           int
}

// Init the options to run the program
func initOptions() {
	fmt.Println()
	flag.StringVar(&cpuprofile, "cpuprofile", "", "Enable profiling")
	flag.StringVar(&currentDir, "t", GetUserHome(),
		"Current directory where the program search for duplicated files")
	flag.BoolVar(&excludeEmptyFiles, "z", true, "Exclude the zero length files")
	flag.BoolVar(&excludeHiddenFiles, "h", true, "Exclude the hidden files")
	flag.BoolVar(&showCurrentValues, "debug", false,
		"Show the current values of the program options")
	flag.BoolVar(&enableRecursion, "r", true, "Follow subdirectories (recursion)")
	flag.BoolVar(&ignoreSymLinks, "sym", true, "Ignore symlinks")
	flag.BoolVar(&showSummary, "m", false, "Show a summary")
	flag.BoolVar(&quiet, "q", false, "Don't show progress info")
	flag.BoolVar(&showNotification, "show-notification", false,
		"Show a desktop notification when the program finish")
	flag.StringVar(&fileExt, "ext", "", "Only find duplicates for the given extension")
	flag.IntVar(&maxDepth, "d", -1, "Max recursion depth, -1 = no limit. 1 = current directory")
	flag.Parse()
}

// Header show the program name and current version
func header() {
	if !quiet {
		fmt.Println("------------------------")
		fmt.Printf("%s - version %s\n", name, version)
		fmt.Println("------------------------")
	}
}

// ShowDebugInfo print all the current option values
func showDebugInfo() {
	if showCurrentValues && !quiet {
		fmt.Println()
		fmt.Println("------------------------")
		fmt.Println("Current option values")
		fmt.Println("------------------------")
		fmt.Println("Target directory          :", currentDir)
		fmt.Println("Exclude zero length files :", excludeEmptyFiles)
		fmt.Println("Exclude hidden files      :", excludeHiddenFiles)
		fmt.Println("Ignore symlinks           :", ignoreSymLinks)
		fmt.Println("Recursive search          :", enableRecursion)
		fmt.Println("Show a summary            :", showSummary)
		fmt.Println("Quiet                     :", quiet)
		fmt.Println("File extension            :", fileExt)
		fmt.Println("Max depth                 :", maxDepth)
		if cpuprofile != "" {
			fmt.Println("Profile output            :", cpuprofile)
		}
		fmt.Println("------------------------")
	}
}

func trackTime(now time.Time) {
	expired := time.Since(now)
	if !quiet {
		fmt.Printf("[+] Program terminated in %v\n", expired)
	}
}

func executeCPUProfile() {
	f, err := os.Create(cpuprofile)
	if err != nil {
		panic(err)
	}
	pprof.StartCPUProfile(f)
}

func main() {
	header()

	initOptions()

	showDebugInfo()

	options := Options{
		currentDir,
		excludeEmptyFiles,
		excludeHiddenFiles,
		enableRecursion,
		ignoreSymLinks,
		showSummary,
		quiet,
		showNotification,
		fileExt,
		maxDepth,
	}

	if cpuprofile != "" {
		executeCPUProfile()
		defer pprof.StopCPUProfile()
	}

	defer trackTime(time.Now())

	runtime.GOMAXPROCS(runtime.NumCPU())
	Start(options)
}
<commit_msg>Version bump<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"runtime"
	"runtime/pprof"
	"time"
)

const (
	name    string = "godedupe"
	version string = "1.2.1"
)

var (
	cpuprofile         string
	currentDir         string
	excludeEmptyDir    bool
	excludeEmptyFiles  bool
	excludeHiddenFiles bool
	showCurrentValues  bool
	enableRecursion    bool
	ignoreSymLinks     bool
	showSummary        bool
	quiet              bool
	showNotification   bool
	fileExt            string
	maxDepth           int
)

// Options for start the program
type Options struct {
	currentDir         string
	excludeEmptyFiles  bool
	excludeHiddenFiles bool
	enableRecursion    bool
	ignoreSymLinks     bool
	showSummary        bool
	quiet              bool
	showNotification   bool
	fileExt            string
	maxDepth           int
}

// Init the options to run the program
func initOptions() {
	fmt.Println()
	flag.StringVar(&cpuprofile, "cpuprofile", "", "Enable profiling")
	flag.StringVar(&currentDir, "t", GetUserHome(),
		"Current directory where the program search for duplicated files")
	flag.BoolVar(&excludeEmptyFiles, "z", true, "Exclude the zero length files")
	flag.BoolVar(&excludeHiddenFiles, "h", true, "Exclude the hidden files")
	flag.BoolVar(&showCurrentValues, "debug", false,
		"Show the current values of the program options")
	flag.BoolVar(&enableRecursion, "r", true, "Follow subdirectories (recursion)")
	flag.BoolVar(&ignoreSymLinks, "sym", true, "Ignore symlinks")
	flag.BoolVar(&showSummary, "m", false, "Show a summary")
	flag.BoolVar(&quiet, "q", false, "Don't show progress info")
	flag.BoolVar(&showNotification, "show-notification", false,
		"Show a desktop notification when the program finish")
	flag.StringVar(&fileExt, "ext", "", "Only find duplicates for the given extension")
	flag.IntVar(&maxDepth, "d", -1, "Max recursion depth, -1 = no limit. 1 = current directory")
	flag.Parse()
}

// Header show the program name and current version
func header() {
	if !quiet {
		fmt.Println("------------------------")
		fmt.Printf("%s - version %s\n", name, version)
		fmt.Println("------------------------")
	}
}

// ShowDebugInfo print all the current option values
func showDebugInfo() {
	if showCurrentValues && !quiet {
		fmt.Println()
		fmt.Println("------------------------")
		fmt.Println("Current option values")
		fmt.Println("------------------------")
		fmt.Println("Target directory          :", currentDir)
		fmt.Println("Exclude zero length files :", excludeEmptyFiles)
		fmt.Println("Exclude hidden files      :", excludeHiddenFiles)
		fmt.Println("Ignore symlinks           :", ignoreSymLinks)
		fmt.Println("Recursive search          :", enableRecursion)
		fmt.Println("Show a summary            :", showSummary)
		fmt.Println("Quiet                     :", quiet)
		fmt.Println("File extension            :", fileExt)
		fmt.Println("Max depth                 :", maxDepth)
		if cpuprofile != "" {
			fmt.Println("Profile output            :", cpuprofile)
		}
		fmt.Println("------------------------")
	}
}

func trackTime(now time.Time) {
	expired := time.Since(now)
	if !quiet {
		fmt.Printf("[+] Program terminated in %v\n", expired)
	}
}

func executeCPUProfile() {
	f, err := os.Create(cpuprofile)
	if err != nil {
		panic(err)
	}
	pprof.StartCPUProfile(f)
}

func main() {
	header()

	initOptions()

	showDebugInfo()

	options := Options{
		currentDir,
		excludeEmptyFiles,
		excludeHiddenFiles,
		enableRecursion,
		ignoreSymLinks,
		showSummary,
		quiet,
		showNotification,
		fileExt,
		maxDepth,
	}

	if cpuprofile != "" {
		executeCPUProfile()
		defer pprof.StopCPUProfile()
	}

	defer trackTime(time.Now())

	runtime.GOMAXPROCS(runtime.NumCPU())
	Start(options)
}
<|endoftext|>


<commit_before>package main

// TODO: figure out how to handle varying numbers of pixels
// when we're getting pixels via our OPC server source

import (
	"bitbucket.org/davidwallace/go-metal/beaglebone"
	"bitbucket.org/davidwallace/go-metal/opc"
	"fmt"
	"github.com/davecheney/profile"
	"github.com/droundy/goopt"
	"os"
	"strings"
	"time"
)

const SPI_MAGIC_WORD = "spi"
const SCREEN_MAGIC_WORD = "screen"
const DEVNULL_MAGIC_WORD = "/dev/null"
const OPC_LOCALHOST_MAGIC_WORD = "localhost"
const SPI_FN = "/dev/spidev1.0"

// these are pointers to the actual values from the command line parser
var LAYOUT_FN = goopt.String([]string{"-l", "--layout"}, "...", "layout file (required)")
var SOURCE = goopt.String([]string{"-s", "--source"}, "spatial-stripes", "pixel source (either a pattern name or "+OPC_LOCALHOST_MAGIC_WORD+"[:port])")
var DEST = goopt.String([]string{"-d", "--dest"}, "localhost", "destination (one of "+SCREEN_MAGIC_WORD+", "+SPI_MAGIC_WORD+", "+DEVNULL_MAGIC_WORD+", or hostname[:port])")
var FPS = goopt.Int([]string{"-f", "--fps"}, 40, "max frames per second")
var SECONDS = goopt.Int([]string{"-n", "--seconds"}, 0, "quit after this many seconds")
var ONCE = goopt.Flag([]string{"-o", "--once"}, []string{}, "quit after one frame", "")

// Parse the command line flags.  If invalid, show help and quit.
// Add default ports if needed.
// Read the layout file.
// Return the number of pixels in the layout, the source and dest thread methods.
func parseFlags() (nPixels int, sourceThread, destThread opc.ByteThread) {
	goopt.Summary = "Available patterns:\n"
	goopt.Summary += "          off \n"
	goopt.Summary += "          raver-plaid \n"
	goopt.Summary += "          spatial-stripes \n"
	goopt.Summary += "          test \n"
	goopt.Parse(nil)

	// layout is required
	if *LAYOUT_FN == "..." {
		fmt.Println(goopt.Usage())
		fmt.Println("--------------------------------------------------------------------------------/")
		os.Exit(1)
	}

	// read locations
	locations := opc.ReadLocations(*LAYOUT_FN)
	nPixels = len(locations) / 3

	// choose source thread method
	switch *SOURCE {
	case "off":
		sourceThread = opc.MakePatternOff(locations)
	case "raver-plaid":
		sourceThread = opc.MakePatternRaverPlaid(locations)
	case "spatial-stripes":
		sourceThread = opc.MakePatternSpatialStripes(locations)
	case "test":
		sourceThread = opc.MakePatternTest(locations)
		// todo: case localhost:7890
		//    add port if needed
		//    sourceThread = opc.MakeOpcServer(*SOURCE)
	default:
		fmt.Printf("Error: unknown source or pattern \"%s\"\n", *SOURCE)
		fmt.Println("--------------------------------------------------------------------------------/")
		os.Exit(1)
	}

	// choose dest thread method
	switch *DEST {
	case DEVNULL_MAGIC_WORD:
		destThread = opc.MakeSendToDevNullThread()
	case SCREEN_MAGIC_WORD:
		destThread = opc.MakeSendToScreenThread()
	case SPI_MAGIC_WORD:
		destThread = opc.MakeSendToLPD8806Thread(SPI_FN)
	default:
		// add default port if needed
		if !strings.Contains(*DEST, ":") {
			*DEST += ":7890"
		}
		destThread = opc.MakeSendToOpcThread(*DEST)
	}

	return // returns nPixels, sourceThread, destThread
}

// Launch the sourceThread and destThread methods and coordinate the transfer of bytes from one to the other.
// Run until timeToRun seconds have passed and return.  If timeToRun is 0, run forever.
// Turn on the CPU profiler if timeToRun seconds > 0.
// Limit the framerate to a max of fps unless fps is 0.
func mainLoop(nPixels int, sourceThread, destThread opc.ByteThread, fps float64, timeToRun float64) {
	if timeToRun > 0 {
		fmt.Printf("[mainLoop] Running for %f seconds with profiling turned on, pixels and network\n", timeToRun)
		defer profile.Start(profile.CPUProfile).Stop()
	} else {
		fmt.Println("[mainLoop] Running forever")
	}

	// prepare the byte slices and channels that connect the source and dest threads
	fillingSlice := make([]byte, nPixels*3)
	sendingSlice := make([]byte, nPixels*3)

	bytesToFillChan := make(chan []byte, 0)
	bytesFilledChan := make(chan []byte, 0)
	bytesToSendChan := make(chan []byte, 0)
	bytesSentChan := make(chan []byte, 0)

	// launch the threads
	go sourceThread(bytesToFillChan, bytesFilledChan)
	go destThread(bytesToSendChan, bytesSentChan)

	// main loop
	frame_budget_ms := 1000.0 / fps
	startTime := float64(time.Now().UnixNano()) / 1.0e9
	lastPrintTime := startTime
	frameStartTime := startTime
	frameEndTime := startTime
	framesSinceLastPrint := 0
	firstIteration := true
	flipper := 0
	for {
		// if we have any frame budget left from last time around, sleep to control the framerate
		if fps > 0 {
			frameEndTime = float64(time.Now().UnixNano()) / 1.0e9
			timeRemaining := float64(frame_budget_ms)/1000 - (frameEndTime - frameStartTime)
			if timeRemaining > 0 {
				time.Sleep(time.Duration(timeRemaining*1000*1000) * time.Microsecond)
			}
		}

		// fps reporting and bookkeeping
		// print framerate occasionally
		frameStartTime = float64(time.Now().UnixNano()) / 1.0e9
		framesSinceLastPrint += 1
		if frameStartTime > lastPrintTime+1 {
			lastPrintTime = frameStartTime
			fmt.Printf("[mainLoop] %f ms/frame (%d fps)\n", 1000.0/float64(framesSinceLastPrint), framesSinceLastPrint)
			framesSinceLastPrint = 0
			// toggle LED
			beaglebone.SetOnboardLED(0, flipper)
			flipper = 1 - flipper
		}

		// if profiling, quit after a while
		if timeToRun > 0 && frameStartTime > startTime+timeToRun {
			return
		}

		// start the threads filling and sending slices in parallel.
		// if this is the first time through the loop we have to skip
		//  the sending stage or we'll send out a whole bunch of zeros.
		bytesToFillChan <- fillingSlice
		if !firstIteration {
			bytesToSendChan <- sendingSlice
		}

		// if only sending one frame, let's just get it all over with now
		//  or we'd have to compute two frames worth of pixels because of
		//  the double buffering effect of the two parallel threads
		if *ONCE {
			// get filled bytes and send them
			bytesToSendChan <- <-bytesFilledChan
			// wait for sending to complete
			<-bytesSentChan
			fmt.Println("[mainLoop] just running once.  quitting now.")
			return
		}

		// wait until both filling and sending threads are done
		<-bytesFilledChan
		if !firstIteration {
			<-bytesSentChan
		}

		// swap the slices
		sendingSlice, fillingSlice = fillingSlice, sendingSlice

		firstIteration = false
	}
}

func main() {
	fmt.Println("--------------------------------------------------------------------------------\\")
	defer fmt.Println("--------------------------------------------------------------------------------/")

	nPixels, sourceThread, destThread := parseFlags()
	mainLoop(nPixels, sourceThread, destThread, float64(*FPS), float64(*SECONDS))
}
<commit_msg>main.go: let it send a full frame of zeroes on the first iteration<commit_after>package main

// TODO: figure out how to handle varying numbers of pixels
// when we're getting pixels via our OPC server source

import (
	"bitbucket.org/davidwallace/go-metal/beaglebone"
	"bitbucket.org/davidwallace/go-metal/opc"
	"fmt"
	"github.com/davecheney/profile"
	"github.com/droundy/goopt"
	"os"
	"strings"
	"time"
)

const SPI_MAGIC_WORD = "spi"
const SCREEN_MAGIC_WORD = "screen"
const DEVNULL_MAGIC_WORD = "/dev/null"
const OPC_LOCALHOST_MAGIC_WORD = "localhost"
const SPI_FN = "/dev/spidev1.0"

// these are pointers to the actual values from the command line parser
var LAYOUT_FN = goopt.String([]string{"-l", "--layout"}, "...", "layout file (required)")
var SOURCE = goopt.String([]string{"-s", "--source"}, "spatial-stripes", "pixel source (either a pattern name or "+OPC_LOCALHOST_MAGIC_WORD+"[:port])")
var DEST = goopt.String([]string{"-d", "--dest"}, "localhost", "destination (one of "+SCREEN_MAGIC_WORD+", "+SPI_MAGIC_WORD+", "+DEVNULL_MAGIC_WORD+", or hostname[:port])")
var FPS = goopt.Int([]string{"-f", "--fps"}, 40, "max frames per second")
var SECONDS = goopt.Int([]string{"-n", "--seconds"}, 0, "quit after this many seconds")
var ONCE = goopt.Flag([]string{"-o", "--once"}, []string{}, "quit after one frame", "")

// Parse the command line flags.  If invalid, show help and quit.
// Add default ports if needed.
// Read the layout file.
// Return the number of pixels in the layout, the source and dest thread methods.
func parseFlags() (nPixels int, sourceThread, destThread opc.ByteThread) {
	goopt.Summary = "Available patterns:\n"
	goopt.Summary += "          off \n"
	goopt.Summary += "          raver-plaid \n"
	goopt.Summary += "          spatial-stripes \n"
	goopt.Summary += "          test \n"
	goopt.Parse(nil)

	// layout is required
	if *LAYOUT_FN == "..." {
		fmt.Println(goopt.Usage())
		fmt.Println("--------------------------------------------------------------------------------/")
		os.Exit(1)
	}

	// read locations
	locations := opc.ReadLocations(*LAYOUT_FN)
	nPixels = len(locations) / 3

	// choose source thread method
	switch *SOURCE {
	case "off":
		sourceThread = opc.MakePatternOff(locations)
	case "raver-plaid":
		sourceThread = opc.MakePatternRaverPlaid(locations)
	case "spatial-stripes":
		sourceThread = opc.MakePatternSpatialStripes(locations)
	case "test":
		sourceThread = opc.MakePatternTest(locations)
		// todo: case localhost:7890
		//    add port if needed
		//    sourceThread = opc.MakeOpcServer(*SOURCE)
	default:
		fmt.Printf("Error: unknown source or pattern \"%s\"\n", *SOURCE)
		fmt.Println("--------------------------------------------------------------------------------/")
		os.Exit(1)
	}

	// choose dest thread method
	switch *DEST {
	case DEVNULL_MAGIC_WORD:
		destThread = opc.MakeSendToDevNullThread()
	case SCREEN_MAGIC_WORD:
		destThread = opc.MakeSendToScreenThread()
	case SPI_MAGIC_WORD:
		destThread = opc.MakeSendToLPD8806Thread(SPI_FN)
	default:
		// add default port if needed
		if !strings.Contains(*DEST, ":") {
			*DEST += ":7890"
		}
		destThread = opc.MakeSendToOpcThread(*DEST)
	}

	return // returns nPixels, sourceThread, destThread
}

// Launch the sourceThread and destThread methods and coordinate the transfer of bytes from one to the other.
// Run until timeToRun seconds have passed and return.  If timeToRun is 0, run forever.
// Turn on the CPU profiler if timeToRun seconds > 0.
// Limit the framerate to a max of fps unless fps is 0.
func mainLoop(nPixels int, sourceThread, destThread opc.ByteThread, fps float64, timeToRun float64) {
	if timeToRun > 0 {
		fmt.Printf("[mainLoop] Running for %f seconds with profiling turned on, pixels and network\n", timeToRun)
		defer profile.Start(profile.CPUProfile).Stop()
	} else {
		fmt.Println("[mainLoop] Running forever")
	}

	// prepare the byte slices and channels that connect the source and dest threads
	fillingSlice := make([]byte, nPixels*3)
	sendingSlice := make([]byte, nPixels*3)

	bytesToFillChan := make(chan []byte, 0)
	bytesFilledChan := make(chan []byte, 0)
	bytesToSendChan := make(chan []byte, 0)
	bytesSentChan := make(chan []byte, 0)

	// launch the threads
	go sourceThread(bytesToFillChan, bytesFilledChan)
	go destThread(bytesToSendChan, bytesSentChan)

	// main loop
	frame_budget_ms := 1000.0 / fps
	startTime := float64(time.Now().UnixNano()) / 1.0e9
	lastPrintTime := startTime
	frameStartTime := startTime
	frameEndTime := startTime
	framesSinceLastPrint := 0
	//firstIteration := true
	flipper := 0
	for {
		// if we have any frame budget left from last time around, sleep to control the framerate
		if fps > 0 {
			frameEndTime = float64(time.Now().UnixNano()) / 1.0e9
			timeRemaining := float64(frame_budget_ms)/1000 - (frameEndTime - frameStartTime)
			if timeRemaining > 0 {
				time.Sleep(time.Duration(timeRemaining*1000*1000) * time.Microsecond)
			}
		}

		// fps reporting and bookkeeping
		// print framerate occasionally
		frameStartTime = float64(time.Now().UnixNano()) / 1.0e9
		framesSinceLastPrint += 1
		if frameStartTime > lastPrintTime+1 {
			lastPrintTime = frameStartTime
			fmt.Printf("[mainLoop] %f ms/frame (%d fps)\n", 1000.0/float64(framesSinceLastPrint), framesSinceLastPrint)
			framesSinceLastPrint = 0
			// toggle LED
			beaglebone.SetOnboardLED(0, flipper)
			flipper = 1 - flipper
		}

		// if profiling, quit after a while
		if timeToRun > 0 && frameStartTime > startTime+timeToRun {
			return
		}

		// start the threads filling and sending slices in parallel.
		// if this is the first time through the loop we have to skip
		//  the sending stage or we'll send out a whole bunch of zeros.
		bytesToFillChan <- fillingSlice
		//if !firstIteration {
		bytesToSendChan <- sendingSlice
		//}

		// if only sending one frame, let's just get it all over with now
		//  or we'd have to compute two frames worth of pixels because of
		//  the double buffering effect of the two parallel threads
		if *ONCE {
			// get filled bytes and send them
			bytesToSendChan <- <-bytesFilledChan
			// wait for sending to complete
			<-bytesSentChan
			fmt.Println("[mainLoop] just running once.  quitting now.")
			return
		}

		// wait until both filling and sending threads are done
		<-bytesFilledChan
		//if !firstIteration {
		<-bytesSentChan
		//}

		// swap the slices
		sendingSlice, fillingSlice = fillingSlice, sendingSlice

		//firstIteration = false
	}
}

func main() {
	fmt.Println("--------------------------------------------------------------------------------\\")
	defer fmt.Println("--------------------------------------------------------------------------------/")

	nPixels, sourceThread, destThread := parseFlags()
	mainLoop(nPixels, sourceThread, destThread, float64(*FPS), float64(*SECONDS))
}
<|endoftext|>


<commit_before>// Package main is the main entrypoint to the worker cleaner
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/golang/glog"
	"gopkg.in/redis.v3"
)

const (
	workerKey        = "resque:worker"
	workersKey       = "resque:workers"
	statProcessedKey = "resque:stat:processed"
	statFailedKey    = "resque:stat:failed"
)

func main() {
	glog.Info("Kubernetes-Resque Worker Cleanup Service")

	namespace := os.Getenv("NAMESPACE")
	kubeClient := getKubernetesClient()
	redisClient := getRedisClient(true)

	glog.Info("Polling worker list every 5 min")
	for {

		runningPods := getLivingWorkers(kubeClient, namespace)
		redisWorkers := getWorkersFromRedis(redisClient)

		deadWorkers := getDeadWorkers(runningPods, redisWorkers)

		for _, dead := range deadWorkers {
			removeDeadWorker(redisClient, dead)
		}

		time.Sleep(5 * time.Minute)
	}
}

type resqueJob struct {
	Queue   string `json:queue`
	Payload json.RawMessage
}

func (j *resqueJob) QueueKey() string {
	return fmt.Sprintf("resque:queue:%s", j.Queue)
}

func newResqueJob(data []byte) (resqueJob, error) {
	var j resqueJob
	err := json.Unmarshal(data, &j)
	return j, err
}

func requeueStuckJob(jobBytes []byte, c *redis.Client) error {
	var err error
	job, err := newResqueJob(jobBytes)
	if err != nil {
		glog.Warning("Could not deserialize job")
		return err
	}
	glog.Infof("Job found on queue %s: %s", job.Queue, job.Payload)
	c.SAdd("resque:queues", job.Queue)
	json, err := job.Payload.MarshalJSON()
	if err != nil {
		glog.Warning("Could not serialize job payload")
		return err
	}
	rowsInserted, err := c.RPush(job.QueueKey(), string(json[:])).Result()
	if err != nil {
		glog.Warningf("Failed to insert job: %s", err)
		return err
	}
	glog.Infof("Inserted %d rows", rowsInserted)
	if rowsInserted != 1 {
		err = errors.New(fmt.Sprintf("Failed to insert job: inserted %d", rowsInserted))
	}

	return err
}

func removeDeadWorker(c *redis.Client, worker string) {
	bytes, err := c.Get(fmt.Sprintf("%s:%s", workerKey, worker)).Bytes()
	if err != nil {
		// if error is redis: nil we just ignore
		if err != redis.Nil {
			glog.Warningf("Could not fetch job for obj: %s", err)
			return
		}
	} else {
		err = requeueStuckJob(bytes, c)
		if err != nil {
			glog.Warningf("Failed to requeue job: %s", err)
			return
		}
		// Else continue to cleaning up the worker
		return
	}

	c.Pipelined(func(pipe *redis.Pipeline) error {
		pipe.SRem(workersKey, worker)
		pipe.Del(fmt.Sprintf("%s:%s", workerKey, worker))
		pipe.Del(fmt.Sprintf("%s:%s:started", workerKey, worker))
		pipe.Del(fmt.Sprintf("%s:%s:shutdown", workerKey, worker))

		// delete stats
		pipe.Del(fmt.Sprintf("%s:%s", statProcessedKey, worker))
		pipe.Del(fmt.Sprintf("%s:%s", statFailedKey, worker))

		return nil
	})
}

func getDeadWorkers(running []string, listedWorkers []string) []string {
	var diff []string

	for _, worker := range listedWorkers {
		workerName := strings.SplitN(worker, ":", 2)[0]
		found := false
		for _, pod := range running {
			if workerName == pod {
				found = true
				break
			}
		}
		if !found {
			diff = append(diff, worker)
		}
	}

	return diff
}

func getLivingWorkers(c *client.Client, namespace string) []string {
	selector := "role=worker,app=vapor"
	labels, err := labels.Parse(selector)
	if err != nil {
		glog.Fatalf("Failed to parse selector %q: %v", selector, err)
	}

	pods, err := c.Pods(namespace).List(labels, fields.Everything())
	if err != nil {
		glog.Fatal("Failed to get pods")
	}

	podItems := pods.Items
	podNames := make([]string, len(podItems))
	for i, pod := range podItems {
		podNames[i] = pod.Name
	}
	return podNames
}

func getWorkersFromRedis(c *redis.Client) []string {
	workers, err := c.SMembers("resque:workers").Result()
	if err != nil {
		glog.Fatal("Failed to connect to Redis")
	}

	return workers
}

func getRedisClient(useSentinel bool) *redis.Client {
	if useSentinel {
		addr := fmt.Sprintf("%s:%s", os.Getenv("REDIS_SENTINEL_SERVICE_HOST"), os.Getenv("REDIS_SENTINEL_SERVICE_PORT"))
		return redis.NewFailoverClient(&redis.FailoverOptions{
			MasterName:    "mymaster",
			SentinelAddrs: []string{addr},
		})
	} else {
		return redis.NewClient(&redis.Options{
			Addr: "localhost:6379",
		})
	}
}

func getKubernetesClient() *client.Client {
	kubernetesService := os.Getenv("KUBERNETES_SERVICE_HOST")
	if kubernetesService == "" {
		glog.Fatalf("Please specify the Kubernetes server")
	}
	apiServer := fmt.Sprintf("https://%s:%s", kubernetesService, os.Getenv("KUBERNETES_SERVICE_PORT"))

	token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
	if err != nil {
		glog.Fatalf("No service account token found")
	}

	config := client.Config{
		Host:        apiServer,
		BearerToken: string(token),
		Insecure:    true,
	}

	c, err := client.New(&config)
	if err != nil {
		glog.Fatalf("Failed to make client: %v", err)
	}
	return c
}
<commit_msg>Add more logging<commit_after>// Package main is the main entrypoint to the worker cleaner
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/golang/glog"
	"gopkg.in/redis.v3"
)

const (
	workerKey        = "resque:worker"
	workersKey       = "resque:workers"
	statProcessedKey = "resque:stat:processed"
	statFailedKey    = "resque:stat:failed"
)

func main() {
	glog.Info("Kubernetes-Resque Worker Cleanup Service")

	namespace := os.Getenv("NAMESPACE")
	kubeClient := getKubernetesClient()
	redisClient := getRedisClient(true)

	glog.Info("Polling worker list every 5 min")
	for {

		runningPods := getLivingWorkers(kubeClient, namespace)
		redisWorkers := getWorkersFromRedis(redisClient)

		deadWorkers := getDeadWorkers(runningPods, redisWorkers)

		for _, dead := range deadWorkers {
			removeDeadWorker(redisClient, dead)
		}

		time.Sleep(5 * time.Minute)
	}
}

type resqueJob struct {
	Queue   string `json:queue`
	Payload json.RawMessage
}

func (j *resqueJob) QueueKey() string {
	return fmt.Sprintf("resque:queue:%s", j.Queue)
}

func newResqueJob(data []byte) (resqueJob, error) {
	var j resqueJob
	err := json.Unmarshal(data, &j)
	return j, err
}

func requeueStuckJob(jobBytes []byte, c *redis.Client) error {
	var err error
	job, err := newResqueJob(jobBytes)
	if err != nil {
		glog.Warning("Could not deserialize job")
		return err
	}
	glog.Infof("Job found on queue %s: %s", job.Queue, job.Payload)
	c.SAdd("resque:queues", job.Queue)
	json, err := job.Payload.MarshalJSON()
	if err != nil {
		glog.Warning("Could not serialize job payload")
		return err
	}
	glog.Infof("Inserting %s to %s", string(json[:]), job.QueueKey())
	rowsInserted, err := c.RPush(job.QueueKey(), string(json[:])).Result()
	if err != nil {
		glog.Warningf("Failed to insert job: %s", err)
		return err
	}
	glog.Infof("Inserted %d rows", rowsInserted)
	if rowsInserted != 1 {
		err = errors.New(fmt.Sprintf("Failed to insert job: inserted %d", rowsInserted))
	}

	return err
}

func removeDeadWorker(c *redis.Client, worker string) {
	bytes, err := c.Get(fmt.Sprintf("%s:%s", workerKey, worker)).Bytes()
	if err != nil {
		// if error is redis: nil we just ignore
		if err != redis.Nil {
			glog.Warningf("Could not fetch job for obj: %s", err)
			return
		}
	} else {
		err = requeueStuckJob(bytes, c)
		if err != nil {
			glog.Warningf("Failed to requeue job: %s", err)
			return
		}
		// Else continue to cleaning up the worker
		return
	}

	c.Pipelined(func(pipe *redis.Pipeline) error {
		pipe.SRem(workersKey, worker)
		pipe.Del(fmt.Sprintf("%s:%s", workerKey, worker))
		pipe.Del(fmt.Sprintf("%s:%s:started", workerKey, worker))
		pipe.Del(fmt.Sprintf("%s:%s:shutdown", workerKey, worker))

		// delete stats
		pipe.Del(fmt.Sprintf("%s:%s", statProcessedKey, worker))
		pipe.Del(fmt.Sprintf("%s:%s", statFailedKey, worker))

		return nil
	})
}

func getDeadWorkers(running []string, listedWorkers []string) []string {
	var diff []string

	for _, worker := range listedWorkers {
		workerName := strings.SplitN(worker, ":", 2)[0]
		found := false
		for _, pod := range running {
			if workerName == pod {
				found = true
				break
			}
		}
		if !found {
			diff = append(diff, worker)
		}
	}

	return diff
}

func getLivingWorkers(c *client.Client, namespace string) []string {
	selector := "role=worker,app=vapor"
	labels, err := labels.Parse(selector)
	if err != nil {
		glog.Fatalf("Failed to parse selector %q: %v", selector, err)
	}

	pods, err := c.Pods(namespace).List(labels, fields.Everything())
	if err != nil {
		glog.Fatal("Failed to get pods")
	}

	podItems := pods.Items
	podNames := make([]string, len(podItems))
	for i, pod := range podItems {
		podNames[i] = pod.Name
	}
	return podNames
}

func getWorkersFromRedis(c *redis.Client) []string {
	workers, err := c.SMembers("resque:workers").Result()
	if err != nil {
		glog.Fatal("Failed to connect to Redis")
	}

	return workers
}

func getRedisClient(useSentinel bool) *redis.Client {
	if useSentinel {
		addr := fmt.Sprintf("%s:%s", os.Getenv("REDIS_SENTINEL_SERVICE_HOST"), os.Getenv("REDIS_SENTINEL_SERVICE_PORT"))
		return redis.NewFailoverClient(&redis.FailoverOptions{
			MasterName:    "mymaster",
			SentinelAddrs: []string{addr},
		})
	} else {
		return redis.NewClient(&redis.Options{
			Addr: "localhost:6379",
		})
	}
}

func getKubernetesClient() *client.Client {
	kubernetesService := os.Getenv("KUBERNETES_SERVICE_HOST")
	if kubernetesService == "" {
		glog.Fatalf("Please specify the Kubernetes server")
	}
	apiServer := fmt.Sprintf("https://%s:%s", kubernetesService, os.Getenv("KUBERNETES_SERVICE_PORT"))

	token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
	if err != nil {
		glog.Fatalf("No service account token found")
	}

	config := client.Config{
		Host:        apiServer,
		BearerToken: string(token),
		Insecure:    true,
	}

	c, err := client.New(&config)
	if err != nil {
		glog.Fatalf("Failed to make client: %v", err)
	}
	return c
}
<|endoftext|>