<commit_before>// PromHouse
// Copyright (C) 2017 Percona LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

// We need Go 1.9+ for proper uint64 handling: https://github.com/kshvakov/clickhouse/issues/69
// +build go1.9

package main

import (
	"bytes"
	"context"
	_ "expvar" // for /debug/expvar
	"html/template"
	"log"
	"net/http"
	_ "net/http/pprof" // for /debug/pprof
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
	"gopkg.in/alecthomas/kingpin.v2"

	"github.com/Percona-Lab/PromHouse/handlers"
	"github.com/Percona-Lab/PromHouse/storages/clickhouse"
)

const (
	shutdownTimeout = 3 * time.Second
)

// runPromServer runs Prometheus API server until context is canceled, then gracefully stops it.
func runPromServer(ctx context.Context, addr string, params *clickhouse.Params) {
	l := logrus.WithField("component", "api")

	storage, err := clickhouse.New(params)
	if err != nil {
		l.Panic(err)
	}

	prometheus.MustRegister(storage)

	promAPI := handlers.NewPromAPI(storage, l)
	prometheus.MustRegister(promAPI)

	mux := http.NewServeMux()
	mux.HandleFunc("/read", promAPI.Read())
	mux.HandleFunc("/write", promAPI.Write())

	l.Infof("Starting server on http://%s/", addr)
	server := &http.Server{
		Addr:     addr,
		ErrorLog: log.New(os.Stderr, "runPromServer: ", 0),
		Handler:  mux,
	}
	go func() {
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			l.Panic(err)
		}
		l.Info("Server stopped.")
	}()

	<-ctx.Done()
	ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
	if err := server.Shutdown(ctx); err != nil {
		l.Errorf("Failed to shutdown gracefully: %s", err)
	}
	cancel()
}

// runDebugServer runs debug server until context is canceled, then gracefully stops it.
func runDebugServer(ctx context.Context, addr string) {
	l := logrus.WithField("component", "debug")

	http.Handle("/debug/metrics", promhttp.Handler())

	handlers := []string{
		"/debug/metrics", "/debug/vars", "/debug/pprof",
	}
	for i, h := range handlers {
		handlers[i] = "http://" + addr + h
	}

	var buf bytes.Buffer
	err := template.Must(template.New("debug").Parse(`

	{{ range . }}

{{ . }}

	{{ end }}

	`)).Execute(&buf, handlers)
	if err != nil {
		l.Panic(err)
	}
	http.HandleFunc("/debug", func(rw http.ResponseWriter, req *http.Request) {
		rw.Write(buf.Bytes())
	})
	l.Infof("Starting server on http://%s/debug\nRegistered handlers:\n\t%s", addr, strings.Join(handlers, "\n\t"))

	server := &http.Server{
		Addr:     addr,
		ErrorLog: log.New(os.Stderr, "runDebugServer: ", 0),
	}
	go func() {
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			l.Panic(err)
		}
		l.Info("Server stopped.")
	}()

	<-ctx.Done()
	ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
	if err := server.Shutdown(ctx); err != nil {
		l.Errorf("Failed to shutdown gracefully: %s", err)
	}
	cancel()
}

func main() {
	log.SetFlags(0)
	log.SetPrefix("stdlog: ")

	var (
		promAddrF     = kingpin.Flag("listen-prom-addr", "Prometheus remote API server listen address").Default("127.0.0.1:7781").String()
		debugAddrF    = kingpin.Flag("listen-debug-addr", "Debug server listen address").Default("127.0.0.1:7782").String()
		dropF         = kingpin.Flag("db.drop-schema", "Drop existing database schema").Bool()
		maxOpenConnsF = kingpin.Flag("db.max-open-conns", "Maximum number of open connections to the database").Default("75").Int()
		logLevelF     = kingpin.Flag("log.level", "Log level").Default("warn").String()
	)
	kingpin.CommandLine.HelpFlag.Short('h')
	kingpin.Parse()

	level, err := logrus.ParseLevel(*logLevelF)
	if err != nil {
		logrus.Fatal(err)
	}
	logrus.SetLevel(level)

	l := logrus.WithField("component", "main")
	ctx, cancel := context.WithCancel(context.Background())
	defer l.Info("Done.")

	// handle termination signals: first one gracefully, force exit on the second one
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
	go func() {
		s := <-signals
		l.Warnf("Got %s, shutting down...", unix.SignalName(s.(syscall.Signal)))
		cancel()

		s = <-signals
		l.Panicf("Got %s, exiting!", s, unix.SignalName(s.(syscall.Signal)))
	}()

	// start servers, wait for them to exit
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		defer wg.Done()
		params := &clickhouse.Params{
			DSN:                  "tcp://127.0.0.1:9000/?database=prometheus",
			DropDatabase:         *dropF,
			MaxOpenConns:         *maxOpenConnsF,
			MaxTimeSeriesInQuery: 50,
		}
		runPromServer(ctx, *promAddrF, params)
	}()
	go func() {
		defer wg.Done()
		runDebugServer(ctx, *debugAddrF)
	}()
	wg.Wait()
}
<commit_msg>Fix exit logging.<commit_after>// PromHouse
// Copyright (C) 2017 Percona LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

// We need Go 1.9+ for proper uint64 handling: https://github.com/kshvakov/clickhouse/issues/69
// +build go1.9

package main

import (
	"bytes"
	"context"
	_ "expvar" // for /debug/expvar
	"html/template"
	"log"
	"net/http"
	_ "net/http/pprof" // for /debug/pprof
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
	"gopkg.in/alecthomas/kingpin.v2"

	"github.com/Percona-Lab/PromHouse/handlers"
	"github.com/Percona-Lab/PromHouse/storages/clickhouse"
)

const (
	shutdownTimeout = 3 * time.Second
)

// runPromServer runs Prometheus API server until context is canceled, then gracefully stops it.
func runPromServer(ctx context.Context, addr string, params *clickhouse.Params) {
	l := logrus.WithField("component", "api")

	storage, err := clickhouse.New(params)
	if err != nil {
		l.Panic(err)
	}

	prometheus.MustRegister(storage)

	promAPI := handlers.NewPromAPI(storage, l)
	prometheus.MustRegister(promAPI)

	mux := http.NewServeMux()
	mux.HandleFunc("/read", promAPI.Read())
	mux.HandleFunc("/write", promAPI.Write())

	l.Infof("Starting server on http://%s/", addr)
	server := &http.Server{
		Addr:     addr,
		ErrorLog: log.New(os.Stderr, "runPromServer: ", 0),
		Handler:  mux,
	}
	go func() {
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			l.Panic(err)
		}
		l.Info("Server stopped.")
	}()

	<-ctx.Done()
	ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
	if err := server.Shutdown(ctx); err != nil {
		l.Errorf("Failed to shutdown gracefully: %s", err)
	}
	cancel()
}

// runDebugServer runs debug server until context is canceled, then gracefully stops it.
func runDebugServer(ctx context.Context, addr string) {
	l := logrus.WithField("component", "debug")

	http.Handle("/debug/metrics", promhttp.Handler())

	handlers := []string{
		"/debug/metrics", "/debug/vars", "/debug/pprof",
	}
	for i, h := range handlers {
		handlers[i] = "http://" + addr + h
	}

	var buf bytes.Buffer
	err := template.Must(template.New("debug").Parse(`

	{{ range . }}

{{ . }}

	{{ end }}

	`)).Execute(&buf, handlers)
	if err != nil {
		l.Panic(err)
	}
	http.HandleFunc("/debug", func(rw http.ResponseWriter, req *http.Request) {
		rw.Write(buf.Bytes())
	})
	l.Infof("Starting server on http://%s/debug\nRegistered handlers:\n\t%s", addr, strings.Join(handlers, "\n\t"))

	server := &http.Server{
		Addr:     addr,
		ErrorLog: log.New(os.Stderr, "runDebugServer: ", 0),
	}
	go func() {
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			l.Panic(err)
		}
		l.Info("Server stopped.")
	}()

	<-ctx.Done()
	ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
	if err := server.Shutdown(ctx); err != nil {
		l.Errorf("Failed to shutdown gracefully: %s", err)
	}
	cancel()
}

func main() {
	log.SetFlags(0)
	log.SetPrefix("stdlog: ")

	var (
		promAddrF     = kingpin.Flag("listen-prom-addr", "Prometheus remote API server listen address").Default("127.0.0.1:7781").String()
		debugAddrF    = kingpin.Flag("listen-debug-addr", "Debug server listen address").Default("127.0.0.1:7782").String()
		dropF         = kingpin.Flag("db.drop-schema", "Drop existing database schema").Bool()
		maxOpenConnsF = kingpin.Flag("db.max-open-conns", "Maximum number of open connections to the database").Default("75").Int()
		logLevelF     = kingpin.Flag("log.level", "Log level").Default("warn").String()
	)
	kingpin.CommandLine.HelpFlag.Short('h')
	kingpin.Parse()

	level, err := logrus.ParseLevel(*logLevelF)
	if err != nil {
		logrus.Fatal(err)
	}
	logrus.SetLevel(level)

	l := logrus.WithField("component", "main")
	ctx, cancel := context.WithCancel(context.Background())
	defer l.Info("Done.")

	// handle termination signals: first one gracefully, force exit on the second one
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
	go func() {
		s := <-signals
		l.Warnf("Got %s, shutting down...", unix.SignalName(s.(syscall.Signal)))
		cancel()

		s = <-signals
		l.Panicf("Got %s, exiting!", unix.SignalName(s.(syscall.Signal)))
	}()

	// start servers, wait for them to exit
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		defer wg.Done()
		params := &clickhouse.Params{
			DSN:                  "tcp://127.0.0.1:9000/?database=prometheus",
			DropDatabase:         *dropF,
			MaxOpenConns:         *maxOpenConnsF,
			MaxTimeSeriesInQuery: 50,
		}
		runPromServer(ctx, *promAddrF, params)
	}()
	go func() {
		defer wg.Done()
		runDebugServer(ctx, *debugAddrF)
	}()
	wg.Wait()
}
<|endoftext|>


<commit_before>package main

//go:generate esc -o templates.go ../../templates

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/fsnotify/fsnotify"
	"github.com/subosito/snowboard"
	"github.com/subosito/snowboard/engines/drafter"
	"github.com/subosito/snowboard/engines/drafterc"
)

const versionStr = "v0.3.0"

var (
	version  = flag.Bool("v", false, "Display version information")
	input    = flag.String("i", "", "API Blueprint file")
	output   = flag.String("o", "index.html", "HTML output file")
	watch    = flag.Bool("w", false, "Watch input (and template, if any) file for changes")
	serve    = flag.Bool("s", false, "Serve HTML via 0.0.0.0:8088")
	tplFile  = flag.String("t", "alpha", "Custom template for documentation")
	engineF  = flag.String("e", "cgo", "Use different engine. Supported engines: cgo, cli")
	validate = flag.Bool("l", false, "Validate input only")
)

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage:\n  snowboard [OPTIONS]\n\nOptions:\n")
		flag.PrintDefaults()
		os.Exit(0)
	}

	flag.Parse()

	engine := parserEngine()

	if *version {
		fmt.Printf("Snowboard version: %s\n", versionStr)
		fmt.Println("Engine:")

		for name, v := range engine.Version() {
			fmt.Printf("  %s version: %s\n", name, v)
		}

		os.Exit(0)
	}

	if *input == "" {
		flag.Usage()
	}

	if *validate {
		cEngine := checkerEngine()

		b, err := readFile(*input)
		checkErr(err)

		bf := bytes.NewReader(b)

		out, err := snowboard.Validate(bf, cEngine)
		if err == nil && out == nil {
			fmt.Fprintf(os.Stdout, "OK\n")
			os.Exit(0)
		}

		if err != nil {
			checkErr(err)
		}

		s := "--------"
		w := tabwriter.NewWriter(os.Stdout, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Row\tCol\tDescription")
		fmt.Fprintf(w, "%s\t%s\t%s\n", s, s, strings.Repeat(s, 12))

		for _, n := range out.Annotations {
			for _, m := range n.SourceMaps {
				fmt.Fprintf(w, "%d\t%d\t%s\n", m.Row, m.Col, n.Description)
			}
		}

		w.Flush()
		os.Exit(1)
	}

	if *watch {
		watcher, err := fsnotify.NewWatcher()
		checkErr(err)
		defer watcher.Close()

		done := make(chan bool)
		go func() {
			for {
				select {
				case event := <-watcher.Events:
					if event.Op&fsnotify.Write == fsnotify.Write {
						renderHTML(engine)
					}
				case err := <-watcher.Errors:
					log.Println("Error:", err)
				}
			}
		}()

		err = watcher.Add(*input)
		checkErr(err)

		_, err = os.Stat(*tplFile)
		if err == nil {
			err = watcher.Add(*tplFile)
			checkErr(err)
		}

		renderHTML(engine)
		serveHTML()

		<-done
	} else {
		renderHTML(engine)
		serveHTML()
	}
}

func readFile(fn string) ([]byte, error) {
	info, err := os.Stat(fn)
	if err != nil {
		return nil, errors.New("File is not exist")
	}

	if info.IsDir() {
		return nil, errors.New("File is a directory")
	}

	return ioutil.ReadFile(fn)
}

func readTemplate(fn string) ([]byte, error) {
	tf, err := readFile(fn)
	if err == nil {
		return tf, nil
	}

	fs := FS(false)
	ff, err := fs.Open("/templates/" + fn + ".html")
	if err != nil {
		return nil, err
	}

	defer ff.Close()
	return ioutil.ReadAll(ff)
}

func checkErr(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		os.Exit(1)
	}
}

func renderHTML(engine snowboard.Parser) {
	b, err := readFile(*input)
	logErr(err)

	log.Println("Generate HTML... START")

	bf := bytes.NewReader(b)
	bp, err := snowboard.Parse(bf, engine)
	logErr(err)

	of, err := os.Create(*output)
	logErr(err)
	defer of.Close()

	tf, err := readTemplate(*tplFile)
	logErr(err)

	err = snowboard.HTML(string(tf), of, bp)
	logErr(err)

	log.Println("Generate HTML... DONE")
}

func logErr(err error) {
	if err != nil {
		log.Fatalln("Error: ", err)
	}
}

func serveHTML() {
	if !*serve {
		return
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, *output)
	})

	err := http.ListenAndServe(":8088", nil)
	logErr(err)
}

func parserEngine() snowboard.Parser {
	switch *engineF {
	case "cli":
		return drafterc.Engine{}
	}

	return drafter.Engine{}
}

func checkerEngine() snowboard.Checker {
	return drafter.Engine{}
}
<commit_msg>bump version<commit_after>package main

//go:generate esc -o templates.go ../../templates

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/fsnotify/fsnotify"
	"github.com/subosito/snowboard"
	"github.com/subosito/snowboard/engines/drafter"
	"github.com/subosito/snowboard/engines/drafterc"
)

const versionStr = "v0.3.1"

var (
	version  = flag.Bool("v", false, "Display version information")
	input    = flag.String("i", "", "API Blueprint file")
	output   = flag.String("o", "index.html", "HTML output file")
	watch    = flag.Bool("w", false, "Watch input (and template, if any) file for changes")
	serve    = flag.Bool("s", false, "Serve HTML via 0.0.0.0:8088")
	tplFile  = flag.String("t", "alpha", "Custom template for documentation")
	engineF  = flag.String("e", "cgo", "Use different engine. Supported engines: cgo, cli")
	validate = flag.Bool("l", false, "Validate input only")
)

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage:\n  snowboard [OPTIONS]\n\nOptions:\n")
		flag.PrintDefaults()
		os.Exit(0)
	}

	flag.Parse()

	engine := parserEngine()

	if *version {
		fmt.Printf("Snowboard version: %s\n", versionStr)
		fmt.Println("Engine:")

		for name, v := range engine.Version() {
			fmt.Printf("  %s version: %s\n", name, v)
		}

		os.Exit(0)
	}

	if *input == "" {
		flag.Usage()
	}

	if *validate {
		cEngine := checkerEngine()

		b, err := readFile(*input)
		checkErr(err)

		bf := bytes.NewReader(b)

		out, err := snowboard.Validate(bf, cEngine)
		if err == nil && out == nil {
			fmt.Fprintf(os.Stdout, "OK\n")
			os.Exit(0)
		}

		if err != nil {
			checkErr(err)
		}

		s := "--------"
		w := tabwriter.NewWriter(os.Stdout, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Row\tCol\tDescription")
		fmt.Fprintf(w, "%s\t%s\t%s\n", s, s, strings.Repeat(s, 12))

		for _, n := range out.Annotations {
			for _, m := range n.SourceMaps {
				fmt.Fprintf(w, "%d\t%d\t%s\n", m.Row, m.Col, n.Description)
			}
		}

		w.Flush()
		os.Exit(1)
	}

	if *watch {
		watcher, err := fsnotify.NewWatcher()
		checkErr(err)
		defer watcher.Close()

		done := make(chan bool)
		go func() {
			for {
				select {
				case event := <-watcher.Events:
					if event.Op&fsnotify.Write == fsnotify.Write {
						renderHTML(engine)
					}
				case err := <-watcher.Errors:
					log.Println("Error:", err)
				}
			}
		}()

		err = watcher.Add(*input)
		checkErr(err)

		_, err = os.Stat(*tplFile)
		if err == nil {
			err = watcher.Add(*tplFile)
			checkErr(err)
		}

		renderHTML(engine)
		serveHTML()

		<-done
	} else {
		renderHTML(engine)
		serveHTML()
	}
}

func readFile(fn string) ([]byte, error) {
	info, err := os.Stat(fn)
	if err != nil {
		return nil, errors.New("File is not exist")
	}

	if info.IsDir() {
		return nil, errors.New("File is a directory")
	}

	return ioutil.ReadFile(fn)
}

func readTemplate(fn string) ([]byte, error) {
	tf, err := readFile(fn)
	if err == nil {
		return tf, nil
	}

	fs := FS(false)
	ff, err := fs.Open("/templates/" + fn + ".html")
	if err != nil {
		return nil, err
	}

	defer ff.Close()
	return ioutil.ReadAll(ff)
}

func checkErr(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		os.Exit(1)
	}
}

func renderHTML(engine snowboard.Parser) {
	b, err := readFile(*input)
	logErr(err)

	log.Println("Generate HTML... START")

	bf := bytes.NewReader(b)
	bp, err := snowboard.Parse(bf, engine)
	logErr(err)

	of, err := os.Create(*output)
	logErr(err)
	defer of.Close()

	tf, err := readTemplate(*tplFile)
	logErr(err)

	err = snowboard.HTML(string(tf), of, bp)
	logErr(err)

	log.Println("Generate HTML... DONE")
}

func logErr(err error) {
	if err != nil {
		log.Fatalln("Error: ", err)
	}
}

func serveHTML() {
	if !*serve {
		return
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, *output)
	})

	err := http.ListenAndServe(":8088", nil)
	logErr(err)
}

func parserEngine() snowboard.Parser {
	switch *engineF {
	case "cli":
		return drafterc.Engine{}
	}

	return drafter.Engine{}
}

func checkerEngine() snowboard.Checker {
	return drafter.Engine{}
}
<|endoftext|>


<commit_before>package main

//go:generate esc -o templates.go ../../templates

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/fsnotify/fsnotify"
	"github.com/subosito/snowboard"
	"github.com/subosito/snowboard/engines/drafter"
	"github.com/subosito/snowboard/engines/drafterc"
)

const versionStr = "v0.1.2"

var (
	version  = flag.Bool("v", false, "Display version information")
	input    = flag.String("i", "", "API Blueprint file")
	output   = flag.String("o", "index.html", "HTML output file")
	watch    = flag.Bool("w", false, "Watch input (and template, if any) file for changes")
	serve    = flag.Bool("s", false, "Serve HTML via 0.0.0.0:8088")
	tplFile  = flag.String("t", "alpha", "Custom template for documentation")
	engineF  = flag.String("e", "cgo", "Use different engine. Supported engines: cgo, cli")
	validate = flag.Bool("l", false, "Validate input only")
)

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage:\n  snowboard [OPTIONS]\n\nOptions:\n")
		flag.PrintDefaults()
		os.Exit(0)
	}

	flag.Parse()

	engine := parserEngine()

	if *version {
		fmt.Printf("Snowboard version: %s\n", versionStr)
		fmt.Println("Engine:")

		for name, v := range engine.Version() {
			fmt.Printf("  %s version: %s\n", name, v)
		}

		os.Exit(0)
	}

	if *input == "" {
		flag.Usage()
	}

	if *validate {
		cEngine := checkerEngine()

		b, err := readFile(*input)
		checkErr(err)

		bf := bytes.NewReader(b)

		out, err := snowboard.Validate(bf, cEngine)
		if err == nil && out == nil {
			fmt.Fprintf(os.Stdout, "OK\n")
			os.Exit(0)
		}

		if err != nil {
			checkErr(err)
		}

		s := "--------"
		w := tabwriter.NewWriter(os.Stdout, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Row\tCol\tDescription")
		fmt.Fprintf(w, "%s\t%s\t%s\n", s, s, strings.Repeat(s, 15))

		for _, n := range out.Annotations {
			for _, m := range n.SourceMaps {
				fmt.Fprintf(w, "%d\t%d\t%s\n", m.Row, m.Col, n.Description)
			}
		}

		w.Flush()
		os.Exit(1)
	}

	if *watch {
		watcher, err := fsnotify.NewWatcher()
		checkErr(err)
		defer watcher.Close()

		done := make(chan bool)
		go func() {
			for {
				select {
				case event := <-watcher.Events:
					if event.Op&fsnotify.Write == fsnotify.Write {
						renderHTML(engine)
					}
				case err := <-watcher.Errors:
					log.Println("Error:", err)
				}
			}
		}()

		err = watcher.Add(*input)
		checkErr(err)

		_, err = os.Stat(*tplFile)
		if err == nil {
			err = watcher.Add(*tplFile)
			checkErr(err)
		}

		renderHTML(engine)
		serveHTML()

		<-done
	} else {
		renderHTML(engine)
		serveHTML()
	}
}

func readFile(fn string) ([]byte, error) {
	info, err := os.Stat(fn)
	if err != nil {
		return nil, errors.New("File is not exist")
	}

	if info.IsDir() {
		return nil, errors.New("File is a directory")
	}

	return ioutil.ReadFile(fn)
}

func readTemplate(fn string) ([]byte, error) {
	tf, err := readFile(fn)
	if err == nil {
		return tf, nil
	}

	fs := FS(false)
	ff, err := fs.Open("/templates/" + fn + ".html")
	if err != nil {
		return nil, err
	}

	defer ff.Close()
	return ioutil.ReadAll(ff)
}

func checkErr(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		os.Exit(1)
	}
}

func renderHTML(engine snowboard.Parser) {
	b, err := readFile(*input)
	logErr(err)

	log.Println("Generate HTML... START")

	bf := bytes.NewReader(b)
	bp, err := snowboard.Parse(bf, engine)
	logErr(err)

	of, err := os.Create(*output)
	logErr(err)
	defer of.Close()

	tf, err := readTemplate(*tplFile)
	logErr(err)

	err = snowboard.HTML(string(tf), of, bp)
	logErr(err)

	log.Println("Generate HTML... DONE")
}

func logErr(err error) {
	if err != nil {
		log.Fatalln("Error: ", err)
	}
}

func serveHTML() {
	if !*serve {
		return
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, *output)
	})

	err := http.ListenAndServe(":8088", nil)
	logErr(err)
}

func parserEngine() snowboard.Parser {
	switch *engineF {
	case "cli":
		return drafterc.Engine{}
	}

	return drafter.Engine{}
}

func checkerEngine() snowboard.Checker {
	return drafter.Engine{}
}
<commit_msg>bump version<commit_after>package main

//go:generate esc -o templates.go ../../templates

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/fsnotify/fsnotify"
	"github.com/subosito/snowboard"
	"github.com/subosito/snowboard/engines/drafter"
	"github.com/subosito/snowboard/engines/drafterc"
)

const versionStr = "v0.2.0"

var (
	version  = flag.Bool("v", false, "Display version information")
	input    = flag.String("i", "", "API Blueprint file")
	output   = flag.String("o", "index.html", "HTML output file")
	watch    = flag.Bool("w", false, "Watch input (and template, if any) file for changes")
	serve    = flag.Bool("s", false, "Serve HTML via 0.0.0.0:8088")
	tplFile  = flag.String("t", "alpha", "Custom template for documentation")
	engineF  = flag.String("e", "cgo", "Use different engine. Supported engines: cgo, cli")
	validate = flag.Bool("l", false, "Validate input only")
)

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage:\n  snowboard [OPTIONS]\n\nOptions:\n")
		flag.PrintDefaults()
		os.Exit(0)
	}

	flag.Parse()

	engine := parserEngine()

	if *version {
		fmt.Printf("Snowboard version: %s\n", versionStr)
		fmt.Println("Engine:")

		for name, v := range engine.Version() {
			fmt.Printf("  %s version: %s\n", name, v)
		}

		os.Exit(0)
	}

	if *input == "" {
		flag.Usage()
	}

	if *validate {
		cEngine := checkerEngine()

		b, err := readFile(*input)
		checkErr(err)

		bf := bytes.NewReader(b)

		out, err := snowboard.Validate(bf, cEngine)
		if err == nil && out == nil {
			fmt.Fprintf(os.Stdout, "OK\n")
			os.Exit(0)
		}

		if err != nil {
			checkErr(err)
		}

		s := "--------"
		w := tabwriter.NewWriter(os.Stdout, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Row\tCol\tDescription")
		fmt.Fprintf(w, "%s\t%s\t%s\n", s, s, strings.Repeat(s, 15))

		for _, n := range out.Annotations {
			for _, m := range n.SourceMaps {
				fmt.Fprintf(w, "%d\t%d\t%s\n", m.Row, m.Col, n.Description)
			}
		}

		w.Flush()
		os.Exit(1)
	}

	if *watch {
		watcher, err := fsnotify.NewWatcher()
		checkErr(err)
		defer watcher.Close()

		done := make(chan bool)
		go func() {
			for {
				select {
				case event := <-watcher.Events:
					if event.Op&fsnotify.Write == fsnotify.Write {
						renderHTML(engine)
					}
				case err := <-watcher.Errors:
					log.Println("Error:", err)
				}
			}
		}()

		err = watcher.Add(*input)
		checkErr(err)

		_, err = os.Stat(*tplFile)
		if err == nil {
			err = watcher.Add(*tplFile)
			checkErr(err)
		}

		renderHTML(engine)
		serveHTML()

		<-done
	} else {
		renderHTML(engine)
		serveHTML()
	}
}

func readFile(fn string) ([]byte, error) {
	info, err := os.Stat(fn)
	if err != nil {
		return nil, errors.New("File is not exist")
	}

	if info.IsDir() {
		return nil, errors.New("File is a directory")
	}

	return ioutil.ReadFile(fn)
}

func readTemplate(fn string) ([]byte, error) {
	tf, err := readFile(fn)
	if err == nil {
		return tf, nil
	}

	fs := FS(false)
	ff, err := fs.Open("/templates/" + fn + ".html")
	if err != nil {
		return nil, err
	}

	defer ff.Close()
	return ioutil.ReadAll(ff)
}

func checkErr(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", err)
		os.Exit(1)
	}
}

func renderHTML(engine snowboard.Parser) {
	b, err := readFile(*input)
	logErr(err)

	log.Println("Generate HTML... START")

	bf := bytes.NewReader(b)
	bp, err := snowboard.Parse(bf, engine)
	logErr(err)

	of, err := os.Create(*output)
	logErr(err)
	defer of.Close()

	tf, err := readTemplate(*tplFile)
	logErr(err)

	err = snowboard.HTML(string(tf), of, bp)
	logErr(err)

	log.Println("Generate HTML... DONE")
}

func logErr(err error) {
	if err != nil {
		log.Fatalln("Error: ", err)
	}
}

func serveHTML() {
	if !*serve {
		return
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, *output)
	})

	err := http.ListenAndServe(":8088", nil)
	logErr(err)
}

func parserEngine() snowboard.Parser {
	switch *engineF {
	case "cli":
		return drafterc.Engine{}
	}

	return drafter.Engine{}
}

func checkerEngine() snowboard.Checker {
	return drafter.Engine{}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"mime"
	"net/http"

	"github.com/chop-dbhi/sql-agent"

	_ "github.com/denisenkom/go-mssqldb"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/lib/pq"
	_ "github.com/mattn/go-oci8"
	_ "github.com/mattn/go-sqlite3"
	_ "github.com/snowflakedb/gosnowflake"
)

var usage = `SQL Agent - HTTP interface

This is an HTTP interface for the SQL Agent.

Run:

	sql-agent [-host=<host>] [-port=<port>]

Example:

	POST /
	Content-Type application/json

	{
		"driver": "postgres",
		"connection": {
			"host": "pghost.org",
			"port": 5432,
		},
		"sql": "SELECT * FROM users WHERE zipcode = :zipcode",
		"parameters": {
			"zipcode": 19104
		}
	}
`

const StatusUnprocessableEntity = 422

var (
	defaultMimetype = "application/json"

	mimetypeFormats = map[string]string{
		"*/*":                  "json",
		"text/csv":             "csv",
		"application/json":     "json",
		"application/x-ldjson": "ldjson",
	}
)

// parseMimetype parses a mimetype from the Accept header.
func parseMimetype(mimetype string) string {
	mimetype, params, err := mime.ParseMediaType(mimetype)

	if err != nil {
		return ""
	}

	// No Accept header passed.
	if mimetype == "" {
		return defaultMimetype
	}

	switch mimetype {
	case "application/json":
		if params["boundary"] == "NL" {
			return "application/x-ldjson"
		}
	default:
		if _, ok := mimetypeFormats[mimetype]; !ok {
			return ""
		}
	}

	return mimetype
}

func init() {
	flag.Usage = func() {
		fmt.Println(usage)
		flag.PrintDefaults()
	}

	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

func main() {
	var (
		host string
		port int
	)

	flag.StringVar(&host, "host", "localhost", "Host of the agent.")
	flag.IntVar(&port, "port", 5000, "Port of the agent.")

	flag.Parse()

	addr := fmt.Sprintf("%s:%d", host, port)
	log.Printf("* Listening on %s...\n", addr)

	http.HandleFunc("/", handleRequest)

	err := http.ListenAndServe(addr, nil)
	sqlagent.Shutdown()
	log.Fatal(err)
}

type Payload struct {
	Driver     string
	Connection map[string]interface{}
	SQL        string
	Params     map[string]interface{}
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// Validate the Accept header and parse it to ensure it is
	// supported.
	mimetype := r.Header.Get("Accept")

	if mimetype = parseMimetype(mimetype); mimetype == "" {
		w.WriteHeader(http.StatusNotAcceptable)
		return
	}

	var payload Payload

	// Decode the body.
	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
		w.WriteHeader(StatusUnprocessableEntity)
		w.Write([]byte(fmt.Sprintf("could not decode JSON: %s", err)))
		return
	}

	if _, ok := sqlagent.Drivers[payload.Driver]; !ok {
		w.WriteHeader(StatusUnprocessableEntity)
		w.Write([]byte(fmt.Sprintf("unknown driver: %v", payload.Driver)))
		return
	}

	db, err := sqlagent.PersistentConnect(payload.Driver, payload.Connection)
	if err != nil {
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(fmt.Sprintf("problem connecting to database: %s", err)))
		return
	}

	iter, err := sqlagent.Execute(db, payload.SQL, payload.Params)

	if err != nil {
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(fmt.Sprintf("error executing query: %s", err)))
		return
	}

	defer iter.Close()

	w.Header().Set("content-type", mimetype)

	switch mimetypeFormats[mimetype] {
	case "csv":
		err = sqlagent.EncodeCSV(w, iter)
	case "json":
		err = sqlagent.EncodeJSON(w, iter)
	case "ldjson":
		err = sqlagent.EncodeLDJSON(w, iter)
	}

	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(fmt.Sprintf("error encoding data: %s", err)))
		return
	}
}
<commit_msg>Support GET and HEAD requests for liveness checks<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"mime"
	"net/http"

	"github.com/chop-dbhi/sql-agent"

	_ "github.com/denisenkom/go-mssqldb"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/lib/pq"
	_ "github.com/mattn/go-oci8"
	_ "github.com/mattn/go-sqlite3"
	_ "github.com/snowflakedb/gosnowflake"
)

var usage = `SQL Agent - HTTP interface

This is an HTTP interface for the SQL Agent.

Run:

	sql-agent [-host=<host>] [-port=<port>]

Example:

	POST /
	Content-Type application/json

	{
		"driver": "postgres",
		"connection": {
			"host": "pghost.org",
			"port": 5432,
		},
		"sql": "SELECT * FROM users WHERE zipcode = :zipcode",
		"parameters": {
			"zipcode": 19104
		}
	}
`

const StatusUnprocessableEntity = 422

var (
	defaultMimetype = "application/json"

	mimetypeFormats = map[string]string{
		"*/*":                  "json",
		"text/csv":             "csv",
		"application/json":     "json",
		"application/x-ldjson": "ldjson",
	}
)

// parseMimetype parses a mimetype from the Accept header.
func parseMimetype(mimetype string) string {
	mimetype, params, err := mime.ParseMediaType(mimetype)

	if err != nil {
		return ""
	}

	// No Accept header passed.
	if mimetype == "" {
		return defaultMimetype
	}

	switch mimetype {
	case "application/json":
		if params["boundary"] == "NL" {
			return "application/x-ldjson"
		}
	default:
		if _, ok := mimetypeFormats[mimetype]; !ok {
			return ""
		}
	}

	return mimetype
}

func init() {
	flag.Usage = func() {
		fmt.Println(usage)
		flag.PrintDefaults()
	}

	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

func main() {
	var (
		host string
		port int
	)

	flag.StringVar(&host, "host", "localhost", "Host of the agent.")
	flag.IntVar(&port, "port", 5000, "Port of the agent.")

	flag.Parse()

	addr := fmt.Sprintf("%s:%d", host, port)
	log.Printf("* Listening on %s...\n", addr)

	http.HandleFunc("/", handleRequest)

	err := http.ListenAndServe(addr, nil)
	sqlagent.Shutdown()
	log.Fatal(err)
}

type Payload struct {
	Driver     string
	Connection map[string]interface{}
	SQL        string
	Params     map[string]interface{}
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
	// Alive.
	if r.Method == "GET" || r.Method == "HEAD" {
		return
	}

	// Requires POST otherwise.
	if r.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// Validate the Accept header and parse it to ensure it is
	// supported.
	mimetype := r.Header.Get("Accept")

	if mimetype = parseMimetype(mimetype); mimetype == "" {
		w.WriteHeader(http.StatusNotAcceptable)
		return
	}

	var payload Payload

	// Decode the body.
	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
		w.WriteHeader(StatusUnprocessableEntity)
		w.Write([]byte(fmt.Sprintf("could not decode JSON: %s", err)))
		return
	}

	if _, ok := sqlagent.Drivers[payload.Driver]; !ok {
		w.WriteHeader(StatusUnprocessableEntity)
		w.Write([]byte(fmt.Sprintf("unknown driver: %v", payload.Driver)))
		return
	}

	db, err := sqlagent.PersistentConnect(payload.Driver, payload.Connection)
	if err != nil {
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(fmt.Sprintf("problem connecting to database: %s", err)))
		return
	}

	iter, err := sqlagent.Execute(db, payload.SQL, payload.Params)

	if err != nil {
		w.WriteHeader(http.StatusServiceUnavailable)
		w.Write([]byte(fmt.Sprintf("error executing query: %s", err)))
		return
	}

	defer iter.Close()

	w.Header().Set("content-type", mimetype)

	switch mimetypeFormats[mimetype] {
	case "csv":
		err = sqlagent.EncodeCSV(w, iter)
	case "json":
		err = sqlagent.EncodeJSON(w, iter)
	case "ldjson":
		err = sqlagent.EncodeLDJSON(w, iter)
	}

	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(fmt.Sprintf("error encoding data: %s", err)))
		return
	}
}
<|endoftext|>


<commit_before>// Copyright © 2018 Banzai Cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"emperror.dev/errors"
	vaultapi "github.com/hashicorp/vault/api"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cast"

	"github.com/banzaicloud/bank-vaults/internal/injector"
	"github.com/banzaicloud/bank-vaults/pkg/sdk/vault"
)

// The special value for VAULT_ENV which marks that the login token needs to be passed through to the application
// which was acquired during the new Vault client creation
const vaultLogin = "vault:login"

type sanitizedEnviron []string

var (
	sanitizeEnvmap = map[string]bool{
		"VAULT_TOKEN":                  true,
		"VAULT_ADDR":                   true,
		"VAULT_CACERT":                 true,
		"VAULT_CAPATH":                 true,
		"VAULT_CLIENT_CERT":            true,
		"VAULT_CLIENT_KEY":             true,
		"VAULT_CLIENT_TIMEOUT":         true,
		"VAULT_CLUSTER_ADDR":           true,
		"VAULT_MAX_RETRIES":            true,
		"VAULT_REDIRECT_ADDR":          true,
		"VAULT_SKIP_VERIFY":            true,
		"VAULT_TLS_SERVER_NAME":        true,
		"VAULT_CLI_NO_COLOR":           true,
		"VAULT_RATE_LIMIT":             true,
		"VAULT_NAMESPACE":              true,
		"VAULT_MFA":                    true,
		"VAULT_ROLE":                   true,
		"VAULT_PATH":                   true,
		"VAULT_TRANSIT_KEY_ID":         true,
		"VAULT_TRANSIT_PATH":           true,
		"VAULT_IGNORE_MISSING_SECRETS": true,
		"VAULT_ENV_PASSTHROUGH":        true,
		"VAULT_JSON_LOG":               true,
		"VAULT_REVOKE_TOKEN":           true,
		"VAULT_ENV_DAEMON":             true,
	}
)

// Appends variable an entry (name=value) into the environ list.
// VAULT_* variables are not populated into this list.
func (environ *sanitizedEnviron) append(name string, value string) {
	if _, ok := sanitizeEnvmap[name]; !ok {
		*environ = append(*environ, fmt.Sprintf("%s=%s", name, value))
	}
}

type daemonSecretRenewer struct {
	client *vault.Client
	sigs   chan os.Signal
	logger logrus.FieldLogger
}

func (r daemonSecretRenewer) Renew(path string, secret *vaultapi.Secret) error {
	renewerInput := vaultapi.RenewerInput{Secret: secret}
	renewer, err := r.client.RawClient().NewRenewer(&renewerInput)
	if err != nil {
		return errors.Wrap(err, "failed to create secret renewer")
	}

	go renewer.Renew()

	go func() {
		for {
			select {
			case renewOutput := <-renewer.RenewCh():
				r.logger.Infof("secret %s renewed for %ds", path, renewOutput.Secret.LeaseDuration)
			case doneError := <-renewer.DoneCh():
				r.logger.WithField("error", doneError).Infof("secret renewal for %s has stopped, sending SIGTERM to process", path)

				r.sigs <- syscall.SIGTERM

				timeout := <-time.After(10 * time.Second)
				r.logger.Infoln("killing process due to SIGTERM timeout =", timeout)
				r.sigs <- syscall.SIGKILL

				return
			}
		}
	}()

	return nil
}

func main() {
	enableJSONLog := cast.ToBool(os.Getenv("VAULT_JSON_LOG"))

	var logger logrus.FieldLogger
	{
		log := logrus.New()
		if enableJSONLog {
			log.SetFormatter(&logrus.JSONFormatter{})
		}
		logger = log.WithField("app", "vault-env")
	}

	daemonMode := cast.ToBool(os.Getenv("VAULT_ENV_DAEMON"))
	sigs := make(chan os.Signal, 1)

	var entrypointCmd []string
	if len(os.Args) == 1 {
		logger.Fatalln("no command is given, vault-env can't determine the entrypoint (command), please specify it explicitly or let the webhook query it (see documentation)")
	} else {
		entrypointCmd = os.Args[1:]
	}

	binary, err := exec.LookPath(entrypointCmd[0])
	if err != nil {
		logger.Fatalln("binary not found", entrypointCmd[0])
	}

	// Used both for reading secrets and transit encryption
	ignoreMissingSecrets := cast.ToBool(os.Getenv("VAULT_IGNORE_MISSING_SECRETS"))

	// The login procedure takes the token from a file (if using Vault Agent)
	// or requests one for itself (Kubernetes Auth), so if we got a VAULT_TOKEN
	// for the special value with "vault:login"
	originalVaultTokenEnvVar := os.Getenv("VAULT_TOKEN")
	if originalVaultTokenEnvVar == vaultLogin {
		os.Unsetenv("VAULT_TOKEN")
	}

	client, err := vault.NewClientWithOptions(
		vault.ClientRole(os.Getenv("VAULT_ROLE")),
		vault.ClientAuthPath(os.Getenv("VAULT_PATH")),
	)
	if err != nil {
		logger.Fatal("failed to create vault client", err.Error())
	}

	passthroughEnvVars := strings.Split(os.Getenv("VAULT_ENV_PASSTHROUGH"), ",")

	if originalVaultTokenEnvVar == vaultLogin {
		os.Setenv("VAULT_TOKEN", vaultLogin)
		passthroughEnvVars = append(passthroughEnvVars, "VAULT_TOKEN")
	}

	// do not sanitize env vars specified in VAULT_ENV_PASSTHROUGH
	for _, envVar := range passthroughEnvVars {
		if trimmed := strings.TrimSpace(envVar); trimmed != "" {
			delete(sanitizeEnvmap, trimmed)
		}
	}

	// initial and sanitized environs
	environ := make(map[string]string, len(os.Environ()))
	sanitized := make(sanitizedEnviron, 0, len(environ))

	config := injector.Config{
		TransitKeyID:         os.Getenv("VAULT_TRANSIT_KEY_ID"),
		TransitPath:          os.Getenv("VAULT_TRANSIT_PATH"),
		DaemonMode:           daemonMode,
		IgnoreMissingSecrets: ignoreMissingSecrets,
	}

	var secretRenewer injector.SecretRenewer
	if daemonMode {
		secretRenewer = daemonSecretRenewer{client: client, sigs: sigs, logger: logger}
	}

	secretInjector := injector.NewSecretInjector(config, client, secretRenewer, logger)

	for _, env := range os.Environ() {
		split := strings.SplitN(env, "=", 2)
		name := split[0]
		value := split[1]
		environ[name] = value
	}

	inject := func(key, value string) {
		sanitized.append(key, value)
	}

	err = secretInjector.InjectSecretsFromVault(environ, inject)
	if err != nil {
		logger.Fatalln("failed to inject secrets from vault:", err)
	}

	if cast.ToBool(os.Getenv("VAULT_REVOKE_TOKEN")) {
		// ref: https://www.vaultproject.io/api/auth/token/index.html#revoke-a-token-self-
		err = client.RawClient().Auth().Token().RevokeSelf(client.RawClient().Token())
		if err != nil {
			// Do not exit on error, token revoking can be denied by policy
			logger.Warn("failed to revoke token")
		}

		client.Close()
	}

	logger.Infoln("spawning process:", entrypointCmd)

	if daemonMode {
		logger.Infoln("in daemon mode...")
		cmd := exec.Command(binary, entrypointCmd[1:]...)
		cmd.Env = append(os.Environ(), sanitized...)
		cmd.Stdin = os.Stdin
		cmd.Stderr = os.Stderr
		cmd.Stdout = os.Stdout

		signal.Notify(sigs)

		err = cmd.Start()
		if err != nil {
			logger.Fatalln("failed to start process", entrypointCmd, err.Error())
		}

		go func() {
			sig := <-sigs
			err := cmd.Process.Signal(sig)
			if err != nil {
				logger.Warnf("failed to signal process with %s: %v", sig, err)
			} else {
				logger.Infof("received signal: %s", sig)
			}
		}()

		err = cmd.Wait()

		close(sigs)

		if _, ok := err.(*exec.ExitError); ok {
			os.Exit(cmd.ProcessState.ExitCode())
		} else if err != nil {
			logger.Fatalln("failed to exec process", entrypointCmd, err.Error())
			os.Exit(-1)
		} else {
			os.Exit(cmd.ProcessState.ExitCode())
		}
	} else {
		err = syscall.Exec(binary, entrypointCmd, sanitized)
		if err != nil {
			logger.Fatalln("failed to exec process", entrypointCmd, err.Error())
		}
	}
}
<commit_msg>vault-env: process all signals (#956)<commit_after>// Copyright © 2018 Banzai Cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"emperror.dev/errors"
	vaultapi "github.com/hashicorp/vault/api"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cast"

	"github.com/banzaicloud/bank-vaults/internal/injector"
	"github.com/banzaicloud/bank-vaults/pkg/sdk/vault"
)

// The special value for VAULT_ENV which marks that the login token needs to be passed through to the application
// which was acquired during the new Vault client creation
const vaultLogin = "vault:login"

type sanitizedEnviron []string

var (
	sanitizeEnvmap = map[string]bool{
		"VAULT_TOKEN":                  true,
		"VAULT_ADDR":                   true,
		"VAULT_CACERT":                 true,
		"VAULT_CAPATH":                 true,
		"VAULT_CLIENT_CERT":            true,
		"VAULT_CLIENT_KEY":             true,
		"VAULT_CLIENT_TIMEOUT":         true,
		"VAULT_CLUSTER_ADDR":           true,
		"VAULT_MAX_RETRIES":            true,
		"VAULT_REDIRECT_ADDR":          true,
		"VAULT_SKIP_VERIFY":            true,
		"VAULT_TLS_SERVER_NAME":        true,
		"VAULT_CLI_NO_COLOR":           true,
		"VAULT_RATE_LIMIT":             true,
		"VAULT_NAMESPACE":              true,
		"VAULT_MFA":                    true,
		"VAULT_ROLE":                   true,
		"VAULT_PATH":                   true,
		"VAULT_TRANSIT_KEY_ID":         true,
		"VAULT_TRANSIT_PATH":           true,
		"VAULT_IGNORE_MISSING_SECRETS": true,
		"VAULT_ENV_PASSTHROUGH":        true,
		"VAULT_JSON_LOG":               true,
		"VAULT_REVOKE_TOKEN":           true,
		"VAULT_ENV_DAEMON":             true,
	}
)

// Appends variable an entry (name=value) into the environ list.
// VAULT_* variables are not populated into this list.
func (environ *sanitizedEnviron) append(name string, value string) {
	if _, ok := sanitizeEnvmap[name]; !ok {
		*environ = append(*environ, fmt.Sprintf("%s=%s", name, value))
	}
}

type daemonSecretRenewer struct {
	client *vault.Client
	sigs   chan os.Signal
	logger logrus.FieldLogger
}

func (r daemonSecretRenewer) Renew(path string, secret *vaultapi.Secret) error {
	renewerInput := vaultapi.RenewerInput{Secret: secret}
	renewer, err := r.client.RawClient().NewRenewer(&renewerInput)
	if err != nil {
		return errors.Wrap(err, "failed to create secret renewer")
	}

	go renewer.Renew()

	go func() {
		for {
			select {
			case renewOutput := <-renewer.RenewCh():
				r.logger.Infof("secret %s renewed for %ds", path, renewOutput.Secret.LeaseDuration)
			case doneError := <-renewer.DoneCh():
				r.logger.WithField("error", doneError).Infof("secret renewal for %s has stopped, sending SIGTERM to process", path)

				r.sigs <- syscall.SIGTERM

				timeout := <-time.After(10 * time.Second)
				r.logger.Infoln("killing process due to SIGTERM timeout =", timeout)
				r.sigs <- syscall.SIGKILL

				return
			}
		}
	}()

	return nil
}

func main() {
	enableJSONLog := cast.ToBool(os.Getenv("VAULT_JSON_LOG"))

	var logger logrus.FieldLogger
	{
		log := logrus.New()
		if enableJSONLog {
			log.SetFormatter(&logrus.JSONFormatter{})
		}
		logger = log.WithField("app", "vault-env")
	}

	daemonMode := cast.ToBool(os.Getenv("VAULT_ENV_DAEMON"))
	sigs := make(chan os.Signal, 1)

	var entrypointCmd []string
	if len(os.Args) == 1 {
		logger.Fatalln("no command is given, vault-env can't determine the entrypoint (command), please specify it explicitly or let the webhook query it (see documentation)")
	} else {
		entrypointCmd = os.Args[1:]
	}

	binary, err := exec.LookPath(entrypointCmd[0])
	if err != nil {
		logger.Fatalln("binary not found", entrypointCmd[0])
	}

	// Used both for reading secrets and transit encryption
	ignoreMissingSecrets := cast.ToBool(os.Getenv("VAULT_IGNORE_MISSING_SECRETS"))

	// The login procedure takes the token from a file (if using Vault Agent)
	// or requests one for itself (Kubernetes Auth), so if we got a VAULT_TOKEN
	// for the special value with "vault:login"
	originalVaultTokenEnvVar := os.Getenv("VAULT_TOKEN")
	if originalVaultTokenEnvVar == vaultLogin {
		os.Unsetenv("VAULT_TOKEN")
	}

	client, err := vault.NewClientWithOptions(
		vault.ClientRole(os.Getenv("VAULT_ROLE")),
		vault.ClientAuthPath(os.Getenv("VAULT_PATH")),
	)
	if err != nil {
		logger.Fatal("failed to create vault client", err.Error())
	}

	passthroughEnvVars := strings.Split(os.Getenv("VAULT_ENV_PASSTHROUGH"), ",")

	if originalVaultTokenEnvVar == vaultLogin {
		os.Setenv("VAULT_TOKEN", vaultLogin)
		passthroughEnvVars = append(passthroughEnvVars, "VAULT_TOKEN")
	}

	// do not sanitize env vars specified in VAULT_ENV_PASSTHROUGH
	for _, envVar := range passthroughEnvVars {
		if trimmed := strings.TrimSpace(envVar); trimmed != "" {
			delete(sanitizeEnvmap, trimmed)
		}
	}

	// initial and sanitized environs
	environ := make(map[string]string, len(os.Environ()))
	sanitized := make(sanitizedEnviron, 0, len(environ))

	config := injector.Config{
		TransitKeyID:         os.Getenv("VAULT_TRANSIT_KEY_ID"),
		TransitPath:          os.Getenv("VAULT_TRANSIT_PATH"),
		DaemonMode:           daemonMode,
		IgnoreMissingSecrets: ignoreMissingSecrets,
	}

	var secretRenewer injector.SecretRenewer
	if daemonMode {
		secretRenewer = daemonSecretRenewer{client: client, sigs: sigs, logger: logger}
	}

	secretInjector := injector.NewSecretInjector(config, client, secretRenewer, logger)

	for _, env := range os.Environ() {
		split := strings.SplitN(env, "=", 2)
		name := split[0]
		value := split[1]
		environ[name] = value
	}

	inject := func(key, value string) {
		sanitized.append(key, value)
	}

	err = secretInjector.InjectSecretsFromVault(environ, inject)
	if err != nil {
		logger.Fatalln("failed to inject secrets from vault:", err)
	}

	if cast.ToBool(os.Getenv("VAULT_REVOKE_TOKEN")) {
		// ref: https://www.vaultproject.io/api/auth/token/index.html#revoke-a-token-self-
		err = client.RawClient().Auth().Token().RevokeSelf(client.RawClient().Token())
		if err != nil {
			// Do not exit on error, token revoking can be denied by policy
			logger.Warn("failed to revoke token")
		}

		client.Close()
	}

	logger.Infoln("spawning process:", entrypointCmd)

	if daemonMode {
		logger.Infoln("in daemon mode...")
		cmd := exec.Command(binary, entrypointCmd[1:]...)
		cmd.Env = append(os.Environ(), sanitized...)
		cmd.Stdin = os.Stdin
		cmd.Stderr = os.Stderr
		cmd.Stdout = os.Stdout

		signal.Notify(sigs)

		err = cmd.Start()
		if err != nil {
			logger.Fatalln("failed to start process", entrypointCmd, err.Error())
		}

		go func() {
			for sig := range sigs {
				// We don't want to signal a non-running process.
				if cmd.ProcessState != nil && cmd.ProcessState.Exited() {
					break
				}

				err := cmd.Process.Signal(sig)
				if err != nil {
					logger.Warnf("failed to signal process with %s: %v", sig, err)
				} else {
					logger.Infof("received signal: %s", sig)
				}
			}
		}()

		err = cmd.Wait()

		close(sigs)

		if _, ok := err.(*exec.ExitError); ok {
			os.Exit(cmd.ProcessState.ExitCode())
		} else if err != nil {
			logger.Fatalln("failed to exec process", entrypointCmd, err.Error())
			os.Exit(-1)
		} else {
			os.Exit(cmd.ProcessState.ExitCode())
		}
	} else {
		err = syscall.Exec(binary, entrypointCmd, sanitized)
		if err != nil {
			logger.Fatalln("failed to exec process", entrypointCmd, err.Error())
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 The astrogo Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// view-fits is a simple program to display images in a FITS file.
//
// Usage of view-fits:
// $ view-fits [file1 [file2 [...]]]
//
// Examples:
//  $ view-fits astrogo/fitsio/testdata/file-img2-bitpix+08.fits
//  $ view-fits astrogo/fitsio/testdata/file-img2-bitpix*.fits
//  $ view-fits http://data.astropy.org/tutorials/FITS-images/HorseHead.fits
//  $ view-fits file:///some/file.fits
//
// Controls:
//  - left/right arrows: switch to previous/next file
//  - up/down arrows:    switch to previous/next image in the current file
//  - r:                 reload/redisplay current image
//  - z:                 resize window to fit current image
//  - p:                 print current image to 'output.png'
//  - ?:                 show help
//  - q/ESC:             quit
//
package main

import (
	"flag"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/astrogo/fitsio"

	"golang.org/x/exp/shiny/driver"
	"golang.org/x/exp/shiny/screen"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/mouse"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
)

type fileInfo struct {
	Name   string
	Images []imageInfo
}

type imageInfo struct {
	image.Image
	scale int // image scale in percents (default: 100%)
	orig  image.Point
}

func main() {

	help := flag.Bool("help", false, "show help")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, `view-fits - a FITS image viewer.

Usage of view-fits:
$ view-fits [file1 [file2 [...]]]

Examples:
$ view-fits astrogo/fitsio/testdata/file-img2-bitpix+08.fits
$ view-fits astrogo/fitsio/testdata/file-img2-bitpix*.fits
$ view-fits http://data.astropy.org/tutorials/FITS-images/HorseHead.fits
$ view-fits file:///some/file.fits

Controls:
- left/right arrows: switch to previous/next file
- up/down arrows:    switch to previous/next image in the current file
- r:                 reload/redisplay current image
- z:                 resize window to fit current image
- p:                 print current image to 'output.png'
- ?:                 show help
- q/ESC:             quit

Mouse controls:
- Left button:       pan image
`)
	}

	flag.Parse()

	if *help || len(os.Args) < 2 {
		flag.Usage()
		os.Exit(0)
	}

	log.SetFlags(0)
	log.SetPrefix("[view-fits] ")

	infos := processFiles()
	if len(infos) == 0 {
		log.Fatal("No image among given FITS files.")
	}

	type cursor struct {
		file int
		img  int
	}

	driver.Main(func(s screen.Screen) {

		// Number of files.
		nbFiles := len(infos)

		// Current displayed file and image in file.
		cur := cursor{file: 0, img: 0}

		// Building the main window.
		w, err := s.NewWindow(&screen.NewWindowOptions{
			Width:  500,
			Height: 500,
		})
		if err != nil {
			log.Fatal(err)
		}
		defer w.Release()

		// Building the screen buffer.
		b, err := s.NewBuffer(image.Point{500, 500})
		if err != nil {
			log.Fatal(err)
		}
		defer release(b)

		w.Fill(b.Bounds(), color.Black, draw.Src)
		w.Publish()

		repaint := true
		var (
			sz size.Event
			//bkg = color.Black
			bkg = color.RGBA{0xe0, 0xe0, 0xe0, 0xff} // Material Design "Grey 300"

			mbl image.Rectangle // mouse button-left position
		)

		for {
			switch e := w.NextEvent().(type) {
			default:
				// ignore

			case lifecycle.Event:
				switch {
				case e.From == lifecycle.StageVisible && e.To == lifecycle.StageFocused:
					repaint = true
				default:
					repaint = false
				}

			case mouse.Event:
				switch e.Button {
				case mouse.ButtonLeft:
					switch e.Direction {
					case mouse.DirPress:
						x := int(e.X)
						y := int(e.Y)
						mbl = image.Rect(x, y, x, y)
					case mouse.DirRelease:
						x := int(e.X)
						y := int(e.Y)
						mbl.Max = image.Point{x, y}

						switch {
						case e.Modifiers&key.ModShift != 0:
							// zoom-in
						default:
							// pan
							repaint = true
							img := &infos[cur.file].Images[cur.img]
							dx := mbl.Dx()
							dy := mbl.Dy()
							img.orig = originTrans(img.orig.Sub(image.Point{dx, dy}), sz.Bounds(), img)
						}
					}
				}

			case key.Event:
				switch e.Code {
				case key.CodeEscape, key.CodeQ:
					return

				case key.CodeSlash:
					if e.Direction == key.DirPress && e.Modifiers&key.ModShift != 0 {
						flag.Usage()
						continue
					}

				case key.CodeRightArrow:
					if e.Direction == key.DirPress {
						repaint = true
						if cur.file < nbFiles-1 {
							cur.file++
						} else {
							cur.file = 0
						}
						cur.img = 0
						log.Printf("file:   %v\n", infos[cur.file].Name)
						log.Printf("images: %d\n", len(infos[cur.file].Images))
					}

				case key.CodeLeftArrow:
					if e.Direction == key.DirPress {
						repaint = true
						if cur.file == 0 {
							cur.file = nbFiles - 1
						} else {
							cur.file--
						}
						cur.img = 0
						log.Printf("file:   %v\n", infos[cur.file].Name)
						log.Printf("images: %d\n", len(infos[cur.file].Images))
					}

				case key.CodeDownArrow:
					if e.Direction == key.DirPress {
						repaint = true
						nbImg := len(infos[cur.file].Images)
						if cur.img < nbImg-1 {
							cur.img++
						} else {
							cur.img = 0
						}
					}

				case key.CodeUpArrow:
					if e.Direction == key.DirPress {
						repaint = true
						nbImg := len(infos[cur.file].Images)
						if cur.img == 0 {
							cur.img = nbImg - 1
						} else {
							cur.img--
						}
					}

				case key.CodeR:
					if e.Direction == key.DirPress {
						repaint = true
					}

				case key.CodeZ:
					if e.Direction == key.DirPress {
						// resize to current image
						// TODO(sbinet)
						repaint = true
					}

				case key.CodeP:
					if e.Direction != key.DirPress {
						continue
					}
					out, err := os.Create("output.png")
					if err != nil {
						log.Fatalf("error printing image: %v\n", err)
					}
					defer out.Close()
					err = png.Encode(out, infos[cur.file].Images[cur.img])
					if err != nil {
						log.Fatalf("error printing image: %v\n", err)
					}
					err = out.Close()
					if err != nil {
						log.Fatalf("error printing image: %v\n", err)
					}
					log.Printf("printed current image to [%s]\n", out.Name())
				}

			case size.Event:
				sz = e

			case paint.Event:
				if !repaint {
					continue
				}
				repaint = false
				img := infos[cur.file].Images[cur.img]

				release(b)
				b, err = s.NewBuffer(img.Bounds().Size())
				if err != nil {
					log.Fatal(err)
				}
				defer release(b)

				draw.Draw(b.RGBA(), b.Bounds(), img, img.orig, draw.Src)

				w.Fill(sz.Bounds(), bkg, draw.Src)
				w.Upload(image.Point{}, b, img.Bounds())
				w.Publish()
			}

			if repaint {
				w.Send(paint.Event{})
			}

		}

	})
}

func processFiles() []fileInfo {
	infos := make([]fileInfo, 0, len(flag.Args()))
	// Parsing input files.
	for _, fname := range flag.Args() {

		finfo := fileInfo{Name: fname}

		r, err := openStream(fname)
		if err != nil {
			log.Fatalf("Can not open the input file: %v", err)
		}
		defer r.Close()

		// Opening the FITS file.
		f, err := fitsio.Open(r)
		if err != nil {
			log.Fatalf("Can not open the FITS input file: %v", err)
		}
		defer f.Close()

		// Getting the file HDUs.
		hdus := f.HDUs()
		for _, hdu := range hdus {
			// Getting the header informations.
			header := hdu.Header()
			axes := header.Axes()

			// Discarding HDU with no axes.
			if len(axes) != 0 {
				if hdu, ok := hdu.(fitsio.Image); ok {
					img := hdu.Image()
					if img != nil {
						finfo.Images = append(finfo.Images, imageInfo{
							Image: img,
							scale: 100,
							orig:  image.Point{},
						})
					}
				}
			}
		}

		if len(finfo.Images) > 0 {
			infos = append(infos, finfo)
		}
	}

	return infos
}

func openStream(name string) (io.ReadCloser, error) {
	switch {
	case strings.HasPrefix(name, "http://") || strings.HasPrefix(name, "https://"):
		resp, err := http.Get(name)
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()

		f, err := ioutil.TempFile("", "view-fits-")
		if err != nil {
			return nil, err
		}

		_, err = io.Copy(f, resp.Body)
		if err != nil {
			f.Close()
			return nil, err
		}

		// make sure we have at least a full FITS block
		f.Seek(0, 2880)
		f.Seek(0, 0)

		return f, nil

	case strings.HasPrefix(name, "file://"):
		name = name[len("file://"):]
		return os.Open(name)
	default:
		return os.Open(name)
	}
}

type releaser interface {
	Release()
}

func release(r releaser) {
	if r != nil {
		r.Release()
	}
}

func min(i, j int) int {
	if i < j {
		return i
	}
	return j
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

// originTrans translates the origin with respect to the current image and the
// current canvas size. This makes sure we never incorrect position the image.
// (i.e., panning never goes too far, and whenever the canvas is bigger than
// the image, the origin is *always* (0, 0).
func originTrans(pt image.Point, win image.Rectangle, img *imageInfo) image.Point {
	// If there's no valid image, then always return (0, 0).
	if img == nil {
		return image.Point{0, 0}
	}

	// Quick aliases.
	ww := win.Dx()
	wh := win.Dy()
	dw := img.Bounds().Dx() - ww
	dh := img.Bounds().Dy() - wh

	// Set the allowable range of the origin point of the image.
	// i.e., never less than (0, 0) and never greater than the width/height
	// of the image that isn't viewable at any given point (which is determined
	// by the canvas size).
	pt.X = min(img.Bounds().Min.X+dw, max(pt.X, 0))
	pt.Y = min(img.Bounds().Min.Y+dh, max(pt.Y, 0))

	// Validate origin point. If the width/height of an image is smaller than
	// the canvas width/height, then the image origin cannot change in x/y
	// direction.
	if img.Bounds().Dx() < ww {
		pt.X = 0
	}
	if img.Bounds().Dy() < wh {
		pt.Y = 0
	}

	return pt
}
<commit_msg>cmd/view-fits: preliminary support for zooming<commit_after>// Copyright 2016 The astrogo Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// view-fits is a simple program to display images in a FITS file.
//
// Usage of view-fits:
// $ view-fits [file1 [file2 [...]]]
//
// Examples:
//  $ view-fits astrogo/fitsio/testdata/file-img2-bitpix+08.fits
//  $ view-fits astrogo/fitsio/testdata/file-img2-bitpix*.fits
//  $ view-fits http://data.astropy.org/tutorials/FITS-images/HorseHead.fits
//  $ view-fits file:///some/file.fits
//
// Controls:
//  - left/right arrows: switch to previous/next file
//  - up/down arrows:    switch to previous/next image in the current file
//  - r:                 reload/redisplay current image
//  - z:                 resize window to fit current image
//  - p:                 print current image to 'output.png'
//  - ?:                 show help
//  - q/ESC:             quit
//
package main

import (
	"flag"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/astrogo/fitsio"

	"golang.org/x/exp/shiny/driver"
	"golang.org/x/exp/shiny/screen"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/mouse"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
)

type fileInfo struct {
	Name   string
	Images []imageInfo
}

type imageInfo struct {
	image.Image
	scale int // image scale in percents (default: 100%)
	orig  image.Point
}

func main() {

	help := flag.Bool("help", false, "show help")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, `view-fits - a FITS image viewer.

Usage of view-fits:
$ view-fits [file1 [file2 [...]]]

Examples:
$ view-fits astrogo/fitsio/testdata/file-img2-bitpix+08.fits
$ view-fits astrogo/fitsio/testdata/file-img2-bitpix*.fits
$ view-fits http://data.astropy.org/tutorials/FITS-images/HorseHead.fits
$ view-fits file:///some/file.fits

Controls:
- left/right arrows: switch to previous/next file
- up/down arrows:    switch to previous/next image in the current file
- r:                 reload/redisplay current image
- z:                 resize window to fit current image
- p:                 print current image to 'output.png'
- +:                 increase zoom-level by 20%
- -:                 decrease zoom-level by 20%
- ?:                 show help
- q/ESC:             quit

Mouse controls:
- Left button:       pan image
`)
	}

	flag.Parse()

	if *help || len(os.Args) < 2 {
		flag.Usage()
		os.Exit(0)
	}

	log.SetFlags(0)
	log.SetPrefix("[view-fits] ")

	infos := processFiles()
	if len(infos) == 0 {
		log.Fatal("No image among given FITS files.")
	}

	type cursor struct {
		file int
		img  int
	}

	driver.Main(func(s screen.Screen) {

		// Number of files.
		nbFiles := len(infos)

		// Current displayed file and image in file.
		cur := cursor{file: 0, img: 0}

		// Building the main window.
		w, err := s.NewWindow(&screen.NewWindowOptions{
			Width:  500,
			Height: 500,
		})
		if err != nil {
			log.Fatal(err)
		}
		defer w.Release()

		// Building the screen buffer.
		b, err := s.NewBuffer(image.Point{500, 500})
		if err != nil {
			log.Fatal(err)
		}
		defer release(b)

		w.Fill(b.Bounds(), color.Black, draw.Src)
		w.Publish()

		repaint := true
		var (
			sz size.Event
			//bkg = color.Black
			bkg = color.RGBA{0xe0, 0xe0, 0xe0, 0xff} // Material Design "Grey 300"

			mbl image.Rectangle // mouse button-left position
		)

		for {
			switch e := w.NextEvent().(type) {
			default:
				// ignore

			case lifecycle.Event:
				switch {
				case e.From == lifecycle.StageVisible && e.To == lifecycle.StageFocused:
					repaint = true
				default:
					repaint = false
				}

			case mouse.Event:
				switch e.Button {
				case mouse.ButtonLeft:
					switch e.Direction {
					case mouse.DirPress:
						x := int(e.X)
						y := int(e.Y)
						mbl = image.Rect(x, y, x, y)
					case mouse.DirRelease:
						x := int(e.X)
						y := int(e.Y)
						mbl.Max = image.Point{x, y}

						switch {
						case e.Modifiers&key.ModShift != 0:
							// zoom-in
						default:
							// pan
							repaint = true
							img := &infos[cur.file].Images[cur.img]
							dx := mbl.Dx()
							dy := mbl.Dy()
							img.orig = originTrans(img.orig.Sub(image.Point{dx, dy}), sz.Bounds(), img)
						}
					}

				case mouse.ButtonRight:
				case mouse.ButtonWheelDown:
					if e.Direction == mouse.DirPress {
						ctrlZoomOut(&infos[cur.file].Images[cur.img], &repaint)
					}
				case mouse.ButtonWheelUp:
					if e.Direction == mouse.DirPress {
						ctrlZoomIn(&infos[cur.file].Images[cur.img], &repaint)
					}
				}

			case key.Event:
				switch e.Code {
				case key.CodeEscape, key.CodeQ:
					return

				case key.CodeSlash:
					if e.Direction == key.DirPress && e.Modifiers&key.ModShift != 0 {
						flag.Usage()
						continue
					}

				case key.CodeKeypadPlusSign:
					if e.Direction == key.DirPress {
						ctrlZoomIn(&infos[cur.file].Images[cur.img], &repaint)
					}

				case key.CodeEqualSign:
					if e.Direction == key.DirPress && e.Modifiers&key.ModShift != 0 {
						ctrlZoomIn(&infos[cur.file].Images[cur.img], &repaint)
					}

				case key.CodeHyphenMinus:
					if e.Direction == key.DirPress {
						ctrlZoomOut(&infos[cur.file].Images[cur.img], &repaint)
					}

				case key.CodeRightArrow:
					if e.Direction == key.DirPress {
						repaint = true
						if cur.file < nbFiles-1 {
							cur.file++
						} else {
							cur.file = 0
						}
						cur.img = 0
						log.Printf("file:   %v\n", infos[cur.file].Name)
						log.Printf("images: %d\n", len(infos[cur.file].Images))
					}

				case key.CodeLeftArrow:
					if e.Direction == key.DirPress {
						repaint = true
						if cur.file == 0 {
							cur.file = nbFiles - 1
						} else {
							cur.file--
						}
						cur.img = 0
						log.Printf("file:   %v\n", infos[cur.file].Name)
						log.Printf("images: %d\n", len(infos[cur.file].Images))
					}

				case key.CodeDownArrow:
					if e.Direction == key.DirPress {
						repaint = true
						nbImg := len(infos[cur.file].Images)
						if cur.img < nbImg-1 {
							cur.img++
						} else {
							cur.img = 0
						}
					}

				case key.CodeUpArrow:
					if e.Direction == key.DirPress {
						repaint = true
						nbImg := len(infos[cur.file].Images)
						if cur.img == 0 {
							cur.img = nbImg - 1
						} else {
							cur.img--
						}
					}

				case key.CodeR:
					if e.Direction == key.DirPress {
						repaint = true
					}

				case key.CodeZ:
					if e.Direction == key.DirPress {
						// resize to current image
						// TODO(sbinet)
						repaint = true
					}

				case key.CodeP:
					if e.Direction != key.DirPress {
						continue
					}
					out, err := os.Create("output.png")
					if err != nil {
						log.Fatalf("error printing image: %v\n", err)
					}
					defer out.Close()
					err = png.Encode(out, infos[cur.file].Images[cur.img])
					if err != nil {
						log.Fatalf("error printing image: %v\n", err)
					}
					err = out.Close()
					if err != nil {
						log.Fatalf("error printing image: %v\n", err)
					}
					log.Printf("printed current image to [%s]\n", out.Name())
				}

			case size.Event:
				sz = e

			case paint.Event:
				if !repaint {
					continue
				}
				repaint = false
				img := infos[cur.file].Images[cur.img]

				release(b)
				b, err = s.NewBuffer(img.Bounds().Size())
				if err != nil {
					log.Fatal(err)
				}
				defer release(b)

				draw.Draw(b.RGBA(), b.Bounds(), img, img.orig, draw.Src)

				w.Fill(sz.Bounds(), bkg, draw.Src)
				w.Upload(image.Point{}, b, img.Bounds())
				w.Publish()
			}

			if repaint {
				w.Send(paint.Event{})
			}

		}

	})
}

func processFiles() []fileInfo {
	infos := make([]fileInfo, 0, len(flag.Args()))
	// Parsing input files.
	for _, fname := range flag.Args() {

		finfo := fileInfo{Name: fname}

		r, err := openStream(fname)
		if err != nil {
			log.Fatalf("Can not open the input file: %v", err)
		}
		defer r.Close()

		// Opening the FITS file.
		f, err := fitsio.Open(r)
		if err != nil {
			log.Fatalf("Can not open the FITS input file: %v", err)
		}
		defer f.Close()

		// Getting the file HDUs.
		hdus := f.HDUs()
		for _, hdu := range hdus {
			// Getting the header informations.
			header := hdu.Header()
			axes := header.Axes()

			// Discarding HDU with no axes.
			if len(axes) != 0 {
				if hdu, ok := hdu.(fitsio.Image); ok {
					img := hdu.Image()
					if img != nil {
						finfo.Images = append(finfo.Images, imageInfo{
							Image: img,
							scale: 100,
							orig:  image.Point{},
						})
					}
				}
			}
		}

		if len(finfo.Images) > 0 {
			infos = append(infos, finfo)
		}
	}

	return infos
}

func openStream(name string) (io.ReadCloser, error) {
	switch {
	case strings.HasPrefix(name, "http://") || strings.HasPrefix(name, "https://"):
		resp, err := http.Get(name)
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()

		f, err := ioutil.TempFile("", "view-fits-")
		if err != nil {
			return nil, err
		}

		_, err = io.Copy(f, resp.Body)
		if err != nil {
			f.Close()
			return nil, err
		}

		// make sure we have at least a full FITS block
		f.Seek(0, 2880)
		f.Seek(0, 0)

		return f, nil

	case strings.HasPrefix(name, "file://"):
		name = name[len("file://"):]
		return os.Open(name)
	default:
		return os.Open(name)
	}
}

type releaser interface {
	Release()
}

func release(r releaser) {
	if r != nil {
		r.Release()
	}
}

func ctrlZoomOut(img *imageInfo, repaint *bool) {
	*repaint = true
	if img.scale <= 20 {
		*repaint = false
		return
	}

	img.scale -= 20

	if img.scale <= 0 {
		img.scale = 20
	}
}

func ctrlZoomIn(img *imageInfo, repaint *bool) {
	*repaint = true
	img.scale += 20
}

func min(i, j int) int {
	if i < j {
		return i
	}
	return j
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

// originTrans translates the origin with respect to the current image and the
// current canvas size. This makes sure we never incorrect position the image.
// (i.e., panning never goes too far, and whenever the canvas is bigger than
// the image, the origin is *always* (0, 0).
func originTrans(pt image.Point, win image.Rectangle, img *imageInfo) image.Point {
	// If there's no valid image, then always return (0, 0).
	if img == nil {
		return image.Point{0, 0}
	}

	// Quick aliases.
	ww := win.Dx()
	wh := win.Dy()
	dw := img.Bounds().Dx() - ww
	dh := img.Bounds().Dy() - wh

	// Set the allowable range of the origin point of the image.
	// i.e., never less than (0, 0) and never greater than the width/height
	// of the image that isn't viewable at any given point (which is determined
	// by the canvas size).
	pt.X = min(img.Bounds().Min.X+dw, max(pt.X, 0))
	pt.Y = min(img.Bounds().Min.Y+dh, max(pt.Y, 0))

	// Validate origin point. If the width/height of an image is smaller than
	// the canvas width/height, then the image origin cannot change in x/y
	// direction.
	if img.Bounds().Dx() < ww {
		pt.X = 0
	}
	if img.Bounds().Dy() < wh {
		pt.Y = 0
	}

	return pt
}
<|endoftext|>


<commit_before>package providers

import (
	"io/ioutil"
	"os"
	"testing"

	log "github.com/Sirupsen/logrus"
	"github.com/camptocamp/conplicity/lib"
	"github.com/docker/engine-api/types"
)

func TestGetProvider(t *testing.T) {
	var dir, expected, got string
	var p Provider

	// Test PostgreSQL detection
	expected = "PostgreSQL"
	dir, _ = ioutil.TempDir("", "test_get_provider_postgresql")
	defer os.RemoveAll(dir)
	ioutil.WriteFile(dir+"/PG_VERSION", []byte{}, 0644)

	p = GetProvider(&conplicity.Conplicity{}, &types.Volume{
		Mountpoint: dir,
	})
	got = p.GetName()
	if got != expected {
		t.Fatalf("Expected provider %s, got %s", expected, got)
	}

	// Test MySQL detection
	expected = "MySQL"
	dir, _ = ioutil.TempDir("", "test_get_provider_mysql")
	defer os.RemoveAll(dir)
	os.Mkdir(dir+"/mysql", 0755)

	p = GetProvider(&conplicity.Conplicity{}, &types.Volume{
		Mountpoint: dir,
	})
	got = p.GetName()
	if got != expected {
		t.Fatalf("Expected provider %s, got %s", expected, got)
	}

	// Test OpenLDAP detection
	expected = "OpenLDAP"
	dir, _ = ioutil.TempDir("", "test_get_provider_openldap")
	defer os.RemoveAll(dir)
	ioutil.WriteFile(dir+"/DB_CONFIG", []byte{}, 0644)

	p = GetProvider(&conplicity.Conplicity{}, &types.Volume{
		Mountpoint: dir,
	})
	got = p.GetName()
	if got != expected {
		t.Fatalf("Expected provider %s, got %s", expected, got)
	}

	// Test Default detection
	expected = "Default"
	dir, _ = ioutil.TempDir("", "test_get_provider_default")
	defer os.RemoveAll(dir)

	p = GetProvider(&conplicity.Conplicity{}, &types.Volume{
		Mountpoint: dir,
	})
	got = p.GetName()
	if got != expected {
		t.Fatalf("Expected provider %s, got %s", expected, got)
	}
}

func TestPrepareBackup(t *testing.T) {
	// Use Default provider
	dir, _ := ioutil.TempDir("", "test_backup_volume")
	defer os.RemoveAll(dir)

	p := &DefaultProvider{
		BaseProvider: &BaseProvider{
			handler: &conplicity.Conplicity{},
			vol: &types.Volume{
				Name:       dir,
				Driver:     "local",
				Mountpoint: "/mnt",
			},
		},
	}

	// Fill Config manually
	p.handler.Config = &conplicity.Config{} // Init Config
	p.handler.Config.Image = "camptocamp/duplicity:latest"
	p.handler.Config.Docker.Endpoint = "unix:///var/run/docker.sock"
	p.handler.Hostname, _ = os.Hostname()
	p.handler.SetupDocker()

	log.SetLevel(log.DebugLevel)

	err := PrepareBackup(p)

	if err != nil {
		t.Fatalf("Expected no error, got error: %v", err)
	}
}

func TestBackupVolume(t *testing.T) {
	t.Skip("Skipping as it hangs forever on Travis CI")

	// Use Base provider
	dir, _ := ioutil.TempDir("", "test_backup_volume")
	defer os.RemoveAll(dir)

	p := &BaseProvider{
		handler: &conplicity.Conplicity{},
	}

	// Fill Config manually
	p.handler.Config = &conplicity.Config{} // Init Config
	p.handler.Config.Duplicity.FullIfOlderThan = "314D"
	p.handler.Config.Duplicity.RemoveOlderThan = "1Y"
	p.handler.Config.Duplicity.TargetURL = "file:///tmp/backup"
	p.handler.Config.Image = "camptocamp/duplicity:latest"
	p.handler.Config.Docker.Endpoint = "unix:///var/run/docker.sock"
	p.handler.Hostname, _ = os.Hostname()
	p.handler.SetupDocker()

	log.SetLevel(log.DebugLevel)

	err := p.BackupVolume(&types.Volume{
		Name:       dir,
		Driver:     "local",
		Mountpoint: "/mnt",
	})

	if err != nil {
		t.Fatalf("Expected no error, got error: %v", err)
	}
}

func TestBaseGetHandler(t *testing.T) {
	expected := ""

	p := &BaseProvider{
		handler: &conplicity.Conplicity{},
	}
	got := p.GetHandler().Hostname
	if expected != got {
		t.Fatalf("Expected %s, got %s", expected, got)
	}
}

func TestBaseGetVolume(t *testing.T) {
	got := (&BaseProvider{}).GetVolume()
	if got != nil {
		t.Fatalf("Expected to get nil, got %s", got)
	}
}

func TestBaseGetBackupDir(t *testing.T) {
	got := (&BaseProvider{}).GetBackupDir()
	if got != "" {
		t.Fatalf("Expected to get nil, got %s", got)
	}
}
<commit_msg>Fix tests<commit_after>package providers

import (
	"io/ioutil"
	"os"
	"testing"

	log "github.com/Sirupsen/logrus"
	"github.com/camptocamp/conplicity/lib"
	"github.com/docker/engine-api/types"
)

func TestGetProvider(t *testing.T) {
	var dir, expected, got string
	var p Provider

	// Test PostgreSQL detection
	expected = "PostgreSQL"
	dir, _ = ioutil.TempDir("", "test_get_provider_postgresql")
	defer os.RemoveAll(dir)
	ioutil.WriteFile(dir+"/PG_VERSION", []byte{}, 0644)

	p = GetProvider(&conplicity.Conplicity{}, &types.Volume{
		Mountpoint: dir,
	})
	got = p.GetName()
	if got != expected {
		t.Fatalf("Expected provider %s, got %s", expected, got)
	}

	// Test MySQL detection
	expected = "MySQL"
	dir, _ = ioutil.TempDir("", "test_get_provider_mysql")
	defer os.RemoveAll(dir)
	os.Mkdir(dir+"/mysql", 0755)

	p = GetProvider(&conplicity.Conplicity{}, &types.Volume{
		Mountpoint: dir,
	})
	got = p.GetName()
	if got != expected {
		t.Fatalf("Expected provider %s, got %s", expected, got)
	}

	// Test OpenLDAP detection
	expected = "OpenLDAP"
	dir, _ = ioutil.TempDir("", "test_get_provider_openldap")
	defer os.RemoveAll(dir)
	ioutil.WriteFile(dir+"/DB_CONFIG", []byte{}, 0644)

	p = GetProvider(&conplicity.Conplicity{}, &types.Volume{
		Mountpoint: dir,
	})
	got = p.GetName()
	if got != expected {
		t.Fatalf("Expected provider %s, got %s", expected, got)
	}

	// Test Default detection
	expected = "Default"
	dir, _ = ioutil.TempDir("", "test_get_provider_default")
	defer os.RemoveAll(dir)

	p = GetProvider(&conplicity.Conplicity{}, &types.Volume{
		Mountpoint: dir,
	})
	got = p.GetName()
	if got != expected {
		t.Fatalf("Expected provider %s, got %s", expected, got)
	}
}

func TestPrepareBackup(t *testing.T) {
	// Use Default provider
	dir, _ := ioutil.TempDir("", "test_backup_volume")
	defer os.RemoveAll(dir)

	p := &DefaultProvider{
		BaseProvider: &BaseProvider{
			handler: &conplicity.Conplicity{},
			vol: &types.Volume{
				Name:       dir,
				Driver:     "local",
				Mountpoint: "/mnt",
			},
		},
	}

	// Fill Config manually
	p.handler.Config = &conplicity.Config{} // Init Config
	p.handler.Config.Duplicity.Image = "camptocamp/duplicity:latest"
	p.handler.Config.Docker.Endpoint = "unix:///var/run/docker.sock"
	p.handler.Hostname, _ = os.Hostname()
	p.handler.SetupDocker()

	log.SetLevel(log.DebugLevel)

	err := PrepareBackup(p)

	if err != nil {
		t.Fatalf("Expected no error, got error: %v", err)
	}
}

func TestBackupVolume(t *testing.T) {
	t.Skip("Skipping as it hangs forever on Travis CI")

	// Use Base provider
	dir, _ := ioutil.TempDir("", "test_backup_volume")
	defer os.RemoveAll(dir)

	p := &BaseProvider{
		handler: &conplicity.Conplicity{},
	}

	// Fill Config manually
	p.handler.Config = &conplicity.Config{} // Init Config
	p.handler.Config.Duplicity.FullIfOlderThan = "314D"
	p.handler.Config.Duplicity.RemoveOlderThan = "1Y"
	p.handler.Config.Duplicity.TargetURL = "file:///tmp/backup"
	p.handler.Config.Duplicity.Image = "camptocamp/duplicity:latest"
	p.handler.Config.Docker.Endpoint = "unix:///var/run/docker.sock"
	p.handler.Hostname, _ = os.Hostname()
	p.handler.SetupDocker()

	log.SetLevel(log.DebugLevel)

	err := p.BackupVolume(&types.Volume{
		Name:       dir,
		Driver:     "local",
		Mountpoint: "/mnt",
	})

	if err != nil {
		t.Fatalf("Expected no error, got error: %v", err)
	}
}

func TestBaseGetHandler(t *testing.T) {
	expected := ""

	p := &BaseProvider{
		handler: &conplicity.Conplicity{},
	}
	got := p.GetHandler().Hostname
	if expected != got {
		t.Fatalf("Expected %s, got %s", expected, got)
	}
}

func TestBaseGetVolume(t *testing.T) {
	got := (&BaseProvider{}).GetVolume()
	if got != nil {
		t.Fatalf("Expected to get nil, got %s", got)
	}
}

func TestBaseGetBackupDir(t *testing.T) {
	got := (&BaseProvider{}).GetBackupDir()
	if got != "" {
		t.Fatalf("Expected to get nil, got %s", got)
	}
}
<|endoftext|>


<commit_before>package oauth2

import (
	"encoding/json"
	"fmt"
	"github.com/tarent/loginsrv/model"
	"io/ioutil"
	"net/http"
	"strings"
)

var googleAPI = "https://www.googleapis.com/plus/v1"

func init() {
	RegisterProvider(providerGoogle)
}

type GoogleUser struct {
	DisplayName string
	Emails      []struct {
		Value string
	}
	Image struct {
		Url string
	}
}

var providerGoogle = Provider{
	Name:     "google",
	AuthURL:  "https://accounts.google.com/o/oauth2/v2/auth",
	TokenURL: "https://www.googleapis.com/oauth2/v4/token",
	GetUserInfo: func(token TokenInfo) (model.UserInfo, string, error) {
		gu := GoogleUser{}
		url := fmt.Sprintf("%v/people/me?alt=json&access_token=%v", googleAPI, token.AccessToken)
		resp, err := http.Get(url)

		if err != nil {
			return model.UserInfo{}, "", err
		}

		if !strings.Contains(resp.Header.Get("Content-Type"), "application/json") {
			return model.UserInfo{}, "", fmt.Errorf("wrong content-type on google get user info: %v", resp.Header.Get("Content-Type"))
		}

		if resp.StatusCode != 200 {
			return model.UserInfo{}, "", fmt.Errorf("got http status %v on google get user info", resp.StatusCode)
		}

		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return model.UserInfo{}, "", fmt.Errorf("error reading google get user info: %v", err)
		}

		err = json.Unmarshal(b, &gu)
		if err != nil {
			return model.UserInfo{}, "", fmt.Errorf("error parsing google get user info: %v", err)
		}

		return model.UserInfo{
			Sub:     gu.Emails[0].Value,
			Picture: gu.Image.Url,
			Name:    gu.DisplayName,
			Email:   gu.Emails[0].Value,
			Origin:  "google",
		}, string(b), nil
	},
}
<commit_msg>avoid panic in case of invalid repons from google<commit_after>package oauth2

import (
	"encoding/json"
	"fmt"
	"github.com/tarent/loginsrv/model"
	"io/ioutil"
	"net/http"
	"strings"
)

var googleAPI = "https://www.googleapis.com/plus/v1"

func init() {
	RegisterProvider(providerGoogle)
}

type GoogleUser struct {
	DisplayName string
	Emails      []struct {
		Value string
	}
	Image struct {
		Url string
	}
}

var providerGoogle = Provider{
	Name:     "google",
	AuthURL:  "https://accounts.google.com/o/oauth2/v2/auth",
	TokenURL: "https://www.googleapis.com/oauth2/v4/token",
	GetUserInfo: func(token TokenInfo) (model.UserInfo, string, error) {
		gu := GoogleUser{}
		url := fmt.Sprintf("%v/people/me?alt=json&access_token=%v", googleAPI, token.AccessToken)
		resp, err := http.Get(url)

		if err != nil {
			return model.UserInfo{}, "", err
		}

		if !strings.Contains(resp.Header.Get("Content-Type"), "application/json") {
			return model.UserInfo{}, "", fmt.Errorf("wrong content-type on google get user info: %v", resp.Header.Get("Content-Type"))
		}

		if resp.StatusCode != 200 {
			return model.UserInfo{}, "", fmt.Errorf("got http status %v on google get user info", resp.StatusCode)
		}

		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return model.UserInfo{}, "", fmt.Errorf("error reading google get user info: %v", err)
		}

		err = json.Unmarshal(b, &gu)
		if err != nil {
			return model.UserInfo{}, "", fmt.Errorf("error parsing google get user info: %v", err)
		}

		if len(gu.Emails) == 0 {
			return model.UserInfo{}, "", fmt.Errorf("invalid google response: no email address returned.", err)
		}

		return model.UserInfo{
			Sub:     gu.Emails[0].Value,
			Picture: gu.Image.Url,
			Name:    gu.DisplayName,
			Email:   gu.Emails[0].Value,
			Origin:  "google",
		}, string(b), nil
	},
}
<|endoftext|>


<commit_before>package tenho

import (
	"fmt"
	"math/rand"
	"sort"
	"time"
)

func Start() {
	start := time.Now()
	// 処理
	var i float64
	i = 0
	for {
		i++
		end := time.Now()
		diff := float64(end.Sub(start) / 1000 / 1000 / 1000)
		m := int(i / diff)
		out := 0
		if m >= 0 {
			out = m
		}
		fmt.Printf("\r")
		fmt.Printf("\r%v回試行  %v秒経過 %v回/秒", i, diff, out)
		seed := time.Now().UnixNano()
		var hai string
		var ok bool
		hai, ok = tryOnce(seed)
		fmt.Printf("%v", hai)
		if ok {
			break
		}
	}
	fmt.Printf("\n")
}

type Hand []int

func tryOnce(seed int64) (string, bool) {
	hand := ShuffledHand(seed)
	hai := hand.HaiString()
	ok := Solve(hand)
	return hai, ok
}

// http://d.hatena.ne.jp/hake/20150930/p1
func shuffle(hand Hand) {
	for i := len(hand); i > 1; i-- {
		j := rand.Intn(i) // 0 .. i-1 の乱数発生
		hand[i-1], hand[j] = hand[j], hand[i-1]
	}
}

func ShuffledHand(seed int64) Hand {
	rand.Seed(seed)

	// データ要素数指定、および初期データ作成
	size := 136
	hand := make(Hand, size, size)
	for i := 0; i < size; i++ {
		hand[i] = i / 4
	}

	// シャッフル
	shuffle(hand)

	return hand[:14]
}

// 牌文字への変換(スペース区切り)
func (c Hand) HaiString() string {
	// http://qiita.com/ruiu/items/2bb83b29baeae2433a79
	// サイズ0、内部バッファの長さ69の[]byteの値を割り当てる
	b := make([]byte, 0, 70)

	// bに文字列を追加
	for j := 0; j < 14; j++ {
		// コードポイント上、普通の麻雀牌はU+1F000からの34個。
		// U+1F000 is 'MAHJONG TILE EAST WIND' ('東')
		// https://codepoints.net/U+1F000
		b = append(b, string(c[j]+0x1F000)...) // appendするには...が必要
		// 自分のMacではスペース区切りでないとうまく表示されないためスペースを挿入する
		// U+0020 is 'SPACE'
		// https://codepoints.net/U+0020
		b = append(b, string(0x20)...) // appendするには...が必要
	}
	return string(b)
}

// あがり判定する
func Solve(hand Hand) bool {
	if is_chitoitsu(hand) {
		return true
	}
	matrix := hand.GroupSuit()
	//fmt.Printf("%v", matrix)

	return group_scan(matrix)
}

type Suits [][]int

// スート分類
func (hand Hand) GroupSuit() Suits {
	m := Suits{{}, {}, {}, {}}
	for _, i := range hand {
		switch {
		case i < 7:
			m[0] = append(m[0], i)
		case i < 7+(9*1):
			m[1] = append(m[1], i-7)
		case i < 7+(9*2):
			m[2] = append(m[2], i-7-(9*1))
		case i < 7+(9*3):
			m[3] = append(m[3], i-7-(9*2))
		}
	}
	return m
}

func group_scan(m Suits) bool {
	if !valid_mod3(m) {
		return false
	}
	if !valid_33332(m) {
		return false
	}
	return true
}

func valid_mod3(m Suits) bool {
	//スートのサイズを3で割った時
	//あまりが2であるすーとグループが1つであること
	c := 0
	for _, a := range m {
		switch len(a) % 3 {
		case 0:
			// noop
		case 1:
			return false
		case 2:
			c++
		}
	}
	return c == 1
}

func valid_33332(m Suits) bool {
	for i := 0; i < 4; i++ {
		if !valid_suit_group(m[i], i) {
			return false
		}
	}
	return true
}

func valid_suit_group(a Hand, i int) bool {
	// 第二引数は字牌のとき0

	//ソート
	sort.Ints(a)
	if len(a)%3 == 2 {
		//ペアを探す
		pair_numbers := pairable_numbers(a)
		//ペア候補がなかったらぬける
		if len(pair_numbers) == 0 {
			return false
		}
		//ペア候補毎に繰り返し処理
		for _, v := range pair_numbers {
			//ペアとなる２枚を除去
			rest := Hand{}
			c := 2
			for _, w := range a {
				// ペア候補以外は新スライスに入れる
				// ペア候補は３枚目以降は新スライスに入れる
				if w != v || c <= 0 {
					rest = append(rest, w)
				}
				if w == v {
					c--
				}
			}
			if valid_3cards(rest, i) {
				return true
			} else {
				continue
			}
		}
		return false
	} else if len(a)%3 == 0 {
		return valid_3cards(a, i)
	}
	// 到達しないはず
	panic("到達しないはず")
}

func valid_3cards(a Hand, i int) bool {
	// 刻子や順子のみで構成されている場合true
	// a is sorted
	// a.size % 3 is0
	// 第二引数は字牌のとき0
	ok := false
	for {
		a, ok = remove_kotsu(a)
		if ok {
			continue
		}
		if i > 0 {
			a, ok = remove_shuntsu(a)
			if ok {
				continue
			}
		}
		return len(a) == 0
	}
}

func remove_kotsu(a Hand) (Hand, bool) {
	// 刻子を除去できればtrue
	// a is sorted
	retval := a
	if len(a) < 3 {
		return retval, false
	}
	if a[0] == a[1] && a[0] == a[2] {
		retval = a[3:]
		return retval, true
	}
	return retval, false
}

func remove_shuntsu(a Hand) (Hand, bool) {
	// 順子を除去できればtrue
	// a is sorted
	rest := Hand{}
	first := -1
	second := -1
	found := false
	for _, v := range a {
		if found {
			rest = append(rest, v)
			continue
		}
		if first == -1 {
			first = v
		} else if second == -1 && first+1 == v {
			second = v
		} else if second != -1 && first+2 == v {
			//flush
			first = -1
			second = -1
			found = true
		} else {
			rest = append(rest, v)
		}
	}
	return rest, found
}

func pairable_numbers(sorted Hand) Hand {
	counter := []int{}
	a := 999 // 2つ前
	b := 999 // 1つ前
	for _, v := range sorted {
		if b == v && a != v {
			counter = append(counter, v)
		} else {
			b = v
		}
	}
	return counter
}

func is_chitoitsu(hand Hand) bool {
	//カウンタ
	c := map[int]int{}

	for _, v := range hand {
		count, ok := c[v]
		if ok {
			if count == 1 {
				c[v] = 2
			} else {
				// c[v] == 2
				return false
			}
		} else {
			c[v] = 1
		}
		//8個チェック
		if len(c) >= 8 {
			return false
		}
	}
	return true
}
<commit_msg>Refactor<commit_after>package tenho

import (
	"fmt"
	"math/rand"
	"sort"
	"time"
)

func Start() {
	start := time.Now()
	// 処理
	var i float64
	i = 0
	for {
		i++
		end := time.Now()
		diff := float64(end.Sub(start) / 1000 / 1000 / 1000)
		m := int(i / diff)
		out := 0
		if m >= 0 {
			out = m
		}
		fmt.Printf("\r")
		fmt.Printf("\r%v回試行  %v秒経過 %v回/秒", i, diff, out)
		seed := time.Now().UnixNano()
		var hai string
		var ok bool
		hai, ok = tryOnce(seed)
		fmt.Printf("%v", hai)
		if ok {
			break
		}
	}
	fmt.Printf("\n")
}

type Hand []int

func tryOnce(seed int64) (string, bool) {
	hand := ShuffledHand(seed)
	hai := hand.HaiString()
	ok := Solve(hand)
	return hai, ok
}

// http://d.hatena.ne.jp/hake/20150930/p1
func shuffle(hand Hand) {
	for i := len(hand); i > 1; i-- {
		j := rand.Intn(i) // 0 .. i-1 の乱数発生
		hand[i-1], hand[j] = hand[j], hand[i-1]
	}
}

func ShuffledHand(seed int64) Hand {
	rand.Seed(seed)

	// データ要素数指定、および初期データ作成
	size := 136
	hand := make(Hand, size, size)
	for i := 0; i < size; i++ {
		hand[i] = i / 4
	}

	// シャッフル
	shuffle(hand)

	return hand[:14]
}

// 牌文字への変換(スペース区切り)
func (c Hand) HaiString() string {
	// http://qiita.com/ruiu/items/2bb83b29baeae2433a79
	// サイズ0、内部バッファの長さ69の[]byteの値を割り当てる
	b := make([]byte, 0, 70)

	// bに文字列を追加
	for j := 0; j < 14; j++ {
		// コードポイント上、普通の麻雀牌はU+1F000からの34個。
		// U+1F000 is 'MAHJONG TILE EAST WIND' ('東')
		// https://codepoints.net/U+1F000
		b = append(b, string(c[j]+0x1F000)...) // appendするには...が必要
		// 自分のMacではスペース区切りでないとうまく表示されないためスペースを挿入する
		// U+0020 is 'SPACE'
		// https://codepoints.net/U+0020
		b = append(b, string(0x20)...) // appendするには...が必要
	}
	return string(b)
}

// あがり判定する
func Solve(hand Hand) bool {
	if is_chitoitsu(hand) {
		return true
	}
	matrix := hand.GroupSuit()
	//fmt.Printf("%v", matrix)

	return group_scan(matrix)
}

type SuitsGroupedHand [][]int

// スート分類
func (hand Hand) GroupSuit() SuitsGroupedHand {
	m := SuitsGroupedHand{{}, {}, {}, {}}
	for _, i := range hand {
		switch {
		case i < 7:
			m[0] = append(m[0], i)
		case i < 7+(9*1):
			m[1] = append(m[1], i-7)
		case i < 7+(9*2):
			m[2] = append(m[2], i-7-(9*1))
		case i < 7+(9*3):
			m[3] = append(m[3], i-7-(9*2))
		}
	}
	return m
}

func group_scan(m SuitsGroupedHand) bool {
	if !valid_mod3(m) {
		return false
	}
	if !valid_33332(m) {
		return false
	}
	return true
}

func valid_mod3(m SuitsGroupedHand) bool {
	//スートのサイズを3で割った時
	//あまりが2であるすーとグループが1つであること
	c := 0
	for _, a := range m {
		switch len(a) % 3 {
		case 0:
			// noop
		case 1:
			return false
		case 2:
			c++
		}
	}
	return c == 1
}

func valid_33332(m SuitsGroupedHand) bool {
	for i := 0; i < 4; i++ {
		if !valid_suit_group(m[i], i) {
			return false
		}
	}
	return true
}

func valid_suit_group(a Hand, i int) bool {
	// 第二引数は字牌のとき0

	//ソート
	sort.Ints(a)
	if len(a)%3 == 2 {
		//ペアを探す
		pair_numbers := pairable_numbers(a)
		//ペア候補がなかったらぬける
		if len(pair_numbers) == 0 {
			return false
		}
		//ペア候補毎に繰り返し処理
		for _, v := range pair_numbers {
			//ペアとなる２枚を除去
			rest := Hand{}
			c := 2
			for _, w := range a {
				// ペア候補以外は新スライスに入れる
				// ペア候補は３枚目以降は新スライスに入れる
				if w != v || c <= 0 {
					rest = append(rest, w)
				}
				if w == v {
					c--
				}
			}
			if valid_3cards(rest, i) {
				return true
			} else {
				continue
			}
		}
		return false
	} else if len(a)%3 == 0 {
		return valid_3cards(a, i)
	}
	// 到達しないはず
	panic("到達しないはず")
}

func valid_3cards(a Hand, i int) bool {
	// 刻子や順子のみで構成されている場合true
	// a is sorted
	// a.size % 3 is0
	// 第二引数は字牌のとき0
	ok := false
	for {
		a, ok = remove_kotsu(a)
		if ok {
			continue
		}
		if i > 0 {
			a, ok = remove_shuntsu(a)
			if ok {
				continue
			}
		}
		return len(a) == 0
	}
}

func remove_kotsu(a Hand) (Hand, bool) {
	// 刻子を除去できればtrue
	// a is sorted
	retval := a
	if len(a) < 3 {
		return retval, false
	}
	if a[0] == a[1] && a[0] == a[2] {
		retval = a[3:]
		return retval, true
	}
	return retval, false
}

func remove_shuntsu(a Hand) (Hand, bool) {
	// 順子を除去できればtrue
	// a is sorted
	rest := Hand{}
	first := -1
	second := -1
	found := false
	for _, v := range a {
		if found {
			rest = append(rest, v)
			continue
		}
		if first == -1 {
			first = v
		} else if second == -1 && first+1 == v {
			second = v
		} else if second != -1 && first+2 == v {
			//flush
			first = -1
			second = -1
			found = true
		} else {
			rest = append(rest, v)
		}
	}
	return rest, found
}

func pairable_numbers(sorted Hand) Hand {
	counter := []int{}
	a := 999 // 2つ前
	b := 999 // 1つ前
	for _, v := range sorted {
		if b == v && a != v {
			counter = append(counter, v)
		} else {
			b = v
		}
	}
	return counter
}

func is_chitoitsu(hand Hand) bool {
	//カウンタ
	c := map[int]int{}

	for _, v := range hand {
		count, ok := c[v]
		if ok {
			if count == 1 {
				c[v] = 2
			} else {
				// c[v] == 2
				return false
			}
		} else {
			c[v] = 1
		}
		//8個チェック
		if len(c) >= 8 {
			return false
		}
	}
	return true
}
<|endoftext|>