<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/signal"
	"strings"

	"github.com/gabstv/sandpiper/internal/pkg/envs"
	"github.com/gabstv/sandpiper/internal/pkg/route"
	"github.com/gabstv/sandpiper/pkg/server"
	"github.com/gabstv/sandpiper/pkg/util"
	"github.com/mattn/go-colorable"
	"github.com/mgutz/ansi"
	"gopkg.in/yaml.v2"
)

var (
	configfile string
	stdout     io.Writer
	stderr     io.Writer
)

func main() {
	stdout = colorable.NewColorableStdout()
	stderr = colorable.NewColorableStderr()
	fmt.Fprintln(stdout, ansi.Color("\nSANDPIPER 1.2.0\n", "green"))
	// init flags
	flag.Parse()
	// print help if needed
	printHelp()
	// find config
	findConfigFile()

	bs, err := ioutil.ReadFile(configfile)
	if err != nil {
		fmt.Fprintf(stderr, "ERROR: Unable to load the config file at %v!\n%v\n",
			ansi.Color(configfile, "red"),
			ansi.Color(err.Error(), "red"))
		os.Exit(1)
	}

	cfg := &Config{}
	err = yaml.Unmarshal(bs, cfg)
	if err != nil {
		fmt.Fprintf(stderr, "ERROR: Unable to unmarshal the config file at %v!\n%v\n",
			ansi.Color(configfile, "red"),
			ansi.Color(err.Error(), "red"))
		os.Exit(1)
	}

	// unpack Config
	svCfg := &server.Config{}
	svCfg.Debug = cfg.Debug
	if svCfg.Debug {
		util.DEBUG = true
	}
	svCfg.ListenAddr = cfg.ListenAddr
	svCfg.ListenAddrTLS = cfg.ListenAddrTLS
	svCfg.DisableTLS = cfg.DisableTLS
	svCfg.NumCPU = cfg.NumCPU
	svCfg.FallbackDomain = cfg.FallbackDomain
	svCfg.Graceful = cfg.Graceful
	svCfg.CachePath = cfg.CachePath
	svCfg.APIListen = cfg.APIListen
	svCfg.APIKey = cfg.APIKey
	svCfg.APIDomain = cfg.APIDomain
	svCfg.APIDomainAutocert = cfg.APIDomainAutocert
	//
	svCfg.S3Cache = cfg.S3Cache
	svCfg.S3ID = cfg.S3ID
	svCfg.S3Secret = cfg.S3Secret
	svCfg.S3Region = cfg.S3Region
	svCfg.S3Bucket = cfg.S3Bucket
	svCfg.S3Folder = cfg.S3Folder

	// ENV VARS
	if dbg, ok := envs.Debug(); ok {
		svCfg.Debug = dbg
	}
	if vv := envs.Listen(); vv != "" {
		svCfg.ListenAddr = vv
	}
	if vv := envs.ListenTLS(); vv != "" {
		svCfg.ListenAddrTLS = vv
	}
	if vv := envs.FallbackDomain(); vv != "" {
		svCfg.FallbackDomain = vv
	}

	if vv := os.Getenv("API_LISTEN"); vv != "" {
		svCfg.APIListen = vv
	}
	if vv := os.Getenv("API_KEY"); vv != "" {
		svCfg.APIKey = vv
	}
	if vv := os.Getenv("API_DOMAIN"); vv != "" {
		svCfg.APIDomain = vv
	}
	if vv := os.Getenv("API_DOMAIN_AUTOCERT"); vv != "" {
		svCfg.APIDomainAutocert = (vv == "1")
	}
	if vv := os.Getenv("LETSENCRYPT_URL"); vv != "" {
		svCfg.LetsEncryptURL = vv
	}
	if vv := os.Getenv("DISABLE_TLS"); vv != "" {
		svCfg.DisableTLS = (vv == "1")
	}
	if vv := os.Getenv("S3_CACHE"); vv != "" {
		svCfg.S3Cache = (vv == "1")
	}
	if vv := os.Getenv("S3_ID"); vv != "" {
		svCfg.S3ID = vv
	}
	if vv := os.Getenv("S3_SECRET"); vv != "" {
		svCfg.S3Secret = vv
	}
	if vv := os.Getenv("S3_REGION"); vv != "" {
		svCfg.S3Region = vv
	}
	if vv := os.Getenv("S3_BUCKET"); vv != "" {
		svCfg.S3Bucket = vv
	}
	if vv := os.Getenv("S3_FOLDER"); vv != "" {
		svCfg.S3Folder = vv
	}

	s := server.Default(svCfg)

	for _, v := range cfg.Routes {
		r := route.Route{}
		// apply Websockets config
		r.WsCFG = v.Websockets
		if ct, ok := unpackConnType(v.OutgoingServerConnType); ok {
			r.Server.OutConnType = ct
		} else {
			fmt.Fprintf(stderr, "\n CONFIGURATION ERROR\nDomain: %v\n ERR: %v\n",
				ansi.Color(v.Domain, "yellow"),
				ansi.Color("Invalid conn type "+v.OutgoingServerConnType, "red"))
			os.Exit(1)
		}
		r.Domain = v.Domain
		r.Server.OutAddress = v.OutgoingServerAddress
		r.Certificate.CertFile = v.TLSCertFile
		r.Certificate.KeyFile = v.TLSKeyFile
		r.Autocert = v.Autocert
		r.AuthMode = v.AuthMode
		r.AuthKey = v.AuthKey
		r.AuthValue = v.AuthValue
		r.ForceHTTPS = v.ForceHTTPS
		r.Server.LoadBalancer = v.LoadBalancer
		if v.Domain == "" && v.Domains != nil && len(v.Domains) > 0 {
			for _, dname := range v.Domains {
				r2 := r
				r2.Domain = dname
				err = s.Add(r2)
				if err != nil {
					fmt.Fprintf(stderr, "\nERROR: Could not add route %v\n%v\n",
						ansi.Color(dname, "yellow"),
						ansi.Color(err.Error(), "red"))
					os.Exit(1)
				}
			}
			fmt.Fprintf(stdout, "%v: %v\n",
				ansi.Color("Domain added", "green"),
				strings.Join(v.Domains, " "))
		} else {
			err = s.Add(r)
			if err != nil {
				fmt.Fprintf(stderr, "\nERROR: Could not add route %v\n%v\n",
					ansi.Color(v.Domain, "yellow"),
					ansi.Color(err.Error(), "red"))
				os.Exit(1)
			}
			fmt.Fprintf(stdout, "%v: %v\n",
				ansi.Color("Domain added", "green"),
				v.Domain)
		}
	}
	//
	if cfg.Debug {
		fmt.Fprintf(stdout, "%v DEBUG MODE IS %v\n",
			ansi.Color("WARNING:", "yellow"),
			ansi.Color("ON", "green"))
	}
	//
	// Close if received signal
	go func() {
		sigchan := make(chan os.Signal, 1)
		signal.Notify(sigchan, os.Interrupt, os.Kill)
		<-sigchan
		s.Close()
	}()
	//
	err = s.Run()
	if err != nil {
		fmt.Fprintf(stderr, "ERROR (s.Run()): %v\n",
			ansi.Color(err.Error(), "red"))
		os.Exit(1)
	}
}

func printHelp() {
	if len(flag.Args()) < 1 {
		return
	}
	if flag.Arg(0) != "help" {
		return
	}
	fmt.Fprintln(stdout, ansi.Color("Usage:", "blue"))
	fmt.Fprintf(stdout, "sandpiper %v\n", ansi.Color("config.yml", "green"))
	fmt.Fprintf(stdout, "sandpiper %v\n", ansi.Color("help", "green"))
	os.Exit(0)
}

func findConfigFile() {
	if len(flag.Args()) > 0 {
		configfile = flag.Arg(0)
		return
	}
	if env := os.Getenv("SANDPIPER_CONFIG"); env != "" {
		configfile = env
		return
	}
	if _, err := os.Stat("config.yml"); err == nil {
		configfile = "config.yml"
		return
	}
	// no config file found!
	fmt.Fprint(stderr, ansi.Color("No configuration file found!\n\n", "red"))
	fmt.Println(`There are three different ways to fix this:
  - Pass it as a parameter:
     sandpiper /path/to/config.yml
  - Set the env var SANDPIPER_CONFIG
  - Have a config.yml in the current working directory`)
	fmt.Fprintf(stdout, "\nrun %v fo more info.\n", ansi.Color("sandpiper help", "blue"))
	os.Exit(1)
}

func unpackConnType(input string) (route.ConnType, bool) {
	if input == "HTTP" {
		return route.HTTP, true
	}
	if input == "HTTPS" || input == "HTTPS_VERIFY" {
		return route.HTTPS_VERIFY, true
	}
	if input == "HTTPS_SKIP_VERIFY" {
		return route.HTTPS_SKIP_VERIFY, true
	}
	if input == "REDIRECT" {
		return route.REDIRECT, true
	}
	if input == "LOAD_BALANCER" {
		return route.LOAD_BALANCER, true
	}
	return route.HTTP, false
}

// Config main config structure (yml)
type Config struct {
	Debug             bool          `yaml:"debug"`
	NumCPU            int           `yaml:"num_cpu"`
	ListenAddr        string        `yaml:"listen_addr"`
	ListenAddrTLS     string        `yaml:"listen_addr_tls"`
	DisableTLS        bool          `yaml:"disable_tls"`
	Routes            []ConfigRoute `yaml:"routes"`
	FallbackDomain    string        `yaml:"fallback_domain"`
	Graceful          bool          `yaml:"graceful"`
	CachePath         string        `yaml:"cache_path"`
	APIListen         string        `yaml:"api_listen"`
	APIKey            string        `yaml:"api_key"`
	APIDomain         string        `yaml:"api_domain"`
	APIDomainAutocert bool          `yaml:"api_domain_autocert"`
	S3Cache           bool          `yaml:"s3_cache"`
	S3ID              string        `yaml:"s3_id"`
	S3Secret          string        `yaml:"s3_secret"`
	S3Region          string        `yaml:"s3_region"`
	S3Bucket          string        `yaml:"s3_bucket"`
	S3Folder          string        `yaml:"s3_folder"`
}

// ConfigRoute represents a domain route
type ConfigRoute struct {
	Domain                 string                    `yaml:"domain"`
	Domains                []string                  `yaml:"domains"`
	OutgoingServerConnType string                    `yaml:"out_conn_type"`
	OutgoingServerAddress  string                    `yaml:"out_addr"`
	TLSCertFile            string                    `yaml:"tls_cert_file"`
	TLSKeyFile             string                    `yaml:"tls_key_file"`
	Autocert               bool                      `yaml:"autocert"`
	Websockets             util.WsConfig             `yaml:"websockets"`
	AuthMode               string                    `yaml:"auth_mode"`
	AuthKey                string                    `yaml:"auth_key"`
	AuthValue              string                    `yaml:"auth_value"`
	ForceHTTPS             bool                      `yaml:"force_https"`
	LoadBalancer           *route.LoadBalancerConfig `yaml:"load_balancer"`
}
<commit_msg>bump version; added null checks on websocket connections<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/signal"
	"strings"

	"github.com/gabstv/sandpiper/internal/pkg/envs"
	"github.com/gabstv/sandpiper/internal/pkg/route"
	"github.com/gabstv/sandpiper/pkg/server"
	"github.com/gabstv/sandpiper/pkg/util"
	"github.com/mattn/go-colorable"
	"github.com/mgutz/ansi"
	"gopkg.in/yaml.v2"
)

var (
	configfile string
	stdout     io.Writer
	stderr     io.Writer
)

func main() {
	stdout = colorable.NewColorableStdout()
	stderr = colorable.NewColorableStderr()
	fmt.Fprintln(stdout, ansi.Color("\nSANDPIPER 1.2.1\n", "green"))
	// init flags
	flag.Parse()
	// print help if needed
	printHelp()
	// find config
	findConfigFile()

	bs, err := ioutil.ReadFile(configfile)
	if err != nil {
		fmt.Fprintf(stderr, "ERROR: Unable to load the config file at %v!\n%v\n",
			ansi.Color(configfile, "red"),
			ansi.Color(err.Error(), "red"))
		os.Exit(1)
	}

	cfg := &Config{}
	err = yaml.Unmarshal(bs, cfg)
	if err != nil {
		fmt.Fprintf(stderr, "ERROR: Unable to unmarshal the config file at %v!\n%v\n",
			ansi.Color(configfile, "red"),
			ansi.Color(err.Error(), "red"))
		os.Exit(1)
	}

	// unpack Config
	svCfg := &server.Config{}
	svCfg.Debug = cfg.Debug
	if svCfg.Debug {
		util.DEBUG = true
	}
	svCfg.ListenAddr = cfg.ListenAddr
	svCfg.ListenAddrTLS = cfg.ListenAddrTLS
	svCfg.DisableTLS = cfg.DisableTLS
	svCfg.NumCPU = cfg.NumCPU
	svCfg.FallbackDomain = cfg.FallbackDomain
	svCfg.Graceful = cfg.Graceful
	svCfg.CachePath = cfg.CachePath
	svCfg.APIListen = cfg.APIListen
	svCfg.APIKey = cfg.APIKey
	svCfg.APIDomain = cfg.APIDomain
	svCfg.APIDomainAutocert = cfg.APIDomainAutocert
	//
	svCfg.S3Cache = cfg.S3Cache
	svCfg.S3ID = cfg.S3ID
	svCfg.S3Secret = cfg.S3Secret
	svCfg.S3Region = cfg.S3Region
	svCfg.S3Bucket = cfg.S3Bucket
	svCfg.S3Folder = cfg.S3Folder

	// ENV VARS
	if dbg, ok := envs.Debug(); ok {
		svCfg.Debug = dbg
	}
	if vv := envs.Listen(); vv != "" {
		svCfg.ListenAddr = vv
	}
	if vv := envs.ListenTLS(); vv != "" {
		svCfg.ListenAddrTLS = vv
	}
	if vv := envs.FallbackDomain(); vv != "" {
		svCfg.FallbackDomain = vv
	}

	if vv := os.Getenv("API_LISTEN"); vv != "" {
		svCfg.APIListen = vv
	}
	if vv := os.Getenv("API_KEY"); vv != "" {
		svCfg.APIKey = vv
	}
	if vv := os.Getenv("API_DOMAIN"); vv != "" {
		svCfg.APIDomain = vv
	}
	if vv := os.Getenv("API_DOMAIN_AUTOCERT"); vv != "" {
		svCfg.APIDomainAutocert = (vv == "1")
	}
	if vv := os.Getenv("LETSENCRYPT_URL"); vv != "" {
		svCfg.LetsEncryptURL = vv
	}
	if vv := os.Getenv("DISABLE_TLS"); vv != "" {
		svCfg.DisableTLS = (vv == "1")
	}
	if vv := os.Getenv("S3_CACHE"); vv != "" {
		svCfg.S3Cache = (vv == "1")
	}
	if vv := os.Getenv("S3_ID"); vv != "" {
		svCfg.S3ID = vv
	}
	if vv := os.Getenv("S3_SECRET"); vv != "" {
		svCfg.S3Secret = vv
	}
	if vv := os.Getenv("S3_REGION"); vv != "" {
		svCfg.S3Region = vv
	}
	if vv := os.Getenv("S3_BUCKET"); vv != "" {
		svCfg.S3Bucket = vv
	}
	if vv := os.Getenv("S3_FOLDER"); vv != "" {
		svCfg.S3Folder = vv
	}

	s := server.Default(svCfg)

	for _, v := range cfg.Routes {
		r := route.Route{}
		// apply Websockets config
		r.WsCFG = v.Websockets
		if ct, ok := unpackConnType(v.OutgoingServerConnType); ok {
			r.Server.OutConnType = ct
		} else {
			fmt.Fprintf(stderr, "\n CONFIGURATION ERROR\nDomain: %v\n ERR: %v\n",
				ansi.Color(v.Domain, "yellow"),
				ansi.Color("Invalid conn type "+v.OutgoingServerConnType, "red"))
			os.Exit(1)
		}
		r.Domain = v.Domain
		r.Server.OutAddress = v.OutgoingServerAddress
		r.Certificate.CertFile = v.TLSCertFile
		r.Certificate.KeyFile = v.TLSKeyFile
		r.Autocert = v.Autocert
		r.AuthMode = v.AuthMode
		r.AuthKey = v.AuthKey
		r.AuthValue = v.AuthValue
		r.ForceHTTPS = v.ForceHTTPS
		r.Server.LoadBalancer = v.LoadBalancer
		if v.Domain == "" && v.Domains != nil && len(v.Domains) > 0 {
			for _, dname := range v.Domains {
				r2 := r
				r2.Domain = dname
				err = s.Add(r2)
				if err != nil {
					fmt.Fprintf(stderr, "\nERROR: Could not add route %v\n%v\n",
						ansi.Color(dname, "yellow"),
						ansi.Color(err.Error(), "red"))
					os.Exit(1)
				}
			}
			fmt.Fprintf(stdout, "%v: %v\n",
				ansi.Color("Domain added", "green"),
				strings.Join(v.Domains, " "))
		} else {
			err = s.Add(r)
			if err != nil {
				fmt.Fprintf(stderr, "\nERROR: Could not add route %v\n%v\n",
					ansi.Color(v.Domain, "yellow"),
					ansi.Color(err.Error(), "red"))
				os.Exit(1)
			}
			fmt.Fprintf(stdout, "%v: %v\n",
				ansi.Color("Domain added", "green"),
				v.Domain)
		}
	}
	//
	if cfg.Debug {
		fmt.Fprintf(stdout, "%v DEBUG MODE IS %v\n",
			ansi.Color("WARNING:", "yellow"),
			ansi.Color("ON", "green"))
	}
	//
	// Close if received signal
	go func() {
		sigchan := make(chan os.Signal, 1)
		signal.Notify(sigchan, os.Interrupt, os.Kill)
		<-sigchan
		s.Close()
	}()
	//
	err = s.Run()
	if err != nil {
		fmt.Fprintf(stderr, "ERROR (s.Run()): %v\n",
			ansi.Color(err.Error(), "red"))
		os.Exit(1)
	}
}

func printHelp() {
	if len(flag.Args()) < 1 {
		return
	}
	if flag.Arg(0) != "help" {
		return
	}
	fmt.Fprintln(stdout, ansi.Color("Usage:", "blue"))
	fmt.Fprintf(stdout, "sandpiper %v\n", ansi.Color("config.yml", "green"))
	fmt.Fprintf(stdout, "sandpiper %v\n", ansi.Color("help", "green"))
	os.Exit(0)
}

func findConfigFile() {
	if len(flag.Args()) > 0 {
		configfile = flag.Arg(0)
		return
	}
	if env := os.Getenv("SANDPIPER_CONFIG"); env != "" {
		configfile = env
		return
	}
	if _, err := os.Stat("config.yml"); err == nil {
		configfile = "config.yml"
		return
	}
	// no config file found!
	fmt.Fprint(stderr, ansi.Color("No configuration file found!\n\n", "red"))
	fmt.Println(`There are three different ways to fix this:
  - Pass it as a parameter:
     sandpiper /path/to/config.yml
  - Set the env var SANDPIPER_CONFIG
  - Have a config.yml in the current working directory`)
	fmt.Fprintf(stdout, "\nrun %v fo more info.\n", ansi.Color("sandpiper help", "blue"))
	os.Exit(1)
}

func unpackConnType(input string) (route.ConnType, bool) {
	if input == "HTTP" {
		return route.HTTP, true
	}
	if input == "HTTPS" || input == "HTTPS_VERIFY" {
		return route.HTTPS_VERIFY, true
	}
	if input == "HTTPS_SKIP_VERIFY" {
		return route.HTTPS_SKIP_VERIFY, true
	}
	if input == "REDIRECT" {
		return route.REDIRECT, true
	}
	if input == "LOAD_BALANCER" {
		return route.LOAD_BALANCER, true
	}
	return route.HTTP, false
}

// Config main config structure (yml)
type Config struct {
	Debug             bool          `yaml:"debug"`
	NumCPU            int           `yaml:"num_cpu"`
	ListenAddr        string        `yaml:"listen_addr"`
	ListenAddrTLS     string        `yaml:"listen_addr_tls"`
	DisableTLS        bool          `yaml:"disable_tls"`
	Routes            []ConfigRoute `yaml:"routes"`
	FallbackDomain    string        `yaml:"fallback_domain"`
	Graceful          bool          `yaml:"graceful"`
	CachePath         string        `yaml:"cache_path"`
	APIListen         string        `yaml:"api_listen"`
	APIKey            string        `yaml:"api_key"`
	APIDomain         string        `yaml:"api_domain"`
	APIDomainAutocert bool          `yaml:"api_domain_autocert"`
	S3Cache           bool          `yaml:"s3_cache"`
	S3ID              string        `yaml:"s3_id"`
	S3Secret          string        `yaml:"s3_secret"`
	S3Region          string        `yaml:"s3_region"`
	S3Bucket          string        `yaml:"s3_bucket"`
	S3Folder          string        `yaml:"s3_folder"`
}

// ConfigRoute represents a domain route
type ConfigRoute struct {
	Domain                 string                    `yaml:"domain"`
	Domains                []string                  `yaml:"domains"`
	OutgoingServerConnType string                    `yaml:"out_conn_type"`
	OutgoingServerAddress  string                    `yaml:"out_addr"`
	TLSCertFile            string                    `yaml:"tls_cert_file"`
	TLSKeyFile             string                    `yaml:"tls_key_file"`
	Autocert               bool                      `yaml:"autocert"`
	Websockets             util.WsConfig             `yaml:"websockets"`
	AuthMode               string                    `yaml:"auth_mode"`
	AuthKey                string                    `yaml:"auth_key"`
	AuthValue              string                    `yaml:"auth_value"`
	ForceHTTPS             bool                      `yaml:"force_https"`
	LoadBalancer           *route.LoadBalancerConfig `yaml:"load_balancer"`
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io"
	"io/ioutil"
	"log"
	"os/exec"
	"path/filepath"

	"github.com/mdlayher/tapcam/camera"
	"github.com/pkg/sftp"
)

var (
	device = flag.String("d", camera.DefaultDevice, "webcam device location")
	format = flag.String("f", string(camera.FormatJPEG), "webcam image capture format")
	size   = flag.String("s", camera.Resolution1080p.String(), "webcam image size")
	host   = flag.String("host", "", "tapcamd host")
)

func main() {
	flag.Parse()

	resolution, err := camera.NewResolution(*size)
	if err != nil {
		log.Fatal(err)
	}

	cam, err := camera.New(
		camera.SetDevice(*device),
		camera.SetFormat(camera.Format(*format)),
		camera.SetResolution(resolution),
	)
	if err != nil {
		log.Fatal(err)
	}

	rc, done, err := cam.Capture()
	if err != nil {
		log.Fatal(err)
	}

	c, cdone, err := sftpClient(*host, ioutil.Discard)
	if err != nil {
		log.Fatal(err)
	}

	const (
		dir = "/tmp/tapcam"

		tmpName  = "latest.tmp.jpg"
		permName = "latest.jpg"
	)

	tmpFullName := filepath.Join(dir, tmpName)
	permFullName := filepath.Join(dir, permName)

	f, err := c.Create(tmpFullName)
	if err != nil {
		log.Fatal(err)
	}

	if _, err := io.Copy(f, rc); err != nil {
		log.Fatal(err)
	}

	if err := done(); err != nil {
		log.Fatal(err)
	}

	if err := f.Close(); err != nil {
		log.Fatal(err)
	}

	if err := c.Remove(permFullName); err != nil {
		log.Fatal(err)
	}

	if err := c.Rename(tmpFullName, permFullName); err != nil {
		log.Fatal(err)
	}

	if err := c.Close(); err != nil {
		log.Fatal(err)
	}

	if err := cdone(); err != nil {
		log.Fatal(err)
	}
}

func sftpClient(host string, out io.Writer) (*sftp.Client, func() error, error) {
	// Connect to a remote host and request the sftp subsystem via the 'ssh'
	// command.  This assumes that passwordless login is correctly configured.
	cmd := exec.Command("ssh", host, "-s", "sftp")
	cmd.Stderr = out

	wr, err := cmd.StdinPipe()
	if err != nil {
		return nil, nil, err
	}
	rd, err := cmd.StdoutPipe()
	if err != nil {
		return nil, nil, err
	}

	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}

	c, err := sftp.NewClientPipe(rd, wr)
	return c, func() error {
		return cmd.Wait()
	}, err
}
<commit_msg>tapcamctl: add optional delay flag, enforce host flag<commit_after>package main

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

	"github.com/mdlayher/tapcam/camera"
	"github.com/pkg/sftp"
)

var (
	device = flag.String("d", camera.DefaultDevice, "webcam device location")
	format = flag.String("f", string(camera.FormatJPEG), "webcam image capture format")
	size   = flag.String("s", camera.Resolution1080p.String(), "webcam image size")
	delay  = flag.Int("delay", 0, "delay in seconds before taking a picture")
	host   = flag.String("host", "", "tapcamd host")
)

func main() {
	flag.Parse()

	if *host == "" {
		log.Fatal("must specify SFTP host")
	}

	resolution, err := camera.NewResolution(*size)
	if err != nil {
		log.Fatal(err)
	}

	cam, err := camera.New(
		camera.SetDevice(*device),
		camera.SetFormat(camera.Format(*format)),
		camera.SetResolution(resolution),
	)
	if err != nil {
		log.Fatal(err)
	}

	if d := *delay; d > 0 {
		log.Printf("taking picture in %d seconds", d)
		for i := 0; i < d; i++ {
			fmt.Print(". ")
			time.Sleep(1 * time.Second)
		}
		fmt.Println()
	}

	rc, done, err := cam.Capture()
	if err != nil {
		log.Fatal(err)
	}

	c, cdone, err := sftpClient(*host, ioutil.Discard)
	if err != nil {
		log.Fatal(err)
	}

	const (
		dir = "/tmp/tapcam"

		tmpName  = "latest.tmp.jpg"
		permName = "latest.jpg"
	)

	tmpFullName := filepath.Join(dir, tmpName)
	permFullName := filepath.Join(dir, permName)

	f, err := c.Create(tmpFullName)
	if err != nil {
		log.Fatal(err)
	}

	if _, err := io.Copy(f, rc); err != nil {
		log.Fatal(err)
	}

	if err := done(); err != nil {
		log.Fatal(err)
	}

	if err := f.Close(); err != nil {
		log.Fatal(err)
	}

	if err := c.Remove(permFullName); err != nil {
		log.Fatal(err)
	}

	if err := c.Rename(tmpFullName, permFullName); err != nil {
		log.Fatal(err)
	}

	if err := c.Close(); err != nil {
		log.Fatal(err)
	}

	if err := cdone(); err != nil {
		log.Fatal(err)
	}
}

func sftpClient(host string, out io.Writer) (*sftp.Client, func() error, error) {
	// Connect to a remote host and request the sftp subsystem via the 'ssh'
	// command.  This assumes that passwordless login is correctly configured.
	cmd := exec.Command("ssh", host, "-s", "sftp")
	cmd.Stderr = out

	wr, err := cmd.StdinPipe()
	if err != nil {
		return nil, nil, err
	}
	rd, err := cmd.StdoutPipe()
	if err != nil {
		return nil, nil, err
	}

	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}

	c, err := sftp.NewClientPipe(rd, wr)
	return c, func() error {
		return cmd.Wait()
	}, err
}
<|endoftext|>


<commit_before>package controllers

import (
	"github.com/airdispatch/dpl"
	"github.com/robfig/revel"
	"melange/app/models"
	"melange/app/routes"
	"net/http"
)

type Dispatch struct {
	GorpController
}

func (c Dispatch) Init() revel.Result {
	if c.Session["user"] == "" {
		return c.Redirect(routes.App.Login())
	}

	// Function to load user apps
	apps, err := models.GetUserApps(c.Txn, c.Session)
	if err != nil {
		panic(err)
	}

	c.RenderArgs["apps"] = apps

	return nil
}

func (d Dispatch) Dashboard() revel.Result {
	// Download all recents from subscribed, download all recents from alerts, sort them chronologically
	recents := []int{0}
	return d.Render(recents)
}

func (d Dispatch) Profile() revel.Result {
	return d.Render()
}

func (d Dispatch) All() revel.Result {
	return d.Render()
}

func (d Dispatch) Applications() revel.Result {
	return d.Render()
}

func (d Dispatch) AddApplication(url string) revel.Result {
	resp, err := http.Get(url)
	if err != nil {
		d.Flash.Error("Unable to download application via supplied url.")
		return d.Redirect(routes.Dispatch.Applications())
	}
	defer resp.Body.Close()

	plugin, err := dpl.ParseDPLStream(resp.Body)
	if err != nil {
		d.Flash.Error("Plugin is not valid.")
		return d.Redirect(routes.Dispatch.Applications())
	}

	in := &models.UserApp{
		UserId: GetUserId(d.Session),
		AppURL: url,
		Name:   plugin.Name,
		Path:   plugin.Path,
	}
	err = d.Txn.Insert(in)
	if err != nil {
		panic(err)
	}

	return d.Redirect(routes.Dispatch.Applications())
}

func (d Dispatch) UninstallApplication(app string) revel.Result {
	u := GetUserId(d.Session)

	var apps []*models.UserApp
	_, err := d.Txn.Select(&apps, "select * from dispatch_app where userid = $1 and UPPER(name) = UPPER($2)", u, app)
	if err != nil {
		panic(err)
	}
	if len(apps) != 1 {
		panic(len(apps))
	}

	toDelete := apps[0]
	count, err := d.Txn.Delete(toDelete)
	if err != nil || count != 1 {
		panic(err)
	}

	return d.Redirect(routes.Dispatch.Applications())
}

func (c Dispatch) Account() revel.Result {
	if c.Session["user"] == "" {
		return c.Redirect(routes.App.Login())
	}
	var users []*models.User

	_, err := c.Txn.Select(&users, "select * from dispatch_user where userid = $1", GetUserId(c.Session))
	if err != nil {
		panic(err)
	}
	user := users[0]

	var subscriptions []*models.UserSubscription
	_, err = c.Txn.Select(&subscriptions, "select * from dispatch_subscription where userid = $1", user.UserId)
	if err != nil {
		panic(err)
	}

	var identities []*models.Identity
	_, err = c.Txn.Select(&identities, "select * from dispatch_identity where userid = $1", user.UserId)
	if err != nil {
		panic(err)
	}

	return c.Render(user, subscriptions, identities)
}

func (c Dispatch) AddSubscription(address string) revel.Result {
	// TODO: Verify the Address Somehow...
	if false {
		c.Flash.Error("Unable to verify that address. It has not been added to your subscriptions.")
		return c.Redirect(routes.Dispatch.Account())
	}

	user := GetUserId(c.Session)
	newSub := &models.UserSubscription{
		UserId:  user,
		Address: address,
	}

	err := c.Txn.Insert(newSub)
	if err != nil {
		panic(err)
	}

	return c.Redirect(routes.Dispatch.Account())
}

func (d Dispatch) RemoveSubscription(id int) revel.Result {
	var apps []*models.UserSubscription
	_, err := d.Txn.Select(&apps, "select * from dispatch_subscription where subscriptionid = $1", id)
	if err != nil {
		panic(err)
	}
	if len(apps) != 1 {
		panic(len(apps))
	}

	toDelete := apps[0]
	count, err := d.Txn.Delete(toDelete)
	if err != nil || count != 1 {
		panic(err)
	}

	return d.Redirect(routes.Dispatch.Account())
}

func (c Dispatch) ProcessAccount() revel.Result {
	return c.Render()
}
<commit_msg>Dashboard Loads Messages<commit_after>package controllers

import (
	"github.com/airdispatch/dpl"
	"github.com/robfig/revel"
	"melange/app/models"
	"melange/app/routes"
	"melange/mailserver"
	"net/http"
	"time"
)

type Dispatch struct {
	GorpController
}

func (c Dispatch) Init() revel.Result {
	if c.Session["user"] == "" {
		return c.Redirect(routes.App.Login())
	}

	// Function to load user apps
	apps, err := models.GetUserApps(c.Txn, c.Session)
	if err != nil {
		panic(err)
	}

	c.RenderArgs["apps"] = apps

	return nil
}

func (d Dispatch) Dashboard() revel.Result {
	// Download all recents from subscribed, download all recents from alerts, sort them chronologically
	u, err := d.Txn.Get(&models.User{}, GetUserId(d.Session))
	if err != nil {
		panic(err)
	}

	id, err := models.UserIdentities(u.(*models.User), d.Txn)
	if err != nil {
		panic(err)
	}

	recents, err := mailserver.Messages(mailserver.LookupRouter, d.Txn, id[0], u.(*models.User), true, true, time.Now().Unix())
	if err != nil {
		panic(err)
	}

	return d.Render(recents)
}

func (d Dispatch) Profile() revel.Result {
	return d.Render()
}

func (d Dispatch) All() revel.Result {
	return d.Render()
}

func (d Dispatch) Applications() revel.Result {
	return d.Render()
}

func (d Dispatch) AddApplication(url string) revel.Result {
	resp, err := http.Get(url)
	if err != nil {
		d.Flash.Error("Unable to download application via supplied url.")
		return d.Redirect(routes.Dispatch.Applications())
	}
	defer resp.Body.Close()

	plugin, err := dpl.ParseDPLStream(resp.Body)
	if err != nil {
		d.Flash.Error("Plugin is not valid.")
		return d.Redirect(routes.Dispatch.Applications())
	}

	in := &models.UserApp{
		UserId: GetUserId(d.Session),
		AppURL: url,
		Name:   plugin.Name,
		Path:   plugin.Path,
	}
	err = d.Txn.Insert(in)
	if err != nil {
		panic(err)
	}

	return d.Redirect(routes.Dispatch.Applications())
}

func (d Dispatch) UninstallApplication(app string) revel.Result {
	u := GetUserId(d.Session)

	var apps []*models.UserApp
	_, err := d.Txn.Select(&apps, "select * from dispatch_app where userid = $1 and UPPER(name) = UPPER($2)", u, app)
	if err != nil {
		panic(err)
	}
	if len(apps) != 1 {
		panic(len(apps))
	}

	toDelete := apps[0]
	count, err := d.Txn.Delete(toDelete)
	if err != nil || count != 1 {
		panic(err)
	}

	return d.Redirect(routes.Dispatch.Applications())
}

func (c Dispatch) Account() revel.Result {
	if c.Session["user"] == "" {
		return c.Redirect(routes.App.Login())
	}
	var users []*models.User

	_, err := c.Txn.Select(&users, "select * from dispatch_user where userid = $1", GetUserId(c.Session))
	if err != nil {
		panic(err)
	}
	user := users[0]

	var subscriptions []*models.UserSubscription
	_, err = c.Txn.Select(&subscriptions, "select * from dispatch_subscription where userid = $1", user.UserId)
	if err != nil {
		panic(err)
	}

	var identities []*models.Identity
	_, err = c.Txn.Select(&identities, "select * from dispatch_identity where userid = $1", user.UserId)
	if err != nil {
		panic(err)
	}

	return c.Render(user, subscriptions, identities)
}

func (c Dispatch) AddSubscription(address string) revel.Result {
	// TODO: Verify the Address Somehow...
	if false {
		c.Flash.Error("Unable to verify that address. It has not been added to your subscriptions.")
		return c.Redirect(routes.Dispatch.Account())
	}

	user := GetUserId(c.Session)
	newSub := &models.UserSubscription{
		UserId:  user,
		Address: address,
	}

	err := c.Txn.Insert(newSub)
	if err != nil {
		panic(err)
	}

	return c.Redirect(routes.Dispatch.Account())
}

func (d Dispatch) RemoveSubscription(id int) revel.Result {
	var apps []*models.UserSubscription
	_, err := d.Txn.Select(&apps, "select * from dispatch_subscription where subscriptionid = $1", id)
	if err != nil {
		panic(err)
	}
	if len(apps) != 1 {
		panic(len(apps))
	}

	toDelete := apps[0]
	count, err := d.Txn.Delete(toDelete)
	if err != nil || count != 1 {
		panic(err)
	}

	return d.Redirect(routes.Dispatch.Account())
}

func (c Dispatch) ProcessAccount() revel.Result {
	return c.Render()
}
<|endoftext|>


<commit_before>package resource

import (
	"fmt"

	tfjson "github.com/hashicorp/terraform-json"
	tftest "github.com/hashicorp/terraform-plugin-test/v2"
	testing "github.com/mitchellh/go-testing-interface"

	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func testStepNewConfig(t testing.T, c TestCase, wd *tftest.WorkingDir, step TestStep) error {
	t.Helper()

	var idRefreshCheck *terraform.ResourceState
	idRefresh := c.IDRefreshName != ""

	if !step.Destroy {
		var state *terraform.State
		err := runProviderCommand(t, func() error {
			state = getState(t, wd)
			return nil
		}, wd, c.ProviderFactories)
		if err != nil {
			return err
		}
		if err := testStepTaint(state, step); err != nil {
			return fmt.Errorf("Error when tainting resources: %s", err)
		}
	}

	err := wd.SetConfig(t, step.Config)
	if err != nil {
		return fmt.Error("Error setting config: %w", err)
	}

	// require a refresh before applying
	// failing to do this will result in data sources not being updated
	err := runProviderCommand(t, func() error {
		return wd.Refresh()
	}, wd, c.ProviderFactories)
	if err != nil {
		return fmt.Errorf("Error running pre-apply refresh: %w", err)
	}

	// If this step is a PlanOnly step, skip over this first Plan and
	// subsequent Apply, and use the follow-up Plan that checks for
	// permadiffs
	if !step.PlanOnly {
		// Plan!
		err := runProviderCommand(t, func() error {
			if step.Destroy {
				return wd.CreateDestroyPlan()
			}
			return wd.CreatePlan()
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error running pre-apply plan: %w", err)
		}

		// We need to keep a copy of the state prior to destroying such
		// that the destroy steps can verify their behavior in the
		// check function
		var stateBeforeApplication *terraform.State
		err = runProviderCommand(t, func() error {
			stateBeforeApplication = getState(t, wd)
			return nil
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error retrieving pre-apply state: %w", err)
		}

		// Apply the diff, creating real resources
		err = runProviderCommand(t, func() error {
			if step.Destroy {
				return wd.Destroy()
			}
			return wd.Apply()
		}, wd, c.ProviderFactories)
		if err != nil {
			if step.Destroy {
				return fmt.Errorf("Error running destroy: %w", err)
			}
			return fmt.Errorf("Error running apply: %w", err)
		}

		// Get the new state
		var state *terraform.State
		err = runProviderCommand(t, func() error {
			state = getState(t, wd)
			return nil
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error retrieving state after apply: %w", err)
		}

		// Run any configured checks
		if step.Check != nil {
			state.IsBinaryDrivenTest = true
			if err := step.Check(state); err != nil {
				return err
			}
		}
	}

	// Test for perpetual diffs by performing a plan, a refresh, and another plan

	// do a plan
	err = runProviderCommand(t, func() error {
		if step.Destroy {
			return wd.CreateDestroyPlan()
		}
		return wd.CreatePlan()
	}, wd, c.ProviderFactories)
	if err != nil {
		return fmt.Errorf("Error running post-apply plan: %w", err)
	}

	var plan *tfjson.Plan
	err = runProviderCommand(t, func() error {
		var err error
		plan, err = wd.SavedPlan(t)
		return err
	}, wd, c.ProviderFactories)
	if err != nil {
		return fmt.Errorf("Error retrieving post-apply plan: %w", err)
	}

	if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan {
		var stdout string
		err = runProviderCommand(t, func() error {
			var err error
			stdout, err = wd.SavedPlanStdout(t)
			return err
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error retrieving formatted plan output: %w", err)
		}
		return fmt.Errorf("After applying this test step, the plan was not empty.\nstdout:\n\n%s", stdout)
	} else if step.ExpectNonEmptyPlan && planIsEmpty(plan) {
		return fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
	}

	// do a refresh
	if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
		err := runProviderCommand(t, func() error {
			return wd.Refresh()
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error running post-apply refresh: %w", err)
		}
	}

	// do another plan
	err = runProviderCommand(t, func() error {
		if step.Destroy {
			return wd.CreateDestroyPlan()
		}
		return wd.CreatePlan()
	}, wd, c.ProviderFactories)
	if err != nil {
		return fmt.Errorf("Error running second post-apply plan: %w", err)
	}

	err = runProviderCommand(t, func() error {
		var err error
		plan, err = wd.SavedPlan(t)
		return err
	}, wd, c.ProviderFactories)
	if err != nil {
		return fmt.Errorf("Error retrieving second post-apply plan: %w", err)
	}

	// check if plan is empty
	if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan {
		var stdout string
		err = runProviderCommand(t, func() error {
			var err error
			stdout, err = wd.SavedPlanStdout(t)
			return err
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error retrieving formatted second plan output: %w", err)
		}
		return fmt.Errorf("After applying this test step and performing a `terraform refresh`, the plan was not empty.\nstdout\n\n%s", stdout)
	} else if step.ExpectNonEmptyPlan && planIsEmpty(plan) {
		return fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
	}

	// ID-ONLY REFRESH
	// If we've never checked an id-only refresh and our state isn't
	// empty, find the first resource and test it.
	var state *terraform.State
	err = runProviderCommand(t, func() error {
		state = getState(t, wd)
		return nil
	}, wd, c.ProviderFactories)
	if err != nil {
		return err
	}
	if idRefresh && idRefreshCheck == nil && !state.Empty() {
		// Find the first non-nil resource in the state
		for _, m := range state.Modules {
			if len(m.Resources) > 0 {
				if v, ok := m.Resources[c.IDRefreshName]; ok {
					idRefreshCheck = v
				}

				break
			}
		}

		// If we have an instance to check for refreshes, do it
		// immediately. We do it in the middle of another test
		// because it shouldn't affect the overall state (refresh
		// is read-only semantically) and we want to fail early if
		// this fails. If refresh isn't read-only, then this will have
		// caught a different bug.
		if idRefreshCheck != nil {
			if err := testIDRefresh(c, t, wd, step, idRefreshCheck); err != nil {
				return fmt.Errorf(
					"[ERROR] Test: ID-only test failed: %s", err)
			}
		}
	}

	return nil
}
<commit_msg>Use pre-apply state when destroying.<commit_after>package resource

import (
	"fmt"

	tfjson "github.com/hashicorp/terraform-json"
	tftest "github.com/hashicorp/terraform-plugin-test/v2"
	testing "github.com/mitchellh/go-testing-interface"

	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func testStepNewConfig(t testing.T, c TestCase, wd *tftest.WorkingDir, step TestStep) error {
	t.Helper()

	var idRefreshCheck *terraform.ResourceState
	idRefresh := c.IDRefreshName != ""

	if !step.Destroy {
		var state *terraform.State
		err := runProviderCommand(t, func() error {
			state = getState(t, wd)
			return nil
		}, wd, c.ProviderFactories)
		if err != nil {
			return err
		}
		if err := testStepTaint(state, step); err != nil {
			return fmt.Errorf("Error when tainting resources: %s", err)
		}
	}

	err := wd.SetConfig(t, step.Config)
	if err != nil {
		return fmt.Error("Error setting config: %w", err)
	}

	// require a refresh before applying
	// failing to do this will result in data sources not being updated
	err := runProviderCommand(t, func() error {
		return wd.Refresh()
	}, wd, c.ProviderFactories)
	if err != nil {
		return fmt.Errorf("Error running pre-apply refresh: %w", err)
	}

	// If this step is a PlanOnly step, skip over this first Plan and
	// subsequent Apply, and use the follow-up Plan that checks for
	// permadiffs
	if !step.PlanOnly {
		// Plan!
		err := runProviderCommand(t, func() error {
			if step.Destroy {
				return wd.CreateDestroyPlan()
			}
			return wd.CreatePlan()
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error running pre-apply plan: %w", err)
		}

		// We need to keep a copy of the state prior to destroying such
		// that the destroy steps can verify their behavior in the
		// check function
		var stateBeforeApplication *terraform.State
		err = runProviderCommand(t, func() error {
			stateBeforeApplication = getState(t, wd)
			return nil
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error retrieving pre-apply state: %w", err)
		}

		// Apply the diff, creating real resources
		err = runProviderCommand(t, func() error {
			if step.Destroy {
				return wd.Destroy()
			}
			return wd.Apply()
		}, wd, c.ProviderFactories)
		if err != nil {
			if step.Destroy {
				return fmt.Errorf("Error running destroy: %w", err)
			}
			return fmt.Errorf("Error running apply: %w", err)
		}

		// Get the new state
		var state *terraform.State
		err = runProviderCommand(t, func() error {
			state = getState(t, wd)
			return nil
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error retrieving state after apply: %w", err)
		}

		// Run any configured checks
		if step.Check != nil {
			state.IsBinaryDrivenTest = true
			if step.Destroy {
				if err := step.Check(stateBeforeApplication); err != nil {
					return fmt.Errorf("Check failed: %w", err)
				}
			} else {
				if err := step.Check(state); err != nil {
					return fmt.Errorf("Check failed: %w", err)
				}
			}
		}
	}

	// Test for perpetual diffs by performing a plan, a refresh, and another plan

	// do a plan
	err = runProviderCommand(t, func() error {
		if step.Destroy {
			return wd.CreateDestroyPlan()
		}
		return wd.CreatePlan()
	}, wd, c.ProviderFactories)
	if err != nil {
		return fmt.Errorf("Error running post-apply plan: %w", err)
	}

	var plan *tfjson.Plan
	err = runProviderCommand(t, func() error {
		var err error
		plan, err = wd.SavedPlan(t)
		return err
	}, wd, c.ProviderFactories)
	if err != nil {
		return fmt.Errorf("Error retrieving post-apply plan: %w", err)
	}

	if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan {
		var stdout string
		err = runProviderCommand(t, func() error {
			var err error
			stdout, err = wd.SavedPlanStdout(t)
			return err
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error retrieving formatted plan output: %w", err)
		}
		return fmt.Errorf("After applying this test step, the plan was not empty.\nstdout:\n\n%s", stdout)
	} else if step.ExpectNonEmptyPlan && planIsEmpty(plan) {
		return fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
	}

	// do a refresh
	if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
		err := runProviderCommand(t, func() error {
			return wd.Refresh()
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error running post-apply refresh: %w", err)
		}
	}

	// do another plan
	err = runProviderCommand(t, func() error {
		if step.Destroy {
			return wd.CreateDestroyPlan()
		}
		return wd.CreatePlan()
	}, wd, c.ProviderFactories)
	if err != nil {
		return fmt.Errorf("Error running second post-apply plan: %w", err)
	}

	err = runProviderCommand(t, func() error {
		var err error
		plan, err = wd.SavedPlan(t)
		return err
	}, wd, c.ProviderFactories)
	if err != nil {
		return fmt.Errorf("Error retrieving second post-apply plan: %w", err)
	}

	// check if plan is empty
	if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan {
		var stdout string
		err = runProviderCommand(t, func() error {
			var err error
			stdout, err = wd.SavedPlanStdout(t)
			return err
		}, wd, c.ProviderFactories)
		if err != nil {
			return fmt.Errorf("Error retrieving formatted second plan output: %w", err)
		}
		return fmt.Errorf("After applying this test step and performing a `terraform refresh`, the plan was not empty.\nstdout\n\n%s", stdout)
	} else if step.ExpectNonEmptyPlan && planIsEmpty(plan) {
		return fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
	}

	// ID-ONLY REFRESH
	// If we've never checked an id-only refresh and our state isn't
	// empty, find the first resource and test it.
	var state *terraform.State
	err = runProviderCommand(t, func() error {
		state = getState(t, wd)
		return nil
	}, wd, c.ProviderFactories)
	if err != nil {
		return err
	}
	if idRefresh && idRefreshCheck == nil && !state.Empty() {
		// Find the first non-nil resource in the state
		for _, m := range state.Modules {
			if len(m.Resources) > 0 {
				if v, ok := m.Resources[c.IDRefreshName]; ok {
					idRefreshCheck = v
				}

				break
			}
		}

		// If we have an instance to check for refreshes, do it
		// immediately. We do it in the middle of another test
		// because it shouldn't affect the overall state (refresh
		// is read-only semantically) and we want to fail early if
		// this fails. If refresh isn't read-only, then this will have
		// caught a different bug.
		if idRefreshCheck != nil {
			if err := testIDRefresh(c, t, wd, step, idRefreshCheck); err != nil {
				return fmt.Errorf(
					"[ERROR] Test: ID-only test failed: %s", err)
			}
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package swim

import (
	"math"
	"sync"

	log "github.com/uber-common/bark"
)

var log10 = math.Log(10)

// A pChange is a change with a p count representing the number of times the
// change has been propagated to other nodes.
type pChange struct {
	Change
	p int
}

// A disseminator propagates changes to other nodes.
type disseminator struct {
	node    *Node
	changes map[string]*pChange
	maxP    int
	pFactor int

	sync.RWMutex
}

// newDisseminator returns a new Disseminator instance.
func newDisseminator(n *Node) *disseminator {
	d := &disseminator{
		node:    n,
		changes: make(map[string]*pChange),
		maxP:    1,
		pFactor: 15,
	}

	return d
}

func (d *disseminator) AdjustMaxPropagations() {
	if !d.node.Ready() {
		return
	}

	d.Lock()

	numPingable := d.node.memberlist.NumPingableMembers()
	prevMaxP := d.maxP

	newMaxP := d.pFactor * int(math.Ceil(math.Log(float64(numPingable+1))/log10))

	if newMaxP != prevMaxP {
		d.maxP = newMaxP

		d.node.emit(MaxPAdjustedEvent{prevMaxP, newMaxP})

		d.node.log.WithFields(log.Fields{
			"newMax":            newMaxP,
			"prevMax":           prevMaxP,
			"propagationFactor": d.pFactor,
			"numPingable":       numPingable,
		}).Debug("adjusted max propagation count")
	}

	d.Unlock()
}

// HasChanges reports whether disseminator has changes to disseminate.
func (d *disseminator) HasChanges() bool {
	return len(d.changes) > 0
}

func (d *disseminator) FullSync() (changes []Change) {
	d.Lock()

	for _, member := range d.node.memberlist.GetMembers() {
		changes = append(changes, Change{
			Address:           member.Address,
			Incarnation:       member.Incarnation,
			Source:            d.node.Address(),
			SourceIncarnation: d.node.Incarnation(),
			Status:            member.Status,
		})
	}

	d.Unlock()

	return changes
}

func (d *disseminator) IssueAsSender() []Change {
	return d.issueChanges(nil)
}

func (d *disseminator) IssueAsReceiver(senderAddress string,
	senderIncarnation int64, senderChecksum uint32) ([]Change, bool) {

	filter := func(c *pChange) bool {
		return senderIncarnation == c.SourceIncarnation &&
			senderAddress == c.Source
	}

	changes := d.issueChanges(filter)

	if len(changes) > 0 {
		return changes, false
	} else if d.node.memberlist.Checksum() != senderChecksum {
		d.node.emit(FullSyncEvent{senderAddress, senderChecksum})

		d.node.log.WithFields(log.Fields{
			"localChecksum":  d.node.memberlist.Checksum(),
			"remote":         senderAddress,
			"remoteChecksum": senderChecksum,
		}).Info("full sync")

		return d.FullSync(), true
	}

	return []Change{}, false
}

func (d *disseminator) issueChanges(filter func(*pChange) bool) (changes []Change) {
	d.Lock()

	// To make JSON output [] instead of null on empty change list
	changes = make([]Change, 0)
	for _, change := range d.changes {
		if filter != nil && filter(change) {
			d.node.emit(ChangeFilteredEvent{change.Change})
			continue
		}

		change.p++

		if change.p > d.maxP {
			delete(d.changes, change.Address)
			continue
		}

		changes = append(changes, change.Change)
	}

	d.Unlock()

	d.node.emit(ChangesCalculatedEvent{changes})

	return changes
}

func (d *disseminator) ClearChanges() {
	d.Lock()
	d.changes = make(map[string]*pChange)
	d.Unlock()
}

func (d *disseminator) RecordChange(change Change) {
	d.Lock()
	d.changes[change.Address] = &pChange{change, 0}
	d.Unlock()
}
<commit_msg>Add locks in disseminator.HasChanges method<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package swim

import (
	"math"
	"sync"

	log "github.com/uber-common/bark"
)

var log10 = math.Log(10)

// A pChange is a change with a p count representing the number of times the
// change has been propagated to other nodes.
type pChange struct {
	Change
	p int
}

// A disseminator propagates changes to other nodes.
type disseminator struct {
	node    *Node
	changes map[string]*pChange
	maxP    int
	pFactor int

	sync.RWMutex
}

// newDisseminator returns a new Disseminator instance.
func newDisseminator(n *Node) *disseminator {
	d := &disseminator{
		node:    n,
		changes: make(map[string]*pChange),
		maxP:    1,
		pFactor: 15,
	}

	return d
}

func (d *disseminator) AdjustMaxPropagations() {
	if !d.node.Ready() {
		return
	}

	d.Lock()

	numPingable := d.node.memberlist.NumPingableMembers()
	prevMaxP := d.maxP

	newMaxP := d.pFactor * int(math.Ceil(math.Log(float64(numPingable+1))/log10))

	if newMaxP != prevMaxP {
		d.maxP = newMaxP

		d.node.emit(MaxPAdjustedEvent{prevMaxP, newMaxP})

		d.node.log.WithFields(log.Fields{
			"newMax":            newMaxP,
			"prevMax":           prevMaxP,
			"propagationFactor": d.pFactor,
			"numPingable":       numPingable,
		}).Debug("adjusted max propagation count")
	}

	d.Unlock()
}

// HasChanges reports whether disseminator has changes to disseminate.
func (d *disseminator) HasChanges() bool {
	d.Lock()
	r := len(d.changes) > 0
	d.Unlock()
	return r
}

func (d *disseminator) FullSync() (changes []Change) {
	d.Lock()

	for _, member := range d.node.memberlist.GetMembers() {
		changes = append(changes, Change{
			Address:           member.Address,
			Incarnation:       member.Incarnation,
			Source:            d.node.Address(),
			SourceIncarnation: d.node.Incarnation(),
			Status:            member.Status,
		})
	}

	d.Unlock()

	return changes
}

func (d *disseminator) IssueAsSender() []Change {
	return d.issueChanges(nil)
}

func (d *disseminator) IssueAsReceiver(senderAddress string,
	senderIncarnation int64, senderChecksum uint32) ([]Change, bool) {

	filter := func(c *pChange) bool {
		return senderIncarnation == c.SourceIncarnation &&
			senderAddress == c.Source
	}

	changes := d.issueChanges(filter)

	if len(changes) > 0 {
		return changes, false
	} else if d.node.memberlist.Checksum() != senderChecksum {
		d.node.emit(FullSyncEvent{senderAddress, senderChecksum})

		d.node.log.WithFields(log.Fields{
			"localChecksum":  d.node.memberlist.Checksum(),
			"remote":         senderAddress,
			"remoteChecksum": senderChecksum,
		}).Info("full sync")

		return d.FullSync(), true
	}

	return []Change{}, false
}

func (d *disseminator) issueChanges(filter func(*pChange) bool) (changes []Change) {
	d.Lock()

	// To make JSON output [] instead of null on empty change list
	changes = make([]Change, 0)
	for _, change := range d.changes {
		if filter != nil && filter(change) {
			d.node.emit(ChangeFilteredEvent{change.Change})
			continue
		}

		change.p++

		if change.p > d.maxP {
			delete(d.changes, change.Address)
			continue
		}

		changes = append(changes, change.Change)
	}

	d.Unlock()

	d.node.emit(ChangesCalculatedEvent{changes})

	return changes
}

func (d *disseminator) ClearChanges() {
	d.Lock()
	d.changes = make(map[string]*pChange)
	d.Unlock()
}

func (d *disseminator) RecordChange(change Change) {
	d.Lock()
	d.changes[change.Address] = &pChange{change, 0}
	d.Unlock()
}
<|endoftext|>


<commit_before>package collins

import (
	"github.com/schallert/iso8601"
)

// AssetFindOpts contains the various options for find queries to collins.
// It can be used to query for things like asset type, asset status or arbitrary
// attributes set on assets. It also provides options for pagination.
type AssetFindOpts struct {
	Details      bool   `url:"details,omitempty"`      // True to return full assets, false to return partials. Defaults to false.
	RemoteLookup bool   `url:"remoteLookup,omitempty"` // True to search remote datacenters as well. See the MultiCollins documentation.
	Operation    string `url:"operation,omitempty"`    // "AND" or "OR", defaults to "OR".
	Type         string `url:"type,omitempty"`         // A valid asset type (e.g. SERVER_NODE)
	Status       string `url:"status,omitempty"`       // A valid asset status (e.g. Unallocated)
	State        string `url:"state,omitempty"`        // A valid asset state (e.g. RUNNING)
	Attribute    string `url:"attribute,omitempty"`    // Specified as tagname;tagvalue. tagname can be a reserved tag such as CPU_COUNT, MEMORY_SIZE_BYTES, etc. Leave tagvalue blank to find assets missing a particular attribute.
	Query        string `url:"query,omitempty"`        // Specify a CQL query

	// These need to be pointers otherwise marshalling them will result in
	// all time fields being sent as "0001-01-01 00:00:00 +0000 UTC"
	CreatedBefore *iso8601.Time `url:"createdBefore,omitempty"` // ISO8601 formatted
	CreatedAfter  *iso8601.Time `url:"createdAfter,omitempty"`  // ISO8601 formatted
	UpdatedBefore *iso8601.Time `url:"updatedBefore,omitempty"` // ISO8601 formatted
	UpdatedAfter  *iso8601.Time `url:"updatedAfter,omitempty"`  // ISO8601 formatted

	// Embed pagination options directly in the options
	PageOpts
}

// AssetService.Find searches for assets in collins. See AssetFindOpts for
// available options.
//
// http://tumblr.github.io/collins/api.html#api-asset-asset-find
func (a AssetService) Find(opts *AssetFindOpts) ([]Asset, *Response, error) {
	ustr, err := addOptions("api/assets", opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := a.client.NewRequest("GET", ustr)
	if err != nil {
		return nil, nil, err
	}

	var c struct {
		Assets []Asset `json:"Data"`
	}

	resp, err := a.client.Do(req, &c)
	if err != nil {
		return nil, resp, err
	}

	return c.Assets, resp, nil
}

// AssetService.FindSimilar queries collins for assets that are similar to the
// asset represented by the tag passed as a parameter. See AssetFindOpts for
// availble options.
//
// http://tumblr.github.io/collins/api.html#api-asset-asset-find-similar
func (a AssetService) FindSimilar(tag string, opts *AssetFindOpts) ([]Asset, *Response, error) {
	ustr, err := addOptions("api/asset/"+tag+"/similar", nil)
	if err != nil {
		return nil, nil, err
	}

	req, err := a.client.NewRequest("GET", ustr)
	if err != nil {
		return nil, nil, err
	}

	var c struct {
		Meta []Metadata `json:"Data"`
	}

	resp, err := a.client.Do(req, &c)
	if err != nil {
		return nil, resp, err
	}

	assets := make([]Asset, len(c.Meta), len(c.Meta))
	for i := range assets {
		assets[i].Metadata = c.Meta[i]
	}

	return assets, resp, nil
}
<commit_msg>fix typo<commit_after>package collins

import (
	"github.com/schallert/iso8601"
)

// AssetFindOpts contains the various options for find queries to collins.
// It can be used to query for things like asset type, asset status or arbitrary
// attributes set on assets. It also provides options for pagination.
type AssetFindOpts struct {
	Details      bool   `url:"details,omitempty"`      // True to return full assets, false to return partials. Defaults to false.
	RemoteLookup bool   `url:"remoteLookup,omitempty"` // True to search remote datacenters as well. See the MultiCollins documentation.
	Operation    string `url:"operation,omitempty"`    // "AND" or "OR", defaults to "OR".
	Type         string `url:"type,omitempty"`         // A valid asset type (e.g. SERVER_NODE)
	Status       string `url:"status,omitempty"`       // A valid asset status (e.g. Unallocated)
	State        string `url:"state,omitempty"`        // A valid asset state (e.g. RUNNING)
	Attribute    string `url:"attribute,omitempty"`    // Specified as tagname;tagvalue. tagname can be a reserved tag such as CPU_COUNT, MEMORY_SIZE_BYTES, etc. Leave tagvalue blank to find assets missing a particular attribute.
	Query        string `url:"query,omitempty"`        // Specify a CQL query

	// These need to be pointers otherwise marshalling them will result in
	// all time fields being sent as "0001-01-01 00:00:00 +0000 UTC"
	CreatedBefore *iso8601.Time `url:"createdBefore,omitempty"` // ISO8601 formatted
	CreatedAfter  *iso8601.Time `url:"createdAfter,omitempty"`  // ISO8601 formatted
	UpdatedBefore *iso8601.Time `url:"updatedBefore,omitempty"` // ISO8601 formatted
	UpdatedAfter  *iso8601.Time `url:"updatedAfter,omitempty"`  // ISO8601 formatted

	// Embed pagination options directly in the options
	PageOpts
}

// AssetService.Find searches for assets in collins. See AssetFindOpts for
// available options.
//
// http://tumblr.github.io/collins/api.html#api-asset-asset-find
func (a AssetService) Find(opts *AssetFindOpts) ([]Asset, *Response, error) {
	ustr, err := addOptions("api/assets", opts)
	if err != nil {
		return nil, nil, err
	}

	req, err := a.client.NewRequest("GET", ustr)
	if err != nil {
		return nil, nil, err
	}

	var c struct {
		Assets []Asset `json:"Data"`
	}

	resp, err := a.client.Do(req, &c)
	if err != nil {
		return nil, resp, err
	}

	return c.Assets, resp, nil
}

// AssetService.FindSimilar queries collins for assets that are similar to the
// asset represented by the tag passed as a parameter. See AssetFindOpts for
// available options.
//
// http://tumblr.github.io/collins/api.html#api-asset-asset-find-similar
func (a AssetService) FindSimilar(tag string, opts *AssetFindOpts) ([]Asset, *Response, error) {
	ustr, err := addOptions("api/asset/"+tag+"/similar", nil)
	if err != nil {
		return nil, nil, err
	}

	req, err := a.client.NewRequest("GET", ustr)
	if err != nil {
		return nil, nil, err
	}

	var c struct {
		Meta []Metadata `json:"Data"`
	}

	resp, err := a.client.Do(req, &c)
	if err != nil {
		return nil, resp, err
	}

	assets := make([]Asset, len(c.Meta), len(c.Meta))
	for i := range assets {
		assets[i].Metadata = c.Meta[i]
	}

	return assets, resp, nil
}
<|endoftext|>


<commit_before>package commands

import (
	"bufio"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/csaunders/themekit"
)

type ConfigurationOptions struct {
	BasicOptions
	Directory   string
	Environment string
	Domain      string
	AccessToken string
	BucketSize  int
	RefillRate  int
}

func (co ConfigurationOptions) areInvalid() bool {
	return co.Domain == "" || co.AccessToken == ""
}

func (co ConfigurationOptions) defaultConfigurationOptions() themekit.Configuration {
	return themekit.Configuration{
		Domain:      co.Domain,
		AccessToken: co.AccessToken,
		BucketSize:  co.BucketSize,
		RefillRate:  co.RefillRate,
	}
}

func (co ConfigurationOptions) configurationErrors() error {
	var errs = []string{}
	if len(co.Domain) <= 0 {
		errs = append(errs, "\t-domain cannot be blank")
	}
	if len(co.AccessToken) <= 0 {
		errs = append(errs, "\t-access_token cannot be blank")
	}
	if len(errs) > 0 {
		fullPath := filepath.Join(co.Directory, "config.yml")
		return errors.New(fmt.Sprintf("Cannot create %s!\nErrors:\n%s", fullPath, strings.Join(errs, "\n")))
	}
	return nil
}

func defaultOptions() ConfigurationOptions {
	currentDir, _ := os.Getwd()
	return ConfigurationOptions{
		Domain:      "",
		AccessToken: "",
		Directory:   currentDir,
		Environment: themekit.DefaultEnvironment,
		BucketSize:  themekit.DefaultBucketSize,
		RefillRate:  themekit.DefaultRefillRate,
	}
}

func ConfigureCommand(args map[string]interface{}) chan bool {
	options := defaultOptions()

	extractString(&options.Environment, "environment", args)
	extractString(&options.Directory, "directory", args)
	extractString(&options.Domain, "domain", args)
	extractString(&options.AccessToken, "access_token", args)
	extractInt(&options.BucketSize, "bucket_size", args)
	extractInt(&options.RefillRate, "refill_rate", args)
	extractEventLog(&options.EventLog, args)

	if options.areInvalid() {
		themekit.NotifyError(options.configurationErrors())
	}

	Configure(options)
	done := make(chan bool)
	close(done)
	return done
}

func Configure(options ConfigurationOptions) {
	config := options.defaultConfigurationOptions()
	AddConfiguration(options.Directory, options.Environment, config)
}

func AddConfiguration(dir, environment string, config themekit.Configuration) {
	environmentLocation := filepath.Join(dir, "config.yml")
	env, err := loadOrInitializeEnvironment(environmentLocation)
	if err != nil {
		themekit.NotifyError(err)
		return
	}
	env.SetConfiguration(environment, config)

	err = env.Save(environmentLocation)
	if err != nil {
		themekit.NotifyError(err)
	}
}

func MigrateConfigurationCommand(args map[string]interface{}) (done chan bool, log chan themekit.ThemeEvent) {
	dir, _ := os.Getwd()
	extractString(&dir, "directory", args)

	MigrateConfiguration(dir)

	done = make(chan bool)
	log = make(chan themekit.ThemeEvent)
	close(done)
	close(log)
	return
}

func PrepareConfigurationMigration(dir string) (func() bool, func() error) {
	environmentLocation := filepath.Join(dir, "config.yml")
	env, err := loadOrInitializeEnvironment(environmentLocation)
	if err != nil {
		themekit.NotifyError(err)
		return func() bool { return false }, func() error { return err }
	}

	confirmationFn := func() bool {
		before, _ := ioutil.ReadFile(environmentLocation)
		after := env.String()
		fmt.Println(themekit.YellowText("Compare changes to configuration:"))
		fmt.Println(themekit.YellowText("Before:\n"), themekit.GreenText(string(before)))
		fmt.Println(themekit.YellowText("After:\n"), themekit.RedText(after))
		reader := bufio.NewReader(os.Stdin)
		fmt.Println(themekit.YellowText("Does this look correct? (y/n)"))
		text, _ := reader.ReadString('\n')
		return text == "y"
	}

	saveFn := func() error {
		return env.Save(environmentLocation)
	}
	return confirmationFn, saveFn
}

func MigrateConfiguration(dir string) error {
	environmentLocation := filepath.Join(dir, "config.yml")
	env, err := loadOrInitializeEnvironment(environmentLocation)
	if err != nil {
		themekit.NotifyError(err)
		return err
	}

	err = env.Save(environmentLocation)
	return err
}

func loadOrInitializeEnvironment(location string) (themekit.Environments, error) {
	contents, err := ioutil.ReadFile(location)
	if err != nil {
		return themekit.Environments{}, err
	}

	env, err := themekit.LoadEnvironments(contents)
	if (err != nil && canProcessWithError(err)) || len(env) <= 0 {
		conf, _ := themekit.LoadConfiguration(contents)
		env[themekit.DefaultEnvironment] = conf
	}
	return env, err
}

func canProcessWithError(e error) bool {
	return strings.Contains(e.Error(), "YAML error") == false
}
<commit_msg>Fixes regression in configuration creation<commit_after>package commands

import (
	"bufio"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/csaunders/themekit"
)

type ConfigurationOptions struct {
	BasicOptions
	Directory   string
	Environment string
	Domain      string
	AccessToken string
	BucketSize  int
	RefillRate  int
}

func (co ConfigurationOptions) areInvalid() bool {
	return co.Domain == "" || co.AccessToken == ""
}

func (co ConfigurationOptions) defaultConfigurationOptions() themekit.Configuration {
	return themekit.Configuration{
		Domain:      co.Domain,
		AccessToken: co.AccessToken,
		BucketSize:  co.BucketSize,
		RefillRate:  co.RefillRate,
	}
}

func (co ConfigurationOptions) configurationErrors() error {
	var errs = []string{}
	if len(co.Domain) <= 0 {
		errs = append(errs, "\t-domain cannot be blank")
	}
	if len(co.AccessToken) <= 0 {
		errs = append(errs, "\t-access_token cannot be blank")
	}
	if len(errs) > 0 {
		fullPath := filepath.Join(co.Directory, "config.yml")
		return errors.New(fmt.Sprintf("Cannot create %s!\nErrors:\n%s", fullPath, strings.Join(errs, "\n")))
	}
	return nil
}

func defaultOptions() ConfigurationOptions {
	currentDir, _ := os.Getwd()
	return ConfigurationOptions{
		Domain:      "",
		AccessToken: "",
		Directory:   currentDir,
		Environment: themekit.DefaultEnvironment,
		BucketSize:  themekit.DefaultBucketSize,
		RefillRate:  themekit.DefaultRefillRate,
	}
}

func ConfigureCommand(args map[string]interface{}) chan bool {
	options := defaultOptions()

	extractString(&options.Environment, "environment", args)
	extractString(&options.Directory, "directory", args)
	extractString(&options.Domain, "domain", args)
	extractString(&options.AccessToken, "access_token", args)
	extractInt(&options.BucketSize, "bucket_size", args)
	extractInt(&options.RefillRate, "refill_rate", args)
	extractEventLog(&options.EventLog, args)

	if options.areInvalid() {
		themekit.NotifyError(options.configurationErrors())
	}

	Configure(options)
	done := make(chan bool)
	close(done)
	return done
}

func Configure(options ConfigurationOptions) {
	config := options.defaultConfigurationOptions()
	AddConfiguration(options.Directory, options.Environment, config)
}

func AddConfiguration(dir, environment string, config themekit.Configuration) {
	environmentLocation := filepath.Join(dir, "config.yml")
	env, err := loadOrInitializeEnvironment(environmentLocation)
	env.SetConfiguration(environment, config)

	err = env.Save(environmentLocation)
	if err != nil {
		themekit.NotifyError(err)
	}
}

func MigrateConfigurationCommand(args map[string]interface{}) (done chan bool, log chan themekit.ThemeEvent) {
	dir, _ := os.Getwd()
	extractString(&dir, "directory", args)

	MigrateConfiguration(dir)

	done = make(chan bool)
	log = make(chan themekit.ThemeEvent)
	close(done)
	close(log)
	return
}

func PrepareConfigurationMigration(dir string) (func() bool, func() error) {
	environmentLocation := filepath.Join(dir, "config.yml")
	env, err := loadOrInitializeEnvironment(environmentLocation)
	if err != nil {
		themekit.NotifyError(err)
		return func() bool { return false }, func() error { return err }
	}

	confirmationFn := func() bool {
		before, _ := ioutil.ReadFile(environmentLocation)
		after := env.String()
		fmt.Println(themekit.YellowText("Compare changes to configuration:"))
		fmt.Println(themekit.YellowText("Before:\n"), themekit.GreenText(string(before)))
		fmt.Println(themekit.YellowText("After:\n"), themekit.RedText(after))
		reader := bufio.NewReader(os.Stdin)
		fmt.Println(themekit.YellowText("Does this look correct? (y/n)"))
		text, _ := reader.ReadString('\n')
		return text == "y"
	}

	saveFn := func() error {
		return env.Save(environmentLocation)
	}
	return confirmationFn, saveFn
}

func MigrateConfiguration(dir string) error {
	environmentLocation := filepath.Join(dir, "config.yml")
	env, err := loadOrInitializeEnvironment(environmentLocation)
	if err != nil {
		themekit.NotifyError(err)
		return err
	}

	err = env.Save(environmentLocation)
	return err
}

func loadOrInitializeEnvironment(location string) (themekit.Environments, error) {
	contents, err := ioutil.ReadFile(location)
	if err != nil {
		return themekit.Environments{}, err
	}

	env, err := themekit.LoadEnvironments(contents)
	if (err != nil && canProcessWithError(err)) || len(env) <= 0 {
		conf, _ := themekit.LoadConfiguration(contents)
		env[themekit.DefaultEnvironment] = conf
	}
	return env, err
}

func canProcessWithError(e error) bool {
	return strings.Contains(e.Error(), "YAML error") == false
}
<|endoftext|>


<commit_before>package commands

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/cloudfoundry/bosh-bootloader/fileio"
	"github.com/cloudfoundry/bosh-bootloader/flags"
	"github.com/cloudfoundry/bosh-bootloader/renderers"
	"github.com/cloudfoundry/bosh-bootloader/storage"
)

// PrintEnv defines a PrintEnv command
type PrintEnv struct {
	stateValidator   stateValidator
	logger           logger
	stderrLogger     logger
	allProxyGetter   allProxyGetter
	terraformManager terraformManager
	credhubGetter    credhubGetter
	fs               fs
	rendererFactory  renderers.Factory
}

type envSetter interface {
	Set(key, value string) error
}

type credhubGetter interface {
	GetServer() (string, error)
	GetCerts() (string, error)
	GetPassword() (string, error)
}

type allProxyGetter interface {
	GeneratePrivateKey() (string, error)
	BoshAllProxy(string, string) string
}

type fs interface {
	fileio.TempDirer
	fileio.FileWriter
}

type PrintEnvConfig struct {
	shellType    string
	metadataFile string
}

// NewPrintEnv creates a new PrintEnv Command
func NewPrintEnv(
	logger logger,
	stderrLogger logger,
	stateValidator stateValidator,
	allProxyGetter allProxyGetter,
	credhubGetter credhubGetter,
	terraformManager terraformManager,
	fs fs,
	rendererFactory renderers.Factory) PrintEnv {
	return PrintEnv{
		stateValidator:   stateValidator,
		logger:           logger,
		stderrLogger:     stderrLogger,
		allProxyGetter:   allProxyGetter,
		terraformManager: terraformManager,
		credhubGetter:    credhubGetter,
		fs:               fs,
		rendererFactory:  rendererFactory,
	}
}

func (p PrintEnv) CheckFastFails(subcommandFlags []string, state storage.State) error {
	// We don't do any validation here, because at this point, we don't know if we're using
	// a bbl-state or a metadata file. Once we know that we're using a bbl-state, in Execute,
	// we validate that the state exists.
	return nil
}

func (p PrintEnv) ParseArgs(args []string, state storage.State) (PrintEnvConfig, error) {
	var (
		config PrintEnvConfig
	)

	printEnvFlags := flags.New("print-env")
	printEnvFlags.String(&config.shellType, "shell-type", "")
	printEnvFlags.String(&config.metadataFile, "file", "")

	err := printEnvFlags.Parse(args)
	if err != nil {
		return PrintEnvConfig{}, err
	}

	return config, nil
}

func (p PrintEnv) Execute(args []string, state storage.State) error {
	variables := make(map[string]string)

	config, err := p.ParseArgs(args, state)
	if err != nil {
		return err
	}

	shell := config.shellType
	renderer, err := p.rendererFactory.Create(shell)
	if err != nil {
		return err
	}

	useMetadata := false
	metadataFile := config.metadataFile
	if metadataFile != "" {
		useMetadata = true
	}

	if useMetadata {
		var metadata struct {
			Name     string            `json:"name"`
			IaasType string            `json:"iaas_type"`
			Bosh     map[string]string `json:"bosh"`
		}
		metadataContents, err := ioutil.ReadFile(metadataFile)
		if err != nil {
			p.stderrLogger.Println(fmt.Sprintf("Failed to read %s: %s", metadataFile, err))
			return err
		}

		err = json.Unmarshal(metadataContents, &metadata)
		if err != nil {
			p.stderrLogger.Println(fmt.Sprintf("Failed to unmarshal %s: %s", metadataFile, err))
			return err
		}

		variables["BOSH_CLIENT"] = metadata.Bosh["bosh_client"]
		variables["BOSH_CLIENT_SECRET"] = metadata.Bosh["bosh_client_secret"]
		variables["BOSH_ENVIRONMENT"] = metadata.Bosh["bosh_environment"]
		variables["BOSH_CA_CERT"] = metadata.Bosh["bosh_ca_cert"]
		variables["CREDHUB_CLIENT"] = metadata.Bosh["credhub_client"]
		variables["CREDHUB_SECRET"] = metadata.Bosh["credhub_secret"]
		variables["CREDHUB_SERVER"] = metadata.Bosh["credhub_server"]
		variables["CREDHUB_CA_CERT"] = metadata.Bosh["credhub_ca_cert"]

		privateKeyPath := fmt.Sprintf("/tmp/%s.priv", metadata.Name)
		err = ioutil.WriteFile(privateKeyPath, []byte(metadata.Bosh["jumpbox_private_key"]), 0600)
		if err != nil {
			p.stderrLogger.Println(fmt.Sprintf("Failed to write private key to %s: %s", privateKeyPath, err))
			return err
		}

		variables["JUMPBOX_PRIVATE_KEY"] = privateKeyPath
		boshAllProxy := fmt.Sprintf("%s=%s", strings.Split(metadata.Bosh["bosh_all_proxy"], "=")[0], privateKeyPath)
		variables["BOSH_ALL_PROXY"] = boshAllProxy
		variables["CREDHUB_PROXY"] = boshAllProxy

		p.renderVariables(renderer, variables)
		return nil
	}

	err = p.stateValidator.Validate()
	if err != nil {
		return err
	}

	variables["BOSH_CLIENT"] = state.BOSH.DirectorUsername
	variables["BOSH_CLIENT_SECRET"] = state.BOSH.DirectorPassword
	variables["BOSH_ENVIRONMENT"] = state.BOSH.DirectorAddress
	variables["BOSH_CA_CERT"] = state.BOSH.DirectorSSLCA
	variables["CREDHUB_CLIENT"] = "credhub-admin"

	credhubPassword, err := p.credhubGetter.GetPassword()
	if err == nil {
		variables["CREDHUB_SECRET"] = credhubPassword
	} else {
		p.stderrLogger.Println("No credhub password found.")
	}

	credhubServer, err := p.credhubGetter.GetServer()
	if err == nil {
		variables["CREDHUB_SERVER"] = credhubServer
	} else {
		p.stderrLogger.Println("No credhub server found.")
	}

	credhubCerts, err := p.credhubGetter.GetCerts()
	if err == nil {
		variables["CREDHUB_CA_CERT"] = credhubCerts
	} else {
		p.stderrLogger.Println("No credhub certs found.")
	}

	privateKeyPath, err := p.allProxyGetter.GeneratePrivateKey()
	if err != nil {
		p.renderVariables(renderer, variables)
		return err
	}

	variables["JUMPBOX_PRIVATE_KEY"] = privateKeyPath
	variables["BOSH_ALL_PROXY"] = p.allProxyGetter.BoshAllProxy(state.Jumpbox.GetURLWithJumpboxUser(), privateKeyPath)
	variables["CREDHUB_PROXY"] = p.allProxyGetter.BoshAllProxy(state.Jumpbox.GetURLWithJumpboxUser(), privateKeyPath)

	p.renderVariables(renderer, variables)
	return nil
}

func (p PrintEnv) renderVariables(renderer renderers.Renderer, variables map[string]string) {
	for k, v := range variables {
		p.logger.Println(renderer.RenderEnvironmentVariable(k, v))
	}
}
<commit_msg>Remove unnecessary useMetadata variable<commit_after>package commands

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/cloudfoundry/bosh-bootloader/fileio"
	"github.com/cloudfoundry/bosh-bootloader/flags"
	"github.com/cloudfoundry/bosh-bootloader/renderers"
	"github.com/cloudfoundry/bosh-bootloader/storage"
)

// PrintEnv defines a PrintEnv command
type PrintEnv struct {
	stateValidator   stateValidator
	logger           logger
	stderrLogger     logger
	allProxyGetter   allProxyGetter
	terraformManager terraformManager
	credhubGetter    credhubGetter
	fs               fs
	rendererFactory  renderers.Factory
}

type envSetter interface {
	Set(key, value string) error
}

type credhubGetter interface {
	GetServer() (string, error)
	GetCerts() (string, error)
	GetPassword() (string, error)
}

type allProxyGetter interface {
	GeneratePrivateKey() (string, error)
	BoshAllProxy(string, string) string
}

type fs interface {
	fileio.TempDirer
	fileio.FileWriter
}

type PrintEnvConfig struct {
	shellType    string
	metadataFile string
}

// NewPrintEnv creates a new PrintEnv Command
func NewPrintEnv(
	logger logger,
	stderrLogger logger,
	stateValidator stateValidator,
	allProxyGetter allProxyGetter,
	credhubGetter credhubGetter,
	terraformManager terraformManager,
	fs fs,
	rendererFactory renderers.Factory) PrintEnv {
	return PrintEnv{
		stateValidator:   stateValidator,
		logger:           logger,
		stderrLogger:     stderrLogger,
		allProxyGetter:   allProxyGetter,
		terraformManager: terraformManager,
		credhubGetter:    credhubGetter,
		fs:               fs,
		rendererFactory:  rendererFactory,
	}
}

func (p PrintEnv) CheckFastFails(subcommandFlags []string, state storage.State) error {
	// We don't do any validation here, because at this point, we don't know if we're using
	// a bbl-state or a metadata file. Once we know that we're using a bbl-state, in Execute,
	// we validate that the state exists.
	return nil
}

func (p PrintEnv) ParseArgs(args []string, state storage.State) (PrintEnvConfig, error) {
	var (
		config PrintEnvConfig
	)

	printEnvFlags := flags.New("print-env")
	printEnvFlags.String(&config.shellType, "shell-type", "")
	printEnvFlags.String(&config.metadataFile, "file", "")

	err := printEnvFlags.Parse(args)
	if err != nil {
		return PrintEnvConfig{}, err
	}

	return config, nil
}

func (p PrintEnv) Execute(args []string, state storage.State) error {
	variables := make(map[string]string)

	config, err := p.ParseArgs(args, state)
	if err != nil {
		return err
	}

	shell := config.shellType
	renderer, err := p.rendererFactory.Create(shell)
	if err != nil {
		return err
	}

	metadataFile := config.metadataFile
	if metadataFile != "" {
		var metadata struct {
			Name     string            `json:"name"`
			IaasType string            `json:"iaas_type"`
			Bosh     map[string]string `json:"bosh"`
		}
		metadataContents, err := ioutil.ReadFile(metadataFile)
		if err != nil {
			p.stderrLogger.Println(fmt.Sprintf("Failed to read %s: %s", metadataFile, err))
			return err
		}

		err = json.Unmarshal(metadataContents, &metadata)
		if err != nil {
			p.stderrLogger.Println(fmt.Sprintf("Failed to unmarshal %s: %s", metadataFile, err))
			return err
		}

		variables["BOSH_CLIENT"] = metadata.Bosh["bosh_client"]
		variables["BOSH_CLIENT_SECRET"] = metadata.Bosh["bosh_client_secret"]
		variables["BOSH_ENVIRONMENT"] = metadata.Bosh["bosh_environment"]
		variables["BOSH_CA_CERT"] = metadata.Bosh["bosh_ca_cert"]
		variables["CREDHUB_CLIENT"] = metadata.Bosh["credhub_client"]
		variables["CREDHUB_SECRET"] = metadata.Bosh["credhub_secret"]
		variables["CREDHUB_SERVER"] = metadata.Bosh["credhub_server"]
		variables["CREDHUB_CA_CERT"] = metadata.Bosh["credhub_ca_cert"]

		privateKeyPath := fmt.Sprintf("/tmp/%s.priv", metadata.Name)
		err = ioutil.WriteFile(privateKeyPath, []byte(metadata.Bosh["jumpbox_private_key"]), 0600)
		if err != nil {
			p.stderrLogger.Println(fmt.Sprintf("Failed to write private key to %s: %s", privateKeyPath, err))
			return err
		}

		variables["JUMPBOX_PRIVATE_KEY"] = privateKeyPath
		boshAllProxy := fmt.Sprintf("%s=%s", strings.Split(metadata.Bosh["bosh_all_proxy"], "=")[0], privateKeyPath)
		variables["BOSH_ALL_PROXY"] = boshAllProxy
		variables["CREDHUB_PROXY"] = boshAllProxy

		p.renderVariables(renderer, variables)
		return nil
	}

	err = p.stateValidator.Validate()
	if err != nil {
		return err
	}

	variables["BOSH_CLIENT"] = state.BOSH.DirectorUsername
	variables["BOSH_CLIENT_SECRET"] = state.BOSH.DirectorPassword
	variables["BOSH_ENVIRONMENT"] = state.BOSH.DirectorAddress
	variables["BOSH_CA_CERT"] = state.BOSH.DirectorSSLCA
	variables["CREDHUB_CLIENT"] = "credhub-admin"

	credhubPassword, err := p.credhubGetter.GetPassword()
	if err == nil {
		variables["CREDHUB_SECRET"] = credhubPassword
	} else {
		p.stderrLogger.Println("No credhub password found.")
	}

	credhubServer, err := p.credhubGetter.GetServer()
	if err == nil {
		variables["CREDHUB_SERVER"] = credhubServer
	} else {
		p.stderrLogger.Println("No credhub server found.")
	}

	credhubCerts, err := p.credhubGetter.GetCerts()
	if err == nil {
		variables["CREDHUB_CA_CERT"] = credhubCerts
	} else {
		p.stderrLogger.Println("No credhub certs found.")
	}

	privateKeyPath, err := p.allProxyGetter.GeneratePrivateKey()
	if err != nil {
		p.renderVariables(renderer, variables)
		return err
	}

	variables["JUMPBOX_PRIVATE_KEY"] = privateKeyPath
	variables["BOSH_ALL_PROXY"] = p.allProxyGetter.BoshAllProxy(state.Jumpbox.GetURLWithJumpboxUser(), privateKeyPath)
	variables["CREDHUB_PROXY"] = p.allProxyGetter.BoshAllProxy(state.Jumpbox.GetURLWithJumpboxUser(), privateKeyPath)

	p.renderVariables(renderer, variables)
	return nil
}

func (p PrintEnv) renderVariables(renderer renderers.Renderer, variables map[string]string) {
	for k, v := range variables {
		p.logger.Println(renderer.RenderEnvironmentVariable(k, v))
	}
}
<|endoftext|>


<commit_before>package common

import (
	"net"
	"os"
	"testing"
)

const (
	confFile = "../bin/quantum-test.yml"
)

var (
	testPacket []byte
)

func init() {
	testPacket = make([]byte, 18)
	// IP (1.1.1.1)
	testPacket[0] = 1
	testPacket[1] = 1
	testPacket[2] = 1
	testPacket[3] = 1

	// Nonce
	testPacket[4] = 2
	testPacket[5] = 2
	testPacket[6] = 2
	testPacket[7] = 2
	testPacket[8] = 2
	testPacket[9] = 2
	testPacket[10] = 2
	testPacket[11] = 2
	testPacket[12] = 2
	testPacket[13] = 2
	testPacket[14] = 2
	testPacket[15] = 2

	// Packet data
	testPacket[16] = 3
	testPacket[17] = 3
}

func testEq(a, b []byte) bool {

	if a == nil && b == nil {
		return true
	}

	if a == nil || b == nil {
		return false
	}

	if len(a) != len(b) {
		return false
	}

	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}

	return true
}

func TestArrayEquals(t *testing.T) {
	if !ArrayEquals(nil, nil) {
		t.Fatal("ArrayEquals returned false comparing nil/nil")
	}
	if ArrayEquals([]byte{0}, nil) {
		t.Fatal("ArrayEquals returned true comparing nil/non-nil")
	}
	if ArrayEquals([]byte{0, 1}, []byte{0}) {
		t.Fatal("ArrayEquals returned true comparing mismatched lengths")
	}
	if !ArrayEquals([]byte{0, 1}, []byte{0, 1}) {
		t.Fatal("ArrayEquals returned false for equal arrays")
	}
}

func TestIPtoInt(t *testing.T) {
	var expected uint32
	actual := IPtoInt(net.ParseIP("0.0.0.0"))
	if expected != actual {
		t.Fatalf("IPtoInt did not return the right value, got: %d, expected: %d", actual, expected)
	}
}

func TestIncrementIP(t *testing.T) {
	expected := net.ParseIP("10.0.0.1")

	actual := net.ParseIP("10.0.0.0")
	IncrementIP(actual)

	if !testEq(expected, actual) {
		t.Fatalf("IncrementIP did not return the right value, got: %s, expected: %s", actual, expected)
	}
}

func TestNewConfig(t *testing.T) {
	os.Setenv("QUANTUM_DEVICE_NAME", "different")
	os.Setenv("QUANTUM_LISTEN_PORT", "1")
	os.Setenv("QUANTUM_CONF_FILE", confFile)
	os.Setenv("QUANTUM_PID_FILE", "../quantum.pid")

	cfg, err := NewConfig()
	if err != nil {
		t.Fatalf("NewConfig returned an error, %s", err)
	}
	t.Log(cfg)
	if cfg == nil {
		t.Fatal("NewConfig returned a blank config")
	}
	if cfg.DeviceName != "different" {
		t.Fatalf("NewConfig didn't pick up the environment variable replacement for DeviceName")
	}
	if cfg.ListenPort != 1 {
		t.Fatalf("NewConfig didn't pick up the environment variable replacement for ListenPort")
	}
	if cfg.Password != "Password1" {
		t.Fatalf("NewConfig didn't pick up the config file replacement for Password")
	}
}

func TestEcdh(t *testing.T) {
	pub, priv := GenerateECKeyPair()
	if len(pub) != keyLength {
		t.Fatalf("GenerateECKeyPair did not return the right length for the public key,\nactual: %d, expected: %d", len(pub), keyLength)
	}
	if len(priv) != keyLength {
		t.Fatalf("GenerateECKeyPair did not return the right length for the private key,\nactual: %d, expected: %d", len(priv), keyLength)
	}
	if testEq(pub, priv) {
		t.Fatalf("GenerateECKeyPair returned identical pub/priv keys this can't possibly happen:\npub: %v, priv: %v", pub, priv)
	}
	secret := GenerateSharedSecret(pub, priv)
	if len(secret) != keyLength {
		t.Fatalf("GenerateECKeyPair did not return the right length for the shared secret,\nactual: %d, expected: %d", len(secret), keyLength)
	}
	if testEq(secret, pub) || testEq(secret, priv) {
		t.Fatalf("GenerateECKeyPair returned identical secret and pub/priv keys this can't possibly happen:\npub: %v, priv: %v, secret: %v", pub, priv, secret)
	}
}

func TestNewMapping(t *testing.T) {
	privateIP := net.ParseIP("0.0.0.0")
	publicip := net.ParseIP("1.1.1.1")
	publicip6 := net.ParseIP("dead::beef")
	publicport := 80
	publicKey := make([]byte, 32)
	machineID := "123456"

	actual := NewMapping(machineID, privateIP, publicip, publicip6, publicport, publicKey)
	if !testEq(actual.IPv4, publicip) || !testEq(actual.IPv6, publicip6) || actual.Port != publicport || !testEq(actual.PrivateIP, privateIP) || !testEq(actual.PublicKey, publicKey) {
		t.Fatalf("NewMapping did not return the right value, got: %v", actual)
	}
}

func TestParseMapping(t *testing.T) {
	privateIP := net.ParseIP("0.0.0.0")
	publicip := net.ParseIP("1.1.1.1")
	publicip6 := net.ParseIP("dead::beef")
	publicport := 80
	publicKey := make([]byte, 32)
	machineID := "123456"

	expected := NewMapping(machineID, privateIP, publicip, publicip6, publicport, publicKey)
	actual, err := ParseMapping(expected.String(), make([]byte, 32))
	if err != nil {
		t.Fatalf("Error occured during test: %s", err)
	}
	if !testEq(actual.IPv4, expected.IPv4) || actual.Port != expected.Port || !testEq(actual.PrivateIP, expected.PrivateIP) || !testEq(actual.PublicKey, expected.PublicKey) {
		t.Fatalf("ParseMapping did not return the right value, got: %v, expected: %v", actual, expected)
	}
}

func TestParseNetworkConfig(t *testing.T) {
	actual, err := ParseNetworkConfig(DefaultNetworkConfig.Bytes())
	if err != nil {
		t.Fatal("ParseNetworkConfig returned an error:", err)
	}
	if actual.Network != DefaultNetworkConfig.Network || actual.LeaseTime != DefaultNetworkConfig.LeaseTime {
		t.Fatalf("ParseNetworkConfig returned the wrong value, got: %v, expected: %v", actual, DefaultNetworkConfig)
	}
}

func TestNewTunPayload(t *testing.T) {
	payload := NewTunPayload(testPacket, 2)
	for i := 0; i < 4; i++ {
		if payload.IPAddress[i] != 1 {
			t.Fatal("NewTunPayload returned an incorrect IP address mapping.")
		}
	}

	for i := 0; i < 12; i++ {
		if payload.Nonce[i] != 2 {
			t.Fatal("NewTunPayload returned an incorrect Nonce mapping.")
		}
	}

	for i := 0; i < 2; i++ {
		if payload.Packet[i] != 3 {
			t.Fatal("NewTunPayload returned an incorrect Packet mapping.")
		}
	}
}

func TestNewSockPayload(t *testing.T) {
	payload := NewSockPayload(testPacket, 18)
	for i := 0; i < 4; i++ {
		if payload.IPAddress[i] != 1 {
			t.Fatal("NewTunPayload returned an incorrect IP address mapping.")
		}
	}

	for i := 0; i < 12; i++ {
		if payload.Nonce[i] != 2 {
			t.Fatal("NewTunPayload returned an incorrect Nonce mapping.")
		}
	}

	for i := 0; i < 2; i++ {
		if payload.Packet[i] != 3 {
			t.Fatal("NewTunPayload returned an incorrect Packet mapping.")
		}
	}
}

func TestNewStats(t *testing.T) {
	stats := NewStats(1)
	if stats.Packets != 0 {
		t.Fatalf("NewStats did not return the correct default for Packets, got: %d, expected: %d", stats.Packets, 0)
	}
	if stats.Bytes != 0 {
		t.Fatalf("NewStats did not return the correct default for Bytes, got: %d, expected: %d", stats.Bytes, 0)
	}
	if stats.Links == nil {
		t.Fatalf("NewStats did not return the correct default for Links, got: %v, expected: %v", stats.Links, make(map[string]*Stats))
	}
	str := stats.String()
	if str == "" {
		t.Fatalf("String didn't return the correct value.")
	}
}

func TestNewLogger(t *testing.T) {
	log := NewLogger()
	if log.Error == nil {
		t.Fatal("NewLogger returned a nil Error log.")
	}
	if log.Info == nil {
		t.Fatal("NewLogger returned a nil Error log.")
	}
}

func TestGenerateLocalMapping(t *testing.T) {
	cfg := &Config{
		PrivateIP:     net.ParseIP("10.10.0.1"),
		PublicIPv4:    net.ParseIP("8.8.8.8"),
		PublicIPv6:    net.ParseIP("::"),
		ListenPort:    1099,
		PublicKey:     make([]byte, 32),
		NetworkConfig: DefaultNetworkConfig,
		MachineID:     "123",
	}

	mappings := make(map[uint32]*Mapping)
	mapping, err := GenerateLocalMapping(cfg, mappings)
	if err != nil {
		t.Fatal(err)
	}

	if !testEq(mapping.PrivateIP.To4(), cfg.PrivateIP.To4()) {
		t.Fatal("GenerateLocalMapping created the wrong mapping.")
	}

	mappings[IPtoInt(cfg.PrivateIP)] = mapping

	_, err = GenerateLocalMapping(cfg, mappings)
	if err != nil {
		t.Fatal(err)
	}
}
<commit_msg>Added some more unit tests for the ip handler module<commit_after>package common

import (
	"net"
	"os"
	"testing"
)

const (
	confFile = "../bin/quantum-test.yml"
)

var (
	testPacket []byte
)

func init() {
	testPacket = make([]byte, 18)
	// IP (1.1.1.1)
	testPacket[0] = 1
	testPacket[1] = 1
	testPacket[2] = 1
	testPacket[3] = 1

	// Nonce
	testPacket[4] = 2
	testPacket[5] = 2
	testPacket[6] = 2
	testPacket[7] = 2
	testPacket[8] = 2
	testPacket[9] = 2
	testPacket[10] = 2
	testPacket[11] = 2
	testPacket[12] = 2
	testPacket[13] = 2
	testPacket[14] = 2
	testPacket[15] = 2

	// Packet data
	testPacket[16] = 3
	testPacket[17] = 3
}

func testEq(a, b []byte) bool {

	if a == nil && b == nil {
		return true
	}

	if a == nil || b == nil {
		return false
	}

	if len(a) != len(b) {
		return false
	}

	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}

	return true
}

func TestArrayEquals(t *testing.T) {
	if !ArrayEquals(nil, nil) {
		t.Fatal("ArrayEquals returned false comparing nil/nil")
	}
	if ArrayEquals([]byte{0}, nil) {
		t.Fatal("ArrayEquals returned true comparing nil/non-nil")
	}
	if ArrayEquals([]byte{0, 1}, []byte{0}) {
		t.Fatal("ArrayEquals returned true comparing mismatched lengths")
	}
	if !ArrayEquals([]byte{0, 1}, []byte{0, 1}) {
		t.Fatal("ArrayEquals returned false for equal arrays")
	}
}

func TestIPtoInt(t *testing.T) {
	var expected uint32
	actual := IPtoInt(net.ParseIP("0.0.0.0"))
	if expected != actual {
		t.Fatalf("IPtoInt did not return the right value, got: %d, expected: %d", actual, expected)
	}
}

func TestIncrementIP(t *testing.T) {
	expected := net.ParseIP("10.0.0.1")

	actual := net.ParseIP("10.0.0.0")
	IncrementIP(actual)

	if !testEq(expected, actual) {
		t.Fatalf("IncrementIP did not return the right value, got: %s, expected: %s", actual, expected)
	}
}

func TestNewConfig(t *testing.T) {
	os.Setenv("QUANTUM_DEVICE_NAME", "different")
	os.Setenv("QUANTUM_LISTEN_PORT", "1")
	os.Setenv("QUANTUM_CONF_FILE", confFile)
	os.Setenv("QUANTUM_PID_FILE", "../quantum.pid")

	cfg, err := NewConfig()
	if err != nil {
		t.Fatalf("NewConfig returned an error, %s", err)
	}
	t.Log(cfg)
	if cfg == nil {
		t.Fatal("NewConfig returned a blank config")
	}
	if cfg.DeviceName != "different" {
		t.Fatalf("NewConfig didn't pick up the environment variable replacement for DeviceName")
	}
	if cfg.ListenPort != 1 {
		t.Fatalf("NewConfig didn't pick up the environment variable replacement for ListenPort")
	}
	if cfg.Password != "Password1" {
		t.Fatalf("NewConfig didn't pick up the config file replacement for Password")
	}
}

func TestEcdh(t *testing.T) {
	pub, priv := GenerateECKeyPair()
	if len(pub) != keyLength {
		t.Fatalf("GenerateECKeyPair did not return the right length for the public key,\nactual: %d, expected: %d", len(pub), keyLength)
	}
	if len(priv) != keyLength {
		t.Fatalf("GenerateECKeyPair did not return the right length for the private key,\nactual: %d, expected: %d", len(priv), keyLength)
	}
	if testEq(pub, priv) {
		t.Fatalf("GenerateECKeyPair returned identical pub/priv keys this can't possibly happen:\npub: %v, priv: %v", pub, priv)
	}
	secret := GenerateSharedSecret(pub, priv)
	if len(secret) != keyLength {
		t.Fatalf("GenerateECKeyPair did not return the right length for the shared secret,\nactual: %d, expected: %d", len(secret), keyLength)
	}
	if testEq(secret, pub) || testEq(secret, priv) {
		t.Fatalf("GenerateECKeyPair returned identical secret and pub/priv keys this can't possibly happen:\npub: %v, priv: %v, secret: %v", pub, priv, secret)
	}
}

func TestNewMapping(t *testing.T) {
	privateIP := net.ParseIP("0.0.0.0")
	publicip := net.ParseIP("1.1.1.1")
	publicip6 := net.ParseIP("dead::beef")
	publicport := 80
	publicKey := make([]byte, 32)
	machineID := "123456"

	actual := NewMapping(machineID, privateIP, publicip, publicip6, publicport, publicKey)
	if !testEq(actual.IPv4, publicip) || !testEq(actual.IPv6, publicip6) || actual.Port != publicport || !testEq(actual.PrivateIP, privateIP) || !testEq(actual.PublicKey, publicKey) {
		t.Fatalf("NewMapping did not return the right value, got: %v", actual)
	}
}

func TestParseMapping(t *testing.T) {
	privateIP := net.ParseIP("0.0.0.0")
	publicip := net.ParseIP("1.1.1.1")
	publicip6 := net.ParseIP("dead::beef")
	publicport := 80
	publicKey := make([]byte, 32)
	machineID := "123456"

	expected := NewMapping(machineID, privateIP, publicip, publicip6, publicport, publicKey)
	actual, err := ParseMapping(expected.String(), make([]byte, 32))
	if err != nil {
		t.Fatalf("Error occured during test: %s", err)
	}
	if !testEq(actual.IPv4, expected.IPv4) || actual.Port != expected.Port || !testEq(actual.PrivateIP, expected.PrivateIP) || !testEq(actual.PublicKey, expected.PublicKey) {
		t.Fatalf("ParseMapping did not return the right value, got: %v, expected: %v", actual, expected)
	}
}

func TestParseNetworkConfig(t *testing.T) {
	actual, err := ParseNetworkConfig(DefaultNetworkConfig.Bytes())
	if err != nil {
		t.Fatal("ParseNetworkConfig returned an error:", err)
	}
	if actual.Network != DefaultNetworkConfig.Network || actual.LeaseTime != DefaultNetworkConfig.LeaseTime {
		t.Fatalf("ParseNetworkConfig returned the wrong value, got: %v, expected: %v", actual, DefaultNetworkConfig)
	}
}

func TestNewTunPayload(t *testing.T) {
	payload := NewTunPayload(testPacket, 2)
	for i := 0; i < 4; i++ {
		if payload.IPAddress[i] != 1 {
			t.Fatal("NewTunPayload returned an incorrect IP address mapping.")
		}
	}

	for i := 0; i < 12; i++ {
		if payload.Nonce[i] != 2 {
			t.Fatal("NewTunPayload returned an incorrect Nonce mapping.")
		}
	}

	for i := 0; i < 2; i++ {
		if payload.Packet[i] != 3 {
			t.Fatal("NewTunPayload returned an incorrect Packet mapping.")
		}
	}
}

func TestNewSockPayload(t *testing.T) {
	payload := NewSockPayload(testPacket, 18)
	for i := 0; i < 4; i++ {
		if payload.IPAddress[i] != 1 {
			t.Fatal("NewTunPayload returned an incorrect IP address mapping.")
		}
	}

	for i := 0; i < 12; i++ {
		if payload.Nonce[i] != 2 {
			t.Fatal("NewTunPayload returned an incorrect Nonce mapping.")
		}
	}

	for i := 0; i < 2; i++ {
		if payload.Packet[i] != 3 {
			t.Fatal("NewTunPayload returned an incorrect Packet mapping.")
		}
	}
}

func TestNewStats(t *testing.T) {
	stats := NewStats(1)
	if stats.Packets != 0 {
		t.Fatalf("NewStats did not return the correct default for Packets, got: %d, expected: %d", stats.Packets, 0)
	}
	if stats.Bytes != 0 {
		t.Fatalf("NewStats did not return the correct default for Bytes, got: %d, expected: %d", stats.Bytes, 0)
	}
	if stats.Links == nil {
		t.Fatalf("NewStats did not return the correct default for Links, got: %v, expected: %v", stats.Links, make(map[string]*Stats))
	}
	str := stats.String()
	if str == "" {
		t.Fatalf("String didn't return the correct value.")
	}
}

func TestNewLogger(t *testing.T) {
	log := NewLogger()
	if log.Error == nil {
		t.Fatal("NewLogger returned a nil Error log.")
	}
	if log.Info == nil {
		t.Fatal("NewLogger returned a nil Error log.")
	}
}

func TestGenerateLocalMapping(t *testing.T) {
	cfg := &Config{
		PrivateIP:     net.ParseIP("10.10.0.1"),
		PublicIPv4:    net.ParseIP("8.8.8.8"),
		PublicIPv6:    net.ParseIP("::"),
		ListenPort:    1099,
		PublicKey:     make([]byte, 32),
		NetworkConfig: DefaultNetworkConfig,
		MachineID:     "123",
	}

	mappings := make(map[uint32]*Mapping)
	mapping, err := GenerateLocalMapping(cfg, mappings)
	if err != nil {
		t.Fatal(err)
	}

	if !testEq(mapping.PrivateIP.To4(), cfg.PrivateIP.To4()) {
		t.Fatal("GenerateLocalMapping created the wrong mapping.")
	}

	mappings[IPtoInt(cfg.PrivateIP)] = mapping

	_, err = GenerateLocalMapping(cfg, mappings)
	if err != nil {
		t.Fatal(err)
	}

	mapping.MachineID = "456"

	_, err = GenerateLocalMapping(cfg, mappings)
	if err == nil {
		t.Fatal("GenerateLocalMapping failed to properly handle an existing ip address")
	}

	cfg.PrivateIP = nil
	mapping.MachineID = "123"

	_, err = GenerateLocalMapping(cfg, mappings)
	if err != nil {
		t.Fatal(err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2019 Tomas Machalek <tomas.machalek@gmail.com>
// Copyright 2019 Institute of the Czech National Corpus,
//                Faculty of Arts, Charles University
//
// 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 celery

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"os/exec"
	"strings"

	"github.com/czcorpus/klogproc/conversion/celery"
)

type nullWriter struct {
}

func (nw *nullWriter) Write(p []byte) (n int, err error) {
	return len(p), nil
}

// ------------

// StatusReader fetches Celery status using os.exec and stores
// the JSON-like output to an InputRecord
type StatusReader struct {
	CeleryBinaryPath string
	AppName          string
	AppWorkdir       string
}

// ReadStatus reads current Celery status via cmd
func (s *StatusReader) ReadStatus() (*celery.InputRecord, error) {
	var stdOutput bytes.Buffer
	outWriter := bufio.NewWriter(&stdOutput)
	cmd := exec.Command(s.CeleryBinaryPath, "inspect", "stats", "--workdir", s.AppWorkdir, "-A", s.AppName)
	cmd.Stdout = outWriter
	cmd.Stderr = &nullWriter{}
	err := cmd.Run()
	if err != nil {
		return nil, err
	}
	outWriter.Flush()
	ret := string(stdOutput.Bytes())
	idx := strings.Index(ret, "{")
	if idx >= 0 {
		var out celery.InputRecord
		err = json.Unmarshal([]byte(ret[idx:]), &out)
		if err != nil {
			return nil, err
		}
		return &out, nil
	}
	return nil, fmt.Errorf("Celery inspect response not recognized")
}

// NewStatusReader is a factory function for StatusReader
func NewStatusReader(celeryBinPath string, conf *AppConf) *StatusReader {
	return &StatusReader{
		CeleryBinaryPath: celeryBinPath,
		AppName:          conf.Name,
		AppWorkdir:       conf.Workdir,
	}
}
<commit_msg>Fetch also error out<commit_after>// Copyright 2019 Tomas Machalek <tomas.machalek@gmail.com>
// Copyright 2019 Institute of the Czech National Corpus,
//                Faculty of Arts, Charles University
//
// 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 celery

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os/exec"
	"strings"

	"github.com/czcorpus/klogproc/conversion/celery"
)

type nullWriter struct {
}

func (nw *nullWriter) Write(p []byte) (n int, err error) {
	return len(p), nil
}

// ------------

// StatusReader fetches Celery status using os.exec and stores
// the JSON-like output to an InputRecord
type StatusReader struct {
	CeleryBinaryPath string
	AppName          string
	AppWorkdir       string
}

// ReadStatus reads current Celery status via cmd
func (s *StatusReader) ReadStatus() (*celery.InputRecord, error) {
	var stdOutput bytes.Buffer
	outWriter := bufio.NewWriter(&stdOutput)
	var errOutput bytes.Buffer
	errWriter := bufio.NewWriter(&errOutput)
	cmd := exec.Command(s.CeleryBinaryPath, "inspect", "stats", "--workdir", s.AppWorkdir, "-A", s.AppName)
	cmd.Stdout = outWriter
	cmd.Stderr = errWriter
	err := cmd.Run()
	if err != nil {
		log.Print("WARNING: Celery inspect error output: ", string(errOutput.Bytes()))
		return nil, err
	}
	outWriter.Flush()
	ret := string(stdOutput.Bytes())
	idx := strings.Index(ret, "{")
	if idx >= 0 {
		var out celery.InputRecord
		err = json.Unmarshal([]byte(ret[idx:]), &out)
		if err != nil {
			return nil, err
		}
		return &out, nil
	}
	return nil, fmt.Errorf("Celery inspect response not recognized")
}

// NewStatusReader is a factory function for StatusReader
func NewStatusReader(celeryBinPath string, conf *AppConf) *StatusReader {
	return &StatusReader{
		CeleryBinaryPath: celeryBinPath,
		AppName:          conf.Name,
		AppWorkdir:       conf.Workdir,
	}
}
<|endoftext|>