<commit_before>/*
 * Copyright 2019 The Vitess Authors.

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"testing"
)

func TestPatchedDockerComposeFileIsValid(t *testing.T) {
	keyspaceInfoMap := make(map[string]keyspaceInfo)
	externalDbInfoMap := make(map[string]externalDbInfo)

	baseFile := readFile(*baseDockerComposeFile)
	patchedFile := applyDockerComposePatches(baseFile, keyspaceInfoMap, externalDbInfoMap)
}
<commit_msg>Add a smoke test for docker compose file generation<commit_after>/*
 * Copyright 2019 The Vitess Authors.

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

var (
	testVtOpts = vtOptions{
		webPort:       DefaultWebPort,
		gRpcPort:      DefaultGrpcPort,
		mySqlPort:     DefaultMysqlPort,
		topologyFlags: DefaultTopologyFlags,
		cell:          DefaultCell,
	}
	testComposeFile       = readFile("./docker-compose.base.yml")
	testKeyspaceInfoMap   = parseKeyspaceInfo(DefaultKeyspaceData)
	testExternalDbInfoMap = parseExternalDbData(DefaultExternalDbData)
	referenceFile         string
)

func TestGeneratesCorrectFileWithDefaultOpts(t *testing.T) {
	baseFile := testComposeFile
	finalFile := applyDockerComposePatches(baseFile, testKeyspaceInfoMap, testExternalDbInfoMap, testVtOpts)

	assert.YAMLEq(t, referenceFile, string(finalFile))
}

func init() {
	referenceFile = `
services:
  consul1:
    command: agent -server -bootstrap-expect 3 -ui -disable-host-node-id -client 0.0.0.0
    hostname: consul1
    image: consul:latest
    ports:
    - 8400:8400
    - 8500:8500
    - 8600:8600
  consul2:
    command: agent -server -retry-join consul1 -disable-host-node-id
    depends_on:
    - consul1
    expose:
    - "8400"
    - "8500"
    - "8600"
    hostname: consul2
    image: consul:latest
  consul3:
    command: agent -server -retry-join consul1 -disable-host-node-id
    depends_on:
    - consul1
    expose:
    - "8400"
    - "8500"
    - "8600"
    hostname: consul3
    image: consul:latest
  schemaload_test_keyspace:
    command:
    - sh
    - -c
    - /script/schemaload.sh
    depends_on:
      vttablet101:
        condition: service_healthy
      vttablet201:
        condition: service_healthy
    environment:
    - TOPOLOGY_FLAGS=-topo_implementation consul -topo_global_server_address consul1:8500
      -topo_global_root vitess/global
    - WEB_PORT=8080
    - GRPC_PORT=15999
    - CELL=test
    - KEYSPACE=test_keyspace
    - TARGETTAB=test-0000000101
    - SLEEPTIME=15
    - VSCHEMA_FILE=test_keyspace_vschema.json
    - SCHEMA_FILES=test_keyspace_schema_file.sql
    - POST_LOAD_FILE=
    - EXTERNAL_DB=0
    image: vitess/base
    volumes:
    - .:/script
  schemaload_unsharded_keyspace:
    command:
    - sh
    - -c
    - /script/schemaload.sh
    depends_on:
      vttablet301:
        condition: service_healthy
    environment:
    - TOPOLOGY_FLAGS=-topo_implementation consul -topo_global_server_address consul1:8500
      -topo_global_root vitess/global
    - WEB_PORT=8080
    - GRPC_PORT=15999
    - CELL=test
    - KEYSPACE=unsharded_keyspace
    - TARGETTAB=test-0000000301
    - SLEEPTIME=15
    - VSCHEMA_FILE=unsharded_keyspace_vschema.json
    - SCHEMA_FILES=unsharded_keyspace_schema_file.sql
    - POST_LOAD_FILE=
    - EXTERNAL_DB=0
    image: vitess/base
    volumes:
    - .:/script
  vtctld:
    command:
    - sh
    - -c
    - ' $$VTROOT/bin/vtctld -topo_implementation consul -topo_global_server_address
      consul1:8500 -topo_global_root vitess/global -cell test -workflow_manager_init
      -workflow_manager_use_election -service_map ''grpc-vtctl'' -backup_storage_implementation
      file -file_backup_storage_root $$VTDATAROOT/backups -logtostderr=true -port
      8080 -grpc_port 15999 -pid_file $$VTDATAROOT/tmp/vtctld.pid '
    depends_on:
    - consul1
    - consul2
    - consul3
    image: vitess/base
    ports:
    - 15000:8080
    - "15999"
    volumes:
    - .:/script
  vtgate:
    command:
    - sh
    - -c
    - '/script/run-forever.sh $$VTROOT/bin/vtgate -topo_implementation consul -topo_global_server_address
      consul1:8500 -topo_global_root vitess/global -logtostderr=true -port 8080 -grpc_port
      15999 -mysql_server_port 15306 -mysql_auth_server_impl none -cell test -cells_to_watch
      test -tablet_types_to_wait MASTER,REPLICA,RDONLY -gateway_implementation discoverygateway
      -service_map ''grpc-vtgateservice'' -pid_file $$VTDATAROOT/tmp/vtgate.pid -normalize_queries=true '
    depends_on:
    - vtctld
    image: vitess/base
    ports:
    - 15099:8080
    - "15999"
    - 15306:15306
    volumes:
    - .:/script
  vttablet101:
    command:
    - sh
    - -c
    - /script/vttablet-up.sh 101
    depends_on:
    - vtctld
    environment:
    - TOPOLOGY_FLAGS=-topo_implementation consul -topo_global_server_address consul1:8500
      -topo_global_root vitess/global
    - WEB_PORT=8080
    - GRPC_PORT=15999
    - CELL=test
    - KEYSPACE=test_keyspace
    - SHARD=-80
    - ROLE=master
    - VTHOST=vttablet101
    - EXTERNAL_DB=0
    - DB_PORT=
    - DB_HOST=
    - DB_USER=
    - DB_PASS=
    - DB_CHARSET=
    healthcheck:
      interval: 30s
      retries: 15
      test:
      - CMD-SHELL
      - curl localhost:8080/debug/health
      timeout: 10s
    image: vitess/base
    ports:
    - 15101:8080
    - "15999"
    - "3306"
    volumes:
    - .:/script
  vttablet102:
    command:
    - sh
    - -c
    - /script/vttablet-up.sh 102
    depends_on:
    - vtctld
    environment:
    - TOPOLOGY_FLAGS=-topo_implementation consul -topo_global_server_address consul1:8500
      -topo_global_root vitess/global
    - WEB_PORT=8080
    - GRPC_PORT=15999
    - CELL=test
    - KEYSPACE=test_keyspace
    - SHARD=-80
    - ROLE=replica
    - VTHOST=vttablet102
    - EXTERNAL_DB=0
    - DB_PORT=
    - DB_HOST=
    - DB_USER=
    - DB_PASS=
    - DB_CHARSET=
    healthcheck:
      interval: 30s
      retries: 15
      test:
      - CMD-SHELL
      - curl localhost:8080/debug/health
      timeout: 10s
    image: vitess/base
    ports:
    - 15102:8080
    - "15999"
    - "3306"
    volumes:
    - .:/script
  vttablet201:
    command:
    - sh
    - -c
    - /script/vttablet-up.sh 201
    depends_on:
    - vtctld
    environment:
    - TOPOLOGY_FLAGS=-topo_implementation consul -topo_global_server_address consul1:8500
      -topo_global_root vitess/global
    - WEB_PORT=8080
    - GRPC_PORT=15999
    - CELL=test
    - KEYSPACE=test_keyspace
    - SHARD=80-
    - ROLE=master
    - VTHOST=vttablet201
    - EXTERNAL_DB=0
    - DB_PORT=
    - DB_HOST=
    - DB_USER=
    - DB_PASS=
    - DB_CHARSET=
    healthcheck:
      interval: 30s
      retries: 15
      test:
      - CMD-SHELL
      - curl localhost:8080/debug/health
      timeout: 10s
    image: vitess/base
    ports:
    - 15201:8080
    - "15999"
    - "3306"
    volumes:
    - .:/script
  vttablet202:
    command:
    - sh
    - -c
    - /script/vttablet-up.sh 202
    depends_on:
    - vtctld
    environment:
    - TOPOLOGY_FLAGS=-topo_implementation consul -topo_global_server_address consul1:8500
      -topo_global_root vitess/global
    - WEB_PORT=8080
    - GRPC_PORT=15999
    - CELL=test
    - KEYSPACE=test_keyspace
    - SHARD=80-
    - ROLE=replica
    - VTHOST=vttablet202
    - EXTERNAL_DB=0
    - DB_PORT=
    - DB_HOST=
    - DB_USER=
    - DB_PASS=
    - DB_CHARSET=
    healthcheck:
      interval: 30s
      retries: 15
      test:
      - CMD-SHELL
      - curl localhost:8080/debug/health
      timeout: 10s
    image: vitess/base
    ports:
    - 15202:8080
    - "15999"
    - "3306"
    volumes:
    - .:/script
  vttablet301:
    command:
    - sh
    - -c
    - /script/vttablet-up.sh 301
    depends_on:
    - vtctld
    environment:
    - TOPOLOGY_FLAGS=-topo_implementation consul -topo_global_server_address consul1:8500
      -topo_global_root vitess/global
    - WEB_PORT=8080
    - GRPC_PORT=15999
    - CELL=test
    - KEYSPACE=unsharded_keyspace
    - SHARD=-
    - ROLE=master
    - VTHOST=vttablet301
    - EXTERNAL_DB=0
    - DB_PORT=
    - DB_HOST=
    - DB_USER=
    - DB_PASS=
    - DB_CHARSET=
    healthcheck:
      interval: 30s
      retries: 15
      test:
      - CMD-SHELL
      - curl localhost:8080/debug/health
      timeout: 10s
    image: vitess/base
    ports:
    - 15301:8080
    - "15999"
    - "3306"
    volumes:
    - .:/script
  vtwork:
    command:
    - sh
    - -c
    - '$$VTROOT/bin/vtworker -topo_implementation consul -topo_global_server_address
      consul1:8500 -topo_global_root vitess/global -cell test -logtostderr=true -service_map
      ''grpc-vtworker'' -port 8080 -grpc_port 15999 -use_v3_resharding_mode=true -pid_file
      $$VTDATAROOT/tmp/vtwork.pid '
    depends_on:
    - vtctld
    image: vitess/base
    ports:
    - 15100:8080
    - "15999"
version: "2.1"
`
}
<|endoftext|>


<commit_before>package main

import (
	tn "github.com/John-Lin/tinynet"
	log "github.com/sirupsen/logrus"
	"strconv"
)

// Custom topology example
// Single switch with 8 hosts

func main() {
	// add a switch as a Switch
	Switch, err := tn.AddSwitch("br0")
	if err != nil {
		log.Fatal("failed to add Switch:", err)
	}

	// add 8 IPs with CIDR 192.168.1.0/24
	ips, _ := tn.GetIPs("192.168.1.0/24", 8)

	var host[8] *tn.Host
	// add 8 hosts and link to switch
	for (i := 0; i < 7; i++) {
		hostName := "h" + strconv.Itoa(i)
		host[i], err = tn.AddHost(hostName, ips[i] + "/24")
		if err != nil {
			log.Fatal("failed to add host:", err)
		}

		// add Link for leftHost - leftSwitch
		if err := tn.AddLink(host[i], Switch); err != nil {
			log.Fatal("failed to add link between Switch and host: ", err)
		}
	}
}
<commit_msg>fixed example<commit_after>package main

import (
	"strconv"

	tn "github.com/John-Lin/tinynet"
	log "github.com/sirupsen/logrus"
)

// Custom topology example
// Single switch with 8 hosts

func main() {
	// add a switch as a Switch
	Switch, err := tn.AddSwitch("br0")
	if err != nil {
		log.Fatal("failed to add Switch:", err)
	}

	// add 8 IPs with CIDR 192.168.1.0/24
	ips, _ := tn.GetIPs("192.168.1.0/24", 8)

	var host [8]*tn.Host
	// add 8 hosts and link to switch
	for i := 0; i < 8; i++ {
		hostName := "h" + strconv.Itoa(i)
		host[i], err = tn.AddHost(hostName, ips[i]+"/24")
		if err != nil {
			log.Fatal("failed to add host:", err)
		}

		// add Link for host[i] to Switch
		if err := tn.AddLink(host[i], Switch); err != nil {
			log.Fatal("failed to add link between Switch and host: ", err)
		}
	}
}
<|endoftext|>


<commit_before>package message

import (
	"fmt"
	"time"
)

const (
	// Reset resets the color
	Reset = "\033[0m"

	// Bold makes the following text bold
	Bold = "\033[1m"

	// Dim dims the following text
	Dim = "\033[2m"

	// Italic makes the following text italic
	Italic = "\033[3m"

	// Underline underlines the following text
	Underline = "\033[4m"

	// Blink blinks the following text
	Blink = "\033[5m"

	// Invert inverts the following text
	Invert = "\033[7m"

	// Newline
	Newline = "\r\n"

	// BEL
	Bel = "\007"
)

// Interface for Styles
type Style interface {
	String() string
	Format(string) string
}

// General hardcoded style, mostly used as a crutch until we flesh out the
// framework to support backgrounds etc.
type style string

func (c style) String() string {
	return string(c)
}

func (c style) Format(s string) string {
	return c.String() + s + Reset
}

// 256 color type, for terminals who support it
type Color256 uint8

// String version of this color
func (c Color256) String() string {
	return fmt.Sprintf("38;05;%d", c)
}

// Return formatted string with this color
func (c Color256) Format(s string) string {
	return "\033[" + c.String() + "m" + s + Reset
}

func Color256Palette(colors ...uint8) *Palette {
	size := len(colors)
	p := make([]Style, 0, size)
	for _, color := range colors {
		p = append(p, Color256(color))
	}
	return &Palette{
		colors: p,
		size:   size,
	}
}

// No color, used for mono theme
type Color0 struct{}

// No-op for Color0
func (c Color0) String() string {
	return ""
}

// No-op for Color0
func (c Color0) Format(s string) string {
	return s
}

// Container for a collection of colors
type Palette struct {
	colors []Style
	size   int
}

// Get a color by index, overflows are looped around.
func (p Palette) Get(i int) Style {
	if p.size == 1 {
		return p.colors[0]
	}
	return p.colors[i%(p.size-1)]
}

func (p Palette) Len() int {
	return p.size
}

func (p Palette) String() string {
	r := ""
	for _, c := range p.colors {
		r += c.Format("X")
	}
	return r
}

// Collection of settings for chat
type Theme struct {
	id        string
	sys       Style
	pm        Style
	highlight Style
	names     *Palette
}

func (theme Theme) ID() string {
	return theme.id
}

// Colorize name string given some index
func (theme Theme) ColorName(u *User) string {
	if theme.names == nil {
		return u.Name()
	}

	return theme.names.Get(u.colorIdx).Format(u.Name())
}

// Colorize the PM string
func (theme Theme) ColorPM(s string) string {
	if theme.pm == nil {
		return s
	}

	return theme.pm.Format(s)
}

// Colorize the Sys message
func (theme Theme) ColorSys(s string) string {
	if theme.sys == nil {
		return s
	}

	return theme.sys.Format(s)
}

// Highlight a matched string, usually name
func (theme Theme) Highlight(s string) string {
	if theme.highlight == nil {
		return s
	}
	return theme.highlight.Format(s)
}

// Timestamp formats and colorizes the timestamp.
func (theme Theme) Timestamp(t time.Time) string {
	// TODO: Change this per-theme? Or config?
	return theme.sys.Format(t.Format("2006-01-02 15:04 UTC"))
}

// List of initialzied themes
var Themes []Theme

// Default theme to use
var DefaultTheme *Theme

func allColors256() *Palette {
	colors := []uint8{}
	var i uint8
	for i = 0; i < 255; i++ {
		colors = append(colors, i)
	}
	return Color256Palette(colors...)
}

func readableColors256() *Palette {
	colors := []uint8{}
	var i uint8
	for i = 0; i < 255; i++ {
		if i == 0 || i == 7 || i == 8 || i == 15 || i == 16 || i == 17 || i > 230 {
			// Skip 31 Shades of Grey, and one hyperintelligent shade of blue.
			continue
		}
		colors = append(colors, i)
	}
	return Color256Palette(colors...)
}

func init() {
	Themes = []Theme{
		{
			id:        "colors",
			names:     readableColors256(),
			sys:       Color256(245),                              // Grey
			pm:        Color256(7),                                // White
			highlight: style(Bold + "\033[48;5;11m\033[38;5;16m"), // Yellow highlight
		},
		{
			id:        "solarized",
			names:     Color256Palette(1, 2, 3, 4, 5, 6, 7, 9, 13),
			sys:       Color256(11),                              // Yellow
			pm:        Color256(15),                              // White
			highlight: style(Bold + "\033[48;5;3m\033[38;5;94m"), // Orange highlight
		},
		{
			id:        "hacker",
			names:     Color256Palette(82),                        // Green
			sys:       Color256(22),                               // Another green
			pm:        Color256(28),                               // More green, slightly lighter
			highlight: style(Bold + "\033[48;5;22m\033[38;5;46m"), // Green on dark green
		},
		{
			id: "mono",
		},
	}

	DefaultTheme = &Themes[0]

	/* Some debug helpers for your convenience:

	// Debug for palettes
	printPalette(allColors256())

	// Debug for themes
	for _, t := range Themes {
		printTheme(t)
	}

	*/
}

func printTheme(t Theme) {
	fmt.Println("Printing theme:", t.ID())
	if t.names != nil {
		for i, color := range t.names.colors {
			fmt.Printf("%s ", color.Format(fmt.Sprintf("name%d", i)))
		}
		fmt.Println("")
	}
	fmt.Println(t.ColorSys("SystemMsg"))
	fmt.Println(t.ColorPM("PrivateMsg"))
	fmt.Println(t.Highlight("Highlight"))
	fmt.Println("")
}

func printPalette(p *Palette) {
	for i, color := range p.colors {
		fmt.Printf("%d\t%s\n", i, color.Format(color.String()+" "))
	}
}
<commit_msg>chat/message: Add second resolution<commit_after>package message

import (
	"fmt"
	"time"
)

const (
	// Reset resets the color
	Reset = "\033[0m"

	// Bold makes the following text bold
	Bold = "\033[1m"

	// Dim dims the following text
	Dim = "\033[2m"

	// Italic makes the following text italic
	Italic = "\033[3m"

	// Underline underlines the following text
	Underline = "\033[4m"

	// Blink blinks the following text
	Blink = "\033[5m"

	// Invert inverts the following text
	Invert = "\033[7m"

	// Newline
	Newline = "\r\n"

	// BEL
	Bel = "\007"
)

// Interface for Styles
type Style interface {
	String() string
	Format(string) string
}

// General hardcoded style, mostly used as a crutch until we flesh out the
// framework to support backgrounds etc.
type style string

func (c style) String() string {
	return string(c)
}

func (c style) Format(s string) string {
	return c.String() + s + Reset
}

// 256 color type, for terminals who support it
type Color256 uint8

// String version of this color
func (c Color256) String() string {
	return fmt.Sprintf("38;05;%d", c)
}

// Return formatted string with this color
func (c Color256) Format(s string) string {
	return "\033[" + c.String() + "m" + s + Reset
}

func Color256Palette(colors ...uint8) *Palette {
	size := len(colors)
	p := make([]Style, 0, size)
	for _, color := range colors {
		p = append(p, Color256(color))
	}
	return &Palette{
		colors: p,
		size:   size,
	}
}

// No color, used for mono theme
type Color0 struct{}

// No-op for Color0
func (c Color0) String() string {
	return ""
}

// No-op for Color0
func (c Color0) Format(s string) string {
	return s
}

// Container for a collection of colors
type Palette struct {
	colors []Style
	size   int
}

// Get a color by index, overflows are looped around.
func (p Palette) Get(i int) Style {
	if p.size == 1 {
		return p.colors[0]
	}
	return p.colors[i%(p.size-1)]
}

func (p Palette) Len() int {
	return p.size
}

func (p Palette) String() string {
	r := ""
	for _, c := range p.colors {
		r += c.Format("X")
	}
	return r
}

// Collection of settings for chat
type Theme struct {
	id        string
	sys       Style
	pm        Style
	highlight Style
	names     *Palette
}

func (theme Theme) ID() string {
	return theme.id
}

// Colorize name string given some index
func (theme Theme) ColorName(u *User) string {
	if theme.names == nil {
		return u.Name()
	}

	return theme.names.Get(u.colorIdx).Format(u.Name())
}

// Colorize the PM string
func (theme Theme) ColorPM(s string) string {
	if theme.pm == nil {
		return s
	}

	return theme.pm.Format(s)
}

// Colorize the Sys message
func (theme Theme) ColorSys(s string) string {
	if theme.sys == nil {
		return s
	}

	return theme.sys.Format(s)
}

// Highlight a matched string, usually name
func (theme Theme) Highlight(s string) string {
	if theme.highlight == nil {
		return s
	}
	return theme.highlight.Format(s)
}

// Timestamp formats and colorizes the timestamp.
func (theme Theme) Timestamp(t time.Time) string {
	// TODO: Change this per-theme? Or config?
	return theme.sys.Format(t.Format("2006-01-02 15:04:05 UTC"))
}

// List of initialzied themes
var Themes []Theme

// Default theme to use
var DefaultTheme *Theme

func allColors256() *Palette {
	colors := []uint8{}
	var i uint8
	for i = 0; i < 255; i++ {
		colors = append(colors, i)
	}
	return Color256Palette(colors...)
}

func readableColors256() *Palette {
	colors := []uint8{}
	var i uint8
	for i = 0; i < 255; i++ {
		if i == 0 || i == 7 || i == 8 || i == 15 || i == 16 || i == 17 || i > 230 {
			// Skip 31 Shades of Grey, and one hyperintelligent shade of blue.
			continue
		}
		colors = append(colors, i)
	}
	return Color256Palette(colors...)
}

func init() {
	Themes = []Theme{
		{
			id:        "colors",
			names:     readableColors256(),
			sys:       Color256(245),                              // Grey
			pm:        Color256(7),                                // White
			highlight: style(Bold + "\033[48;5;11m\033[38;5;16m"), // Yellow highlight
		},
		{
			id:        "solarized",
			names:     Color256Palette(1, 2, 3, 4, 5, 6, 7, 9, 13),
			sys:       Color256(11),                              // Yellow
			pm:        Color256(15),                              // White
			highlight: style(Bold + "\033[48;5;3m\033[38;5;94m"), // Orange highlight
		},
		{
			id:        "hacker",
			names:     Color256Palette(82),                        // Green
			sys:       Color256(22),                               // Another green
			pm:        Color256(28),                               // More green, slightly lighter
			highlight: style(Bold + "\033[48;5;22m\033[38;5;46m"), // Green on dark green
		},
		{
			id: "mono",
		},
	}

	DefaultTheme = &Themes[0]

	/* Some debug helpers for your convenience:

	// Debug for palettes
	printPalette(allColors256())

	// Debug for themes
	for _, t := range Themes {
		printTheme(t)
	}

	*/
}

func printTheme(t Theme) {
	fmt.Println("Printing theme:", t.ID())
	if t.names != nil {
		for i, color := range t.names.colors {
			fmt.Printf("%s ", color.Format(fmt.Sprintf("name%d", i)))
		}
		fmt.Println("")
	}
	fmt.Println(t.ColorSys("SystemMsg"))
	fmt.Println(t.ColorPM("PrivateMsg"))
	fmt.Println(t.Highlight("Highlight"))
	fmt.Println("")
}

func printPalette(p *Palette) {
	for i, color := range p.colors {
		fmt.Printf("%d\t%s\n", i, color.Format(color.String()+" "))
	}
}
<|endoftext|>


<commit_before>package ec2

import (
	"fmt"
	"launchpad.net/goamz/ec2"
	"launchpad.net/goamz/s3"
	"launchpad.net/juju/go/environs"
	"launchpad.net/juju/go/state"
	"sync"
)

const zkPort = 2181
var zkPortSuffix = fmt.Sprintf(":%d", zkPort)

func init() {
	environs.RegisterProvider("ec2", environProvider{})
}

type environProvider struct{}

var _ environs.EnvironProvider = environProvider{}

type environ struct {
	name             string
	config           *providerConfig
	ec2              *ec2.EC2
	s3               *s3.S3
	checkBucket      sync.Once
	checkBucketError error
}

var _ environs.Environ = (*environ)(nil)

type instance struct {
	*ec2.Instance
}

func (inst *instance) String() string {
	return inst.Id()
}

var _ environs.Instance = (*instance)(nil)

func (inst *instance) Id() string {
	return inst.InstanceId
}

func (inst *instance) DNSName() string {
	return inst.Instance.DNSName
}

func (environProvider) Open(name string, config interface{}) (e environs.Environ, err error) {
	cfg := config.(*providerConfig)
	if Regions[cfg.region].EC2Endpoint == "" {
		return nil, fmt.Errorf("no ec2 endpoint found for region %q, opening %q", cfg.region, name)
	}
	return &environ{
		name:   name,
		config: cfg,
		ec2:    ec2.New(cfg.auth, Regions[cfg.region]),
		s3:     s3.New(cfg.auth, Regions[cfg.region]),
	}, nil
}

func (e *environ) Bootstrap() (*state.Info, error) {
	_, err := e.loadState()
	if err == nil {
		return nil, fmt.Errorf("environment is already bootstrapped")
	}
	if s3err, _ := err.(*s3.Error); s3err != nil && s3err.StatusCode != 404 {
		return nil, err
	}
	inst, err := e.startInstance(0, nil, true)
	if err != nil {
		return nil, fmt.Errorf("cannot start bootstrap instance: %v", err)
	}
	err = e.saveState(&bootstrapState{
		ZookeeperInstances: []string{inst.Id()},
	})
	if err != nil {
		// ignore error on StopInstance because the previous error is
		// more important.
		e.StopInstances([]environs.Instance{inst})
		return nil, err
	}
	// TODO wait for the DNS name of the instance to appear.
	// This will happen in a later CL.

	// TODO make safe in the case of racing Bootstraps
	// If two Bootstraps are called concurrently, there's
	// no way to use S3 to make sure that only one succeeds.
	// Perhaps consider using SimpleDB for state storage
	// which would enable that possibility.
	return &state.Info{[]string{inst.DNSName() + zkPortSuffix}}, nil
}

func (e *environ) StateInfo() (*state.Info, error) {
	st, err := e.loadState()
	if err != nil {
		return nil, err
	}
	resp, err := e.ec2.Instances(st.ZookeeperInstances, nil)
	if err != nil {
		return nil, fmt.Errorf("cannot list instances: %v", err)
	}
	var insts []environs.Instance
	for i := range resp.Reservations {
		r := &resp.Reservations[i]
		for j := range r.Instances {
			insts = append(insts, &instance{&r.Instances[j]})
		}
	}

	addrs := make([]string, len(insts))
	for i, inst := range insts {
		addr := inst.DNSName()
		if addr == "" {
			return nil, fmt.Errorf("zookeeper instance %q does not yet have a DNS address", inst.Id())
		}
		addrs[i] = addr + zkPortSuffix
	}
	return &state.Info{Addrs: addrs}, nil
}

func (e *environ) StartInstance(machineId int, info *state.Info) (environs.Instance, error) {
	return e.startInstance(machineId, info, false)
}

// startInstance is the internal version of StartInstance, used by Bootstrap
// as well as via StartInstance itself. If master is true, a bootstrap
// instance will be started.
func (e *environ) startInstance(machineId int, info *state.Info, master bool) (environs.Instance, error) {
	image, err := FindImageSpec(DefaultImageConstraint)
	if err != nil {
		return nil, fmt.Errorf("cannot find image: %v", err)
	}
	groups, err := e.setUpGroups(machineId)
	if err != nil {
		return nil, fmt.Errorf("cannot set up groups: %v", err)
	}
	instances, err := e.ec2.RunInstances(&ec2.RunInstances{
		ImageId:        image.ImageId,
		MinCount:       1,
		MaxCount:       1,
		UserData:       nil,
		InstanceType:   "m1.small",
		SecurityGroups: groups,
	})
	if err != nil {
		return nil, fmt.Errorf("cannot run instances: %v", err)
	}
	if len(instances.Instances) != 1 {
		return nil, fmt.Errorf("expected 1 started instance, got %d", len(instances.Instances))
	}
	return &instance{&instances.Instances[0]}, nil
}

func (e *environ) StopInstances(insts []environs.Instance) error {
	if len(insts) == 0 {
		return nil
	}
	names := make([]string, len(insts))
	for i, inst := range insts {
		names[i] = inst.(*instance).InstanceId
	}
	_, err := e.ec2.TerminateInstances(names)
	return err
}

func (e *environ) Instances(ids []string) ([]environs.Instance, error) {
	if len(ids) == 0 {
		return nil, nil
	}
	insts := make([]environs.Instance, len(ids))

	// TODO make a series of requests to cope with eventual consistency.
	filter := ec2.NewFilter()
	filter.Add("instance-state-name", "pending", "running")
	filter.Add("group-name", e.groupName())
	filter.Add("instance-id", ids...)
	resp, err := e.ec2.Instances(nil, filter)
	if err != nil {
		return nil, err
	}
	// For each requested id, add it to the returned instances
	// if we find it in the response.
	n := 0
	for i, id := range ids {
		if insts[i] != nil {
			continue
		}
		for j := range resp.Reservations {
			r := &resp.Reservations[j]
			for k := range r.Instances {
				inst := & r.Instances[k]
				if inst.InstanceId == id {
					insts[i] = &instance{inst}
					n++
				}
			}
		}
	}
	if n == 0 {
		return nil, environs.ErrMissingInstance
	}
	if n < len(ids) {
		return insts, environs.ErrMissingInstance
	}
	return insts, err
}

func (e *environ) Destroy(insts []environs.Instance) error {
	// Try to find all the instances in the environ's group.
	filter := ec2.NewFilter()
	filter.Add("instance-state-name", "pending", "running")
	filter.Add("group-name", e.groupName())
	resp, err := e.ec2.Instances(nil, filter)
	if err != nil {
		return fmt.Errorf("cannot get instances: %v", err)
	}
	var ids []string
	hasId := make(map[string]bool)
	for _, r := range resp.Reservations {
		for _, inst := range r.Instances {
			ids = append(ids, inst.InstanceId)
			hasId[inst.InstanceId] = true
		}
	}

	// Then add any instances we've been told about
	// but haven't yet shown up in the instance list.
	for _, inst := range insts {
		id := inst.Id()
		if !hasId[id] {
			ids = append(ids, id)
			hasId[id] = true
		}
	}
	if len(ids) > 0 {
		_, err = e.ec2.TerminateInstances(ids)
	}
	// If the instance doesn't exist, we don't care
	if err != nil && !hasCode(err, "InvalidInstance.NotFound") {
		return err
	}
	err = e.deleteState()
	if err != nil {
		return err
	}
	return nil
}

func (e *environ) machineGroupName(machineId int) string {
	return fmt.Sprintf("%s-%d", e.groupName(), machineId)
}

func (e *environ) groupName() string {
	return "juju-" + e.name
}

// setUpGroups creates the security groups for the new machine, and
// returns them.
//
// Instances are tagged with a group so they can be distinguished from
// other instances that might be running on the same EC2 account.  In
// addition, a specific machine security group is created for each
// machine, so that its firewall rules can be configured per machine.
func (e *environ) setUpGroups(machineId int) ([]ec2.SecurityGroup, error) {
	jujuGroup := ec2.SecurityGroup{Name: e.groupName()}
	jujuMachineGroup := ec2.SecurityGroup{Name: e.machineGroupName(machineId)}

	f := ec2.NewFilter()
	f.Add("group-name", jujuGroup.Name, jujuMachineGroup.Name)
	groups, err := e.ec2.SecurityGroups(nil, f)
	if err != nil {
		return nil, fmt.Errorf("cannot get security groups: %v", err)
	}

	for _, g := range groups.Groups {
		switch g.Name {
		case jujuGroup.Name:
			jujuGroup = g.SecurityGroup
		case jujuMachineGroup.Name:
			jujuMachineGroup = g.SecurityGroup
		}
	}

	// Create the provider group if doesn't exist.
	if jujuGroup.Id == "" {
		r, err := e.ec2.CreateSecurityGroup(jujuGroup.Name, "juju group for "+e.name)
		if err != nil {
			return nil, fmt.Errorf("cannot create juju security group: %v", err)
		}
		jujuGroup = r.SecurityGroup

		_, err = e.ec2.AuthorizeSecurityGroup(jujuGroup, []ec2.IPPerm{
			// TODO delete this authorization when we can do
			// the zookeeper ssh tunnelling.
			{
				Protocol:  "tcp",
				FromPort:  zkPort,
				ToPort:    zkPort,
				SourceIPs: []string{"0.0.0.0/0"},
			},
			{
				Protocol:  "tcp",
				FromPort:  22,
				ToPort:    22,
				SourceIPs: []string{"0.0.0.0/0"},
			},
			// TODO authorize internal traffic
		})
		if err != nil && !hasCode(err, "InvalidPermission.Duplicate") {
			return nil, fmt.Errorf("cannot authorize security group: %v", err)
		}
	}

	// Create the machine-specific group, but first see if there's
	// one already existing from a previous machine launch;
	// if so, delete it, since it can have the wrong firewall setup
	if jujuMachineGroup.Id != "" {
		_, err := e.ec2.DeleteSecurityGroup(jujuMachineGroup)
		if err != nil {
			return nil, fmt.Errorf("cannot delete old security group %q: %v", jujuMachineGroup.Name, err)
		}
	}
	descr := fmt.Sprintf("juju group for %s machine %d", e.name, machineId)
	r, err := e.ec2.CreateSecurityGroup(jujuMachineGroup.Name, descr)
	if err != nil {
		return nil, fmt.Errorf("cannot create machine group %q: %v", jujuMachineGroup.Name, err)
	}
	return []ec2.SecurityGroup{jujuGroup, r.SecurityGroup}, nil
}

// hasCode true if the provided error has the given ec2 error code.
func hasCode(err error, code string) bool {
	ec2err, _ := err.(*ec2.Error)
	return ec2err != nil && ec2err.Code == code
}
<commit_msg>rename hasCode to ec2ErrCode<commit_after>package ec2

import (
	"fmt"
	"launchpad.net/goamz/ec2"
	"launchpad.net/goamz/s3"
	"launchpad.net/juju/go/environs"
	"launchpad.net/juju/go/state"
	"sync"
)

const zkPort = 2181
var zkPortSuffix = fmt.Sprintf(":%d", zkPort)

func init() {
	environs.RegisterProvider("ec2", environProvider{})
}

type environProvider struct{}

var _ environs.EnvironProvider = environProvider{}

type environ struct {
	name             string
	config           *providerConfig
	ec2              *ec2.EC2
	s3               *s3.S3
	checkBucket      sync.Once
	checkBucketError error
}

var _ environs.Environ = (*environ)(nil)

type instance struct {
	*ec2.Instance
}

func (inst *instance) String() string {
	return inst.Id()
}

var _ environs.Instance = (*instance)(nil)

func (inst *instance) Id() string {
	return inst.InstanceId
}

func (inst *instance) DNSName() string {
	return inst.Instance.DNSName
}

func (environProvider) Open(name string, config interface{}) (e environs.Environ, err error) {
	cfg := config.(*providerConfig)
	if Regions[cfg.region].EC2Endpoint == "" {
		return nil, fmt.Errorf("no ec2 endpoint found for region %q, opening %q", cfg.region, name)
	}
	return &environ{
		name:   name,
		config: cfg,
		ec2:    ec2.New(cfg.auth, Regions[cfg.region]),
		s3:     s3.New(cfg.auth, Regions[cfg.region]),
	}, nil
}

func (e *environ) Bootstrap() (*state.Info, error) {
	_, err := e.loadState()
	if err == nil {
		return nil, fmt.Errorf("environment is already bootstrapped")
	}
	if s3err, _ := err.(*s3.Error); s3err != nil && s3err.StatusCode != 404 {
		return nil, err
	}
	inst, err := e.startInstance(0, nil, true)
	if err != nil {
		return nil, fmt.Errorf("cannot start bootstrap instance: %v", err)
	}
	err = e.saveState(&bootstrapState{
		ZookeeperInstances: []string{inst.Id()},
	})
	if err != nil {
		// ignore error on StopInstance because the previous error is
		// more important.
		e.StopInstances([]environs.Instance{inst})
		return nil, err
	}
	// TODO wait for the DNS name of the instance to appear.
	// This will happen in a later CL.

	// TODO make safe in the case of racing Bootstraps
	// If two Bootstraps are called concurrently, there's
	// no way to use S3 to make sure that only one succeeds.
	// Perhaps consider using SimpleDB for state storage
	// which would enable that possibility.
	return &state.Info{[]string{inst.DNSName() + zkPortSuffix}}, nil
}

func (e *environ) StateInfo() (*state.Info, error) {
	st, err := e.loadState()
	if err != nil {
		return nil, err
	}
	resp, err := e.ec2.Instances(st.ZookeeperInstances, nil)
	if err != nil {
		return nil, fmt.Errorf("cannot list instances: %v", err)
	}
	var insts []environs.Instance
	for i := range resp.Reservations {
		r := &resp.Reservations[i]
		for j := range r.Instances {
			insts = append(insts, &instance{&r.Instances[j]})
		}
	}

	addrs := make([]string, len(insts))
	for i, inst := range insts {
		addr := inst.DNSName()
		if addr == "" {
			return nil, fmt.Errorf("zookeeper instance %q does not yet have a DNS address", inst.Id())
		}
		addrs[i] = addr + zkPortSuffix
	}
	return &state.Info{Addrs: addrs}, nil
}

func (e *environ) StartInstance(machineId int, info *state.Info) (environs.Instance, error) {
	return e.startInstance(machineId, info, false)
}

// startInstance is the internal version of StartInstance, used by Bootstrap
// as well as via StartInstance itself. If master is true, a bootstrap
// instance will be started.
func (e *environ) startInstance(machineId int, info *state.Info, master bool) (environs.Instance, error) {
	image, err := FindImageSpec(DefaultImageConstraint)
	if err != nil {
		return nil, fmt.Errorf("cannot find image: %v", err)
	}
	groups, err := e.setUpGroups(machineId)
	if err != nil {
		return nil, fmt.Errorf("cannot set up groups: %v", err)
	}
	instances, err := e.ec2.RunInstances(&ec2.RunInstances{
		ImageId:        image.ImageId,
		MinCount:       1,
		MaxCount:       1,
		UserData:       nil,
		InstanceType:   "m1.small",
		SecurityGroups: groups,
	})
	if err != nil {
		return nil, fmt.Errorf("cannot run instances: %v", err)
	}
	if len(instances.Instances) != 1 {
		return nil, fmt.Errorf("expected 1 started instance, got %d", len(instances.Instances))
	}
	return &instance{&instances.Instances[0]}, nil
}

func (e *environ) StopInstances(insts []environs.Instance) error {
	if len(insts) == 0 {
		return nil
	}
	names := make([]string, len(insts))
	for i, inst := range insts {
		names[i] = inst.(*instance).InstanceId
	}
	_, err := e.ec2.TerminateInstances(names)
	return err
}

func (e *environ) Instances(ids []string) ([]environs.Instance, error) {
	if len(ids) == 0 {
		return nil, nil
	}
	insts := make([]environs.Instance, len(ids))

	// TODO make a series of requests to cope with eventual consistency.
	filter := ec2.NewFilter()
	filter.Add("instance-state-name", "pending", "running")
	filter.Add("group-name", e.groupName())
	filter.Add("instance-id", ids...)
	resp, err := e.ec2.Instances(nil, filter)
	if err != nil {
		return nil, err
	}
	// For each requested id, add it to the returned instances
	// if we find it in the response.
	n := 0
	for i, id := range ids {
		if insts[i] != nil {
			continue
		}
		for j := range resp.Reservations {
			r := &resp.Reservations[j]
			for k := range r.Instances {
				inst := & r.Instances[k]
				if inst.InstanceId == id {
					insts[i] = &instance{inst}
					n++
				}
			}
		}
	}
	if n == 0 {
		return nil, environs.ErrMissingInstance
	}
	if n < len(ids) {
		return insts, environs.ErrMissingInstance
	}
	return insts, err
}

func (e *environ) Destroy(insts []environs.Instance) error {
	// Try to find all the instances in the environ's group.
	filter := ec2.NewFilter()
	filter.Add("instance-state-name", "pending", "running")
	filter.Add("group-name", e.groupName())
	resp, err := e.ec2.Instances(nil, filter)
	if err != nil {
		return fmt.Errorf("cannot get instances: %v", err)
	}
	var ids []string
	hasId := make(map[string]bool)
	for _, r := range resp.Reservations {
		for _, inst := range r.Instances {
			ids = append(ids, inst.InstanceId)
			hasId[inst.InstanceId] = true
		}
	}

	// Then add any instances we've been told about
	// but haven't yet shown up in the instance list.
	for _, inst := range insts {
		id := inst.Id()
		if !hasId[id] {
			ids = append(ids, id)
			hasId[id] = true
		}
	}
	if len(ids) > 0 {
		_, err = e.ec2.TerminateInstances(ids)
	}
	// If the instance doesn't exist, we don't care
	if err != nil && ec2ErrCode(err) != "InvalidInstance.NotFound" {
		return err
	}
	err = e.deleteState()
	if err != nil {
		return err
	}
	return nil
}

func (e *environ) machineGroupName(machineId int) string {
	return fmt.Sprintf("%s-%d", e.groupName(), machineId)
}

func (e *environ) groupName() string {
	return "juju-" + e.name
}

// setUpGroups creates the security groups for the new machine, and
// returns them.
//
// Instances are tagged with a group so they can be distinguished from
// other instances that might be running on the same EC2 account.  In
// addition, a specific machine security group is created for each
// machine, so that its firewall rules can be configured per machine.
func (e *environ) setUpGroups(machineId int) ([]ec2.SecurityGroup, error) {
	jujuGroup := ec2.SecurityGroup{Name: e.groupName()}
	jujuMachineGroup := ec2.SecurityGroup{Name: e.machineGroupName(machineId)}

	f := ec2.NewFilter()
	f.Add("group-name", jujuGroup.Name, jujuMachineGroup.Name)
	groups, err := e.ec2.SecurityGroups(nil, f)
	if err != nil {
		return nil, fmt.Errorf("cannot get security groups: %v", err)
	}

	for _, g := range groups.Groups {
		switch g.Name {
		case jujuGroup.Name:
			jujuGroup = g.SecurityGroup
		case jujuMachineGroup.Name:
			jujuMachineGroup = g.SecurityGroup
		}
	}

	// Create the provider group if doesn't exist.
	if jujuGroup.Id == "" {
		r, err := e.ec2.CreateSecurityGroup(jujuGroup.Name, "juju group for "+e.name)
		if err != nil {
			return nil, fmt.Errorf("cannot create juju security group: %v", err)
		}
		jujuGroup = r.SecurityGroup

		_, err = e.ec2.AuthorizeSecurityGroup(jujuGroup, []ec2.IPPerm{
			// TODO delete this authorization when we can do
			// the zookeeper ssh tunnelling.
			{
				Protocol:  "tcp",
				FromPort:  zkPort,
				ToPort:    zkPort,
				SourceIPs: []string{"0.0.0.0/0"},
			},
			{
				Protocol:  "tcp",
				FromPort:  22,
				ToPort:    22,
				SourceIPs: []string{"0.0.0.0/0"},
			},
			// TODO authorize internal traffic
		})
		if err != nil && ec2ErrCode(err) != "InvalidPermission.Duplicate" {
			return nil, fmt.Errorf("cannot authorize security group: %v", err)
		}
	}

	// Create the machine-specific group, but first see if there's
	// one already existing from a previous machine launch;
	// if so, delete it, since it can have the wrong firewall setup
	if jujuMachineGroup.Id != "" {
		_, err := e.ec2.DeleteSecurityGroup(jujuMachineGroup)
		if err != nil {
			return nil, fmt.Errorf("cannot delete old security group %q: %v", jujuMachineGroup.Name, err)
		}
	}
	descr := fmt.Sprintf("juju group for %s machine %d", e.name, machineId)
	r, err := e.ec2.CreateSecurityGroup(jujuMachineGroup.Name, descr)
	if err != nil {
		return nil, fmt.Errorf("cannot create machine group %q: %v", jujuMachineGroup.Name, err)
	}
	return []ec2.SecurityGroup{jujuGroup, r.SecurityGroup}, nil
}

// If the err is of type *ec2.Error, ec2ErrCode returns
// its code, otherwise it returns the empty string.
func ec2ErrCode(err error) string {
	ec2err, _ := err.(*ec2.Error)
	if ec2err == nil {
		return ""
	}
	return ec2err.Code
}
<|endoftext|>


<commit_before>package device

import (
	"github.com/Comcast/webpa-common/wrp"
)

// EventType is the type of device-related event
type EventType uint8

const (
	Connect EventType = iota
	Disconnect
	MessageReceived
	MessageFailed
	Pong

	InvalidEventString string = "!!INVALID DEVICE EVENT TYPE!!"
)

var (
	// emptyString is a convenient instance of an empty string
	emptyString string
)

func (et EventType) String() string {
	switch et {
	case Connect:
		return "Connect"
	case Disconnect:
		return "Disconnect"
	case MessageReceived:
		return "MessageReceived"
	case MessageFailed:
		return "MessageFailed"
	case Pong:
		return "Pong"
	default:
		return InvalidEventString
	}
}

// Event represents a single occurrence of interest for device-related applications.
// Instances of Event should be considered immutable by application code.  Also, Event
// instances should not be stored across calls to a listener, as the infrastructure is
// free to reuse Event instances.
type Event struct {
	// Type describes the kind of this event.  This field is always set.
	Type EventType

	// Device refers to the device, possibly disconnected, for which this event is being set.
	// This field is always set.
	Device Interface

	// Message is the WRP message relevant to this event.  This field is only set for
	// MessageReceived and MessageFailed events.
	//
	// Never assume that it is safe to use this Message outside the listener invocation.  Make
	// a copy if this Message is needed by other goroutines or if it needs to be part of a long-lived
	// data structure.
	Message wrp.Routable

	// Format is the encoding format of the Contents field
	Format wrp.Format

	// Contents is the encoded representation of the Message field.  It is always set if and only if
	// the Message field is set.
	//
	// Never assume that it is safe to use this byte slice outside the listener invocation.  Make
	// a copy if this byte slice is needed by other goroutines or if it needs to be part of a long-lived
	// data structure.
	Contents []byte

	// Error is the error which occurred during an attempt to send a message.  This field is only populated
	// for MessageFailed events when there was an actual error.  For MessageFailed events that indicate a
	// device was disconnected with enqueued messages, this field will be nil.
	Error error

	// Data is the pong data associated with this event.  This field is only set for a Pong event.
	Data string
}

// setMessageFailed sets or resets this event's fields to represent a MessageFailed event.
func (e *Event) setMessageFailed(device Interface, message wrp.Routable, format wrp.Format, contents []byte, err error) {
	e.Type = MessageFailed
	e.Device = device
	e.Message = message
	e.Format = format
	e.Contents = contents
	e.Error = err
	e.Data = emptyString
}

// setRequestFailed sets or resets this event's field to represent a MessageFailed event for a device Request
func (e *Event) setRequestFailed(device Interface, request *Request, err error) {
	e.Type = MessageFailed
	e.Device = device
	e.Message = request.Message
	e.Format = request.Format
	e.Contents = request.Contents
	e.Error = err
	e.Data = emptyString
}

// setMessageReceived sets or resets this event's fields to represent a MessageReceived event.
func (e *Event) setMessageReceived(device Interface, message wrp.Routable, format wrp.Format, contents []byte) {
	e.Type = MessageReceived
	e.Device = device
	e.Message = message
	e.Format = format
	e.Contents = contents
	e.Error = nil
	e.Data = emptyString
}

// setPong sets or resets this event's fields to represent a Pong event.
func (e *Event) setPong(device Interface, data string) {
	e.Type = Pong
	e.Device = device
	e.Message = nil
	e.Format = wrp.Format(-1)
	e.Contents = nil
	e.Error = nil
	e.Data = data
}

// Listener is an event sink.  Listeners should never modify events and should never
// store events for later use.  If data from an event is needed for another goroutine
// or for long-term storage, a copy should be made.
type Listener func(*Event)
<commit_msg>Comments on each event type<commit_after>package device

import (
	"github.com/Comcast/webpa-common/wrp"
)

// EventType is the type of device-related event
type EventType uint8

const (
	// Connect indicates a successful device connection.  After receipt of this event, the given
	// Device is able to receive requests.
	Connect EventType = iota

	// Disconnect indicates a device disconnection.  After receipt of this event, the given
	// Device can no longer receive requests.
	Disconnect

	// MessageSent indicates that a message was successfully dispatched to a device.
	MessageSent

	// MessageReceived indicates that a message has been successfully received and
	// dispatched to any goroutine waiting on it, as would be the case for a response.
	MessageReceived

	// MessageFailed indicates that a message could not be sent to a device, either because
	// of a communications error or due to the device disconnecting.  For each enqueued message
	// at the time of a device's disconnection, there will be (1) MessageFailed event.
	MessageFailed

	// Pong occurs when a device has responded to a ping
	Pong

	InvalidEventString string = "!!INVALID DEVICE EVENT TYPE!!"
)

var (
	// emptyString is a convenient instance of an empty string
	emptyString string
)

func (et EventType) String() string {
	switch et {
	case Connect:
		return "Connect"
	case Disconnect:
		return "Disconnect"
	case MessageSent:
		return "MessageSent"
	case MessageReceived:
		return "MessageReceived"
	case MessageFailed:
		return "MessageFailed"
	case Pong:
		return "Pong"
	default:
		return InvalidEventString
	}
}

// Event represents a single occurrence of interest for device-related applications.
// Instances of Event should be considered immutable by application code.  Also, Event
// instances should not be stored across calls to a listener, as the infrastructure is
// free to reuse Event instances.
type Event struct {
	// Type describes the kind of this event.  This field is always set.
	Type EventType

	// Device refers to the device, possibly disconnected, for which this event is being set.
	// This field is always set.
	Device Interface

	// Message is the WRP message relevant to this event.  This field is only set for
	// MessageReceived and MessageFailed events.
	//
	// Never assume that it is safe to use this Message outside the listener invocation.  Make
	// a copy if this Message is needed by other goroutines or if it needs to be part of a long-lived
	// data structure.
	Message wrp.Routable

	// Format is the encoding format of the Contents field
	Format wrp.Format

	// Contents is the encoded representation of the Message field.  It is always set if and only if
	// the Message field is set.
	//
	// Never assume that it is safe to use this byte slice outside the listener invocation.  Make
	// a copy if this byte slice is needed by other goroutines or if it needs to be part of a long-lived
	// data structure.
	Contents []byte

	// Error is the error which occurred during an attempt to send a message.  This field is only populated
	// for MessageFailed events when there was an actual error.  For MessageFailed events that indicate a
	// device was disconnected with enqueued messages, this field will be nil.
	Error error

	// Data is the pong data associated with this event.  This field is only set for a Pong event.
	Data string
}

// setMessageFailed sets or resets this event's fields to represent a MessageFailed event.
func (e *Event) setMessageFailed(device Interface, message wrp.Routable, format wrp.Format, contents []byte, err error) {
	e.Type = MessageFailed
	e.Device = device
	e.Message = message
	e.Format = format
	e.Contents = contents
	e.Error = err
	e.Data = emptyString
}

// setRequestFailed sets or resets this event's field to represent a MessageFailed event for a device Request
func (e *Event) setRequestFailed(device Interface, request *Request, err error) {
	e.Type = MessageFailed
	e.Device = device
	e.Message = request.Message
	e.Format = request.Format
	e.Contents = request.Contents
	e.Error = err
	e.Data = emptyString
}

// setMessageReceived sets or resets this event's fields to represent a MessageReceived event.
func (e *Event) setMessageReceived(device Interface, message wrp.Routable, format wrp.Format, contents []byte) {
	e.Type = MessageReceived
	e.Device = device
	e.Message = message
	e.Format = format
	e.Contents = contents
	e.Error = nil
	e.Data = emptyString
}

// setPong sets or resets this event's fields to represent a Pong event.
func (e *Event) setPong(device Interface, data string) {
	e.Type = Pong
	e.Device = device
	e.Message = nil
	e.Format = wrp.Format(-1)
	e.Contents = nil
	e.Error = nil
	e.Data = data
}

// Listener is an event sink.  Listeners should never modify events and should never
// store events for later use.  If data from an event is needed for another goroutine
// or for long-term storage, a copy should be made.
type Listener func(*Event)
<|endoftext|>


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

package device

import (
	"github.com/dirkjabl/bricker/net/errors"
	"github.com/dirkjabl/bricker/net/packet"
)

// Type for a event result.
type Resulter interface {
	FromPacket(*packet.Packet) error
	String() string
	Copy() Resulter
}

// CheckForFromPacket tests if a needed parameter is nil and returns an error.
func CheckForFromPacket(r Resulter, p *packet.Packet) error {
	if r == nil {
		return NewDeviceError(ErrorNoMemoryForResult)
	}
	if p == nil {
		return NewDeviceError(ErrorNoPacketToConvert)
	}
	return nil
}

// Emptry result type. Needful for Events/Result, where only the errors are checkable.
type EmptyResult struct{}

// FromPacket converts a packet.
// It checks only the errors.
func (er *EmptyResult) FromPacket(p *packet.Packet) error {
	err := CheckForFromPacket(er, p)
	if err != nil {
		return err
	}
	if p.Head != nil {
		err := p.Head.ErrorCode()
		if err != nil && err.Type != errors.ErrorOK {
			return err
		}
	}
	return nil
}

// String fullfill stringer interface.
func (er *EmptyResult) String() string {
	return "EmptyResult []"
}

// IsEmptyResultOk checks if an error occur by an empty result
func IsEmptyResultOk(r Resulter, e error) bool {
	var v bool = false
	if e == nil {
		if _, ok := r.(*EmptyResult); ok {
			return true
		}
	}
	return v
}
<commit_msg>EmptyResult gets a Copy() routine<commit_after>// Copyright 2014 Dirk Jablonowski. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package device

import (
	"github.com/dirkjabl/bricker/net/errors"
	"github.com/dirkjabl/bricker/net/packet"
)

// Type for a event result.
type Resulter interface {
	FromPacket(*packet.Packet) error
	String() string
	Copy() Resulter
}

// CheckForFromPacket tests if a needed parameter is nil and returns an error.
func CheckForFromPacket(r Resulter, p *packet.Packet) error {
	if r == nil {
		return NewDeviceError(ErrorNoMemoryForResult)
	}
	if p == nil {
		return NewDeviceError(ErrorNoPacketToConvert)
	}
	return nil
}

// Emptry result type. Needful for Events/Result, where only the errors are checkable.
type EmptyResult struct{}

// FromPacket converts a packet.
// It checks only the errors.
func (er *EmptyResult) FromPacket(p *packet.Packet) error {
	err := CheckForFromPacket(er, p)
	if err != nil {
		return err
	}
	if p.Head != nil {
		err := p.Head.ErrorCode()
		if err != nil && err.Type != errors.ErrorOK {
			return err
		}
	}
	return nil
}

// String fullfill stringer interface.
func (er *EmptyResult) String() string {
	return "EmptyResult []"
}

// Copy creates new EmptyResult, it needs not to copy.
func (er *EmptyResult) Copy() Resulter {
	return &EmptyResult{}
}

// IsEmptyResultOk checks if an error occur by an empty result
func IsEmptyResultOk(r Resulter, e error) bool {
	var v bool = false
	if e == nil {
		if _, ok := r.(*EmptyResult); ok {
			return true
		}
	}
	return v
}
<|endoftext|>


<commit_before>package devtool

import (
	"context"
	"encoding/json"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"sync"

	"github.com/mafredri/cdp/internal/errors"
)

// DevToolsOption represents a function that sets a DevTools option.
type DevToolsOption func(*DevTools)

// WithClient returns a DevToolsOption that sets the http Client used
// for HTTP GET requests.
func WithClient(client *http.Client) DevToolsOption {
	return func(d *DevTools) {
		d.client = client
	}
}

// DevTools represents a devtools endpoint for managing and querying
// information about targets.
type DevTools struct {
	url    string
	client *http.Client

	mu     sync.Mutex // Protects following.
	lookup bool
}

// New returns a DevTools instance that uses URL.
func New(url string, opts ...DevToolsOption) *DevTools {
	devtools := &DevTools{url: url}
	for _, o := range opts {
		o(devtools)
	}
	if devtools.client == nil {
		devtools.client = &http.Client{}
	}
	return devtools
}

// Type represents the type of Target.
type Type string

// Type enums.
const (
	BackgroundPage Type = "background_page"
	Node           Type = "node"
	Other          Type = "other"
	Page           Type = "page"
	ServiceWorker  Type = "service_worker"
)

// Target represents a devtools target, e.g. a browser tab.
type Target struct {
	Description          string `json:"description"`
	DevToolsFrontendURL  string `json:"devtoolsFrontendUrl"`
	ID                   string `json:"id"`
	Title                string `json:"title"`
	Type                 Type   `json:"type"`
	URL                  string `json:"url"`
	WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"`
}

// Create a new Target, usually a page with about:blank as URL.
func (d *DevTools) Create(ctx context.Context) (*Target, error) {
	return d.CreateURL(ctx, "")
}

// CreateURL is like Create but opens the provided URL. The URL must be
// valid and begin with "http://" or "https://".
func (d *DevTools) CreateURL(ctx context.Context, openURL string) (*Target, error) {
	var escapedQueryURL string

	if openURL != "" {
		if parsed, err := url.Parse(openURL); err != nil || !parsed.IsAbs() {
			return nil, errors.New("devtool: CreateURL: invalid openURL: " + openURL)
		}
		escapedQueryURL = "?" + url.QueryEscape(openURL)
	}

	resp, err := d.httpGet(ctx, "/json/new"+escapedQueryURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	// Returned by Headless Chrome that does
	// not support the "/json/new" endpoint.
	case http.StatusInternalServerError:
		err2 := parseError("CreateUrl: StatusInternalServerError", resp.Body)

		v, err := d.Version(ctx)
		if err != nil {
			return nil, err2
		}

		if v.WebSocketDebuggerURL != "" {
			// This version is too new since it has a debugger URL set.
			return nil, err2
		}

		return fallbackHeadlessCreateURL(ctx, d, openURL)

	case http.StatusOK:
		t := new(Target)
		return t, json.NewDecoder(resp.Body).Decode(t)

	default:
		return nil, parseError("CreateURL", resp.Body)
	}
}

// Get the first Target that matches Type.
func (d *DevTools) Get(ctx context.Context, typ Type) (*Target, error) {
	list, err := d.List(ctx)
	if err != nil {
		return nil, err
	}

	for _, t := range list {
		if t.Type == typ {
			return t, nil
		}
	}

	return nil, errors.New("devtool: Get: could not find target of type: " + string(typ))
}

// List returns a list with all devtools Targets.
func (d *DevTools) List(ctx context.Context) ([]*Target, error) {
	resp, err := d.httpGet(ctx, "/json/list")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, parseError("List", resp.Body)
	}

	var t []*Target
	return t, json.NewDecoder(resp.Body).Decode(&t)
}

// Activate brings focus to the Target.
func (d *DevTools) Activate(ctx context.Context, t *Target) error {
	resp, err := d.httpGet(ctx, "/json/activate/"+t.ID)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return parseError("Activate", resp.Body)
	}

	return nil
}

// Close the Target.
func (d *DevTools) Close(ctx context.Context, t *Target) error {
	resp, err := d.httpGet(ctx, "/json/close/"+t.ID)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return parseError("Close", resp.Body)
	}

	return nil
}

// Version contains the version information for the DevTools endpoint.
type Version struct {
	// Present in Chrome, Edge, Node, etc.
	Browser  string `json:"Browser"`
	Protocol string `json:"Protocol-Version"`

	// Present in Chrome, Edge.
	UserAgent string `json:"User-Agent"`
	V8        string `json:"V8-Version"`
	WebKit    string `json:"WebKit-Version"`

	// Present on Android.
	AndroidPackage string `json:"Android-Package"`

	// Present in Chrome >= 62. Generic browser websocket URL.
	WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"`
}

// Version returns the version information for the DevTools endpoint.
func (d *DevTools) Version(ctx context.Context) (*Version, error) {
	resp, err := d.httpGet(ctx, "/json/version")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, parseError("Version", resp.Body)
	}

	v := new(Version)
	return v, json.NewDecoder(resp.Body).Decode(&v)
}

func (d *DevTools) httpGet(ctx context.Context, path string) (*http.Response, error) {
	if ctx == nil {
		ctx = context.Background()
	}

	err := d.resolveHost(ctx)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(http.MethodGet, d.url+path, nil)
	if err != nil {
		return nil, err
	}

	return d.client.Do(req.WithContext(ctx))
}

// resolveHost does a lookup on the hostname in d.url and tries to
// replace it with a valid IP address. Ever since Chrome 66, the
// DevTools endpoint disallows hostnames other than "localhost".
//
// Example error:
// < HTTP/1.1 500 Internal Server Error
// < Content-Length:63
// < Content-Type:text/html
// <
// Host header is specified and is not an IP address or localhost.
func (d *DevTools) resolveHost(ctx context.Context) error {
	d.mu.Lock()
	defer d.mu.Unlock()

	if d.lookup {
		return nil
	}
	d.lookup = true

	u, err := url.Parse(d.url)
	if err != nil {
		return err
	}
	host := strings.Split(u.Host, ":")
	origHost := host[0]

	if origHost == "localhost" {
		return nil // Nothing to do, localhost is allowed.
	}

	addrs, err := net.DefaultResolver.LookupHost(ctx, origHost)
	if err != nil {
		return err
	}

	newURL := ""
	for _, a := range addrs {
		host[0] = a
		u.Host = strings.Join(host, ":")
		try := u.String()

		// The selection of "/json/version" here is arbitrary,
		// it just needs to exist and not have side-effects.
		req, err := http.NewRequest(http.MethodGet, try+"/json/version", nil)
		if err != nil {
			return err
		}

		resp, err := d.client.Do(req.WithContext(ctx))
		if err == nil && resp.StatusCode == 200 {
			newURL = try
			break
		}
	}
	if newURL == "" {
		return errors.New("could not resolve IP for " + origHost)
	}
	d.url = newURL

	return nil
}

func parseError(from string, r io.Reader) error {
	m, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}
	return errors.New("devtool: " + from + ": " + string(m))
}
<commit_msg>devtool: Close http client request body (#126)<commit_after>package devtool

import (
	"context"
	"encoding/json"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"sync"

	"github.com/mafredri/cdp/internal/errors"
)

// DevToolsOption represents a function that sets a DevTools option.
type DevToolsOption func(*DevTools)

// WithClient returns a DevToolsOption that sets the http Client used
// for HTTP GET requests.
func WithClient(client *http.Client) DevToolsOption {
	return func(d *DevTools) {
		d.client = client
	}
}

// DevTools represents a devtools endpoint for managing and querying
// information about targets.
type DevTools struct {
	url    string
	client *http.Client

	mu     sync.Mutex // Protects following.
	lookup bool
}

// New returns a DevTools instance that uses URL.
func New(url string, opts ...DevToolsOption) *DevTools {
	devtools := &DevTools{url: url}
	for _, o := range opts {
		o(devtools)
	}
	if devtools.client == nil {
		devtools.client = &http.Client{}
	}
	return devtools
}

// Type represents the type of Target.
type Type string

// Type enums.
const (
	BackgroundPage Type = "background_page"
	Node           Type = "node"
	Other          Type = "other"
	Page           Type = "page"
	ServiceWorker  Type = "service_worker"
)

// Target represents a devtools target, e.g. a browser tab.
type Target struct {
	Description          string `json:"description"`
	DevToolsFrontendURL  string `json:"devtoolsFrontendUrl"`
	ID                   string `json:"id"`
	Title                string `json:"title"`
	Type                 Type   `json:"type"`
	URL                  string `json:"url"`
	WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"`
}

// Create a new Target, usually a page with about:blank as URL.
func (d *DevTools) Create(ctx context.Context) (*Target, error) {
	return d.CreateURL(ctx, "")
}

// CreateURL is like Create but opens the provided URL. The URL must be
// valid and begin with "http://" or "https://".
func (d *DevTools) CreateURL(ctx context.Context, openURL string) (*Target, error) {
	var escapedQueryURL string

	if openURL != "" {
		if parsed, err := url.Parse(openURL); err != nil || !parsed.IsAbs() {
			return nil, errors.New("devtool: CreateURL: invalid openURL: " + openURL)
		}
		escapedQueryURL = "?" + url.QueryEscape(openURL)
	}

	resp, err := d.httpGet(ctx, "/json/new"+escapedQueryURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	// Returned by Headless Chrome that does
	// not support the "/json/new" endpoint.
	case http.StatusInternalServerError:
		err2 := parseError("CreateUrl: StatusInternalServerError", resp.Body)

		v, err := d.Version(ctx)
		if err != nil {
			return nil, err2
		}

		if v.WebSocketDebuggerURL != "" {
			// This version is too new since it has a debugger URL set.
			return nil, err2
		}

		return fallbackHeadlessCreateURL(ctx, d, openURL)

	case http.StatusOK:
		t := new(Target)
		return t, json.NewDecoder(resp.Body).Decode(t)

	default:
		return nil, parseError("CreateURL", resp.Body)
	}
}

// Get the first Target that matches Type.
func (d *DevTools) Get(ctx context.Context, typ Type) (*Target, error) {
	list, err := d.List(ctx)
	if err != nil {
		return nil, err
	}

	for _, t := range list {
		if t.Type == typ {
			return t, nil
		}
	}

	return nil, errors.New("devtool: Get: could not find target of type: " + string(typ))
}

// List returns a list with all devtools Targets.
func (d *DevTools) List(ctx context.Context) ([]*Target, error) {
	resp, err := d.httpGet(ctx, "/json/list")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, parseError("List", resp.Body)
	}

	var t []*Target
	return t, json.NewDecoder(resp.Body).Decode(&t)
}

// Activate brings focus to the Target.
func (d *DevTools) Activate(ctx context.Context, t *Target) error {
	resp, err := d.httpGet(ctx, "/json/activate/"+t.ID)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return parseError("Activate", resp.Body)
	}

	return nil
}

// Close the Target.
func (d *DevTools) Close(ctx context.Context, t *Target) error {
	resp, err := d.httpGet(ctx, "/json/close/"+t.ID)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return parseError("Close", resp.Body)
	}

	return nil
}

// Version contains the version information for the DevTools endpoint.
type Version struct {
	// Present in Chrome, Edge, Node, etc.
	Browser  string `json:"Browser"`
	Protocol string `json:"Protocol-Version"`

	// Present in Chrome, Edge.
	UserAgent string `json:"User-Agent"`
	V8        string `json:"V8-Version"`
	WebKit    string `json:"WebKit-Version"`

	// Present on Android.
	AndroidPackage string `json:"Android-Package"`

	// Present in Chrome >= 62. Generic browser websocket URL.
	WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"`
}

// Version returns the version information for the DevTools endpoint.
func (d *DevTools) Version(ctx context.Context) (*Version, error) {
	resp, err := d.httpGet(ctx, "/json/version")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, parseError("Version", resp.Body)
	}

	v := new(Version)
	return v, json.NewDecoder(resp.Body).Decode(&v)
}

func (d *DevTools) httpGet(ctx context.Context, path string) (*http.Response, error) {
	if ctx == nil {
		ctx = context.Background()
	}

	err := d.resolveHost(ctx)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(http.MethodGet, d.url+path, nil)
	if err != nil {
		return nil, err
	}

	return d.client.Do(req.WithContext(ctx))
}

// resolveHost does a lookup on the hostname in d.url and tries to
// replace it with a valid IP address. Ever since Chrome 66, the
// DevTools endpoint disallows hostnames other than "localhost".
//
// Example error:
// < HTTP/1.1 500 Internal Server Error
// < Content-Length:63
// < Content-Type:text/html
// <
// Host header is specified and is not an IP address or localhost.
func (d *DevTools) resolveHost(ctx context.Context) error {
	d.mu.Lock()
	defer d.mu.Unlock()

	if d.lookup {
		return nil
	}
	d.lookup = true

	u, err := url.Parse(d.url)
	if err != nil {
		return err
	}
	host := strings.Split(u.Host, ":")
	origHost := host[0]

	if origHost == "localhost" {
		return nil // Nothing to do, localhost is allowed.
	}

	addrs, err := net.DefaultResolver.LookupHost(ctx, origHost)
	if err != nil {
		return err
	}

	newURL := ""
	for _, a := range addrs {
		host[0] = a
		u.Host = strings.Join(host, ":")
		try := u.String()

		// The selection of "/json/version" here is arbitrary,
		// it just needs to exist and not have side-effects.
		req, err := http.NewRequest(http.MethodGet, try+"/json/version", nil)
		if err != nil {
			return err
		}

		resp, err := d.client.Do(req.WithContext(ctx))
		if err != nil {
			continue
		}
		defer resp.Body.Close()

		if resp.StatusCode == 200 {
			newURL = try
			break
		}
	}
	if newURL == "" {
		return errors.New("could not resolve IP for " + origHost)
	}
	d.url = newURL

	return nil
}

func parseError(from string, r io.Reader) error {
	m, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}
	return errors.New("devtool: " + from + ": " + string(m))
}
<|endoftext|>


<commit_before>package util

import (
	"bytes"
	"encoding/json"
	"fmt"

	"gopkg.in/resty.v0"
)

func (u SomaUtil) DecodeResultFromResponse(resp *resty.Response) *proto.Result {
	decoder := json.NewDecoder(bytes.NewReader(resp.Body()))
	res := proto.Result{}
	err := decoder.Decode(&res)
	u.AbortOnError(err, "Error decoding server response body")
	if res.StatusCode > 299 {
		s := fmt.Sprintf("Request failed: %d - %s", res.StatusCode, res.StatusText)
		msgs := []string{s}
		if res.Errors != nil { // pointer to slice
			msgs = append(msgs, *res.Errors...)
		}
		u.Abort(msgs...)
	}
	return &res
}

func (u SomaUtil) UnfilteredResultFromResponse(resp *resty.Response) *proto.Result {
	decoder := json.NewDecoder(bytes.NewReader(resp.Body()))
	res := proto.Result{}
	err := decoder.Decode(&res)
	u.AbortOnError(err, "Error decoding server response body")
	return &res
}

func (u SomaUtil) VerifyEnvironment(c *resty.Client, env string) {
	resp := u.GetRequest(c, "/environments/")
	res := u.DecodeResultFromResponse(resp)
	for _, e := range *res.Environments {
		if e.Name == env {
			return
		}
	}
	u.Abort(fmt.Sprintf("Invalid environment specified: %s", env))
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Implement custom error types<commit_after>package util

import (
	"bytes"
	"encoding/json"
	"fmt"

	"gopkg.in/resty.v0"
)

type SomaError struct {
	code     int
	somaCode uint16
	text     string
}

func (e SomaError) Error() string {
	return e.text
}

func (e SomaError) RequestError() bool {
	return e.code > 299
}

func (e SomaError) Code() uint16 {
	return e.somaCode
}

func (u SomaUtil) DecodeResultFromResponse(resp *resty.Response) *proto.Result {
	decoder := json.NewDecoder(bytes.NewReader(resp.Body()))
	res := proto.Result{}
	err := decoder.Decode(&res)
	u.AbortOnError(err, "Error decoding server response body")
	if res.StatusCode > 299 {
		s := fmt.Sprintf("Request failed: %d - %s", res.StatusCode, res.StatusText)
		msgs := []string{s}
		if res.Errors != nil { // pointer to slice
			msgs = append(msgs, *res.Errors...)
		}
		u.Abort(msgs...)
	}
	return &res
}

func (u SomaUtil) UnfilteredResultFromResponse(resp *resty.Response) *proto.Result {
	decoder := json.NewDecoder(bytes.NewReader(resp.Body()))
	res := proto.Result{}
	err := decoder.Decode(&res)
	u.AbortOnError(err, "Error decoding server response body")
	return &res
}

func (u SomaUtil) VerifyEnvironment(c *resty.Client, env string) {
	resp := u.GetRequest(c, "/environments/")
	res := u.DecodeResultFromResponse(resp)
	for _, e := range *res.Environments {
		if e.Name == env {
			return
		}
	}
	u.Abort(fmt.Sprintf("Invalid environment specified: %s", env))
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>package utils

import (
	"database/sql"
	"errors"
	"fmt"

	e "github.com/techjanitor/pram-get/errors"
)

var (
	ErrUserNotConfirmed error = errors.New("Account not confirmed")
	ErrUserBanned       error = errors.New("Account banned")
	ErrUserLocked       error = errors.New("Account locked")
	ErrUserNotExist     error = errors.New("User does not exist")
	userdataWorker      *userWorker
)

// struct for database insert worker
type userWorker struct {
	send chan *User
	done chan *User
}

// user struct
type User struct {
	Id              uint   `json:"id"`
	Name            string `json:"name"`
	Email           string `json:"email"`
	Group           uint   `json:"group"`
	IsConfirmed     bool   `json:"-"`
	IsLocked        bool   `json:"-"`
	IsBanned        bool   `json:"-"`
	IsAuthenticated bool   `json:"-"`
	err             error  `json:"-"`
}

func UserInit() {

	fmt.Println("Initializing User Info Worker...")

	// make worker channel
	userdataWorker = &userWorker{
		send: make(chan *User, 64),
		done: make(chan bool, 64),
	}

	go func() {

		// Get Database handle
		db, err := GetDb()
		if err != nil {
			fmt.Println(err)
			return
		}

		// prepare query for users table
		ps1, err := db.Prepare("SELECT usergroup_id,user_name,user_email,user_confirmed,user_locked,user_banned FROM users WHERE user_id = ?")
		if err != nil {
			fmt.Println(err)
			return
		}

		// range through tasks channel
		for u := range userdataWorker.send {

			// input data
			err = ps1.QueryRow(u.Id).Scan(&u.Group, &u.Name, &u.Email, &u.IsConfirmed, &u.IsLocked, &u.IsBanned)
			if err != nil {
				u.err = err
			}

			// send back data
			userdataWorker.done <- true

		}

	}()

}

// get the user info from id
func (u *User) Info() (err error) {

	// this needs an id
	if u.Id == 0 || u.Id == 1 {
		return e.ErrInvalidParam
	}

	// get original uid
	uid := u.Id

	// send to worker
	userdataWorker.send <- u

	// block until done
	<-userdataWorker.done

	// check error
	if u.err == sql.ErrNoRows {
		return ErrUserNotExist
	} else if u.err != nil {
		return e.ErrInternalError
	}

	// check that theyre still the same just in case
	if u.Id != uid {
		return e.ErrInternalError
	}

	// if account is not confirmed
	if !u.IsConfirmed {
		return ErrUserNotConfirmed
	}

	// if locked
	if u.IsLocked {
		return ErrUserLocked
	}

	// if banned
	if u.IsBanned {
		return ErrUserBanned
	}

	// mark authenticated
	u.IsAuthenticated = true

	return

}
<commit_msg>add better blocking<commit_after>package utils

import (
	"database/sql"
	"errors"
	"fmt"

	e "github.com/techjanitor/pram-get/errors"
)

var (
	ErrUserNotConfirmed error = errors.New("Account not confirmed")
	ErrUserBanned       error = errors.New("Account banned")
	ErrUserLocked       error = errors.New("Account locked")
	ErrUserNotExist     error = errors.New("User does not exist")
	userdataWorker      *userWorker
)

// struct for database insert worker
type userWorker struct {
	send chan *User
	done chan bool
}

// user struct
type User struct {
	Id              uint   `json:"id"`
	Name            string `json:"name"`
	Email           string `json:"email"`
	Group           uint   `json:"group"`
	IsConfirmed     bool   `json:"-"`
	IsLocked        bool   `json:"-"`
	IsBanned        bool   `json:"-"`
	IsAuthenticated bool   `json:"-"`
	err             error  `json:"-"`
}

func UserInit() {

	fmt.Println("Initializing User Info Worker...")

	// make worker channel
	userdataWorker = &userWorker{
		send: make(chan *User, 64),
		done: make(chan bool, 64),
	}

	go func() {

		// Get Database handle
		db, err := GetDb()
		if err != nil {
			fmt.Println(err)
			return
		}

		// prepare query for users table
		ps1, err := db.Prepare("SELECT usergroup_id,user_name,user_email,user_confirmed,user_locked,user_banned FROM users WHERE user_id = ?")
		if err != nil {
			fmt.Println(err)
			return
		}

		// range through tasks channel
		for u := range userdataWorker.send {

			// input data
			err = ps1.QueryRow(u.Id).Scan(&u.Group, &u.Name, &u.Email, &u.IsConfirmed, &u.IsLocked, &u.IsBanned)
			if err != nil {
				u.err = err
			}

			// send back data
			userdataWorker.done <- true

		}

	}()

}

// get the user info from id
func (u *User) Info() (err error) {

	// this needs an id
	if u.Id == 0 || u.Id == 1 {
		return e.ErrInvalidParam
	}

	// get original uid
	uid := u.Id

	// send to worker
	userdataWorker.send <- u

	// block until done
	<-userdataWorker.done

	// check error
	if u.err == sql.ErrNoRows {
		return ErrUserNotExist
	} else if u.err != nil {
		return e.ErrInternalError
	}

	// check that theyre still the same just in case
	if u.Id != uid {
		return e.ErrInternalError
	}

	// if account is not confirmed
	if !u.IsConfirmed {
		return ErrUserNotConfirmed
	}

	// if locked
	if u.IsLocked {
		return ErrUserLocked
	}

	// if banned
	if u.IsBanned {
		return ErrUserBanned
	}

	// mark authenticated
	u.IsAuthenticated = true

	return

}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"testing"
)

func Test_splitStringHalfBySpace(t *testing.T) {
	data := []struct {
		in             string
		outOne, outTwo string
	}{
		{
			"/cmd args",
			"/cmd", "args",
		}, {
			"/cmd   args",
			"/cmd", "args",
		}, {
			"/cmd",
			"/cmd", "",
		}, {
			"plain text",
			"plain", "text",
		}, {
			"plain     text",
			"plain", "text",
		}, {
			"",
			"", "",
		},
	}

	for _, item := range data {
		one, two := splitStringHalfBySpace(item.in)
		if !(one == item.outOne && two == item.outTwo) {
			t.Errorf("Failing for \"%s\"\nexpected: (%#v, %#v)\nreal: (%#v, %#v)\n", item.in, item.outOne, item.outTwo, one, two)
		}
	}
}

func Test_cleanUserName(t *testing.T) {
	data := []struct {
		in  string
		out string
	}{
		{
			"1234",
			"1234",
		}, {
			"name",
			"name",
		}, {
			"@name",
			"name",
		}, {
			" name@str ",
			" namestr ",
		}, {
			"",
			"",
		},
	}

	for _, item := range data {
		out := cleanUserName(item.in)
		if out != item.out {
			t.Errorf("Failing for \"%s\"\nexpected: %s, real: %s\n", item.in, item.out, out)
		}
	}
}

func Test_parseBotCommand(t *testing.T) {
	data := []struct {
		// in
		pathRaw, shellCmd string
		// out
		path    string
		command Command
		errFunc error
	}{
		{
			pathRaw:  "/cmd",
			shellCmd: "ls",
			// out
			path: "/cmd",
			command: Command{
				shellCmd:    "ls",
				description: "",
				vars:        nil,
				isMarkdown:  false,
			},
			errFunc: nil,
		},
		{
			pathRaw:  "/",
			shellCmd: "ls",
			// out
			path: "/",
			command: Command{
				shellCmd:    "ls",
				description: "",
				vars:        nil,
				isMarkdown:  false,
			},
			errFunc: nil,
		},
		// empty shell command
		{
			pathRaw:  "/cmd",
			shellCmd: "",
			// out
			path: "",
			command: Command{
				shellCmd:    "",
				description: "",
				vars:        nil,
				isMarkdown:  false,
			},
			errFunc: fmt.Errorf("error"),
		},
		{
			pathRaw:  "/cmd:vars=VAR1,VAR2:desc=Command name",
			shellCmd: "ls",
			// out
			path: "/cmd",
			command: Command{
				shellCmd:    "ls",
				description: "Command name",
				vars:        []string{"VAR1", "VAR2"},
				isMarkdown:  false,
			},
			errFunc: nil,
		},
		{
			// markdown test
			pathRaw:  "/cmd:vars=VAR1,VAR2:desc=Command name:md",
			shellCmd: "ls",
			// out
			path: "/cmd",
			command: Command{
				shellCmd:    "ls",
				description: "Command name",
				vars:        []string{"VAR1", "VAR2"},
				isMarkdown:  true,
			},
			errFunc: nil,
		},
	}

	for _, item := range data {
		path, command, errFunc := parseBotCommand(item.pathRaw, item.shellCmd)
		commandMust := fmt.Sprintf("%#v", item.command)
		commandGet := fmt.Sprintf("%#v", command)

		if path != item.path || ((errFunc == nil) != (item.errFunc == nil) || commandGet != commandMust) {
			t.Errorf("Failing for %v (path: %s)\nMust: %s\nGot:  %#v\n", item, path, commandMust, command)
		}
	}

	invalidPaths := []string{
		"",
		" ",
		"NotValidPath",
		" /cmd",
		"/:aaa",
		"/cmd:aaa=23",
		"/cmd:aaa",
		"/cmd:desc",
		"/cmd:desc=",
		"/cmd:vars=,,,,",
	}
	for _, path := range invalidPaths {
		_, _, errFunc := parseBotCommand(path, "ls")
		if errFunc == nil {
			t.Errorf("Failing check invalid path for: %s", path)
		}
	}
}

func Test_stringIsEmpty(t *testing.T) {
	data := []struct {
		in  string
		out bool
	}{
		{
			"1234",
			false,
		}, {
			" str ",
			false,
		}, {
			"",
			true,
		}, {
			"  ",
			true,
		}, {
			"\n",
			true,
		}, {
			"  \ndew",
			false,
		},
	}

	for _, item := range data {
		out := stringIsEmpty(item.in)
		if out != item.out {
			t.Errorf("Failing for %#v\nexpected: %v, real: %v\n", item.in, item.out, out)
		}
	}
}

func Test_splitStringLinesBySize(t *testing.T) {
	data := []struct {
		in      string
		maxSize int
		out     []string
	}{
		{
			"12345",
			6,
			[]string{"12345"},
		}, {
			"12345\n67890",
			11,
			[]string{"12345\n67890"},
		}, {
			"1234567890\n1234567890",
			3,
			[]string{"1234567890", "1234567890"},
		}, {
			"12\n34\n56\n78\n90",
			6,
			[]string{"12\n34", "56\n78", "90"},
		}, {
			"12\n34aaaaaaaaaaaaa\n56\n78\n90",
			6,
			[]string{"12", "34aaaaaaaaaaaaa", "56\n78", "90"},
		},
	}

	for _, item := range data {
		out := splitStringLinesBySize(item.in, item.maxSize)
		mustOut := fmt.Sprintf("%#v", item.out)
		getOut := fmt.Sprintf("%#v", out)
		if mustOut != getOut {
			t.Errorf("Failing for %#v (by %d)\nexpected: %s, real: %s\n", item.in, item.maxSize, mustOut, getOut)
		}
	}
}
<commit_msg>Added test for getRandomCode()<commit_after>package main

import (
	"fmt"
	"testing"
)

func Test_splitStringHalfBySpace(t *testing.T) {
	data := []struct {
		in             string
		outOne, outTwo string
	}{
		{
			"/cmd args",
			"/cmd", "args",
		}, {
			"/cmd   args",
			"/cmd", "args",
		}, {
			"/cmd",
			"/cmd", "",
		}, {
			"plain text",
			"plain", "text",
		}, {
			"plain     text",
			"plain", "text",
		}, {
			"",
			"", "",
		},
	}

	for _, item := range data {
		one, two := splitStringHalfBySpace(item.in)
		if !(one == item.outOne && two == item.outTwo) {
			t.Errorf("Failing for \"%s\"\nexpected: (%#v, %#v)\nreal: (%#v, %#v)\n", item.in, item.outOne, item.outTwo, one, two)
		}
	}
}

func Test_cleanUserName(t *testing.T) {
	data := []struct {
		in  string
		out string
	}{
		{
			"1234",
			"1234",
		}, {
			"name",
			"name",
		}, {
			"@name",
			"name",
		}, {
			" name@str ",
			" namestr ",
		}, {
			"",
			"",
		},
	}

	for _, item := range data {
		out := cleanUserName(item.in)
		if out != item.out {
			t.Errorf("Failing for \"%s\"\nexpected: %s, real: %s\n", item.in, item.out, out)
		}
	}
}

func Test_parseBotCommand(t *testing.T) {
	data := []struct {
		// in
		pathRaw, shellCmd string
		// out
		path    string
		command Command
		errFunc error
	}{
		{
			pathRaw:  "/cmd",
			shellCmd: "ls",
			// out
			path: "/cmd",
			command: Command{
				shellCmd:    "ls",
				description: "",
				vars:        nil,
				isMarkdown:  false,
			},
			errFunc: nil,
		},
		{
			pathRaw:  "/",
			shellCmd: "ls",
			// out
			path: "/",
			command: Command{
				shellCmd:    "ls",
				description: "",
				vars:        nil,
				isMarkdown:  false,
			},
			errFunc: nil,
		},
		// empty shell command
		{
			pathRaw:  "/cmd",
			shellCmd: "",
			// out
			path: "",
			command: Command{
				shellCmd:    "",
				description: "",
				vars:        nil,
				isMarkdown:  false,
			},
			errFunc: fmt.Errorf("error"),
		},
		{
			pathRaw:  "/cmd:vars=VAR1,VAR2:desc=Command name",
			shellCmd: "ls",
			// out
			path: "/cmd",
			command: Command{
				shellCmd:    "ls",
				description: "Command name",
				vars:        []string{"VAR1", "VAR2"},
				isMarkdown:  false,
			},
			errFunc: nil,
		},
		{
			// markdown test
			pathRaw:  "/cmd:vars=VAR1,VAR2:desc=Command name:md",
			shellCmd: "ls",
			// out
			path: "/cmd",
			command: Command{
				shellCmd:    "ls",
				description: "Command name",
				vars:        []string{"VAR1", "VAR2"},
				isMarkdown:  true,
			},
			errFunc: nil,
		},
	}

	for _, item := range data {
		path, command, errFunc := parseBotCommand(item.pathRaw, item.shellCmd)
		commandMust := fmt.Sprintf("%#v", item.command)
		commandGet := fmt.Sprintf("%#v", command)

		if path != item.path || ((errFunc == nil) != (item.errFunc == nil) || commandGet != commandMust) {
			t.Errorf("Failing for %v (path: %s)\nMust: %s\nGot:  %#v\n", item, path, commandMust, command)
		}
	}

	invalidPaths := []string{
		"",
		" ",
		"NotValidPath",
		" /cmd",
		"/:aaa",
		"/cmd:aaa=23",
		"/cmd:aaa",
		"/cmd:desc",
		"/cmd:desc=",
		"/cmd:vars=,,,,",
	}
	for _, path := range invalidPaths {
		_, _, errFunc := parseBotCommand(path, "ls")
		if errFunc == nil {
			t.Errorf("Failing check invalid path for: %s", path)
		}
	}
}

func Test_stringIsEmpty(t *testing.T) {
	data := []struct {
		in  string
		out bool
	}{
		{
			"1234",
			false,
		}, {
			" str ",
			false,
		}, {
			"",
			true,
		}, {
			"  ",
			true,
		}, {
			"\n",
			true,
		}, {
			"  \ndew",
			false,
		},
	}

	for _, item := range data {
		out := stringIsEmpty(item.in)
		if out != item.out {
			t.Errorf("Failing for %#v\nexpected: %v, real: %v\n", item.in, item.out, out)
		}
	}
}

func Test_splitStringLinesBySize(t *testing.T) {
	data := []struct {
		in      string
		maxSize int
		out     []string
	}{
		{
			"12345",
			6,
			[]string{"12345"},
		}, {
			"12345\n67890",
			11,
			[]string{"12345\n67890"},
		}, {
			"1234567890\n1234567890",
			3,
			[]string{"1234567890", "1234567890"},
		}, {
			"12\n34\n56\n78\n90",
			6,
			[]string{"12\n34", "56\n78", "90"},
		}, {
			"12\n34aaaaaaaaaaaaa\n56\n78\n90",
			6,
			[]string{"12", "34aaaaaaaaaaaaa", "56\n78", "90"},
		},
	}

	for _, item := range data {
		out := splitStringLinesBySize(item.in, item.maxSize)
		mustOut := fmt.Sprintf("%#v", item.out)
		getOut := fmt.Sprintf("%#v", out)
		if mustOut != getOut {
			t.Errorf("Failing for %#v (by %d)\nexpected: %s, real: %s\n", item.in, item.maxSize, mustOut, getOut)
		}
	}
}

func Test_getRandomCode(t *testing.T) {
	rnd := getRandomCode()
	if len(rnd) == 0 {
		t.Errorf("getRandomCode() failed")
	}
}
<|endoftext|>


<commit_before>package onecache

import (
	"reflect"
	"testing"
	"time"
)

func TestItem_IsExpired(t *testing.T) {

	item := &Item{ExpiresAt: time.Now().Add(-2 * time.Minute), Data: "Ping-Pong"}

	if !item.IsExpired() {
		t.Fatal("Item should be expired since it's expiration date is set 2 minutes backwards")
	}
}

func TestBytesToItem(t *testing.T) {

	item := &Item{ExpiresAt: time.Now().Add(-2 * time.Minute), Data: "Ping-Pong"}

	b, err := item.Bytes()

	if err != nil {
		t.Error(err)
	}

	i, err := BytesToItem(b)

	if err != nil {
		t.Error(err)
	}

	if !reflect.DeepEqual(item, i) {
		t.Fatalf("Items differ..  \n Expected %v. \n Got %v", item, i)
	}

}
<commit_msg>Test Bytes conversion into a struct<commit_after>package onecache

import (
	"testing"
	"time"
	"reflect"
)

func TestItem_IsExpired(t *testing.T) {

	item := &Item{ExpiresAt: time.Now().Add(-2 * time.Minute), Data: "Ping-Pong"}

	if !item.IsExpired() {
		t.Fatal("Item should be expired since it's expiration date is set 2 minutes backwards")
	}
}

func TestBytesToItem(t *testing.T) {

	item := &Item{ExpiresAt: time.Now(), Data: "Ping-Pong"}

	b, err := item.Bytes()

	if err != nil {
		t.Error(err)
	}

	i, err := BytesToItem(b)

	if err != nil {
		t.Error(err)
	}

	if !item.ExpiresAt.Equal(i.ExpiresAt) {
		t.Fatalf("Time should equal.. Expected %v \n Got %v", item.ExpiresAt, i.ExpiresAt)
	}

	if !reflect.DeepEqual(item.Data, i.Data) {
		t.Fatalf("Data not equal.. Expected %v \n. Got %v", item.ExpiresAt, i.ExpiresAt)
	}

}
<|endoftext|>