<commit_before>// Copyright (c) 2012-2018 Eli Janssen
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// go-camo daemon (go-camod)
package main

// //go:generate go run ../../tools/genversion.go -pkg $GOPACKAGE -input ../../go.mod -output main_vers_gen.go

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/cactus/go-camo/pkg/camo"
	"github.com/cactus/go-camo/pkg/router"
	"github.com/cactus/go-camo/pkg/stats"

	"github.com/cactus/mlog"
	flags "github.com/jessevdk/go-flags"
)

var (
	// ServerVersion holds the server version string
	ServerVersion = "no-version"
)

func main() {
	// command line flags
	var opts struct {
		Version             []bool        `short:"V" long:"version" description:"Print version and exit; specify twice to show license information"`
		AddHeaders          []string      `short:"H" long:"header" description:"Extra header to return for each response. This option can be used multiple times to add multiple headers"`
		HMACKey             string        `short:"k" long:"key" description:"HMAC key"`
		SSLKey              string        `long:"ssl-key" description:"ssl private key (key.pem) path"`
		SSLCert             string        `long:"ssl-cert" description:"ssl cert (cert.pem) path"`
		AllowList           string        `long:"allow-list" description:"Text file of hostname allow regexes (one per line)"`
		BindAddress         string        `long:"listen" default:"0.0.0.0:8080" description:"Address:Port to bind to for HTTP"`
		BindAddressSSL      string        `long:"ssl-listen" description:"Address:Port to bind to for HTTPS/SSL/TLS"`
		MaxSize             int64         `long:"max-size" default:"5120" description:"Max allowed response size (KB)"`
		ReqTimeout          time.Duration `long:"timeout" default:"4s" description:"Upstream request timeout"`
		MaxRedirects        int           `long:"max-redirects" default:"3" description:"Maximum number of redirects to follow"`
		Stats               bool          `long:"stats" description:"Enable Stats"`
		NoLogTS             bool          `long:"no-log-ts" description:"Do not add a timestamp to logging"`
		DisableKeepAlivesFE bool          `long:"no-fk" description:"Disable frontend http keep-alive support"`
		DisableKeepAlivesBE bool          `long:"no-bk" description:"Disable backend http keep-alive support"`
		AllowContentVideo   bool          `long:"allow-content-video" description:"Additionally allow 'video/*' content"`
		Verbose             bool          `short:"v" long:"verbose" description:"Show verbose (debug) log level output"`
		ServerName          string        `long:"server-name" default:"go-camo" description:"Value to use for the HTTP server field"`
		ExposeServerVersion bool          `long:"expose-server-version" description:"Include the server version in the HTTP server response header"`
		EnableXFwdFor       bool          `long:"enable-xfwd4" description:"Enable x-forwarded-for passthrough/generation"`
	}

	// parse said flags
	_, err := flags.Parse(&opts)
	if err != nil {
		if e, ok := err.(*flags.Error); ok {
			if e.Type == flags.ErrHelp {
				os.Exit(0)
			}
		}
		os.Exit(1)
	}

	// set the server name
	ServerName := opts.ServerName

	// setup the server response field
	ServerResponse := opts.ServerName

	if opts.ExposeServerVersion {
		ServerResponse = fmt.Sprintf("%s %s", opts.ServerName, ServerVersion)
	}

	// setup -V version output
	if len(opts.Version) > 0 {
		fmt.Printf("%s %s (%s,%s-%s)\n", ServerName, ServerVersion, runtime.Version(), runtime.Compiler, runtime.GOARCH)
		if len(opts.Version) > 1 {
			fmt.Printf("\n%s\n", strings.TrimSpace(licenseText))
		}
		os.Exit(0)
	}

	// start out with a very bare logger that only prints
	// the message (no special format or log elements)
	mlog.SetFlags(0)

	config := camo.Config{}
	if hmacKey := os.Getenv("GOCAMO_HMAC"); hmacKey != "" {
		config.HMACKey = []byte(hmacKey)
	}

	// flags override env var
	if opts.HMACKey != "" {
		config.HMACKey = []byte(opts.HMACKey)
	}

	if len(config.HMACKey) == 0 {
		mlog.Fatal("HMAC key required")
	}

	if opts.BindAddress == "" && opts.BindAddressSSL == "" {
		mlog.Fatal("One of listen or ssl-listen required")
	}

	if opts.BindAddressSSL != "" && opts.SSLKey == "" {
		mlog.Fatal("ssl-key is required when specifying ssl-listen")
	}
	if opts.BindAddressSSL != "" && opts.SSLCert == "" {
		mlog.Fatal("ssl-cert is required when specifying ssl-listen")
	}

	// set keepalive options
	config.DisableKeepAlivesBE = opts.DisableKeepAlivesBE
	config.DisableKeepAlivesFE = opts.DisableKeepAlivesFE

	// other options
	config.EnableXFwdFor = opts.EnableXFwdFor

	// additonal content types to allow
	config.AllowContentVideo = opts.AllowContentVideo

	if opts.AllowList != "" {
		b, err := ioutil.ReadFile(opts.AllowList)
		if err != nil {
			mlog.Fatal("Could not read allow-list", err)
		}
		config.AllowList = strings.Split(string(b), "\n")
	}

	AddHeaders := map[string]string{
		"X-Content-Type-Options":  "nosniff",
		"X-XSS-Protection":        "1; mode=block",
		"Content-Security-Policy": "default-src 'none'; img-src data:; style-src 'unsafe-inline'",
	}

	for _, v := range opts.AddHeaders {
		s := strings.SplitN(v, ":", 2)
		if len(s) != 2 {
			mlog.Printf("ignoring bad header: '%s'", v)
			continue
		}

		s0 := strings.TrimSpace(s[0])
		s1 := strings.TrimSpace(s[1])

		if len(s0) == 0 || len(s1) == 0 {
			mlog.Printf("ignoring bad header: '%s'", v)
			continue
		}
		AddHeaders[s[0]] = s[1]
	}

	// now configure a standard logger
	mlog.SetFlags(mlog.Lstd)
	if opts.NoLogTS {
		mlog.SetFlags(mlog.Flags() ^ mlog.Ltimestamp)
	}

	if opts.Verbose {
		mlog.SetFlags(mlog.Flags() | mlog.Ldebug)
		mlog.Debug("debug logging enabled")
	}

	// convert from KB to Bytes
	config.MaxSize = opts.MaxSize * 1024
	config.RequestTimeout = opts.ReqTimeout
	config.MaxRedirects = opts.MaxRedirects
	config.ServerName = ServerName

	proxy, err := camo.New(config)
	if err != nil {
		mlog.Fatal("Error creating camo", err)
	}

	dumbrouter := &router.DumbRouter{
		ServerName:  ServerResponse,
		AddHeaders:  AddHeaders,
		CamoHandler: proxy,
	}

	if opts.Stats {
		ps := &stats.ProxyStats{}
		proxy.SetMetricsCollector(ps)
		mlog.Printf("Enabling stats at /status")
		dumbrouter.StatsHandler = stats.Handler(ps)
	}

	http.Handle("/", dumbrouter)

	if opts.BindAddress != "" {
		mlog.Printf("Starting server on: %s", opts.BindAddress)
		go func() {
			srv := &http.Server{
				Addr:        opts.BindAddress,
				ReadTimeout: 30 * time.Second}
			mlog.Fatal(srv.ListenAndServe())
		}()
	}
	if opts.BindAddressSSL != "" {
		mlog.Printf("Starting TLS server on: %s", opts.BindAddressSSL)
		go func() {
			srv := &http.Server{
				Addr:        opts.BindAddressSSL,
				ReadTimeout: 30 * time.Second}
			mlog.Fatal(srv.ListenAndServeTLS(opts.SSLCert, opts.SSLKey))
		}()
	}

	// just block. listen and serve will exit the program if they fail/return
	// so we just need to block to prevent main from exiting.
	select {}
}
<commit_msg>fix misspelling<commit_after>// Copyright (c) 2012-2018 Eli Janssen
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// go-camo daemon (go-camod)
package main

// //go:generate go run ../../tools/genversion.go -pkg $GOPACKAGE -input ../../go.mod -output main_vers_gen.go

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/cactus/go-camo/pkg/camo"
	"github.com/cactus/go-camo/pkg/router"
	"github.com/cactus/go-camo/pkg/stats"

	"github.com/cactus/mlog"
	flags "github.com/jessevdk/go-flags"
)

var (
	// ServerVersion holds the server version string
	ServerVersion = "no-version"
)

func main() {
	// command line flags
	var opts struct {
		Version             []bool        `short:"V" long:"version" description:"Print version and exit; specify twice to show license information"`
		AddHeaders          []string      `short:"H" long:"header" description:"Extra header to return for each response. This option can be used multiple times to add multiple headers"`
		HMACKey             string        `short:"k" long:"key" description:"HMAC key"`
		SSLKey              string        `long:"ssl-key" description:"ssl private key (key.pem) path"`
		SSLCert             string        `long:"ssl-cert" description:"ssl cert (cert.pem) path"`
		AllowList           string        `long:"allow-list" description:"Text file of hostname allow regexes (one per line)"`
		BindAddress         string        `long:"listen" default:"0.0.0.0:8080" description:"Address:Port to bind to for HTTP"`
		BindAddressSSL      string        `long:"ssl-listen" description:"Address:Port to bind to for HTTPS/SSL/TLS"`
		MaxSize             int64         `long:"max-size" default:"5120" description:"Max allowed response size (KB)"`
		ReqTimeout          time.Duration `long:"timeout" default:"4s" description:"Upstream request timeout"`
		MaxRedirects        int           `long:"max-redirects" default:"3" description:"Maximum number of redirects to follow"`
		Stats               bool          `long:"stats" description:"Enable Stats"`
		NoLogTS             bool          `long:"no-log-ts" description:"Do not add a timestamp to logging"`
		DisableKeepAlivesFE bool          `long:"no-fk" description:"Disable frontend http keep-alive support"`
		DisableKeepAlivesBE bool          `long:"no-bk" description:"Disable backend http keep-alive support"`
		AllowContentVideo   bool          `long:"allow-content-video" description:"Additionally allow 'video/*' content"`
		Verbose             bool          `short:"v" long:"verbose" description:"Show verbose (debug) log level output"`
		ServerName          string        `long:"server-name" default:"go-camo" description:"Value to use for the HTTP server field"`
		ExposeServerVersion bool          `long:"expose-server-version" description:"Include the server version in the HTTP server response header"`
		EnableXFwdFor       bool          `long:"enable-xfwd4" description:"Enable x-forwarded-for passthrough/generation"`
	}

	// parse said flags
	_, err := flags.Parse(&opts)
	if err != nil {
		if e, ok := err.(*flags.Error); ok {
			if e.Type == flags.ErrHelp {
				os.Exit(0)
			}
		}
		os.Exit(1)
	}

	// set the server name
	ServerName := opts.ServerName

	// setup the server response field
	ServerResponse := opts.ServerName

	if opts.ExposeServerVersion {
		ServerResponse = fmt.Sprintf("%s %s", opts.ServerName, ServerVersion)
	}

	// setup -V version output
	if len(opts.Version) > 0 {
		fmt.Printf("%s %s (%s,%s-%s)\n", ServerName, ServerVersion, runtime.Version(), runtime.Compiler, runtime.GOARCH)
		if len(opts.Version) > 1 {
			fmt.Printf("\n%s\n", strings.TrimSpace(licenseText))
		}
		os.Exit(0)
	}

	// start out with a very bare logger that only prints
	// the message (no special format or log elements)
	mlog.SetFlags(0)

	config := camo.Config{}
	if hmacKey := os.Getenv("GOCAMO_HMAC"); hmacKey != "" {
		config.HMACKey = []byte(hmacKey)
	}

	// flags override env var
	if opts.HMACKey != "" {
		config.HMACKey = []byte(opts.HMACKey)
	}

	if len(config.HMACKey) == 0 {
		mlog.Fatal("HMAC key required")
	}

	if opts.BindAddress == "" && opts.BindAddressSSL == "" {
		mlog.Fatal("One of listen or ssl-listen required")
	}

	if opts.BindAddressSSL != "" && opts.SSLKey == "" {
		mlog.Fatal("ssl-key is required when specifying ssl-listen")
	}
	if opts.BindAddressSSL != "" && opts.SSLCert == "" {
		mlog.Fatal("ssl-cert is required when specifying ssl-listen")
	}

	// set keepalive options
	config.DisableKeepAlivesBE = opts.DisableKeepAlivesBE
	config.DisableKeepAlivesFE = opts.DisableKeepAlivesFE

	// other options
	config.EnableXFwdFor = opts.EnableXFwdFor

	// additional content types to allow
	config.AllowContentVideo = opts.AllowContentVideo

	if opts.AllowList != "" {
		b, err := ioutil.ReadFile(opts.AllowList)
		if err != nil {
			mlog.Fatal("Could not read allow-list", err)
		}
		config.AllowList = strings.Split(string(b), "\n")
	}

	AddHeaders := map[string]string{
		"X-Content-Type-Options":  "nosniff",
		"X-XSS-Protection":        "1; mode=block",
		"Content-Security-Policy": "default-src 'none'; img-src data:; style-src 'unsafe-inline'",
	}

	for _, v := range opts.AddHeaders {
		s := strings.SplitN(v, ":", 2)
		if len(s) != 2 {
			mlog.Printf("ignoring bad header: '%s'", v)
			continue
		}

		s0 := strings.TrimSpace(s[0])
		s1 := strings.TrimSpace(s[1])

		if len(s0) == 0 || len(s1) == 0 {
			mlog.Printf("ignoring bad header: '%s'", v)
			continue
		}
		AddHeaders[s[0]] = s[1]
	}

	// now configure a standard logger
	mlog.SetFlags(mlog.Lstd)
	if opts.NoLogTS {
		mlog.SetFlags(mlog.Flags() ^ mlog.Ltimestamp)
	}

	if opts.Verbose {
		mlog.SetFlags(mlog.Flags() | mlog.Ldebug)
		mlog.Debug("debug logging enabled")
	}

	// convert from KB to Bytes
	config.MaxSize = opts.MaxSize * 1024
	config.RequestTimeout = opts.ReqTimeout
	config.MaxRedirects = opts.MaxRedirects
	config.ServerName = ServerName

	proxy, err := camo.New(config)
	if err != nil {
		mlog.Fatal("Error creating camo", err)
	}

	dumbrouter := &router.DumbRouter{
		ServerName:  ServerResponse,
		AddHeaders:  AddHeaders,
		CamoHandler: proxy,
	}

	if opts.Stats {
		ps := &stats.ProxyStats{}
		proxy.SetMetricsCollector(ps)
		mlog.Printf("Enabling stats at /status")
		dumbrouter.StatsHandler = stats.Handler(ps)
	}

	http.Handle("/", dumbrouter)

	if opts.BindAddress != "" {
		mlog.Printf("Starting server on: %s", opts.BindAddress)
		go func() {
			srv := &http.Server{
				Addr:        opts.BindAddress,
				ReadTimeout: 30 * time.Second}
			mlog.Fatal(srv.ListenAndServe())
		}()
	}
	if opts.BindAddressSSL != "" {
		mlog.Printf("Starting TLS server on: %s", opts.BindAddressSSL)
		go func() {
			srv := &http.Server{
				Addr:        opts.BindAddressSSL,
				ReadTimeout: 30 * time.Second}
			mlog.Fatal(srv.ListenAndServeTLS(opts.SSLCert, opts.SSLKey))
		}()
	}

	// just block. listen and serve will exit the program if they fail/return
	// so we just need to block to prevent main from exiting.
	select {}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"mime"
	"net/http"
	"strings"
	"time"

	"github.com/chop-dbhi/origins"
	"github.com/chop-dbhi/origins/chrono"
	"github.com/chop-dbhi/origins/storage"
	"github.com/chop-dbhi/origins/view"
	"github.com/julienschmidt/httprouter"
	"github.com/rs/cors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var httpCmd = &cobra.Command{
	Use: "http",

	Short: "Starts an HTTP peer.",

	Long: "Runs a process exposing an HTTP interface.",

	Run: func(cmd *cobra.Command, args []string) {
		var (
			host = viper.GetString("http_host")
			port = viper.GetInt("http_port")
		)

		engine := initStorage()

		serveHTTP(engine, host, port)
	},
}

func init() {
	flags := httpCmd.Flags()

	flags.String("host", "", "The host the HTTP service will listen on.")
	flags.Int("port", 49110, "The port the HTTP will bind to.")

	viper.BindPFlag("http_host", flags.Lookup("host"))
	viper.BindPFlag("http_port", flags.Lookup("port"))
}

const (
	defaultFormat = "text"

	StatusUnprocessableEntity = 422
)

var (
	mimetypes = map[string]string{
		"application/json": "json",
		"text/csv":         "csv",
		"text/plain":       "text",
	}

	formatMimetypes = map[string]string{
		"csv":  "text/csv",
		"json": "application/json",
		"text": "text/plain",
	}

	queryFormats = map[string]string{
		"json": "json",
		"csv":  "csv",
		"text": "text",
	}
)

// detectFormat applies content negotiation logic to determine the
// appropriate response representation.
func detectFormat(w http.ResponseWriter, r *http.Request) string {
	var (
		ok     bool
		format string
	)

	format = queryFormats[strings.ToLower(r.URL.Query().Get("format"))]

	// Query parameter
	if format == "" {
		// Accept header
		acceptType := r.Header.Get("Accept")
		acceptType, _, _ = mime.ParseMediaType(acceptType)

		// Fallback to default
		if format, ok = mimetypes[acceptType]; !ok {
			format = defaultFormat
		}
	}

	w.Header().Set("content-type", formatMimetypes[format])

	return format
}

func serveHTTP(engine storage.Engine, host string, port int) {
	addr := fmt.Sprintf("%s:%d", host, port)

	// Bind the routes.
	router := httprouter.New()

	router.GET("/", httpRoot)
	router.GET("/log/:domain", httpLogView)

	// Add CORS middleware
	c := cors.New(cors.Options{
		ExposedHeaders: []string{
			"Link",
			"Link-Template",
		},
	})

	handler := c.Handler(router)

	// Serve it up.
	logrus.Infof("* Listening on %s...", addr)

	logrus.Fatal(http.ListenAndServe(addr, handler))
}

// jsonResponse attempts to encode the passed value as JSON.
func jsonResponse(w http.ResponseWriter, v interface{}) {
	e := json.NewEncoder(w)

	if err := e.Encode(v); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(fmt.Sprint(err)))
	}
}

func httpRoot(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	jsonResponse(w, map[string]interface{}{
		"Title":   "Origins HTTP Service",
		"Version": origins.Version,
	})
}

func httpLogView(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	domain := p.ByName("domain")

	var (
		err         error
		since, asof time.Time
	)

	q := r.URL.Query()

	// Parse query parameters
	if q.Get("since") != "" {
		since, err = chrono.Parse(q.Get("since"))

		if err != nil {
			w.WriteHeader(StatusUnprocessableEntity)
			w.Write([]byte(fmt.Sprint(err)))
			return
		}
	}

	if q.Get("asof") != "" {
		asof, err = chrono.Parse(q.Get("asof"))

		if err != nil {
			w.WriteHeader(StatusUnprocessableEntity)
			w.Write([]byte(fmt.Sprint(err)))
			return
		}
	}

	// Open the log.
	engine := initStorage()

	log, err := view.OpenLog(engine, domain, "log.commit")

	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(fmt.Sprint(err)))
		return
	}

	// Construct a view of the log for the specified window of time.
	v := log.View(since, asof)

	var fw origins.Writer

	format := detectFormat(w, r)

	switch format {
	case "text", "csv":
		fw = origins.CSVWriter(w)
	default:
		w.WriteHeader(http.StatusNotAcceptable)
		return
	}

	if _, err := origins.ReadWriter(v, fw); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(fmt.Sprint(err)))
		return
	}
}
<commit_msg>Fix HTTP log view resource to only use log name<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"mime"
	"net/http"
	"strings"
	"time"

	"github.com/chop-dbhi/origins"
	"github.com/chop-dbhi/origins/chrono"
	"github.com/chop-dbhi/origins/storage"
	"github.com/chop-dbhi/origins/view"
	"github.com/julienschmidt/httprouter"
	"github.com/rs/cors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var httpCmd = &cobra.Command{
	Use: "http",

	Short: "Starts an HTTP peer.",

	Long: "Runs a process exposing an HTTP interface.",

	Run: func(cmd *cobra.Command, args []string) {
		var (
			host = viper.GetString("http_host")
			port = viper.GetInt("http_port")
		)

		engine := initStorage()

		serveHTTP(engine, host, port)
	},
}

func init() {
	flags := httpCmd.Flags()

	flags.String("host", "", "The host the HTTP service will listen on.")
	flags.Int("port", 49110, "The port the HTTP will bind to.")

	viper.BindPFlag("http_host", flags.Lookup("host"))
	viper.BindPFlag("http_port", flags.Lookup("port"))
}

const (
	defaultFormat = "text"

	StatusUnprocessableEntity = 422
)

var (
	mimetypes = map[string]string{
		"application/json": "json",
		"text/csv":         "csv",
		"text/plain":       "text",
	}

	formatMimetypes = map[string]string{
		"csv":  "text/csv",
		"json": "application/json",
		"text": "text/plain",
	}

	queryFormats = map[string]string{
		"json": "json",
		"csv":  "csv",
		"text": "text",
	}
)

// detectFormat applies content negotiation logic to determine the
// appropriate response representation.
func detectFormat(w http.ResponseWriter, r *http.Request) string {
	var (
		ok     bool
		format string
	)

	format = queryFormats[strings.ToLower(r.URL.Query().Get("format"))]

	// Query parameter
	if format == "" {
		// Accept header
		acceptType := r.Header.Get("Accept")
		acceptType, _, _ = mime.ParseMediaType(acceptType)

		// Fallback to default
		if format, ok = mimetypes[acceptType]; !ok {
			format = defaultFormat
		}
	}

	w.Header().Set("content-type", formatMimetypes[format])

	return format
}

func serveHTTP(engine storage.Engine, host string, port int) {
	addr := fmt.Sprintf("%s:%d", host, port)

	// Bind the routes.
	router := httprouter.New()

	router.GET("/", httpRoot)
	router.GET("/log/:domain", httpLogView)

	// Add CORS middleware
	c := cors.New(cors.Options{
		ExposedHeaders: []string{
			"Link",
			"Link-Template",
		},
	})

	handler := c.Handler(router)

	// Serve it up.
	logrus.Infof("* Listening on %s...", addr)

	logrus.Fatal(http.ListenAndServe(addr, handler))
}

// jsonResponse attempts to encode the passed value as JSON.
func jsonResponse(w http.ResponseWriter, v interface{}) {
	e := json.NewEncoder(w)

	if err := e.Encode(v); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(fmt.Sprint(err)))
	}
}

func httpRoot(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	jsonResponse(w, map[string]interface{}{
		"Title":   "Origins HTTP Service",
		"Version": origins.Version,
	})
}

func httpLogView(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	domain := p.ByName("domain")

	var (
		err         error
		since, asof time.Time
	)

	q := r.URL.Query()

	// Parse query parameters
	if q.Get("since") != "" {
		since, err = chrono.Parse(q.Get("since"))

		if err != nil {
			w.WriteHeader(StatusUnprocessableEntity)
			w.Write([]byte(fmt.Sprint(err)))
			return
		}
	}

	if q.Get("asof") != "" {
		asof, err = chrono.Parse(q.Get("asof"))

		if err != nil {
			w.WriteHeader(StatusUnprocessableEntity)
			w.Write([]byte(fmt.Sprint(err)))
			return
		}
	}

	// Open the log.
	engine := initStorage()

	log, err := view.OpenLog(engine, domain, "commit")

	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(fmt.Sprint(err)))
		return
	}

	// Construct a view of the log for the specified window of time.
	v := log.View(since, asof)

	var fw origins.Writer

	format := detectFormat(w, r)

	switch format {
	case "text", "csv":
		fw = origins.CSVWriter(w)
	default:
		w.WriteHeader(http.StatusNotAcceptable)
		return
	}

	if _, err := origins.ReadWriter(v, fw); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(fmt.Sprint(err)))
		return
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 The Go AUTHORS. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

// Command tipgodoc is the beginning of the new tip.golang.org server,
// serving the latest HEAD straight from the Git oven.
package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"sync"
	"time"
)

const metaURL = "https://go.googlesource.com/?b=master&format=JSON"

func init() {
	p := new(Proxy)
	go p.run()
	http.Handle("/", p)
}

type Proxy struct {
	mu    sync.Mutex // protects the followin'
	proxy *httputil.ReverseProxy
	cur   string // signature of gorepo+toolsrepo
	side  string
}

// run runs in its own goroutine.
func (p *Proxy) run() {
	p.side = "a"
	for {
		p.poll()
		time.Sleep(30 * time.Second)
	}
}

// poll runs from the run loop goroutine.
func (p *Proxy) poll() {
	heads := gerritMetaMap()
	if heads == nil {
		return
	}

	sig := heads["go"] + "-" + heads["tools"]

	p.mu.Lock()
	changes := sig != p.cur
	curSide := p.side
	p.cur = sig
	p.mu.Unlock()

	if !changes {
		return
	}

	newSide := "b"
	if curSide == "b" {
		newSide = "a"
	}

	hostport, err := initSide(newSide, heads["go"], heads["tools"])
	if err != nil {
		log.Println(err)
		return
	}

	p.mu.Lock()
	defer p.mu.Unlock()
	u, err := url.Parse(fmt.Sprintf("http://%v/", hostport))
	if err != nil {
		log.Println(err)
		return
	}
	p.side = newSide
	p.proxy = httputil.NewSingleHostReverseProxy(u)
}

func initSide(side, goHash, toolsHash string) (hostport string, err error) {
	dir := filepath.Join(os.TempDir(), "tipgodoc", side)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return "", err
	}

	goDir := filepath.Join(dir, "go")
	toolsDir := filepath.Join(dir, "gopath/src/golang.org/x/tools")
	if err := checkout("https://go.googlesource.com/go", goHash, goDir); err != nil {
		return "", err
	}
	if err := checkout("https://go.googlesource.com/tools", toolsHash, toolsDir); err != nil {
		return "", err

	}

	env := []string{"GOROOT=" + goDir, "GOPATH=" + filepath.Join(dir, "gopath")}

	make := exec.Command(filepath.Join(goDir, "src/make.bash"))
	make.Stdout = os.Stdout
	make.Stderr = os.Stderr
	make.Dir = filepath.Join(goDir, "src")
	if err := make.Run(); err != nil {
		return "", err
	}
	goBin := filepath.Join(goDir, "bin/go")
	install := exec.Command(goBin, "install", "golang.org/x/tools/cmd/godoc")
	install.Stdout = os.Stdout
	install.Stderr = os.Stderr
	install.Env = env
	if err := install.Run(); err != nil {
		return "", err
	}

	godocBin := filepath.Join(goDir, "bin/godoc")
	hostport = "localhost:8081"
	if side == "b" {
		hostport = "localhost:8082"
	}
	godoc := exec.Command(godocBin, "-http="+hostport)
	godoc.Env = env
	godoc.Stdout = os.Stdout
	godoc.Stderr = os.Stderr
	if err := godoc.Start(); err != nil {
		return "", err
	}
	go func() {
		// TODO(bradfitz): tell the proxy that this side is dead
		if err := godoc.Wait(); err != nil {
			log.Printf("side %v exited: %v", side, err)
		}
	}()

	for i := 0; i < 15; i++ {
		time.Sleep(time.Second)
		var res *http.Response
		res, err = http.Get(fmt.Sprintf("http://%v/", hostport))
		if err != nil {
			continue
		}
		res.Body.Close()
		if res.StatusCode == http.StatusOK {
			return hostport, nil
		}
	}
	return "", fmt.Errorf("timed out waiting for side %v at %v (%v)", side, hostport, err)
}

func checkout(repo, hash, path string) error {
	// Clone git repo if it doesn't exist.
	if _, err := os.Stat(filepath.Join(path, ".git")); os.IsNotExist(err) {
		if err := os.MkdirAll(filepath.Base(path), 0755); err != nil {
			return err
		}
		cmd := exec.Command("git", "clone", repo, path)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			// TODO(bradfitz): capture the standard error output
			return err
		}
	} else if err != nil {
		return err
	}

	cmd := exec.Command("git", "fetch")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = path
	if err := cmd.Run(); err != nil {
		return err
	}
	cmd = exec.Command("git", "reset", "--hard", hash)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = path
	if err := cmd.Run(); err != nil {
		return err
	}
	cmd = exec.Command("git", "clean", "-d", "-f", "-x")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = path
	return cmd.Run()
}

func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/_tipstatus" {
		p.serveStatus(w, r)
		return
	}
	p.mu.Lock()
	proxy := p.proxy
	p.mu.Unlock()
	if proxy == nil {
		http.Error(w, "tip.golang.org is currently starting up, compiling tip", http.StatusInternalServerError)
		return
	}
	proxy.ServeHTTP(w, r)
}

func (p *Proxy) serveStatus(w http.ResponseWriter, r *http.Request) {
	p.mu.Lock()
	defer p.mu.Unlock()
	fmt.Fprintf(w, "side=%v\ncurrent=%v\n", p.side, p.cur)
}

// gerritMetaMap returns the map from repo name (e.g. "go") to its
// latest master hash.
// The returned map is nil on any transient error.
func gerritMetaMap() map[string]string {
	res, err := http.Get(metaURL)
	if err != nil {
		return nil
	}
	defer res.Body.Close()
	defer io.Copy(ioutil.Discard, res.Body) // ensure EOF for keep-alive
	if res.StatusCode != 200 {
		return nil
	}
	var meta map[string]struct {
		Branches map[string]string
	}
	br := bufio.NewReader(res.Body)
	// For security reasons or something, this URL starts with ")]}'\n" before
	// the JSON object. So ignore that.
	// Shawn Pearce says it's guaranteed to always be just one line, ending in '\n'.
	for {
		b, err := br.ReadByte()
		if err != nil {
			return nil
		}
		if b == '\n' {
			break
		}
	}
	if err := json.NewDecoder(br).Decode(&meta); err != nil {
		log.Printf("JSON decoding error from %v: %s", metaURL, err)
		return nil
	}
	m := map[string]string{}
	for repo, v := range meta {
		if master, ok := v.Branches["master"]; ok {
			m[repo] = master
		}
	}
	return m
}
<commit_msg>cmd/tipgodoc: add appenginevm build tag<commit_after>// Copyright 2014 The Go AUTHORS. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

// +build appenginevm

// Command tipgodoc is the beginning of the new tip.golang.org server,
// serving the latest HEAD straight from the Git oven.
package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"sync"
	"time"
)

const metaURL = "https://go.googlesource.com/?b=master&format=JSON"

func init() {
	p := new(Proxy)
	go p.run()
	http.Handle("/", p)
}

type Proxy struct {
	mu    sync.Mutex // protects the followin'
	proxy *httputil.ReverseProxy
	cur   string // signature of gorepo+toolsrepo
	side  string
}

// run runs in its own goroutine.
func (p *Proxy) run() {
	p.side = "a"
	for {
		p.poll()
		time.Sleep(30 * time.Second)
	}
}

// poll runs from the run loop goroutine.
func (p *Proxy) poll() {
	heads := gerritMetaMap()
	if heads == nil {
		return
	}

	sig := heads["go"] + "-" + heads["tools"]

	p.mu.Lock()
	changes := sig != p.cur
	curSide := p.side
	p.cur = sig
	p.mu.Unlock()

	if !changes {
		return
	}

	newSide := "b"
	if curSide == "b" {
		newSide = "a"
	}

	hostport, err := initSide(newSide, heads["go"], heads["tools"])
	if err != nil {
		log.Println(err)
		return
	}

	p.mu.Lock()
	defer p.mu.Unlock()
	u, err := url.Parse(fmt.Sprintf("http://%v/", hostport))
	if err != nil {
		log.Println(err)
		return
	}
	p.side = newSide
	p.proxy = httputil.NewSingleHostReverseProxy(u)
}

func initSide(side, goHash, toolsHash string) (hostport string, err error) {
	dir := filepath.Join(os.TempDir(), "tipgodoc", side)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return "", err
	}

	goDir := filepath.Join(dir, "go")
	toolsDir := filepath.Join(dir, "gopath/src/golang.org/x/tools")
	if err := checkout("https://go.googlesource.com/go", goHash, goDir); err != nil {
		return "", err
	}
	if err := checkout("https://go.googlesource.com/tools", toolsHash, toolsDir); err != nil {
		return "", err

	}

	env := []string{"GOROOT=" + goDir, "GOPATH=" + filepath.Join(dir, "gopath")}

	make := exec.Command(filepath.Join(goDir, "src/make.bash"))
	make.Stdout = os.Stdout
	make.Stderr = os.Stderr
	make.Dir = filepath.Join(goDir, "src")
	if err := make.Run(); err != nil {
		return "", err
	}
	goBin := filepath.Join(goDir, "bin/go")
	install := exec.Command(goBin, "install", "golang.org/x/tools/cmd/godoc")
	install.Stdout = os.Stdout
	install.Stderr = os.Stderr
	install.Env = env
	if err := install.Run(); err != nil {
		return "", err
	}

	godocBin := filepath.Join(goDir, "bin/godoc")
	hostport = "localhost:8081"
	if side == "b" {
		hostport = "localhost:8082"
	}
	godoc := exec.Command(godocBin, "-http="+hostport)
	godoc.Env = env
	godoc.Stdout = os.Stdout
	godoc.Stderr = os.Stderr
	if err := godoc.Start(); err != nil {
		return "", err
	}
	go func() {
		// TODO(bradfitz): tell the proxy that this side is dead
		if err := godoc.Wait(); err != nil {
			log.Printf("side %v exited: %v", side, err)
		}
	}()

	for i := 0; i < 15; i++ {
		time.Sleep(time.Second)
		var res *http.Response
		res, err = http.Get(fmt.Sprintf("http://%v/", hostport))
		if err != nil {
			continue
		}
		res.Body.Close()
		if res.StatusCode == http.StatusOK {
			return hostport, nil
		}
	}
	return "", fmt.Errorf("timed out waiting for side %v at %v (%v)", side, hostport, err)
}

func checkout(repo, hash, path string) error {
	// Clone git repo if it doesn't exist.
	if _, err := os.Stat(filepath.Join(path, ".git")); os.IsNotExist(err) {
		if err := os.MkdirAll(filepath.Base(path), 0755); err != nil {
			return err
		}
		cmd := exec.Command("git", "clone", repo, path)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			// TODO(bradfitz): capture the standard error output
			return err
		}
	} else if err != nil {
		return err
	}

	cmd := exec.Command("git", "fetch")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = path
	if err := cmd.Run(); err != nil {
		return err
	}
	cmd = exec.Command("git", "reset", "--hard", hash)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = path
	if err := cmd.Run(); err != nil {
		return err
	}
	cmd = exec.Command("git", "clean", "-d", "-f", "-x")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = path
	return cmd.Run()
}

func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/_tipstatus" {
		p.serveStatus(w, r)
		return
	}
	p.mu.Lock()
	proxy := p.proxy
	p.mu.Unlock()
	if proxy == nil {
		http.Error(w, "tip.golang.org is currently starting up, compiling tip", http.StatusInternalServerError)
		return
	}
	proxy.ServeHTTP(w, r)
}

func (p *Proxy) serveStatus(w http.ResponseWriter, r *http.Request) {
	p.mu.Lock()
	defer p.mu.Unlock()
	fmt.Fprintf(w, "side=%v\ncurrent=%v\n", p.side, p.cur)
}

// gerritMetaMap returns the map from repo name (e.g. "go") to its
// latest master hash.
// The returned map is nil on any transient error.
func gerritMetaMap() map[string]string {
	res, err := http.Get(metaURL)
	if err != nil {
		return nil
	}
	defer res.Body.Close()
	defer io.Copy(ioutil.Discard, res.Body) // ensure EOF for keep-alive
	if res.StatusCode != 200 {
		return nil
	}
	var meta map[string]struct {
		Branches map[string]string
	}
	br := bufio.NewReader(res.Body)
	// For security reasons or something, this URL starts with ")]}'\n" before
	// the JSON object. So ignore that.
	// Shawn Pearce says it's guaranteed to always be just one line, ending in '\n'.
	for {
		b, err := br.ReadByte()
		if err != nil {
			return nil
		}
		if b == '\n' {
			break
		}
	}
	if err := json.NewDecoder(br).Decode(&meta); err != nil {
		log.Printf("JSON decoding error from %v: %s", metaURL, err)
		return nil
	}
	m := map[string]string{}
	for repo, v := range meta {
		if master, ok := v.Branches["master"]; ok {
			m[repo] = master
		}
	}
	return m
}
<|endoftext|>


<commit_before>package cmd

import (
	"bytes"
	"fmt"
	"html/template"
	"net/http"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/exercism/cli/cli"
	"github.com/exercism/cli/config"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

// fullAPIKey flag for troubleshoot command.
var fullAPIKey bool

// troubleshootCmd does a diagnostic self-check.
var troubleshootCmd = &cobra.Command{
	Use:     "troubleshoot",
	Aliases: []string{"t"},
	Short:   "Troubleshoot does a diagnostic self-check.",
	Long: `Provides output to help with troubleshooting.

If you're running into trouble, copy and paste the output from the troubleshoot
command into a GitHub issue so we can help figure out what's going on.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		cli.HTTPClient = &http.Client{Timeout: 20 * time.Second}
		c := cli.New(Version)

		cfg := config.NewConfiguration()

		v := viper.New()
		v.AddConfigPath(cfg.Dir)
		v.SetConfigName("user")
		v.SetConfigType("json")
		// Ignore error. If the file doesn't exist, that is fine.
		_ = v.ReadInConfig()

		status := newStatus(c, v)
		status.Censor = !fullAPIKey
		s, err := status.check()
		if err != nil {
			return err
		}

		fmt.Printf("%s", s)
		return nil
	},
}

// Status represents the results of a CLI self test.
type Status struct {
	Censor          bool
	Version         versionStatus
	System          systemStatus
	Configuration   configurationStatus
	APIReachability apiReachabilityStatus
	cli             *cli.CLI
	cfg             *viper.Viper
}

type versionStatus struct {
	Current  string
	Latest   string
	Status   string
	Error    error
	UpToDate bool
}

type systemStatus struct {
	OS           string
	Architecture string
	Build        string
}

type configurationStatus struct {
	Home      string
	Workspace string
	File      string
	Token     string
	TokenURL  string
}

type apiReachabilityStatus struct {
	Services []*apiPing
}

type apiPing struct {
	Service string
	URL     string
	Status  string
	Latency time.Duration
}

// newStatus prepares a value to perform a diagnostic self-check.
func newStatus(c *cli.CLI, v *viper.Viper) Status {
	status := Status{
		cli: c,
		cfg: v,
	}
	return status
}

// check runs the CLI's diagnostic self-check.
func (status *Status) check() (string, error) {
	status.Version = newVersionStatus(status.cli)
	status.System = newSystemStatus()
	status.Configuration = newConfigurationStatus(status)
	status.APIReachability = newAPIReachabilityStatus(status.cfg.GetString("apibaseurl"))

	return status.compile()
}
func (status *Status) compile() (string, error) {
	t, err := template.New("self-test").Parse(tmplSelfTest)
	if err != nil {
		return "", err
	}

	var bb bytes.Buffer
	t.Execute(&bb, status)
	return bb.String(), nil
}

func newAPIReachabilityStatus(baseURL string) apiReachabilityStatus {
	ar := apiReachabilityStatus{
		Services: []*apiPing{
			{Service: "GitHub", URL: "https://api.github.com"},
			{Service: "Exercism", URL: fmt.Sprintf("%s/ping", baseURL)},
		},
	}
	var wg sync.WaitGroup
	wg.Add(len(ar.Services))
	for _, service := range ar.Services {
		go service.Call(&wg)
	}
	wg.Wait()
	return ar
}

func newVersionStatus(c *cli.CLI) versionStatus {
	vs := versionStatus{
		Current: c.Version,
	}
	ok, err := c.IsUpToDate()
	if err == nil {
		vs.Latest = c.LatestRelease.Version()
	} else {
		vs.Error = fmt.Errorf("Error: %s", err)
	}
	vs.UpToDate = ok
	return vs
}

func newSystemStatus() systemStatus {
	ss := systemStatus{
		OS:           runtime.GOOS,
		Architecture: runtime.GOARCH,
	}
	if cli.BuildOS != "" && cli.BuildARCH != "" {
		ss.Build = fmt.Sprintf("%s/%s", cli.BuildOS, cli.BuildARCH)
	}
	if cli.BuildARM != "" {
		ss.Build = fmt.Sprintf("%s ARMv%s", ss.Build, cli.BuildARM)
	}
	return ss
}

func newConfigurationStatus(status *Status) configurationStatus {
	token := status.cfg.GetString("token")
	cs := configurationStatus{
		Home:      status.cfg.GetString("home"),
		Workspace: status.cfg.GetString("workspace"),
		File:      status.cfg.ConfigFileUsed(),
		Token:     token,
		TokenURL:  config.InferSiteURL(status.cfg.GetString("apibaseurl")) + "/my/settings",
	}
	if status.Censor && token != "" {
		cs.Token = redact(token)
	}
	return cs
}

func (ping *apiPing) Call(wg *sync.WaitGroup) {
	defer wg.Done()

	now := time.Now()
	res, err := cli.HTTPClient.Get(ping.URL)
	delta := time.Since(now)
	ping.Latency = delta
	if err != nil {
		ping.Status = err.Error()
		return
	}
	res.Body.Close()
	ping.Status = "connected"
}

func redact(token string) string {
	str := token[4 : len(token)-3]
	redaction := strings.Repeat("*", len(str))
	return string(token[:4]) + redaction + string(token[len(token)-3:])
}

const tmplSelfTest = `
Troubleshooting Information
===========================

Version
----------------
Current: {{ .Version.Current }}
Latest:  {{ with .Version.Latest }}{{ . }}{{ else }}<unknown>{{ end }}
{{ with .Version.Error }}
{{ . }}
{{ end -}}
{{ if not .Version.UpToDate }}
Call 'exercism upgrade' to get the latest version.
See the release notes at https://github.com/exercism/cli/releases/tag/{{ .Version.Latest }} for details.
{{ end }}

Operating System
----------------
OS:           {{ .System.OS }}
Architecture: {{ .System.Architecture }}
{{ with .System.Build }}
Build: {{ . }}
{{ end }}

Configuration
----------------
Home:      {{ .Configuration.Home }}
Workspace: {{ .Configuration.Workspace }}
Config:    {{ .Configuration.File }}
API key:   {{ with .Configuration.Token }}{{ . }}{{ else }}<not configured>
Find your API key at {{ .Configuration.TokenURL }}{{ end }}

API Reachability
----------------
{{ range .APIReachability.Services }}
{{ .Service }}:
    * {{ .URL }}
    * [{{ .Status }}]
    * {{ .Latency }}
{{ end }}

If you are having trouble please file a GitHub issue at
https://github.com/exercism/exercism.io/issues and include
this information.
{{ if not .Censor }}
Don't share your API key. Keep that private.
{{ end }}`

func init() {
	RootCmd.AddCommand(troubleshootCmd)
	troubleshootCmd.Flags().BoolVarP(&fullAPIKey, "full-api-key", "f", false, "display the user's full API key, censored by default")
}
<commit_msg>Add debug as alias for troubleshoot (#669)<commit_after>package cmd

import (
	"bytes"
	"fmt"
	"html/template"
	"net/http"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/exercism/cli/cli"
	"github.com/exercism/cli/config"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

// fullAPIKey flag for troubleshoot command.
var fullAPIKey bool

// troubleshootCmd does a diagnostic self-check.
var troubleshootCmd = &cobra.Command{
	Use:     "troubleshoot",
	Aliases: []string{"t", "debug"},
	Short:   "Troubleshoot does a diagnostic self-check.",
	Long: `Provides output to help with troubleshooting.

If you're running into trouble, copy and paste the output from the troubleshoot
command into a GitHub issue so we can help figure out what's going on.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		cli.HTTPClient = &http.Client{Timeout: 20 * time.Second}
		c := cli.New(Version)

		cfg := config.NewConfiguration()

		v := viper.New()
		v.AddConfigPath(cfg.Dir)
		v.SetConfigName("user")
		v.SetConfigType("json")
		// Ignore error. If the file doesn't exist, that is fine.
		_ = v.ReadInConfig()

		status := newStatus(c, v)
		status.Censor = !fullAPIKey
		s, err := status.check()
		if err != nil {
			return err
		}

		fmt.Printf("%s", s)
		return nil
	},
}

// Status represents the results of a CLI self test.
type Status struct {
	Censor          bool
	Version         versionStatus
	System          systemStatus
	Configuration   configurationStatus
	APIReachability apiReachabilityStatus
	cli             *cli.CLI
	cfg             *viper.Viper
}

type versionStatus struct {
	Current  string
	Latest   string
	Status   string
	Error    error
	UpToDate bool
}

type systemStatus struct {
	OS           string
	Architecture string
	Build        string
}

type configurationStatus struct {
	Home      string
	Workspace string
	File      string
	Token     string
	TokenURL  string
}

type apiReachabilityStatus struct {
	Services []*apiPing
}

type apiPing struct {
	Service string
	URL     string
	Status  string
	Latency time.Duration
}

// newStatus prepares a value to perform a diagnostic self-check.
func newStatus(c *cli.CLI, v *viper.Viper) Status {
	status := Status{
		cli: c,
		cfg: v,
	}
	return status
}

// check runs the CLI's diagnostic self-check.
func (status *Status) check() (string, error) {
	status.Version = newVersionStatus(status.cli)
	status.System = newSystemStatus()
	status.Configuration = newConfigurationStatus(status)
	status.APIReachability = newAPIReachabilityStatus(status.cfg.GetString("apibaseurl"))

	return status.compile()
}
func (status *Status) compile() (string, error) {
	t, err := template.New("self-test").Parse(tmplSelfTest)
	if err != nil {
		return "", err
	}

	var bb bytes.Buffer
	t.Execute(&bb, status)
	return bb.String(), nil
}

func newAPIReachabilityStatus(baseURL string) apiReachabilityStatus {
	ar := apiReachabilityStatus{
		Services: []*apiPing{
			{Service: "GitHub", URL: "https://api.github.com"},
			{Service: "Exercism", URL: fmt.Sprintf("%s/ping", baseURL)},
		},
	}
	var wg sync.WaitGroup
	wg.Add(len(ar.Services))
	for _, service := range ar.Services {
		go service.Call(&wg)
	}
	wg.Wait()
	return ar
}

func newVersionStatus(c *cli.CLI) versionStatus {
	vs := versionStatus{
		Current: c.Version,
	}
	ok, err := c.IsUpToDate()
	if err == nil {
		vs.Latest = c.LatestRelease.Version()
	} else {
		vs.Error = fmt.Errorf("Error: %s", err)
	}
	vs.UpToDate = ok
	return vs
}

func newSystemStatus() systemStatus {
	ss := systemStatus{
		OS:           runtime.GOOS,
		Architecture: runtime.GOARCH,
	}
	if cli.BuildOS != "" && cli.BuildARCH != "" {
		ss.Build = fmt.Sprintf("%s/%s", cli.BuildOS, cli.BuildARCH)
	}
	if cli.BuildARM != "" {
		ss.Build = fmt.Sprintf("%s ARMv%s", ss.Build, cli.BuildARM)
	}
	return ss
}

func newConfigurationStatus(status *Status) configurationStatus {
	token := status.cfg.GetString("token")
	cs := configurationStatus{
		Home:      status.cfg.GetString("home"),
		Workspace: status.cfg.GetString("workspace"),
		File:      status.cfg.ConfigFileUsed(),
		Token:     token,
		TokenURL:  config.InferSiteURL(status.cfg.GetString("apibaseurl")) + "/my/settings",
	}
	if status.Censor && token != "" {
		cs.Token = redact(token)
	}
	return cs
}

func (ping *apiPing) Call(wg *sync.WaitGroup) {
	defer wg.Done()

	now := time.Now()
	res, err := cli.HTTPClient.Get(ping.URL)
	delta := time.Since(now)
	ping.Latency = delta
	if err != nil {
		ping.Status = err.Error()
		return
	}
	res.Body.Close()
	ping.Status = "connected"
}

func redact(token string) string {
	str := token[4 : len(token)-3]
	redaction := strings.Repeat("*", len(str))
	return string(token[:4]) + redaction + string(token[len(token)-3:])
}

const tmplSelfTest = `
Troubleshooting Information
===========================

Version
----------------
Current: {{ .Version.Current }}
Latest:  {{ with .Version.Latest }}{{ . }}{{ else }}<unknown>{{ end }}
{{ with .Version.Error }}
{{ . }}
{{ end -}}
{{ if not .Version.UpToDate }}
Call 'exercism upgrade' to get the latest version.
See the release notes at https://github.com/exercism/cli/releases/tag/{{ .Version.Latest }} for details.
{{ end }}

Operating System
----------------
OS:           {{ .System.OS }}
Architecture: {{ .System.Architecture }}
{{ with .System.Build }}
Build: {{ . }}
{{ end }}

Configuration
----------------
Home:      {{ .Configuration.Home }}
Workspace: {{ .Configuration.Workspace }}
Config:    {{ .Configuration.File }}
API key:   {{ with .Configuration.Token }}{{ . }}{{ else }}<not configured>
Find your API key at {{ .Configuration.TokenURL }}{{ end }}

API Reachability
----------------
{{ range .APIReachability.Services }}
{{ .Service }}:
    * {{ .URL }}
    * [{{ .Status }}]
    * {{ .Latency }}
{{ end }}

If you are having trouble please file a GitHub issue at
https://github.com/exercism/exercism.io/issues and include
this information.
{{ if not .Censor }}
Don't share your API key. Keep that private.
{{ end }}`

func init() {
	RootCmd.AddCommand(troubleshootCmd)
	troubleshootCmd.Flags().BoolVarP(&fullAPIKey, "full-api-key", "f", false, "display the user's full API key, censored by default")
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Knative Authors

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 (
	"context"

	"k8s.io/apimachinery/pkg/runtime/schema"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/injection/sharedmain"
	"knative.dev/pkg/logging"
	"knative.dev/pkg/metrics"
	"knative.dev/pkg/signals"
	"knative.dev/pkg/webhook"
	"knative.dev/pkg/webhook/certificates"
	"knative.dev/pkg/webhook/configmaps"
	"knative.dev/pkg/webhook/resourcesemantics"
	"knative.dev/pkg/webhook/resourcesemantics/defaulting"
	"knative.dev/pkg/webhook/resourcesemantics/validation"

	// resource validation types
	autoscalingv1alpha1 "knative.dev/serving/pkg/apis/autoscaling/v1alpha1"
	net "knative.dev/serving/pkg/apis/networking/v1alpha1"
	v1 "knative.dev/serving/pkg/apis/serving/v1"
	"knative.dev/serving/pkg/apis/serving/v1alpha1"
	"knative.dev/serving/pkg/apis/serving/v1beta1"

	// config validation constructors
	tracingconfig "knative.dev/pkg/tracing/config"
	defaultconfig "knative.dev/serving/pkg/apis/config"
	"knative.dev/serving/pkg/autoscaler"
	"knative.dev/serving/pkg/deployment"
	"knative.dev/serving/pkg/gc"
	metricsconfig "knative.dev/serving/pkg/metrics"
	"knative.dev/serving/pkg/network"
	certconfig "knative.dev/serving/pkg/reconciler/certificate/config"
	istioconfig "knative.dev/serving/pkg/reconciler/ingress/config"
	domainconfig "knative.dev/serving/pkg/reconciler/route/config"
)

var types = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{
	v1alpha1.SchemeGroupVersion.WithKind("Revision"):      &v1alpha1.Revision{},
	v1alpha1.SchemeGroupVersion.WithKind("Configuration"): &v1alpha1.Configuration{},
	v1alpha1.SchemeGroupVersion.WithKind("Route"):         &v1alpha1.Route{},
	v1alpha1.SchemeGroupVersion.WithKind("Service"):       &v1alpha1.Service{},
	v1beta1.SchemeGroupVersion.WithKind("Revision"):       &v1beta1.Revision{},
	v1beta1.SchemeGroupVersion.WithKind("Configuration"):  &v1beta1.Configuration{},
	v1beta1.SchemeGroupVersion.WithKind("Route"):          &v1beta1.Route{},
	v1beta1.SchemeGroupVersion.WithKind("Service"):        &v1beta1.Service{},
	v1.SchemeGroupVersion.WithKind("Revision"):            &v1.Revision{},
	v1.SchemeGroupVersion.WithKind("Configuration"):       &v1.Configuration{},
	v1.SchemeGroupVersion.WithKind("Route"):               &v1.Route{},
	v1.SchemeGroupVersion.WithKind("Service"):             &v1.Service{},

	autoscalingv1alpha1.SchemeGroupVersion.WithKind("PodAutoscaler"): &autoscalingv1alpha1.PodAutoscaler{},
	autoscalingv1alpha1.SchemeGroupVersion.WithKind("Metric"):        &autoscalingv1alpha1.Metric{},

	net.SchemeGroupVersion.WithKind("Certificate"):       &net.Certificate{},
	net.SchemeGroupVersion.WithKind("Ingress"):           &net.Ingress{},
	net.SchemeGroupVersion.WithKind("ServerlessService"): &net.ServerlessService{},
}

func NewDefaultingAdmissionController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	// Decorate contexts with the current state of the config.
	store := defaultconfig.NewStore(logging.FromContext(ctx).Named("config-store"))
	store.WatchConfigs(cmw)

	return defaulting.NewAdmissionController(ctx,

		// Name of the resource webhook.
		"webhook.serving.knative.dev",

		// The path on which to serve the webhook.
		// TODO(mattmoor): This can be changed after 0.11 once
		// we have release reconciliation-based webhooks.
		"/",

		// The resources to validate and default.
		types,

		// A function that infuses the context passed to Validate/SetDefaults with custom metadata.
		func(ctx context.Context) context.Context {
			return v1.WithUpgradeViaDefaulting(store.ToContext(ctx))
		},

		// Whether to disallow unknown fields.
		true,
	)
}

func NewValidationAdmissionController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	return validation.NewAdmissionController(ctx,

		// Name of the resource webhook.
		"validation.webhook.serving.knative.dev",

		// The path on which to serve the webhook.
		// TODO(mattmoor): This can be changed after 0.11 once
		// we have release reconciliation-based webhooks.
		"/",

		// The resources to validate and default.
		types,

		// A function that infuses the context passed to Validate/SetDefaults with custom metadata.
		func(ctx context.Context) context.Context {
			return ctx
		},

		// Whether to disallow unknown fields.
		true,
	)
}

func NewConfigValidationController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	return configmaps.NewAdmissionController(ctx,

		// Name of the configmap webhook.
		"config.webhook.serving.knative.dev",

		// The path on which to serve the webhook.
		"/config-validation",

		// The configmaps to validate.
		configmap.Constructors{
			tracingconfig.ConfigName:         tracingconfig.NewTracingConfigFromConfigMap,
			autoscaler.ConfigName:            autoscaler.NewConfigFromConfigMap,
			certconfig.CertManagerConfigName: certconfig.NewCertManagerConfigFromConfigMap,
			gc.ConfigName:                    gc.NewConfigFromConfigMapFunc(ctx),
			network.ConfigName:               network.NewConfigFromConfigMap,
			istioconfig.IstioConfigName:      istioconfig.NewIstioFromConfigMap,
			deployment.ConfigName:            deployment.NewConfigFromConfigMap,
			metrics.ConfigMapName():          metricsconfig.NewObservabilityConfigFromConfigMap,
			logging.ConfigMapName():          logging.NewConfigFromConfigMap,
			domainconfig.DomainConfigName:    domainconfig.NewDomainFromConfigMap,
			defaultconfig.DefaultsConfigName: defaultconfig.NewDefaultsConfigFromConfigMap,
		},
	)
}

func main() {
	// Set up a signal context with our webhook options
	ctx := webhook.WithOptions(signals.NewContext(), webhook.Options{
		ServiceName: "webhook",
		Port:        8443,
		SecretName:  "webhook-certs",
	})

	sharedmain.MainWithContext(ctx, "webhook",
		certificates.NewController,
		NewDefaultingAdmissionController,
		NewValidationAdmissionController,
		NewConfigValidationController,
	)
}
<commit_msg>Move webhooks to separate paths. (#6215)<commit_after>/*
Copyright 2018 The Knative Authors

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 (
	"context"

	"k8s.io/apimachinery/pkg/runtime/schema"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/injection/sharedmain"
	"knative.dev/pkg/logging"
	"knative.dev/pkg/metrics"
	"knative.dev/pkg/signals"
	"knative.dev/pkg/webhook"
	"knative.dev/pkg/webhook/certificates"
	"knative.dev/pkg/webhook/configmaps"
	"knative.dev/pkg/webhook/resourcesemantics"
	"knative.dev/pkg/webhook/resourcesemantics/defaulting"
	"knative.dev/pkg/webhook/resourcesemantics/validation"

	// resource validation types
	autoscalingv1alpha1 "knative.dev/serving/pkg/apis/autoscaling/v1alpha1"
	net "knative.dev/serving/pkg/apis/networking/v1alpha1"
	v1 "knative.dev/serving/pkg/apis/serving/v1"
	"knative.dev/serving/pkg/apis/serving/v1alpha1"
	"knative.dev/serving/pkg/apis/serving/v1beta1"

	// config validation constructors
	tracingconfig "knative.dev/pkg/tracing/config"
	defaultconfig "knative.dev/serving/pkg/apis/config"
	"knative.dev/serving/pkg/autoscaler"
	"knative.dev/serving/pkg/deployment"
	"knative.dev/serving/pkg/gc"
	metricsconfig "knative.dev/serving/pkg/metrics"
	"knative.dev/serving/pkg/network"
	certconfig "knative.dev/serving/pkg/reconciler/certificate/config"
	istioconfig "knative.dev/serving/pkg/reconciler/ingress/config"
	domainconfig "knative.dev/serving/pkg/reconciler/route/config"
)

var types = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{
	v1alpha1.SchemeGroupVersion.WithKind("Revision"):      &v1alpha1.Revision{},
	v1alpha1.SchemeGroupVersion.WithKind("Configuration"): &v1alpha1.Configuration{},
	v1alpha1.SchemeGroupVersion.WithKind("Route"):         &v1alpha1.Route{},
	v1alpha1.SchemeGroupVersion.WithKind("Service"):       &v1alpha1.Service{},
	v1beta1.SchemeGroupVersion.WithKind("Revision"):       &v1beta1.Revision{},
	v1beta1.SchemeGroupVersion.WithKind("Configuration"):  &v1beta1.Configuration{},
	v1beta1.SchemeGroupVersion.WithKind("Route"):          &v1beta1.Route{},
	v1beta1.SchemeGroupVersion.WithKind("Service"):        &v1beta1.Service{},
	v1.SchemeGroupVersion.WithKind("Revision"):            &v1.Revision{},
	v1.SchemeGroupVersion.WithKind("Configuration"):       &v1.Configuration{},
	v1.SchemeGroupVersion.WithKind("Route"):               &v1.Route{},
	v1.SchemeGroupVersion.WithKind("Service"):             &v1.Service{},

	autoscalingv1alpha1.SchemeGroupVersion.WithKind("PodAutoscaler"): &autoscalingv1alpha1.PodAutoscaler{},
	autoscalingv1alpha1.SchemeGroupVersion.WithKind("Metric"):        &autoscalingv1alpha1.Metric{},

	net.SchemeGroupVersion.WithKind("Certificate"):       &net.Certificate{},
	net.SchemeGroupVersion.WithKind("Ingress"):           &net.Ingress{},
	net.SchemeGroupVersion.WithKind("ServerlessService"): &net.ServerlessService{},
}

func NewDefaultingAdmissionController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	// Decorate contexts with the current state of the config.
	store := defaultconfig.NewStore(logging.FromContext(ctx).Named("config-store"))
	store.WatchConfigs(cmw)

	return defaulting.NewAdmissionController(ctx,

		// Name of the resource webhook.
		"webhook.serving.knative.dev",

		// The path on which to serve the webhook.
		"/defaulting",

		// The resources to validate and default.
		types,

		// A function that infuses the context passed to Validate/SetDefaults with custom metadata.
		func(ctx context.Context) context.Context {
			return v1.WithUpgradeViaDefaulting(store.ToContext(ctx))
		},

		// Whether to disallow unknown fields.
		true,
	)
}

func NewValidationAdmissionController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	return validation.NewAdmissionController(ctx,

		// Name of the resource webhook.
		"validation.webhook.serving.knative.dev",

		// The path on which to serve the webhook.
		"/resource-validation",

		// The resources to validate and default.
		types,

		// A function that infuses the context passed to Validate/SetDefaults with custom metadata.
		func(ctx context.Context) context.Context {
			return ctx
		},

		// Whether to disallow unknown fields.
		true,
	)
}

func NewConfigValidationController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	return configmaps.NewAdmissionController(ctx,

		// Name of the configmap webhook.
		"config.webhook.serving.knative.dev",

		// The path on which to serve the webhook.
		"/config-validation",

		// The configmaps to validate.
		configmap.Constructors{
			tracingconfig.ConfigName:         tracingconfig.NewTracingConfigFromConfigMap,
			autoscaler.ConfigName:            autoscaler.NewConfigFromConfigMap,
			certconfig.CertManagerConfigName: certconfig.NewCertManagerConfigFromConfigMap,
			gc.ConfigName:                    gc.NewConfigFromConfigMapFunc(ctx),
			network.ConfigName:               network.NewConfigFromConfigMap,
			istioconfig.IstioConfigName:      istioconfig.NewIstioFromConfigMap,
			deployment.ConfigName:            deployment.NewConfigFromConfigMap,
			metrics.ConfigMapName():          metricsconfig.NewObservabilityConfigFromConfigMap,
			logging.ConfigMapName():          logging.NewConfigFromConfigMap,
			domainconfig.DomainConfigName:    domainconfig.NewDomainFromConfigMap,
			defaultconfig.DefaultsConfigName: defaultconfig.NewDefaultsConfigFromConfigMap,
		},
	)
}

func main() {
	// Set up a signal context with our webhook options
	ctx := webhook.WithOptions(signals.NewContext(), webhook.Options{
		ServiceName: "webhook",
		Port:        8443,
		SecretName:  "webhook-certs",
	})

	sharedmain.MainWithContext(ctx, "webhook",
		certificates.NewController,
		NewDefaultingAdmissionController,
		NewValidationAdmissionController,
		NewConfigValidationController,
	)
}
<|endoftext|>


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

// pox builds a portable executable as a squashfs image.
// It is intended to create files compatible with tinycore
// tcz files. One of more of the files can be programs
// but that is not required.
// This could have been a simple program but mksquashfs does not
// preserve path information.
// Yeah.
//
// Synopsis:
//     pox [-[-debug]|d] -[-run|r file] [-[-create]|c]  [-[-file]|f tcz-file] file [...file]
//
// Description:
//     pox makes portable executables in squashfs format compatible with
//     tcz format. We don't build in the execution code, rather, we set it
//     up so we can use the command itself. You can create, create and run a command,
//     mount a pox, or mount a pox and run a command in it.
//
// Options:
//     debug|d: verbose
//     file|f file: file name (default /tmp/pox.tcz)
//     run|r: run a file by loopback mounting the squashfs and using the first arg as a command to run in a chroot
//     create|c: create the file.
//     both -c and -r can be used on the same command.
//
// Example:
//	pox -d -r /bin/bash /bin/cat /bin/ls /etc/hosts
//	Will build a squashfs, mount it, and drop you into it running bash.
//	You can use ls and cat on /etc/hosts.
//	Simpler example:
//	pox -d -r /bin/ls /etc/hosts
//	will run ls and exit.
package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"

	flag "github.com/spf13/pflag"
	"github.com/u-root/u-root/pkg/ldd"
	"github.com/u-root/u-root/pkg/loop"
)

const usage = "pox [-[-debug]|d] -[-run|r file] [-[-create]|c]  [-[-file]|f tcz-file] file [...file]"

var (
	debug  = flag.BoolP("debug", "d", false, "enable debug prints")
	run    = flag.BoolP("run", "r", false, "run a test with the first argument")
	create = flag.BoolP("create", "c", true, "create it")
	file   = flag.StringP("output", "f", "/tmp/pox.tcz", "Output file")
	v      = func(string, ...interface{}) {}
)

func pox() error {
	flag.Parse()
	if *debug {
		v = log.Printf
	}
	names := flag.Args()
	if len(names) == 0 {
		return fmt.Errorf(usage)
	}

	if *create {
		l, err := ldd.Ldd(names)
		if err != nil {
			return fmt.Errorf("Running ldd on %v: %v", names, err)
		}

		for _, dep := range l {
			v("%s", dep.FullName)
			names = append(names, dep.FullName)
		}
		// Now we need to make a template file hierarchy and put
		// the stuff we want in there.
		dir, err := ioutil.TempDir("", "pox")
		if err != nil {
			return err
		}
		if !*debug {
			defer os.RemoveAll(dir)
		}
		// We don't use defer() here to close files as
		// that can cause open failures with a large enough number.
		for _, f := range names {
			v("Process %v", f)
			fi, err := os.Stat(f)
			if err != nil {
				return err
			}
			in, err := os.Open(f)
			if err != nil {
				return err
			}
			dfile := filepath.Join(dir, f)
			d := filepath.Dir(dfile)
			if err := os.MkdirAll(d, 0755); err != nil {
				in.Close()
				return err
			}
			out, err := os.OpenFile(dfile, os.O_WRONLY|os.O_CREATE, fi.Mode().Perm())
			if err != nil {
				in.Close()
				return err
			}
			_, err = io.Copy(out, in)
			in.Close()
			out.Close()
			if err != nil {
				return err
			}

		}
		c := exec.Command("mksquashfs", dir, *file, "-noappend")
		o, err := c.CombinedOutput()
		v("%v", string(o))
		if err != nil {
			return fmt.Errorf("%v: %v: %v", c.Args, string(o), err)
		}
	}

	if !*run {
		return nil
	}
	dir, err := ioutil.TempDir("", "pox")
	if err != nil {
		return err
	}
	if !*debug {
		defer os.RemoveAll(dir)
	}

	lo, err := loop.New(*file, "squashfs", "")
	if err != nil {
		return err
	}
	defer lo.Free() //nolint:errcheck

	mountPoint, err := lo.Mount(dir, 0)
	if err != nil {
		return err
	}
	defer mountPoint.Unmount(0) //nolint:errcheck

	c := exec.Command(names[0])
	c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr
	c.SysProcAttr = &syscall.SysProcAttr{
		Chroot: dir,
	}

	if err = c.Run(); err != nil {
		log.Printf("Running test: %v", err)
	}

	v("Done, your pox is in %v", *file)
	return err
}

func main() {
	if err := pox(); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>pox: output stderr from the interpreter<commit_after>// Copyright 2012-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// pox builds a portable executable as a squashfs image.
// It is intended to create files compatible with tinycore
// tcz files. One of more of the files can be programs
// but that is not required.
// This could have been a simple program but mksquashfs does not
// preserve path information.
// Yeah.
//
// Synopsis:
//     pox [-[-debug]|d] -[-run|r file] [-[-create]|c]  [-[-file]|f tcz-file] file [...file]
//
// Description:
//     pox makes portable executables in squashfs format compatible with
//     tcz format. We don't build in the execution code, rather, we set it
//     up so we can use the command itself. You can create, create and run a command,
//     mount a pox, or mount a pox and run a command in it.
//
// Options:
//     debug|d: verbose
//     file|f file: file name (default /tmp/pox.tcz)
//     run|r: run a file by loopback mounting the squashfs and using the first arg as a command to run in a chroot
//     create|c: create the file.
//     both -c and -r can be used on the same command.
//
// Example:
//	pox -d -r /bin/bash /bin/cat /bin/ls /etc/hosts
//	Will build a squashfs, mount it, and drop you into it running bash.
//	You can use ls and cat on /etc/hosts.
//	Simpler example:
//	pox -d -r /bin/ls /etc/hosts
//	will run ls and exit.
package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"

	flag "github.com/spf13/pflag"
	"github.com/u-root/u-root/pkg/ldd"
	"github.com/u-root/u-root/pkg/loop"
)

const usage = "pox [-[-debug]|d] -[-run|r file] [-[-create]|c]  [-[-file]|f tcz-file] file [...file]"

var (
	debug  = flag.BoolP("debug", "d", false, "enable debug prints")
	run    = flag.BoolP("run", "r", false, "run a test with the first argument")
	create = flag.BoolP("create", "c", true, "create it")
	file   = flag.StringP("output", "f", "/tmp/pox.tcz", "Output file")
	v      = func(string, ...interface{}) {}
)

func pox() error {
	flag.Parse()
	if *debug {
		v = log.Printf
	}
	names := flag.Args()
	if len(names) == 0 {
		return fmt.Errorf(usage)
	}

	if *create {
		l, err := ldd.Ldd(names)
		if err != nil {
			var stderr []byte
			if eerr, ok := err.(*exec.ExitError); ok {
				stderr = eerr.Stderr
			}
			return fmt.Errorf("Running ldd on %v: %v %s", names,
				err, stderr)
		}

		for _, dep := range l {
			v("%s", dep.FullName)
			names = append(names, dep.FullName)
		}
		// Now we need to make a template file hierarchy and put
		// the stuff we want in there.
		dir, err := ioutil.TempDir("", "pox")
		if err != nil {
			return err
		}
		if !*debug {
			defer os.RemoveAll(dir)
		}
		// We don't use defer() here to close files as
		// that can cause open failures with a large enough number.
		for _, f := range names {
			v("Process %v", f)
			fi, err := os.Stat(f)
			if err != nil {
				return err
			}
			in, err := os.Open(f)
			if err != nil {
				return err
			}
			dfile := filepath.Join(dir, f)
			d := filepath.Dir(dfile)
			if err := os.MkdirAll(d, 0755); err != nil {
				in.Close()
				return err
			}
			out, err := os.OpenFile(dfile, os.O_WRONLY|os.O_CREATE, fi.Mode().Perm())
			if err != nil {
				in.Close()
				return err
			}
			_, err = io.Copy(out, in)
			in.Close()
			out.Close()
			if err != nil {
				return err
			}

		}
		c := exec.Command("mksquashfs", dir, *file, "-noappend")
		o, err := c.CombinedOutput()
		v("%v", string(o))
		if err != nil {
			return fmt.Errorf("%v: %v: %v", c.Args, string(o), err)
		}
	}

	if !*run {
		return nil
	}
	dir, err := ioutil.TempDir("", "pox")
	if err != nil {
		return err
	}
	if !*debug {
		defer os.RemoveAll(dir)
	}

	lo, err := loop.New(*file, "squashfs", "")
	if err != nil {
		return err
	}
	defer lo.Free() //nolint:errcheck

	mountPoint, err := lo.Mount(dir, 0)
	if err != nil {
		return err
	}
	defer mountPoint.Unmount(0) //nolint:errcheck

	c := exec.Command(names[0])
	c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr
	c.SysProcAttr = &syscall.SysProcAttr{
		Chroot: dir,
	}

	if err = c.Run(); err != nil {
		log.Printf("Running test: %v", err)
	}

	v("Done, your pox is in %v", *file)
	return err
}

func main() {
	if err := pox(); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>// Package sign provides utilities to generate signed URLs for Amazon CloudFront.
//
// More information about signed URLs and their structure can be found at:
// http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html
//
// To sign a URL create a URLSigner with your private key and credential pair key ID.
// Once you have a URLSigner instance you can call Sign or SignWithPolicy to
// sign the URLs.
//
// Example:
//
//    // Sign URL to be valid for 1 hour from now.
//    signer := sign.NewURLSigner(keyID, privKey)
//    signedURL, err := signer.Sign(rawURL, time.Now().Add(1*time.Hour))
//    if err != nil {
//        log.Fatalf("Failed to sign url, err: %s\n", err.Error())
//    }
//
package sign

import (
	"crypto/rsa"
	"fmt"
	"net/url"
	"strings"
	"time"
)

// An URLSigner provides URL signing utilities to sign URLs for Amazon CloudFront
// resources. Using a private key and Credential Key Pair key ID the URLSigner
// only needs to be created once per Credential Key Pair key ID and private key.
//
// The signer is safe to use concurrently.
type URLSigner struct {
	keyID   string
	privKey *rsa.PrivateKey
}

// NewURLSigner constructs and returns a new URLSigner to be used to for signing
// Amazon CloudFront URL resources with.
func NewURLSigner(keyID string, privKey *rsa.PrivateKey) *URLSigner {
	return &URLSigner{
		keyID:   keyID,
		privKey: privKey,
	}
}

// Sign will sign a single URL to expire at the time of expires sign using the
// Amazon CloudFront default Canned Policy. The URL will be signed with the
// private key and Credential Key Pair Key ID previously provided to URLSigner.
//
// This is the default method of signing Amazon CloudFront URLs. If extra policy
// conditions are need other than URL expiry use SignWithPolicy instead.
//
// Example:
//
//    // Sign URL to be valid for 1 hour from now.
//    signer := sign.NewURLSigner(keyID, privKey)
//    signedURL, err := signer.Sign(rawURL, time.Now().Add(1*time.Hour))
//    if err != nil {
//        log.Fatalf("Failed to sign url, err: %s\n", err.Error())
//    }
//
func (s URLSigner) Sign(url string, expires time.Time) (string, error) {
	scheme, cleanedURL, err := cleanURLScheme(url)
	if err != nil {
		return "", err
	}

	resource, err := CreateResource(scheme, url)
	if err != nil {
		return "", err
	}

	return signURL(scheme, cleanedURL, s.keyID, NewCannedPolicy(resource, expires), false, s.privKey)
}

// SignWithPolicy will sign a URL with the Policy provided.  The URL will be
// signed with the private key and Credential Key Pair Key ID previously provided to URLSigner.
//
// Use this signing method if you are looking to sign a URL with more than just
// the URL's expiry time, or reusing Policies between multiple URL signings.
// If only the expiry time is needed you can use Sign and provide just the
// URL's expiry time. A minimum of at least one policy statement is required for a signed URL.
//
// Note: It is not safe to use Polices between multiple signers concurrently
//
// Example:
//
//     // Sign URL to be valid for 30 minutes from now, expires one hour from now, and
//     // restricted to the 192.0.2.0/24 IP address range.
//     policy := &sign.Policy{
//         Statements: []Statement{
//             {
//                 Resource: rawURL,
//                 Condition: Condition{
//                     // Optional IP source address range
//                     IPAddress: &IPAddress{SourceIP: "192.0.2.0/24"},
//                     // Optional date URL is not valid until
//                     DateGreaterThan: &AWSEpochTime{time.Now().Add(30 * time.Minute)},
//                     // Required date the URL will expire after
//                     DateLessThan: &AWSEpochTime{time.Now().Add(1 * time.Hour)},
//                 }
//             }
//         }
//     }
//
//     signer := sign.NewURLSigner(keyID, privKey)
//     signedURL, err := signer.SignWithPolicy(rawURL, policy)
//     if err != nil {
//         log.Fatalf("Failed to sign url, err: %s\n", err.Error())
//     }
//
func (s URLSigner) SignWithPolicy(url string, p *Policy) (string, error) {
	scheme, cleanedURL, err := cleanURLScheme(url)
	if err != nil {
		return "", err
	}

	return signURL(scheme, cleanedURL, s.keyID, p, true, s.privKey)
}

func signURL(scheme, url, keyID string, p *Policy, customPolicy bool, privKey *rsa.PrivateKey) (string, error) {
	// Validation URL elements
	if err := validateURL(url); err != nil {
		return "", err
	}

	b64Signature, b64Policy, err := p.Sign(privKey)
	if err != nil {
		return "", err
	}

	// build and return signed URL
	builtURL := buildSignedURL(url, keyID, p, customPolicy, b64Policy, b64Signature)
	if scheme == "rtmp" {
		return buildRTMPURL(builtURL)
	}

	return builtURL, nil
}

func buildSignedURL(baseURL, keyID string, p *Policy, customPolicy bool, b64Policy, b64Signature []byte) string {
	pred := "?"
	if strings.Contains(baseURL, "?") {
		pred = "&"
	}
	signedURL := baseURL + pred

	if customPolicy {
		signedURL += "Policy=" + string(b64Policy)
	} else {
		signedURL += fmt.Sprintf("Expires=%d", p.Statements[0].Condition.DateLessThan.UTC().Unix())
	}
	signedURL += fmt.Sprintf("&Signature=%s&Key-Pair-Id=%s", string(b64Signature), keyID)

	return signedURL
}

func buildRTMPURL(u string) (string, error) {
	parsed, err := url.Parse(u)
	if err != nil {
		return "", fmt.Errorf("unable to parse rtmp signed URL, err: %s", err)
	}

	rtmpURL := strings.TrimLeft(parsed.Path, "/")
	if parsed.RawQuery != "" {
		rtmpURL = fmt.Sprintf("%s?%s", rtmpURL, parsed.RawQuery)
	}

	return rtmpURL, nil
}

func cleanURLScheme(u string) (scheme, cleanedURL string, err error) {
	parts := strings.SplitN(u, "://", 2)
	if len(parts) != 2 {
		return "", "", fmt.Errorf("invalid URL, missing scheme and domain/path")
	}
	scheme = strings.Replace(parts[0], "*", "", 1)
	cleanedURL = fmt.Sprintf("%s://%s", scheme, parts[1])

	return strings.ToLower(scheme), cleanedURL, nil
}

var illegalQueryParms = []string{"Expires", "Policy", "Signature", "Key-Pair-Id"}

func validateURL(u string) error {
	parsed, err := url.Parse(u)
	if err != nil {
		return fmt.Errorf("unable to parse URL, err: %s", err.Error())
	}

	if parsed.Scheme == "" {
		return fmt.Errorf("URL missing valid scheme, %s", u)
	}

	q := parsed.Query()
	for _, p := range illegalQueryParms {
		if _, ok := q[p]; ok {
			return fmt.Errorf("%s cannot be a query parameter for a signed URL", p)
		}
	}

	return nil
}
<commit_msg>Update sign_url.go<commit_after>// Package sign provides utilities to generate signed URLs for Amazon CloudFront.
//
// More information about signed URLs and their structure can be found at:
// http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-canned-policy.html
//
// To sign a URL create a URLSigner with your private key and credential pair key ID.
// Once you have a URLSigner instance you can call Sign or SignWithPolicy to
// sign the URLs.
//
// Example:
//
//    // Sign URL to be valid for 1 hour from now.
//    signer := sign.NewURLSigner(keyID, privKey)
//    signedURL, err := signer.Sign(rawURL, time.Now().Add(1*time.Hour))
//    if err != nil {
//        log.Fatalf("Failed to sign url, err: %s\n", err.Error())
//    }
//
package sign

import (
	"crypto/rsa"
	"fmt"
	"net/url"
	"strings"
	"time"
)

// An URLSigner provides URL signing utilities to sign URLs for Amazon CloudFront
// resources. Using a private key and Credential Key Pair key ID the URLSigner
// only needs to be created once per Credential Key Pair key ID and private key.
//
// The signer is safe to use concurrently.
type URLSigner struct {
	keyID   string
	privKey *rsa.PrivateKey
}

// NewURLSigner constructs and returns a new URLSigner to be used to for signing
// Amazon CloudFront URL resources with.
func NewURLSigner(keyID string, privKey *rsa.PrivateKey) *URLSigner {
	return &URLSigner{
		keyID:   keyID,
		privKey: privKey,
	}
}

// Sign will sign a single URL to expire at the time of expires sign using the
// Amazon CloudFront default Canned Policy. The URL will be signed with the
// private key and Credential Key Pair Key ID previously provided to URLSigner.
//
// This is the default method of signing Amazon CloudFront URLs. If extra policy
// conditions are need other than URL expiry use SignWithPolicy instead.
//
// Example:
//
//    // Sign URL to be valid for 1 hour from now.
//    signer := sign.NewURLSigner(keyID, privKey)
//    signedURL, err := signer.Sign(rawURL, time.Now().Add(1*time.Hour))
//    if err != nil {
//        log.Fatalf("Failed to sign url, err: %s\n", err.Error())
//    }
//
func (s URLSigner) Sign(url string, expires time.Time) (string, error) {
	scheme, cleanedURL, err := cleanURLScheme(url)
	if err != nil {
		return "", err
	}

	resource, err := CreateResource(scheme, url)
	if err != nil {
		return "", err
	}

	return signURL(scheme, cleanedURL, s.keyID, NewCannedPolicy(resource, expires), false, s.privKey)
}

// SignWithPolicy will sign a URL with the Policy provided.  The URL will be
// signed with the private key and Credential Key Pair Key ID previously provided to URLSigner.
//
// Use this signing method if you are looking to sign a URL with more than just
// the URL's expiry time, or reusing Policies between multiple URL signings.
// If only the expiry time is needed you can use Sign and provide just the
// URL's expiry time. A minimum of at least one policy statement is required for a signed URL.
//
// Note: It is not safe to use Polices between multiple signers concurrently
//
// Example:
//
//     // Sign URL to be valid for 30 minutes from now, expires one hour from now, and
//     // restricted to the 192.0.2.0/24 IP address range.
//     policy := &sign.Policy{
//         Statements: []sign.Statement{
//             {
//                 Resource: rawURL,
//                 Condition: sign.Condition{
//                     // Optional IP source address range
//                     IPAddress: &sign.IPAddress{SourceIP: "192.0.2.0/24"},
//                     // Optional date URL is not valid until
//                     DateGreaterThan: &sign.AWSEpochTime{time.Now().Add(30 * time.Minute)},
//                     // Required date the URL will expire after
//                     DateLessThan: &sign.AWSEpochTime{time.Now().Add(1 * time.Hour)},
//                 },
//             },
//         },
//     }
//
//     signer := sign.NewURLSigner(keyID, privKey)
//     signedURL, err := signer.SignWithPolicy(rawURL, policy)
//     if err != nil {
//         log.Fatalf("Failed to sign url, err: %s\n", err.Error())
//     }
//
func (s URLSigner) SignWithPolicy(url string, p *Policy) (string, error) {
	scheme, cleanedURL, err := cleanURLScheme(url)
	if err != nil {
		return "", err
	}

	return signURL(scheme, cleanedURL, s.keyID, p, true, s.privKey)
}

func signURL(scheme, url, keyID string, p *Policy, customPolicy bool, privKey *rsa.PrivateKey) (string, error) {
	// Validation URL elements
	if err := validateURL(url); err != nil {
		return "", err
	}

	b64Signature, b64Policy, err := p.Sign(privKey)
	if err != nil {
		return "", err
	}

	// build and return signed URL
	builtURL := buildSignedURL(url, keyID, p, customPolicy, b64Policy, b64Signature)
	if scheme == "rtmp" {
		return buildRTMPURL(builtURL)
	}

	return builtURL, nil
}

func buildSignedURL(baseURL, keyID string, p *Policy, customPolicy bool, b64Policy, b64Signature []byte) string {
	pred := "?"
	if strings.Contains(baseURL, "?") {
		pred = "&"
	}
	signedURL := baseURL + pred

	if customPolicy {
		signedURL += "Policy=" + string(b64Policy)
	} else {
		signedURL += fmt.Sprintf("Expires=%d", p.Statements[0].Condition.DateLessThan.UTC().Unix())
	}
	signedURL += fmt.Sprintf("&Signature=%s&Key-Pair-Id=%s", string(b64Signature), keyID)

	return signedURL
}

func buildRTMPURL(u string) (string, error) {
	parsed, err := url.Parse(u)
	if err != nil {
		return "", fmt.Errorf("unable to parse rtmp signed URL, err: %s", err)
	}

	rtmpURL := strings.TrimLeft(parsed.Path, "/")
	if parsed.RawQuery != "" {
		rtmpURL = fmt.Sprintf("%s?%s", rtmpURL, parsed.RawQuery)
	}

	return rtmpURL, nil
}

func cleanURLScheme(u string) (scheme, cleanedURL string, err error) {
	parts := strings.SplitN(u, "://", 2)
	if len(parts) != 2 {
		return "", "", fmt.Errorf("invalid URL, missing scheme and domain/path")
	}
	scheme = strings.Replace(parts[0], "*", "", 1)
	cleanedURL = fmt.Sprintf("%s://%s", scheme, parts[1])

	return strings.ToLower(scheme), cleanedURL, nil
}

var illegalQueryParms = []string{"Expires", "Policy", "Signature", "Key-Pair-Id"}

func validateURL(u string) error {
	parsed, err := url.Parse(u)
	if err != nil {
		return fmt.Errorf("unable to parse URL, err: %s", err.Error())
	}

	if parsed.Scheme == "" {
		return fmt.Errorf("URL missing valid scheme, %s", u)
	}

	q := parsed.Query()
	for _, p := range illegalQueryParms {
		if _, ok := q[p]; ok {
			return fmt.Errorf("%s cannot be a query parameter for a signed URL", p)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package semtech

import (
	"github.com/thethingsnetwork/ttn/utils/pointer"
	. "github.com/thethingsnetwork/ttn/utils/testing"
	"reflect"
	"testing"
	"time"
)

func TestMarshalBinary(t *testing.T) {
	tests := []struct {
		Desc       string
		Packet     Packet
		WantError  bool
		WantHeader [12]byte
		WantJSON   string
	}{
		{
			Desc: "Invalid PUSH_DATA, invalid token",
			Packet: Packet{
				Version: VERSION,
				//No Token
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Chan: pointer.Uint(14),
							Codr: pointer.String("4/7"),
							Freq: pointer.Float64(873.14),
							Rssi: pointer.Int(-42),
						},
					},
				},
			},
			WantError: true,
		},
		{
			Desc: "Invalid PUSH_DATA, invalid gateway id",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				// No Gateway id
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Chan: pointer.Uint(14),
							Codr: pointer.String("4/7"),
							Freq: pointer.Float64(873.14),
							Rssi: pointer.Int(-42),
						},
					},
				},
			},
			WantError: true,
		},
		{
			Desc: "PUSH_DATA with no payload",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
			},
			WantError:  false,
			WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{}`,
		},
		{
			Desc: "PUSH_DATA with only basic typed-attributes  uint, string, float64 and int",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Chan: pointer.Uint(14),
							Codr: pointer.String("4/7"),
							Freq: pointer.Float64(873.14),
							Rssi: pointer.Int(-42),
						},
					},
				},
			},
			WantError:  false,
			WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"chan":14,"codr":"4/7","freq":873.14,"rssi":-42}]}`,
		},
		{
			Desc: "PUSH_DATA with datr field and modu -> LORA",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Datr: pointer.String("SF7BW125"),
							Modu: pointer.String("LORA"),
						},
					},
				},
			},
			WantError:  false,
			WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"modu":"LORA","datr":"SF7BW125"}]}`,
		},
		{
			Desc: "PUSH_DATA with datr field and modu -> FSK",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Datr: pointer.String("50000"),
							Modu: pointer.String("FSK"),
						},
					},
				},
			},
			WantError:  false,
			WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"modu":"FSK","datr":50000}]}`,
		},
		{
			Desc: "PUSH_DATA with time field",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)),
						},
					},
				},
			},
			WantError:  false,
			WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"time":"2016-01-13T17:40:57.000000376Z"}]}`,
		},
		{
			Desc: "PUSH_DATA with several RXPK",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Size: pointer.Uint(14),
						},
						RXPK{
							Chan: pointer.Uint(14),
						},
					},
				},
			},
			WantError:  false,
			WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"size":14},{"chan":14}]}`,
		},
		{
			Desc: "PUSH_DATA with several RXPK and Stat(basic fields)",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Size: pointer.Uint(14),
						},
					},
					Stat: &Stat{
						Ackr: pointer.Float64(0.78),
						Alti: pointer.Int(72),
						Rxok: pointer.Uint(42),
					},
				},
			},
			WantError:  false,
			WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"size":14}],"stat":{"ackr":0.78,"alti":72,"rxok":42}}`,
		},
		{
			Desc: "PUSH_DATA with Stat(time field)",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					Stat: &Stat{
						Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)),
					},
				},
			},
			WantError:  false,
			WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"stat":{"time":"2016-01-13 17:40:57 GMT"}}`,
		},
		{
			Desc: "PUSH_DATA with rxpk and txpk (?)",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Codr: pointer.String("4/7"),
							Rssi: pointer.Int(-42),
						},
					},
					TXPK: &TXPK{
						Ipol: pointer.Bool(true),
						Powe: pointer.Uint(12),
					},
				},
			},
			WantError:  false,
			WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"codr":"4/7","rssi":-42}],"txpk":{"ipol":true,"powe":12}}`,
		},
	}

	for _, test := range tests {
		Desc(t, test.Desc)
		raw, err := test.Packet.MarshalBinary()
		checkErrors(t, test.WantError, err)
		if test.WantError {
			continue
		}
		checkHeaders(t, test.WantHeader, raw)
		checkJSON(t, test.WantJSON, raw)
	}
}

// ----- Check utilities

func checkErrors(t *testing.T, want bool, got error) {
	if (!want && got == nil) || (want && got != nil) {
		Ok(t, "Check errors")
		return
	}
	Ko(t, "Expected no error but got: %v", got)
}

func checkHeaders(t *testing.T, want [12]byte, got []byte) {
	if len(got) < 12 {
		Ko(t, "Received header does not match expectations.\nWant: %+x\nGot:  %+x", want, got)
		return
	}
	if !reflect.DeepEqual(want[:], got[:12]) {
		Ko(t, "Received header does not match expectations.\nWant: %+x\nGot:  %+x", want, got[:12])
		return
	}
	Ok(t, "Check Headers")
}

func checkJSON(t *testing.T, want string, got []byte) {
	l := len([]byte(want))
	if len(got) < l {
		Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot:  %v", want, got)
		return
	}
	str := string(got[len(got)-l:])
	if want != str {
		Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot:  %s", want, str)
		return
	}
	Ok(t, "Check JSON")
}
<commit_msg>[refactor.semtech] Complete encode tests<commit_after>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package semtech

import (
	"github.com/thethingsnetwork/ttn/utils/pointer"
	. "github.com/thethingsnetwork/ttn/utils/testing"
	"reflect"
	"testing"
	"time"
)

func TestMarshalBinary(t *testing.T) {
	tests := []struct {
		Desc       string
		Packet     Packet
		WantError  bool
		WantHeader []byte
		WantJSON   string
	}{
		{
			Desc: "Invalid PUSH_DATA, invalid token",
			Packet: Packet{
				Version: VERSION,
				//No Token
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Chan: pointer.Uint(14),
							Codr: pointer.String("4/7"),
							Freq: pointer.Float64(873.14),
							Rssi: pointer.Int(-42),
						},
					},
				},
			},
			WantError: true,
		},
		{
			Desc: "Invalid PUSH_DATA, invalid gateway id",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				// No Gateway id
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Chan: pointer.Uint(14),
							Codr: pointer.String("4/7"),
							Freq: pointer.Float64(873.14),
							Rssi: pointer.Int(-42),
						},
					},
				},
			},
			WantError: true,
		},
		{
			Desc: "PUSH_DATA with no payload",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{}`,
		},
		{
			Desc: "PUSH_DATA with only basic typed-attributes  uint, string, float64 and int",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Chan: pointer.Uint(14),
							Codr: pointer.String("4/7"),
							Freq: pointer.Float64(873.14),
							Rssi: pointer.Int(-42),
						},
					},
				},
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"chan":14,"codr":"4/7","freq":873.14,"rssi":-42}]}`,
		},
		{
			Desc: "PUSH_DATA with datr field and modu -> LORA",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Datr: pointer.String("SF7BW125"),
							Modu: pointer.String("LORA"),
						},
					},
				},
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"modu":"LORA","datr":"SF7BW125"}]}`,
		},
		{
			Desc: "PUSH_DATA with datr field and modu -> FSK",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Datr: pointer.String("50000"),
							Modu: pointer.String("FSK"),
						},
					},
				},
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"modu":"FSK","datr":50000}]}`,
		},
		{
			Desc: "PUSH_DATA with time field",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)),
						},
					},
				},
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"time":"2016-01-13T17:40:57.000000376Z"}]}`,
		},
		{
			Desc: "PUSH_DATA with several RXPK",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Size: pointer.Uint(14),
						},
						RXPK{
							Chan: pointer.Uint(14),
						},
					},
				},
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"size":14},{"chan":14}]}`,
		},
		{
			Desc: "PUSH_DATA with several RXPK and Stat(basic fields)",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Size: pointer.Uint(14),
						},
					},
					Stat: &Stat{
						Ackr: pointer.Float64(0.78),
						Alti: pointer.Int(72),
						Rxok: pointer.Uint(42),
					},
				},
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"size":14}],"stat":{"ackr":0.78,"alti":72,"rxok":42}}`,
		},
		{
			Desc: "PUSH_DATA with Stat(time field)",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					Stat: &Stat{
						Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)),
					},
				},
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"stat":{"time":"2016-01-13 17:40:57 GMT"}}`,
		},
		{
			Desc: "PUSH_DATA with rxpk and txpk (?)",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
				Payload: &Payload{
					RXPK: []RXPK{
						RXPK{
							Codr: pointer.String("4/7"),
							Rssi: pointer.Int(-42),
						},
					},
					TXPK: &TXPK{
						Ipol: pointer.Bool(true),
						Powe: pointer.Uint(12),
					},
				},
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8},
			WantJSON:   `{"rxpk":[{"codr":"4/7","rssi":-42}],"txpk":{"ipol":true,"powe":12}}`,
		},
		{
			Desc: "PUSH_ACK valid",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PUSH_ACK,
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PUSH_ACK},
		},
		{
			Desc: "PUSH_ACK missing token",
			Packet: Packet{
				Version:    VERSION,
				Identifier: PUSH_ACK,
			},
			WantError: true,
		},
		{
			Desc: "PULL_DATA valid",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PULL_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PULL_DATA, 1, 2, 3, 4, 5, 6, 7, 8},
		},
		{
			Desc: "PULL_DATA missing token",
			Packet: Packet{
				Version:    VERSION,
				Identifier: PULL_DATA,
				GatewayId:  []byte{1, 2, 3, 4, 5, 6, 7, 8},
			},
			WantError: true,
		},
		{
			Desc: "PULL_DATA missing gatewayid",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PULL_DATA,
			},
			WantError: true,
		},
		{
			Desc: "PULL_RESP with data",
			Packet: Packet{
				Version:    VERSION,
				Identifier: PULL_RESP,
				Payload: &Payload{
					TXPK: &TXPK{
						Ipol: pointer.Bool(true),
						Powe: pointer.Uint(12),
					},
				},
			},
			WantError:  false,
			WantHeader: []byte{1, 0, 0, PULL_RESP},
			WantJSON:   `{"txpk":{"ipol":true,"powe":12}}`,
		},
		{
			Desc: "PULL_RESP empty payload",
			Packet: Packet{
				Version:    VERSION,
				Identifier: PULL_RESP,
				Payload:    &Payload{},
			},
			WantError:  false,
			WantHeader: []byte{1, 0, 0, PULL_RESP},
			WantJSON:   `{}`,
		},
		{
			Desc: "PULL_RESP no payload",
			Packet: Packet{
				Version:    VERSION,
				Identifier: PULL_RESP,
			},
			WantError:  false,
			WantHeader: []byte{1, 0, 0, PULL_RESP},
			WantJSON:   `{}`,
		},
		{
			Desc: "PULL_ACK valid",
			Packet: Packet{
				Version:    VERSION,
				Token:      []byte{0x14, 0x42},
				Identifier: PULL_ACK,
			},
			WantError:  false,
			WantHeader: []byte{1, 0x14, 0x42, PULL_ACK},
		},
		{
			Desc: "PULL_ACK missing token",
			Packet: Packet{
				Version:    VERSION,
				Identifier: PULL_ACK,
			},
			WantError: true,
		},
	}

	for _, test := range tests {
		Desc(t, test.Desc)
		raw, err := test.Packet.MarshalBinary()
		checkErrors(t, test.WantError, err)
		if test.WantError {
			continue
		}
		checkHeaders(t, test.WantHeader, raw)
		checkJSON(t, test.WantJSON, raw)
	}
}

// ----- Check utilities

func checkErrors(t *testing.T, want bool, got error) {
	if (!want && got == nil) || (want && got != nil) {
		Ok(t, "Check errors")
		return
	}
	Ko(t, "Expected no error but got: %v", got)
}

func checkHeaders(t *testing.T, want []byte, got []byte) {
	l := len(want)
	if len(got) < l {
		Ko(t, "Received header does not match expectations.\nWant: %+x\nGot:  %+x", want, got)
		return
	}
	if !reflect.DeepEqual(want[:], got[:l]) {
		Ko(t, "Received header does not match expectations.\nWant: %+x\nGot:  %+x", want, got[:l])
		return
	}
	Ok(t, "Check Headers")
}

func checkJSON(t *testing.T, want string, got []byte) {
	l := len([]byte(want))
	if len(got) < l {
		Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot:  %v", want, got)
		return
	}
	str := string(got[len(got)-l:])
	if want != str {
		Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot:  %s", want, str)
		return
	}
	Ok(t, "Check JSON")
}
<|endoftext|>