<commit_before>package coreos_users

import (
	"bufio"
	"os/user"
	"path/filepath"
	"os"
	"os/exec"
	"fmt"
	"strings"
	"strconv"
)

var Output []string

const (
	AuthorizedKeysFile = "authorized_keys"
	SSHDir             = ".ssh"
)

type awsUser struct {
	iamUser   string
	Group     string
	SudoGroup string
	Keys      []string
  localUser *user.User
}

// Initiate the user function
func New(user string, group string, sgroup string, keys []string) *awsUser {
	ustruct := &awsUser{
		iamUser:   user,
		Group:     group,
		SudoGroup: sgroup,
		Keys:      keys,
	}
	return ustruct
}

// sshDirPath returns the path to the .ssh dir for the user.
func sshDirPath(u *user.User) string {
	return filepath.Join(u.HomeDir, SSHDir)
}

// authKeysFilePath returns the path to the authorized_keys file for the user.
func authKeysFilePath(u *user.User) string {
	return filepath.Join(sshDirPath(u), AuthorizedKeysFile)
}

// Remove users from system that are not in the group list
func RemoveUser(usr string) error {
	u, err := user.Lookup(usr)
	if err != nil {
		return err
	}
	CMD := "userdel"
	CMD_ARGS := []string{"-r",  u.Username}
	if _, err := exec.Command(CMD, CMD_ARGS...).Output(); err != nil {
		return err
	}
	return nil
}

// Compare the keys to find what keys are missing locally compared to what is in IAM
func GetArrayDiff(k1 []string, k2 []string) ([]string) {
	var diff []string
	for i := 0; i < 2; i++ {
		for _, s1 := range k1 {
			found := false
			for _, s2 := range k2 {
				if s1 == s2 {
					found = true
					break
				}
			}
			// Key not found so add it to difference
			if !found {
				diff = append(diff, s1)
			}
		}
		// Swap the slices, only if it was the first loop
		if i == 0 {
			k1, k2 = k2, k1
		}
	}
	return diff
}

// Loop through the keys and call add key to add key to the box
func Keys(l *user.User, kp string, ks []string) error {
//	f, err := os.OpenFile(kp, os.O_WRONLY|os.O_CREATE, 0640)
  fmt.Printf("keys: %v", ks)
	f, err := os.Create(kp)
	defer f.Close()
	if err != nil {
		return err
	}
	w := bufio.NewWriter(f)
	for _, k := range ks {
		fmt.Fprintln(w, k)
	}
	w.Flush()
	if err := setPerms(l, kp); err != nil {
		return err
	}
	return nil
}

// Set permissions on file
func setPerms(u *user.User, keypath string) error {
	gid, err := strconv.Atoi(u.Gid)
	uid, err := strconv.Atoi(u.Uid)
	if err != nil {
		return err
	}
	if err := os.Chown(keypath, uid, gid); err != nil {
		return err
	}
	return nil
}

// Add the users SSH key onto the system
//func addKey(u *user.User, keypath string, key string) error {
//	Output = append(Output, fmt.Sprintf("Calling addkey to add key %v for user: %v\n", key[0:15], u.Username))
//	f, err := os.OpenFile(keypath, os.O_WRONLY|os.O_CREATE, 0640)
//	defer f.Close()
//	if err != nil {
//		return err
//	}
//	if _, err := f.WriteString(key); err != nil {
//		return err
//	} else {
//		f.Close()
//	}
//	gid, err := strconv.Atoi(u.Gid)
//	uid, err := strconv.Atoi(u.Uid)
//	if err != nil {
//		return err
//	}
//	if err := os.Chown(keypath, uid, gid); err != nil {
//		return err
//	}
//	return nil
//}

// Add carriage return for new array elements
//func addNewLine(s1 []string) []string {
//	if (len(s1) >= 1) {
//		var newslice []string
//		for i, e := range s1 {
//			if i == 0 {
//				newslice = append(newslice, e)
//				continue
//			}
//			newslice = append(newslice, fmt.Sprintf("\n%v", e))
//		}
//		return newslice
//	} else {
//		return s1
//	}
//}

// Get the keys of user if there are any locally if not then add keys from iam.
// if there are keys for the user then find out if there are more local keys than there are in iam in which case
// set it to delete keys locally that are no longer in iam otherwise, just add the key differences
func (l *awsUser) DoKeys() error {
	keys := l.Keys
	keyPath := authKeysFilePath(l.localUser)
 	keys, _ = l.getKeys(keyPath)
	writekeys := true
	if keys != nil {
		fmt.Println("keys are nil")
		if (len(keys) == len(l.Keys)) {
			if len(GetArrayDiff(keys, l.Keys)) == 0 {
				Output = append(Output, fmt.Sprintf("No new keys"))
				writekeys = false
			}
		} else {
			keys = l.Keys
		}
	}
	if writekeys == true {
		if err := Keys(l.localUser, keyPath, keys); err != nil {
			return err
		}
	}
	return nil
}

// Check if there is the authorized keys file if it is then return all the keys from it
func (l *awsUser) getKeys(keyPath string) ([]string, error) {
	if _, err := os.Stat(keyPath); os.IsNotExist(err) {
		return nil, err
	} else {
		kfile, err := os.Open(keyPath)
		if err != nil {
			return nil, err
		}
		defer kfile.Close()
		var keys []string
		scanner := bufio.NewScanner(kfile)
		for scanner.Scan() {
			keys = append(keys, scanner.Text())
		}
		return keys, scanner.Err()
	}
}

func GetAllUsers() ([]string, error) {
	passwd := "/etc/passwd"
	fpasswd, err := os.Open(passwd)
	if err != nil {
		return nil, err
	}
	defer fpasswd.Close()
	var users []string
	scanner := bufio.NewScanner(fpasswd)
	for scanner.Scan() {
		users = append(users, strings.Split(scanner.Text(), ":")[0])
	}
	return users, scanner.Err()
}

// Add user onto the system using useradd exec
func (l *awsUser) addUser() error {
	if l.localUser == nil {
		CMD_ARGS   := []string{"-p", "123", "-U", "-m", l.iamUser, "-G", l.SudoGroup}
		_, err := exec.Command("useradd", CMD_ARGS...).Output()
		if err != nil {
			return err
		}
		Output = append(Output, fmt.Sprintf("Creating user %v", l.iamUser))
		lusr, _ := user.Lookup(l.iamUser)
		l.localUser = lusr
	}
	return nil
}

// Sync all users and keys onto the coreos host this is the primary function
func (l *awsUser) Sync() ([]string, error) {
	usr, err := user.Lookup(l.iamUser)
	if err != nil {
		if err := l.addUser(); err != nil {
			return nil, err
		}
	} else {
		l.localUser = usr
	}
	if err := l.DoKeys(); err != nil {
		return nil, err
	}
	return Output, nil
}
<commit_msg>Removing functions no longer needed since doing a cleanup<commit_after>package coreos_users

import (
	"bufio"
	"os/user"
	"path/filepath"
	"os"
	"os/exec"
	"fmt"
	"strings"
	"strconv"
)

var Output []string

const (
	AuthorizedKeysFile = "authorized_keys"
	SSHDir             = ".ssh"
)

type awsUser struct {
	iamUser   string
	Group     string
	SudoGroup string
	Keys      []string
  localUser *user.User
}

// Initiate the user function
func New(user string, group string, sgroup string, keys []string) *awsUser {
	ustruct := &awsUser{
		iamUser:   user,
		Group:     group,
		SudoGroup: sgroup,
		Keys:      keys,
	}
	return ustruct
}

// sshDirPath returns the path to the .ssh dir for the user.
func sshDirPath(u *user.User) string {
	return filepath.Join(u.HomeDir, SSHDir)
}

// authKeysFilePath returns the path to the authorized_keys file for the user.
func authKeysFilePath(u *user.User) string {
	return filepath.Join(sshDirPath(u), AuthorizedKeysFile)
}

// Remove users from system that are not in the group list
func RemoveUser(usr string) error {
	u, err := user.Lookup(usr)
	if err != nil {
		return err
	}
	CMD := "userdel"
	CMD_ARGS := []string{"-r",  u.Username}
	if _, err := exec.Command(CMD, CMD_ARGS...).Output(); err != nil {
		return err
	}
	return nil
}

// Compare the keys to find what keys are missing locally compared to what is in IAM
func GetArrayDiff(k1 []string, k2 []string) ([]string) {
	var diff []string
	for i := 0; i < 2; i++ {
		for _, s1 := range k1 {
			found := false
			for _, s2 := range k2 {
				if s1 == s2 {
					found = true
					break
				}
			}
			// Key not found so add it to difference
			if !found {
				diff = append(diff, s1)
			}
		}
		// Swap the slices, only if it was the first loop
		if i == 0 {
			k1, k2 = k2, k1
		}
	}
	return diff
}

// Loop through the keys and call add key to add key to the box
func Keys(l *user.User, kp string, ks []string) error {
	f, err := os.Create(kp)
	defer f.Close()
	if err != nil {
		return err
	}
	w := bufio.NewWriter(f)
	for _, k := range ks {
		fmt.Fprintln(w, k)
	}
	w.Flush()
	if err := setPerms(l, kp); err != nil {
		return err
	}
	return nil
}

// Set permissions on file
func setPerms(u *user.User, keypath string) error {
	gid, err := strconv.Atoi(u.Gid)
	uid, err := strconv.Atoi(u.Uid)
	if err != nil {
		return err
	}
	if err := os.Chown(keypath, uid, gid); err != nil {
		return err
	}
	return nil
}

// Get the keys of user if there are any locally if not then add keys from iam.
// if there are keys for the user then find out if there are more local keys than there are in iam in which case
// set it to replace the keys
func (l *awsUser) DoKeys() error {
	keys := l.Keys
	keyPath := authKeysFilePath(l.localUser)
 	keys, _ = l.getKeys(keyPath)
	writekeys := true
	if keys != nil {
		fmt.Println("keys are nil")
		if (len(keys) == len(l.Keys)) {
			if len(GetArrayDiff(keys, l.Keys)) == 0 {
				Output = append(Output, fmt.Sprintf("No new keys"))
				writekeys = false
			}
		} else {
			keys = l.Keys
		}
	}
	if writekeys == true {
		if err := Keys(l.localUser, keyPath, keys); err != nil {
			return err
		}
	}
	return nil
}

// Check if there is the authorized keys file if it is then return all the keys from it
func (l *awsUser) getKeys(keyPath string) ([]string, error) {
	if _, err := os.Stat(keyPath); os.IsNotExist(err) {
		return nil, err
	} else {
		kfile, err := os.Open(keyPath)
		if err != nil {
			return nil, err
		}
		defer kfile.Close()
		var keys []string
		scanner := bufio.NewScanner(kfile)
		for scanner.Scan() {
			keys = append(keys, scanner.Text())
		}
		return keys, scanner.Err()
	}
}

func GetAllUsers() ([]string, error) {
	passwd := "/etc/passwd"
	fpasswd, err := os.Open(passwd)
	if err != nil {
		return nil, err
	}
	defer fpasswd.Close()
	var users []string
	scanner := bufio.NewScanner(fpasswd)
	for scanner.Scan() {
		users = append(users, strings.Split(scanner.Text(), ":")[0])
	}
	return users, scanner.Err()
}

// Add user onto the system using useradd exec
func (l *awsUser) addUser() error {
	if l.localUser == nil {
		CMD_ARGS   := []string{"-p", "123", "-U", "-m", l.iamUser, "-G", l.SudoGroup}
		_, err := exec.Command("useradd", CMD_ARGS...).Output()
		if err != nil {
			return err
		}
		Output = append(Output, fmt.Sprintf("Creating user %v", l.iamUser))
		lusr, _ := user.Lookup(l.iamUser)
		l.localUser = lusr
	}
	return nil
}

// Sync all users and keys onto the coreos host this is the primary function
func (l *awsUser) Sync() ([]string, error) {
	usr, err := user.Lookup(l.iamUser)
	if err != nil {
		if err := l.addUser(); err != nil {
			return nil, err
		}
	} else {
		l.localUser = usr
	}
	if err := l.DoKeys(); err != nil {
		return nil, err
	}
	return Output, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Smudge 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 smudge

import (
	"net"
	"reflect"
	"testing"
)

// Identical but distinct instance from node1b
var node1a = Node{
	ip:          net.IP([]byte{127, 0, 0, 1}),
	port:        1234,
	timestamp:   87878787,
	status:      StatusAlive,
	emitCounter: 42}

// Identical but distinct instance from node1a
var node1b = Node{
	ip:          net.IP([]byte{127, 0, 0, 1}),
	port:        1234,
	timestamp:   87878787,
	status:      StatusAlive,
	emitCounter: 42}

// Different from node1a and node1b
var node2 = Node{
	ip:          net.IP([]byte{127, 0, 0, 1}),
	port:        10001,
	timestamp:   GetNowInMillis(),
	status:      StatusAlive,
	emitCounter: 42}

var message1a = message{
	sender:     &node1a,
	senderCode: 255,
	verb:       verbPing}

var message1b = message{
	sender:     &node1b,
	senderCode: 255,
	verb:       verbPing}

var message2 = message{
	sender:     &node2,
	senderCode: 23,
	verb:       verbAck}

// Does deep equality of two different but identical messages return true?
func TestDeepEqualityTrue(t *testing.T) {
	if !reflect.DeepEqual(message1a, message1b) {
		t.Fail()
	}
}

// Does deep equality of two different messages return false?
func TestDeepEqualityFalse(t *testing.T) {
	if reflect.DeepEqual(message1a, message2) {
		t.Fail()
	}
}

// Endode and decode a simple message without any members, and see if
// the input/output match.
func TestEncodeDecodeBasic(t *testing.T) {
	timestamp := uint32(87878787)

	sender := Node{
		ip:        net.IP([]byte{127, 0, 0, 1}),
		port:      1234,
		timestamp: timestamp}

	message := message{
		sender:     &sender,
		senderCode: 255,
		verb:       verbPing}

	ip := net.IP([]byte{127, 0, 0, 1})
	bytes := message.encode()
	decoded, err := decodeMessage(ip, bytes)
	decoded.sender.timestamp = timestamp

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

	if !reflect.DeepEqual(message, decoded) {
		t.Error("Messages do not match:")

		t.Log(" Input:", message)
		t.Log("Output:", decoded)
		t.Log(" Input node:", message.sender)
		t.Log("Output node:", decoded.sender)
	}
}

// Endode and decode a simple message without one member, and see if
// the input/output match.
func TestEncodeDecode1Member(t *testing.T) {
	timestamp := uint32(87878787)

	sender := Node{
		ip:        net.IP([]byte{127, 0, 0, 1}),
		port:      1234,
		timestamp: timestamp}

	member := Node{
		ip:        net.IP([]byte{127, 0, 0, 2}),
		port:      9000,
		timestamp: timestamp}

	message := message{
		sender:     &sender,
		senderCode: 255,
		verb:       verbPing}
	message.addMember(&member, StatusDead, 38)

	if len(message.members) != 1 {
		t.Error("No member in the input members list!")
	}

	ip := net.IP([]byte{127, 0, 0, 1})
	bytes := message.encode()
	decoded, err := decodeMessage(ip, bytes)
	decoded.sender.timestamp = timestamp
	decoded.members[0].node.timestamp = timestamp

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

	if len(decoded.members) != 1 {
		t.Error("No member in the output members list!")
	}

	if !reflect.DeepEqual(message, decoded) {
		t.Error("Messages do not match")

		t.Log(" Input:", message.members[0])
		t.Log("Output:", decoded.members[0])
		t.Log(" Input node:", message.members[0].node)
		t.Log("Output node:", decoded.members[0].node)
	}
}

// Endode and decode a simple message without one member, and see if
// the input/output match.
func TestEncodeDecode1MemberBroadcast(t *testing.T) {
	timestamp := uint32(87878787)

	sender := Node{
		ip:        net.IP([]byte{127, 0, 0, 1}),
		port:      1234,
		timestamp: timestamp}

	member := Node{
		ip:        net.IP([]byte{127, 0, 0, 2}),
		port:      9000,
		timestamp: timestamp}

	message := message{
		sender:     &sender,
		senderCode: 255,
		verb:       verbPing}
	message.addMember(&member, StatusDead, 38)

	broadcast := Broadcast{
		bytes:  []byte("This is a message"),
		origin: &sender,
		index:  42}
	message.addBroadcast(&broadcast)

	if message.broadcast == nil {
		t.Error("Broadcast not set properly")
	}

	ip := net.IP([]byte{127, 0, 0, 1})
	bytes := message.encode()
	decoded, err := decodeMessage(ip, bytes)
	decoded.sender.timestamp = timestamp
	decoded.members[0].node.timestamp = timestamp

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

	if decoded.broadcast == nil {
		t.Error("Broadcast not decoded")
	}

	message.broadcast.origin = nil
	decoded.broadcast.origin = nil

	if !reflect.DeepEqual(message.broadcast, decoded.broadcast) {
		t.Error("Broadcasts do not match:")
		t.Error(" Input bcast:", message.broadcast)
		t.Error("Output bcast:", decoded.broadcast)
	}
}
<commit_msg>Updating test appropriately<commit_after>/*
Copyright 2016 The Smudge 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 smudge

import (
	"net"
	"reflect"
	"testing"
)

// Identical but distinct instance from node1b
var node1a = Node{
	ip:          net.IP([]byte{127, 0, 0, 1}),
	port:        1234,
	timestamp:   87878787,
	status:      StatusAlive,
	emitCounter: 42}

// Identical but distinct instance from node1a
var node1b = Node{
	ip:          net.IP([]byte{127, 0, 0, 1}),
	port:        1234,
	timestamp:   87878787,
	status:      StatusAlive,
	emitCounter: 42}

// Different from node1a and node1b
var node2 = Node{
	ip:          net.IP([]byte{127, 0, 0, 1}),
	port:        10001,
	timestamp:   GetNowInMillis(),
	status:      StatusAlive,
	emitCounter: 42}

var message1a = message{
	sender:          &node1a,
	senderHeartbeat: 255,
	verb:            verbPing}

var message1b = message{
	sender:          &node1b,
	senderHeartbeat: 255,
	verb:            verbPing}

var message2 = message{
	sender:          &node2,
	senderHeartbeat: 23,
	verb:            verbAck}

// Does deep equality of two different but identical messages return true?
func TestDeepEqualityTrue(t *testing.T) {
	if !reflect.DeepEqual(message1a, message1b) {
		t.Fail()
	}
}

// Does deep equality of two different messages return false?
func TestDeepEqualityFalse(t *testing.T) {
	if reflect.DeepEqual(message1a, message2) {
		t.Fail()
	}
}

// Endode and decode a simple message without any members, and see if
// the input/output match.
func TestEncodeDecodeBasic(t *testing.T) {
	timestamp := uint32(87878787)

	sender := Node{
		ip:        net.IP([]byte{127, 0, 0, 1}),
		port:      1234,
		timestamp: timestamp}

	message := message{
		sender:          &sender,
		senderHeartbeat: 255,
		verb:            verbPing}

	ip := net.IP([]byte{127, 0, 0, 1})
	bytes := message.encode()
	decoded, err := decodeMessage(ip, bytes)
	decoded.sender.timestamp = timestamp

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

	if !reflect.DeepEqual(message, decoded) {
		t.Error("Messages do not match:")

		t.Log(" Input:", message)
		t.Log("Output:", decoded)
		t.Log(" Input node:", message.sender)
		t.Log("Output node:", decoded.sender)
	}
}

// Endode and decode a simple message without one member, and see if
// the input/output match.
func TestEncodeDecode1Member(t *testing.T) {
	timestamp := uint32(87878787)

	sender := Node{
		ip:        net.IP([]byte{127, 0, 0, 1}),
		port:      1234,
		timestamp: timestamp}

	member := Node{
		ip:        net.IP([]byte{127, 0, 0, 2}),
		port:      9000,
		timestamp: timestamp}

	message := message{
		sender:          &sender,
		senderHeartbeat: 255,
		verb:            verbPing}
	message.addMember(&member, StatusDead, 38)

	if len(message.members) != 1 {
		t.Error("No member in the input members list!")
	}

	ip := net.IP([]byte{127, 0, 0, 1})
	bytes := message.encode()
	decoded, err := decodeMessage(ip, bytes)
	decoded.sender.timestamp = timestamp
	decoded.members[0].node.timestamp = timestamp

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

	if len(decoded.members) != 1 {
		t.Error("No member in the output members list!")
	}

	if !reflect.DeepEqual(message, decoded) {
		t.Error("Messages do not match")

		t.Log(" Input:", message.members[0])
		t.Log("Output:", decoded.members[0])
		t.Log(" Input node:", message.members[0].node)
		t.Log("Output node:", decoded.members[0].node)
	}
}

// Endode and decode a simple message without one member, and see if
// the input/output match.
func TestEncodeDecode1MemberBroadcast(t *testing.T) {
	timestamp := uint32(87878787)

	sender := Node{
		ip:        net.IP([]byte{127, 0, 0, 1}),
		port:      1234,
		timestamp: timestamp}

	member := Node{
		ip:        net.IP([]byte{127, 0, 0, 2}),
		port:      9000,
		timestamp: timestamp}

	message := message{
		sender:          &sender,
		senderHeartbeat: 255,
		verb:            verbPing}
	message.addMember(&member, StatusDead, 38)

	broadcast := Broadcast{
		bytes:  []byte("This is a message"),
		origin: &sender,
		index:  42}
	message.addBroadcast(&broadcast)

	if message.broadcast == nil {
		t.Error("Broadcast not set properly")
	}

	ip := net.IP([]byte{127, 0, 0, 1})
	bytes := message.encode()
	decoded, err := decodeMessage(ip, bytes)
	decoded.sender.timestamp = timestamp
	decoded.members[0].node.timestamp = timestamp

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

	if decoded.broadcast == nil {
		t.Error("Broadcast not decoded")
	}

	message.broadcast.origin = nil
	decoded.broadcast.origin = nil

	if !reflect.DeepEqual(message.broadcast, decoded.broadcast) {
		t.Error("Broadcasts do not match:")
		t.Error(" Input bcast:", message.broadcast)
		t.Error("Output bcast:", decoded.broadcast)
	}
}
<|endoftext|>


<commit_before>//  bstar - Binary Star reactor.
package bstar

import (
	zmq "github.com/pebbe/zmq3"

	"errors"
	"log"
	"strconv"
	"time"
)

const (
	PRIMARY = true
	BACKUP  = false
)

//  States we can be in at any point in time
type state_t int

const (
	_             = state_t(iota)
	state_PRIMARY //  Primary, waiting for peer to connect
	state_BACKUP  //  Backup, waiting for peer to connect
	state_ACTIVE  //  Active - accepting connections
	state_PASSIVE //  Passive - not accepting connections
)

//  Events, which start with the states our peer can be in
type event_t int

const (
	_              = event_t(iota)
	peer_PRIMARY   //  HA peer is pending primary
	peer_BACKUP    //  HA peer is pending backup
	peer_ACTIVE    //  HA peer is active
	peer_PASSIVE   //  HA peer is passive
	client_REQUEST //  Client makes request
)

//  Structure of our class

type Bstar struct {
	statepub    *zmq.Socket             //  State publisher
	statesub    *zmq.Socket             //  State subscriber
	state       state_t                 //  Current state
	event       event_t                 //  Current event
	peer_expiry time.Time               //  When peer is considered 'dead'
	voter_fn    func(*zmq.Socket) error //  Voting socket handler
	active_fn   func() error            //  Call when become active
	passive_fn  func() error            //  Call when become passive

	handlers map[*zmq.Socket]func(*zmq.Socket) error
	poller   *zmq.Poller
	verbose  bool
}

//  The finite-state machine is the same as in the proof-of-concept server.
//  To understand this reactor in detail, first read the CZMQ zloop class.

//  We send state information every this often
//  If peer doesn't respond in two heartbeats, it is 'dead'
const (
	bstar_HEARTBEAT = 1000 * time.Millisecond //  In msecs
)

//  ---------------------------------------------------------------------
//  Binary Star finite state machine (applies event to state)
//  Returns error if there was an exception, nil if event was valid.

func (bstar *Bstar) execute_fsm() (exception error) {
	//  Primary server is waiting for peer to connect
	//  Accepts client_REQUEST events in this state
	if bstar.state == state_PRIMARY {
		if bstar.event == peer_BACKUP {
			log.Println("I: connected to backup (passive), ready as active")
			bstar.state = state_ACTIVE
			if bstar.active_fn != nil {
				bstar.active_fn()
			}
		} else if bstar.event == peer_ACTIVE {
			log.Println("I: connected to backup (active), ready as passive")
			bstar.state = state_PASSIVE
			if bstar.passive_fn != nil {
				bstar.passive_fn()
			}
		} else if bstar.event == client_REQUEST {
			// Allow client requests to turn us into the active if we've
			// waited sufficiently long to believe the backup is not
			// currently acting as active (i.e., after a failover)
			if time.Now().After(bstar.peer_expiry) {
				log.Println("I: request from client, ready as active")
				bstar.state = state_ACTIVE
				if bstar.active_fn != nil {
					bstar.active_fn()
				}
			} else {
				// Don't respond to clients yet - it's possible we're
				// performing a failback and the backup is currently active
				exception = errors.New("Exception")
			}
		}
	} else if bstar.state == state_BACKUP {
		//  Backup server is waiting for peer to connect
		//  Rejects client_REQUEST events in this state
		if bstar.event == peer_ACTIVE {
			log.Println("I: connected to primary (active), ready as passive")
			bstar.state = state_PASSIVE
			if bstar.passive_fn != nil {
				bstar.passive_fn()
			}
		} else if bstar.event == client_REQUEST {
			exception = errors.New("Exception")
		}
	} else if bstar.state == state_ACTIVE {
		//  Server is active
		//  Accepts client_REQUEST events in this state
		//  The only way out of ACTIVE is death
		if bstar.event == peer_ACTIVE {
			//  Two actives would mean split-brain
			log.Println("E: fatal error - dual actives, aborting")
			exception = errors.New("Exception")
		}
	} else if bstar.state == state_PASSIVE {
		//  Server is passive
		//  client_REQUEST events can trigger failover if peer looks dead
		if bstar.event == peer_PRIMARY {
			//  Peer is restarting - become active, peer will go passive
			log.Println("I: primary (passive) is restarting, ready as active")
			bstar.state = state_ACTIVE
		} else if bstar.event == peer_BACKUP {
			//  Peer is restarting - become active, peer will go passive
			log.Println("I: backup (passive) is restarting, ready as active")
			bstar.state = state_ACTIVE
		} else if bstar.event == peer_PASSIVE {
			//  Two passives would mean cluster would be non-responsive
			log.Println("E: fatal error - dual passives, aborting")
			exception = errors.New("Exception")
		} else if bstar.event == client_REQUEST {
			//  Peer becomes active if timeout has passed
			//  It's the client request that triggers the failover
			if time.Now().After(bstar.peer_expiry) {
				//  If peer is dead, switch to the active state
				log.Println("I: failover successful, ready as active")
				bstar.state = state_ACTIVE
			} else {
				//  If peer is alive, reject connections
				exception = errors.New("Exception")
			}
		}
		//  Call state change handler if necessary
		if bstar.state == state_ACTIVE && bstar.active_fn != nil {
			bstar.active_fn()
		}
	}
	return
}

func (bstar *Bstar) update_peer_expiry() {
	bstar.peer_expiry = time.Now().Add(2 * bstar_HEARTBEAT)
}

//  ---------------------------------------------------------------------
//  Reactor event handlers...

//  Publish our state to peer
func (bstar *Bstar) send_state() (err error) {
	_, err = bstar.statepub.SendMessage(int(bstar.state))
	return
}

//  Receive state from peer, execute finite state machine
func (bstar *Bstar) recv_state(socket *zmq.Socket) (err error) {
	msg, err := socket.RecvMessage(0)
	if err == nil {
		e, _ := strconv.Atoi(msg[0])
		bstar.event = event_t(e)
	}
	return bstar.execute_fsm()
}

//  Application wants to speak to us, see if it's possible
func (bstar *Bstar) voter_ready(socket *zmq.Socket) error {
	//  If server can accept input now, call appl handler
	bstar.event = client_REQUEST
	err := bstar.execute_fsm()
	if err == nil {
		bstar.voter_fn(socket)
	} else {
		//  Destroy waiting message, no-one to read it
		socket.RecvMessage(0)
	}
	return nil
}

//  This is the constructor for our bstar class. We have to tell it whether
//  we're primary or backup server, and our local and remote endpoints to
//  bind and connect to:

func New(primary bool, local, remote string) (bstar *Bstar, err error) {

	bstar = &Bstar{}

	//  Initialize the Binary Star
	if primary {
		bstar.state = state_PRIMARY
	} else {
		bstar.state = state_BACKUP
	}

	//  Create publisher for state going to peer
	bstar.statepub, err = zmq.NewSocket(zmq.PUB)
	bstar.statepub.Bind(local)

	//  Create subscriber for state coming from peer
	bstar.statesub, err = zmq.NewSocket(zmq.SUB)
	bstar.statesub.SetSubscribe("")
	bstar.statesub.Connect(remote)

	//  Set-up basic reactor events
	bstar.poller = zmq.NewPoller()
	bstar.poller.Add(bstar.statesub, zmq.POLLIN)

	bstar.handlers = make(map[*zmq.Socket]func(*zmq.Socket) error)
	bstar.handlers[bstar.statesub] = func(socket *zmq.Socket) error { return bstar.recv_state(socket) }

	return
}

//  The voter method registers a client voter socket. Messages received
//  on this socket provide the client_REQUEST events for the Binary Star
//  FSM and are passed to the provided application handler. We require
//  exactly one voter per bstar instance:

func (bstar *Bstar) Voter(endpoint string, socket_type zmq.Type, handler func(*zmq.Socket) error) {
	//  Hold actual handler so we can call this later
	socket, _ := zmq.NewSocket(socket_type)
	socket.Bind(endpoint)
	if bstar.voter_fn != nil {
		panic("Double voter function")
	}
	bstar.voter_fn = handler
	bstar.poller.Add(socket, zmq.POLLIN)
	bstar.handlers[socket] = func(soc *zmq.Socket) error {
		return bstar.voter_ready(soc)
	}
}

//  Register handlers to be called each time there's a state change:

func (bstar *Bstar) NewActive(handler func() error) {
	if bstar.active_fn != nil {
		panic("Double Active")
	}
	bstar.active_fn = handler
}

func (bstar *Bstar) NewPassive(handler func() error) {
	if bstar.passive_fn != nil {
		panic("Double Passive")
	}
	bstar.passive_fn = handler
}

//  Enable/disable verbose tracing, for debugging:

func (bstar *Bstar) SetVerbose(verbose bool) {
	bstar.verbose = verbose
}

//?  Finally, start the configured reactor. It will end if any handler
//?  returns error to the reactor, or if the process receives SIGINT or SIGTERM:

func (bstar *Bstar) Start() {
	if bstar.voter_fn == nil {
		panic("Missing voter function")
	}

	bstar.update_peer_expiry()

	// save to use another goroutine, because it uses a socket that isn't used in the main goroutine
	go func() {
		for {
			time.Sleep(bstar_HEARTBEAT)
			bstar.send_state()
		}
	}()

LOOP:
	for {
		polled, err := bstar.poller.Poll(-1)
		if err != nil {
			break
		}
		for _, socket := range polled {
			if bstar.verbose {
				log.Println(socket.Socket)
			}
			err := bstar.handlers[socket.Socket](socket.Socket)
			if err != nil {
				break LOOP
			}
		}
	}
}
<commit_msg>Updated example, using Reactor: bstar, bstarsrv2<commit_after>//  bstar - Binary Star reactor.
package bstar

import (
	zmq "github.com/pebbe/zmq3"

	"errors"
	"log"
	"strconv"
	"time"
)

const (
	PRIMARY = true
	BACKUP  = false
)

//  States we can be in at any point in time
type state_t int

const (
	_             = state_t(iota)
	state_PRIMARY //  Primary, waiting for peer to connect
	state_BACKUP  //  Backup, waiting for peer to connect
	state_ACTIVE  //  Active - accepting connections
	state_PASSIVE //  Passive - not accepting connections
)

//  Events, which start with the states our peer can be in
type event_t int

const (
	_              = event_t(iota)
	peer_PRIMARY   //  HA peer is pending primary
	peer_BACKUP    //  HA peer is pending backup
	peer_ACTIVE    //  HA peer is active
	peer_PASSIVE   //  HA peer is passive
	client_REQUEST //  Client makes request
)

//  Structure of our class

type Bstar struct {
	reactor     *zmq.Reactor //  Reactor loop
	statepub    *zmq.Socket  //  State publisher
	statesub    *zmq.Socket  //  State subscriber
	state       state_t      //  Current state
	event       event_t      //  Current event
	peer_expiry time.Time    //  When peer is considered 'dead'
	voter_fn    func(*zmq.Socket) error //  Voting socket handler
	active_fn   func() error //  Call when become active
	passive_fn  func() error //  Call when become passive
}

//  The finite-state machine is the same as in the proof-of-concept server.
//  To understand this reactor in detail, first read the CZMQ zloop class.

//  We send state information every this often
//  If peer doesn't respond in two heartbeats, it is 'dead'
const (
	bstar_HEARTBEAT = 1000 * time.Millisecond //  In msecs
)

//  ---------------------------------------------------------------------
//  Binary Star finite state machine (applies event to state)
//  Returns error if there was an exception, nil if event was valid.

func (bstar *Bstar) execute_fsm() (exception error) {
	//  Primary server is waiting for peer to connect
	//  Accepts client_REQUEST events in this state
	if bstar.state == state_PRIMARY {
		if bstar.event == peer_BACKUP {
			log.Println("I: connected to backup (passive), ready as active")
			bstar.state = state_ACTIVE
			if bstar.active_fn != nil {
				bstar.active_fn()
			}
		} else if bstar.event == peer_ACTIVE {
			log.Println("I: connected to backup (active), ready as passive")
			bstar.state = state_PASSIVE
			if bstar.passive_fn != nil {
				bstar.passive_fn()
			}
		} else if bstar.event == client_REQUEST {
			// Allow client requests to turn us into the active if we've
			// waited sufficiently long to believe the backup is not
			// currently acting as active (i.e., after a failover)
			if time.Now().After(bstar.peer_expiry) {
				log.Println("I: request from client, ready as active")
				bstar.state = state_ACTIVE
				if bstar.active_fn != nil {
					bstar.active_fn()
				}
			} else {
				// Don't respond to clients yet - it's possible we're
				// performing a failback and the backup is currently active
				exception = errors.New("Exception")
			}
		}
	} else if bstar.state == state_BACKUP {
		//  Backup server is waiting for peer to connect
		//  Rejects client_REQUEST events in this state
		if bstar.event == peer_ACTIVE {
			log.Println("I: connected to primary (active), ready as passive")
			bstar.state = state_PASSIVE
			if bstar.passive_fn != nil {
				bstar.passive_fn()
			}
		} else if bstar.event == client_REQUEST {
			exception = errors.New("Exception")
		}
	} else if bstar.state == state_ACTIVE {
		//  Server is active
		//  Accepts client_REQUEST events in this state
		//  The only way out of ACTIVE is death
		if bstar.event == peer_ACTIVE {
			//  Two actives would mean split-brain
			log.Println("E: fatal error - dual actives, aborting")
			exception = errors.New("Exception")
		}
	} else if bstar.state == state_PASSIVE {
		//  Server is passive
		//  client_REQUEST events can trigger failover if peer looks dead
		if bstar.event == peer_PRIMARY {
			//  Peer is restarting - become active, peer will go passive
			log.Println("I: primary (passive) is restarting, ready as active")
			bstar.state = state_ACTIVE
		} else if bstar.event == peer_BACKUP {
			//  Peer is restarting - become active, peer will go passive
			log.Println("I: backup (passive) is restarting, ready as active")
			bstar.state = state_ACTIVE
		} else if bstar.event == peer_PASSIVE {
			//  Two passives would mean cluster would be non-responsive
			log.Println("E: fatal error - dual passives, aborting")
			exception = errors.New("Exception")
		} else if bstar.event == client_REQUEST {
			//  Peer becomes active if timeout has passed
			//  It's the client request that triggers the failover
			if time.Now().After(bstar.peer_expiry) {
				//  If peer is dead, switch to the active state
				log.Println("I: failover successful, ready as active")
				bstar.state = state_ACTIVE
			} else {
				//  If peer is alive, reject connections
				exception = errors.New("Exception")
			}
		}
		//  Call state change handler if necessary
		if bstar.state == state_ACTIVE && bstar.active_fn != nil {
			bstar.active_fn()
		}
	}
	return
}

func (bstar *Bstar) update_peer_expiry() {
	bstar.peer_expiry = time.Now().Add(2 * bstar_HEARTBEAT)
}

//  ---------------------------------------------------------------------
//  Reactor event handlers...

//  Publish our state to peer
func (bstar *Bstar) send_state() (err error) {
	_, err = bstar.statepub.SendMessage(int(bstar.state))
	return
}

//  Receive state from peer, execute finite state machine
func (bstar *Bstar) recv_state() (err error) {
	msg, err := bstar.statesub.RecvMessage(0)
	if err == nil {
		e, _ := strconv.Atoi(msg[0])
		bstar.event = event_t(e)
	}
	return bstar.execute_fsm()
}

//  Application wants to speak to us, see if it's possible
func (bstar *Bstar) voter_ready(socket *zmq.Socket) error {
	//  If server can accept input now, call appl handler
	bstar.event = client_REQUEST
	err := bstar.execute_fsm()
	if err == nil {
		bstar.voter_fn(socket)
	} else {
		//  Destroy waiting message, no-one to read it
		socket.RecvMessage(0)
	}
	return nil
}

//  This is the constructor for our bstar class. We have to tell it whether
//  we're primary or backup server, and our local and remote endpoints to
//  bind and connect to:

func New(primary bool, local, remote string) (bstar *Bstar, err error) {

	bstar = &Bstar{}

	//  Initialize the Binary Star
	bstar.reactor = zmq.NewReactor()
	if primary {
		bstar.state = state_PRIMARY
	} else {
		bstar.state = state_BACKUP
	}

	//  Create publisher for state going to peer
	bstar.statepub, err = zmq.NewSocket(zmq.PUB)
	bstar.statepub.Bind(local)

	//  Create subscriber for state coming from peer
	bstar.statesub, err = zmq.NewSocket(zmq.SUB)
	bstar.statesub.SetSubscribe("")
	bstar.statesub.Connect(remote)

	//  Set-up basic reactor events
	bstar.reactor.AddChannelTime(time.Tick(bstar_HEARTBEAT), 1,
		func(i interface{}) error { return bstar.send_state() })
	bstar.reactor.AddSocket(bstar.statesub, zmq.POLLIN,
		func(e zmq.State) error { return bstar.recv_state() })

	return
}

//  The voter method registers a client voter socket. Messages received
//  on this socket provide the client_REQUEST events for the Binary Star
//  FSM and are passed to the provided application handler. We require
//  exactly one voter per bstar instance:

func (bstar *Bstar) Voter(endpoint string, socket_type zmq.Type, handler func(*zmq.Socket) error) {
	//  Hold actual handler so we can call this later
	socket, _ := zmq.NewSocket(socket_type)
	socket.Bind(endpoint)
	if bstar.voter_fn != nil {
		panic("Double voter function")
	}
	bstar.voter_fn = handler
	bstar.reactor.AddSocket(socket, zmq.POLLIN,
		func (e zmq.State) error { return bstar.voter_ready(socket) })
}

//  Register handlers to be called each time there's a state change:

func (bstar *Bstar) NewActive(handler func() error) {
	if bstar.active_fn != nil {
		panic("Double Active")
	}
	bstar.active_fn = handler
}

func (bstar *Bstar) NewPassive(handler func() error) {
	if bstar.passive_fn != nil {
		panic("Double Passive")
	}
	bstar.passive_fn = handler
}

//  Enable/disable verbose tracing, for debugging:

func (bstar *Bstar) SetVerbose(verbose bool) {
	bstar.reactor.SetVerbose(verbose)
}

//?  Finally, start the configured reactor. It will end if any handler
//?  returns error to the reactor, or if the process receives SIGINT or SIGTERM:

func (bstar *Bstar) Start() error {
	if bstar.voter_fn == nil {
		panic("Missing voter function")
	}
	bstar.update_peer_expiry()
	return bstar.reactor.Run(bstar_HEARTBEAT / 5)
}
<|endoftext|>


<commit_before>package ui

import (
	"image"
	"math"

	"code.google.com/p/draw2d/draw2d"

	"github.com/lucasb-eyer/go-colorful"
	"github.com/ninjasphere/gestic-tools/go-gestic-sdk"
	"github.com/ninjasphere/go-ninja/config"
)

var enableGesturePane = config.Bool(false, "led.gestures.testPane")

type GesturePane struct {
	last *gestic.GestureMessage
}

func NewGesturePane() *GesturePane {
	return &GesturePane{}
}

func (p *GesturePane) IsEnabled() bool {
	return enableGesturePane
}

func (p *GesturePane) KeepAwake() bool {
	return false
}

func (p *GesturePane) Gesture(gesture *gestic.GestureMessage) {
	p.last = gesture
}

func (p *GesturePane) Render() (*image.RGBA, error) {
	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	if p.last != nil {

		x := math.Floor(float64(p.last.Position.X)/float64(0xffff)*float64(16)) + 0.5
		y := math.Floor(float64(p.last.Position.Y)/float64(0xffff)*float64(16)) + 0.5
		z := math.Floor(float64(p.last.Position.Z)/float64(0xffff)*float64(16)) + 0.5

		r, _ := colorful.Hex("#FF000")
		g, _ := colorful.Hex("#00FF00")
		b, _ := colorful.Hex("#0000FF")

		gc := draw2d.NewGraphicContext(img)

		gc.SetStrokeColor(r)
		gc.MoveTo(0, x)
		gc.LineTo(16, x)
		gc.Stroke()

		gc.SetStrokeColor(g)
		gc.MoveTo(y, 0)
		gc.LineTo(y, 16)
		gc.Stroke()

		gc.SetStrokeColor(b)
		gc.MoveTo(16-z, 0)
		gc.LineTo(16-z, 16)
		gc.Stroke()
	}

	return img, nil
}

func (p *GesturePane) IsDirty() bool {
	return true
}
<commit_msg>fix location of draw2d package.<commit_after>package ui

import (
	"image"
	"math"

	"github.com/ninjasphere/draw2d"

	"github.com/lucasb-eyer/go-colorful"
	"github.com/ninjasphere/gestic-tools/go-gestic-sdk"
	"github.com/ninjasphere/go-ninja/config"
)

var enableGesturePane = config.Bool(false, "led.gestures.testPane")

type GesturePane struct {
	last *gestic.GestureMessage
}

func NewGesturePane() *GesturePane {
	return &GesturePane{}
}

func (p *GesturePane) IsEnabled() bool {
	return enableGesturePane
}

func (p *GesturePane) KeepAwake() bool {
	return false
}

func (p *GesturePane) Gesture(gesture *gestic.GestureMessage) {
	p.last = gesture
}

func (p *GesturePane) Render() (*image.RGBA, error) {
	img := image.NewRGBA(image.Rect(0, 0, 16, 16))

	if p.last != nil {

		x := math.Floor(float64(p.last.Position.X)/float64(0xffff)*float64(16)) + 0.5
		y := math.Floor(float64(p.last.Position.Y)/float64(0xffff)*float64(16)) + 0.5
		z := math.Floor(float64(p.last.Position.Z)/float64(0xffff)*float64(16)) + 0.5

		r, _ := colorful.Hex("#FF000")
		g, _ := colorful.Hex("#00FF00")
		b, _ := colorful.Hex("#0000FF")

		gc := draw2d.NewGraphicContext(img)

		gc.SetStrokeColor(r)
		gc.MoveTo(0, x)
		gc.LineTo(16, x)
		gc.Stroke()

		gc.SetStrokeColor(g)
		gc.MoveTo(y, 0)
		gc.LineTo(y, 16)
		gc.Stroke()

		gc.SetStrokeColor(b)
		gc.MoveTo(16-z, 0)
		gc.LineTo(16-z, 16)
		gc.Stroke()
	}

	return img, nil
}

func (p *GesturePane) IsDirty() bool {
	return true
}
<|endoftext|>


<commit_before>/*
MIT License

Copyright (c) 2016 Kyriacos Kyriacou

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 gofollow provides an automated way to follow new users
on Twitter which are found by searching keywords provided

Flags
-s    (search term)
-max  (max number of users to follow)
*/
package main

import (
	"flag"
	"fmt"
	"log"
	"net/url"
	"os"
	"strconv"
	"time"

	"github.com/ChimeraCoder/anaconda"
)

// searchTerm is the search term used by the search functions
var searchTerm = flag.String("s", "", "(required) search term to find users by (i.e. gopher)")

// maxFollow is the maximum number of users that the application should follow. It is
// an upper bound since there might be a case where not enough users are found to follow
var maxFollow = flag.Int("max", 50, "(optional) max number of users to follow (hard maximum of 100 to avoid limiting by Twitter)")

// alreadyFollowing is a slice that contains all users that are already being followed.
// This is used so that the application does not attempt to follow users that are already friends
var alreadyFollowing []anaconda.User

// toFollow is a slice that should be populated with the users found by the search functions and
// will subsequently be used to follow each user
var toFollow []anaconda.User

func main() {
	api, err := newTwitterAPI()
	if err != nil {
		log.Fatal(err)
	}

	flag.Parse()
	if len(*searchTerm) == 0 {
		flag.PrintDefaults()
		os.Exit(1)
	}
	const hardMaxFollow = 100
	if *maxFollow > hardMaxFollow {
		*maxFollow = hardMaxFollow
	}

	alreadyFollowing = getAllFriends(api)

	// Start finding users

	fmt.Println("Finding users...")
	done := make(chan struct{})
	go spinner(done) // feedback for user

	found := 0
	n, err := findUsers(api)
	if err != nil {
		log.Fatal(err)
	}
	found += n
	n, err = findUsersByTweet(api)
	if err != nil {
		log.Fatal(err)
	}
	done <- struct{}{}
	found += n
	fmt.Printf("\rFound %d unique users\n", found)
	if found == 0 {
		fmt.Println("Try a broader search term next time!")
		os.Exit(0)
	}

	// Start following users

	const userURLFormat = "https://twitter.com/%s"
	fmt.Println("Following...")
	newFriends := 0
	for _, user := range toFollow {
		err := followUser(api, user.Id)
		if err != nil {
			log.Print(fmt.Errorf("could not follow %s: %v\n", user.ScreenName, err))
			break // could continue, but error is most likely due to limiting by twitter and will fall through
		}
		fmt.Printf("%-40s%-s\n", user.Name, fmt.Sprintf(userURLFormat, user.ScreenName))
		newFriends++
	}
	fmt.Println("-------------------------------------------------------------------------------")
	fmt.Printf("You are now following %d new users!\n", newFriends)
}

// newTwitterAPI returns a new Twitter API using keys from the environment
func newTwitterAPI() (*anaconda.TwitterApi, error) {
	keys := []string{
		"TWITTER_CONSUMER_KEY",
		"TWITTER_CONSUMER_SECRET",
		"TWITTER_ACCESS_TOKEN",
		"TWITTER_ACCESS_SECRET",
	}
	pairs := make(map[string]string, 4)
	for _, k := range keys {
		v := os.Getenv(k)
		if v == "" {
			return nil, fmt.Errorf("environment variable %q required", k)
		}
		pairs[k] = v
	}

	anaconda.SetConsumerKey(pairs["TWITTER_CONSUMER_KEY"])
	anaconda.SetConsumerSecret(pairs["TWITTER_CONSUMER_SECRET"])
	api := anaconda.NewTwitterApi(pairs["TWITTER_ACCESS_TOKEN"], pairs["TWITTER_ACCESS_SECRET"])
	_, err := api.VerifyCredentials()
	if err != nil {
		return nil, err
	}
	return api, nil
}

// spinner displays a spinner on the std output
func spinner(stop <-chan struct{}) {
	for {
		select {
		case <-stop:
			return
		default:
			for _, r := range `-\|/` {
				fmt.Printf("\r%c", r)
				time.Sleep(80 * time.Millisecond)
			}
		}
	}
}

// findUsers uses "searchTerm" to search for tweets using the users/search API
// (https://dev.twitter.com/rest/reference/get/users/search).
// Returns the number of users found
func findUsers(api *anaconda.TwitterApi) (int, error) {
	maxCount := 20
	// don't ask for more than required
	usersRequired := *maxFollow - len(toFollow)
	if maxCount > usersRequired {
		maxCount = usersRequired
	}
	if maxCount == 0 {
		return 0, nil
	}
	page := 0
	values := make(url.Values)
	values.Add("count", strconv.Itoa(maxCount))
	values.Add("include_entities", "false")

	added := 0
	for {
		values.Set("page", strconv.Itoa(page))
		resp, err := api.GetUserSearch(*searchTerm, values)
		if err != nil {
			return 0, fmt.Errorf("findUsers: %v", err)
		}
		for _, user := range resp {
			if !isFollowing(user) {
				if len(toFollow) >= *maxFollow { // check if we reached max number of people to follow
					return added, nil
				}
				toFollow = append(toFollow, user)
				added++
			}
		}
		if len(resp) != maxCount { // there are no more pages available
			break
		}
		if len(toFollow) >= *maxFollow { // got number of followers required
			break
		}
		page++
	}

	return added, nil
}

// findUsersByTweet uses "searchTerm" to search for tweets using the search/tweets API
// (https://dev.twitter.com/rest/reference/get/search/tweets).
// Returns the number of users found
func findUsersByTweet(api *anaconda.TwitterApi) (int, error) {
	values := make(url.Values)
	maxCount := 100
	// don't ask for more than required
	usersRequired := *maxFollow - len(toFollow)
	if maxCount > usersRequired {
		maxCount = usersRequired
	}
	if maxCount == 0 {
		return 0, nil
	}
	values.Add("result_type", "mixed")
	values.Add("count", strconv.Itoa(maxCount))
	values.Add("include_entities", "false")
	values.Add("lang", "en")

	added := 0
	fn := func(resp anaconda.SearchResponse) {
		for _, tweet := range resp.Statuses {
			if !isFollowing(tweet.User) {
				if len(toFollow) >= *maxFollow { // check if we reached max number of people to follow
					return
				}
				toFollow = append(toFollow, tweet.User)
				added++
			}
		}
	}

	resp, err := api.GetSearch(*searchTerm, values)
	for {
		if err != nil {
			return 0, fmt.Errorf("findUsersByTweet: %v", err)
		}
		fn(resp)
		if len(resp.Statuses) != maxCount { // no more pages
			break
		}
		if len(toFollow) >= *maxFollow { // got number of followers required
			break
		}
		resp, err = resp.GetNext(api)
	}

	return added, nil
}

// getAllFriends returns a slice of all friends of the user
func getAllFriends(api *anaconda.TwitterApi) []anaconda.User {
	var friends []anaconda.User
	ch := api.GetFriendsListAll(nil)
	for f := range ch {
		friends = append(friends, f.Friends...)
	}
	return friends
}

// isFollowing returns true if the user passed is already a friend or is
// already scheduled to be followed
func isFollowing(user anaconda.User) bool {
	for _, u := range alreadyFollowing {
		if u.Id == user.Id {
			return true
		}
	}
	for _, u := range toFollow {
		if u.Id == user.Id {
			return true
		}
	}
	return false
}

// followUser uses the "TwitterApi" passed to follow a "userID".
// If followed without error, the user's screen name is returned.
// Attempting to follow a user which is already a friend will treat
// it as a user who is not a friend
func followUser(api *anaconda.TwitterApi, userID int64) error {
	_, err := api.FollowUserId(userID, nil)
	if err != nil {
		return fmt.Errorf("follow %d: %v\n", userID, err)
	}
	return nil
}
<commit_msg>updated comments<commit_after>/*
MIT License

Copyright (c) 2016 Kyriacos Kyriacou

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 gofollow provides an automated way to follow new users
on Twitter which are found by searching with the search term provided

Flags:
-s    (search term)
-max  (max number of users to follow)
*/
package main

import (
	"flag"
	"fmt"
	"log"
	"net/url"
	"os"
	"strconv"
	"time"

	"github.com/ChimeraCoder/anaconda"
)

// searchTerm is the search term used by the search functions
var searchTerm = flag.String("s", "", "(required) search term to find users by (i.e. gopher)")

// maxFollow is the maximum number of users that the application should follow. It is
// an upper bound since there might be a case where not enough users are found to follow
var maxFollow = flag.Int("max", 50, "(optional) max number of users to follow (hard maximum of 100 to avoid limiting by Twitter)")

// alreadyFollowing is a slice that contains all users that are already being followed.
// This is used so that the application does not attempt to follow users that are already friends
var alreadyFollowing []anaconda.User

// toFollow is a slice that should be populated with the users found by the search functions and
// will subsequently be used to follow each user
var toFollow []anaconda.User

func main() {
	api, err := newTwitterAPI()
	if err != nil {
		log.Fatal(err)
	}

	flag.Parse()
	if len(*searchTerm) == 0 {
		flag.PrintDefaults()
		os.Exit(1)
	}
	const hardMaxFollow = 100
	if *maxFollow > hardMaxFollow {
		*maxFollow = hardMaxFollow
	}

	alreadyFollowing = getAllFriends(api)

	// Start finding users

	fmt.Println("Finding users...")
	done := make(chan struct{})
	go spinner(done) // feedback for user

	found := 0
	n, err := findUsers(api)
	if err != nil {
		log.Fatal(err)
	}
	found += n
	n, err = findUsersByTweet(api)
	if err != nil {
		log.Fatal(err)
	}
	done <- struct{}{}
	found += n
	fmt.Printf("\rFound %d unique users\n", found)
	if found == 0 {
		fmt.Println("Try a broader search term next time!")
		os.Exit(0)
	}

	// Start following users

	const userURLFormat = "https://twitter.com/%s"
	fmt.Println("Following...")
	newFriends := 0
	for _, user := range toFollow {
		err := followUser(api, user.Id)
		if err != nil {
			log.Print(fmt.Errorf("could not follow %s: %v\n", user.ScreenName, err))
			break // could continue, but error is most likely due to limiting by twitter and will fall through
		}
		fmt.Printf("%-40s%-s\n", user.Name, fmt.Sprintf(userURLFormat, user.ScreenName))
		newFriends++
	}
	fmt.Println("-------------------------------------------------------------------------------")
	fmt.Printf("You are now following %d new users!\n", newFriends)
}

// newTwitterAPI returns a new Twitter API using keys from the environment
func newTwitterAPI() (*anaconda.TwitterApi, error) {
	keys := []string{
		"TWITTER_CONSUMER_KEY",
		"TWITTER_CONSUMER_SECRET",
		"TWITTER_ACCESS_TOKEN",
		"TWITTER_ACCESS_SECRET",
	}
	pairs := make(map[string]string, 4)
	for _, k := range keys {
		v := os.Getenv(k)
		if v == "" {
			return nil, fmt.Errorf("environment variable %q required", k)
		}
		pairs[k] = v
	}

	anaconda.SetConsumerKey(pairs["TWITTER_CONSUMER_KEY"])
	anaconda.SetConsumerSecret(pairs["TWITTER_CONSUMER_SECRET"])
	api := anaconda.NewTwitterApi(pairs["TWITTER_ACCESS_TOKEN"], pairs["TWITTER_ACCESS_SECRET"])
	_, err := api.VerifyCredentials()
	if err != nil {
		return nil, err
	}
	return api, nil
}

// spinner displays a spinner on the std output
func spinner(stop <-chan struct{}) {
	for {
		select {
		case <-stop:
			return
		default:
			for _, r := range `-\|/` {
				fmt.Printf("\r%c", r)
				time.Sleep(80 * time.Millisecond)
			}
		}
	}
}

// findUsers uses "searchTerm" to search for tweets using the users/search API
// (https://dev.twitter.com/rest/reference/get/users/search).
// Returns the number of users found
func findUsers(api *anaconda.TwitterApi) (int, error) {
	maxCount := 20
	// don't ask for more than required
	usersRequired := *maxFollow - len(toFollow)
	if maxCount > usersRequired {
		maxCount = usersRequired
	}
	if maxCount == 0 {
		return 0, nil
	}
	page := 0
	values := make(url.Values)
	values.Add("count", strconv.Itoa(maxCount))
	values.Add("include_entities", "false")

	added := 0
	for {
		values.Set("page", strconv.Itoa(page))
		resp, err := api.GetUserSearch(*searchTerm, values)
		if err != nil {
			return 0, fmt.Errorf("findUsers: %v", err)
		}
		for _, user := range resp {
			if !isFollowing(user) {
				if len(toFollow) >= *maxFollow { // check if we reached max number of people to follow
					return added, nil
				}
				toFollow = append(toFollow, user)
				added++
			}
		}
		if len(resp) != maxCount { // there are no more pages available
			break
		}
		if len(toFollow) >= *maxFollow { // got number of followers required
			break
		}
		page++
	}

	return added, nil
}

// findUsersByTweet uses "searchTerm" to search for tweets using the search/tweets API
// (https://dev.twitter.com/rest/reference/get/search/tweets).
// Returns the number of users found
func findUsersByTweet(api *anaconda.TwitterApi) (int, error) {
	values := make(url.Values)
	maxCount := 100
	// don't ask for more than required
	usersRequired := *maxFollow - len(toFollow)
	if maxCount > usersRequired {
		maxCount = usersRequired
	}
	if maxCount == 0 {
		return 0, nil
	}
	values.Add("result_type", "mixed")
	values.Add("count", strconv.Itoa(maxCount))
	values.Add("include_entities", "false")
	values.Add("lang", "en")

	added := 0
	fn := func(resp anaconda.SearchResponse) {
		for _, tweet := range resp.Statuses {
			if !isFollowing(tweet.User) {
				if len(toFollow) >= *maxFollow { // check if we reached max number of people to follow
					return
				}
				toFollow = append(toFollow, tweet.User)
				added++
			}
		}
	}

	resp, err := api.GetSearch(*searchTerm, values)
	for {
		if err != nil {
			return 0, fmt.Errorf("findUsersByTweet: %v", err)
		}
		fn(resp)
		if len(resp.Statuses) != maxCount { // no more pages
			break
		}
		if len(toFollow) >= *maxFollow { // got number of followers required
			break
		}
		resp, err = resp.GetNext(api)
	}

	return added, nil
}

// getAllFriends returns a slice of all friends of the user
func getAllFriends(api *anaconda.TwitterApi) []anaconda.User {
	var friends []anaconda.User
	ch := api.GetFriendsListAll(nil)
	for f := range ch {
		friends = append(friends, f.Friends...)
	}
	return friends
}

// isFollowing returns true if the user passed is already a friend or is
// already scheduled to be followed
func isFollowing(user anaconda.User) bool {
	for _, u := range alreadyFollowing {
		if u.Id == user.Id {
			return true
		}
	}
	for _, u := range toFollow {
		if u.Id == user.Id {
			return true
		}
	}
	return false
}

// followUser uses the "TwitterApi" passed to follow a "userID".
// If followed without error, the user's screen name is returned.
// Attempting to follow a user which is already a friend will treat
// it as a user who is not a friend
func followUser(api *anaconda.TwitterApi, userID int64) error {
	_, err := api.FollowUserId(userID, nil)
	if err != nil {
		return fmt.Errorf("follow %d: %v\n", userID, err)
	}
	return nil
}
<|endoftext|>


<commit_before>package migrate

import (
	"database/sql"
	"os"

	"github.com/coopernurse/gorp"
	_ "github.com/mattn/go-sqlite3"
	. "gopkg.in/check.v1"
)

var filename = "/tmp/gorp-migrate-sqlite.db"
var sqliteMigrations = []*Migration{
	&Migration{
		Id:   "123",
		Up:   []string{"CREATE TABLE people (id int)"},
		Down: []string{"DROP TABLE people"},
	},
	&Migration{
		Id:   "124",
		Up:   []string{"ALTER TABLE people ADD COLUMN first_name text"},
		Down: []string{"SELECT 0"}, // Not really supported
	},
}

type SqliteMigrateSuite struct {
	DbMap *gorp.DbMap
}

var _ = Suite(&SqliteMigrateSuite{})

func (s *SqliteMigrateSuite) SetUpTest(c *C) {
	db, err := sql.Open("sqlite3", filename)
	c.Assert(err, IsNil)

	s.DbMap = &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
}

func (s *SqliteMigrateSuite) TearDownTest(c *C) {
	err := os.Remove(filename)
	c.Assert(err, IsNil)
}

func (s *SqliteMigrateSuite) TestRunMigration(c *C) {
	migrations := &MemoryMigrationSource{
		Migrations: sqliteMigrations[:1],
	}

	// Executes one migration
	n, err := Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 1)

	// Can use table now
	_, err = s.DbMap.Exec("SELECT * FROM people")
	c.Assert(err, IsNil)

	// Shouldn't apply migration again
	n, err = Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 0)
}

func (s *SqliteMigrateSuite) TestMigrateMultiple(c *C) {
	migrations := &MemoryMigrationSource{
		Migrations: sqliteMigrations[:2],
	}

	// Executes one migration
	n, err := Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 2)

	// Can use column now
	_, err = s.DbMap.Exec("SELECT first_name FROM people")
	c.Assert(err, IsNil)
}

func (s *SqliteMigrateSuite) TestMigrateIncremental(c *C) {
	migrations := &MemoryMigrationSource{
		Migrations: sqliteMigrations[:1],
	}

	// Executes one migration
	n, err := Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 1)

	// Execute a new migration
	migrations = &MemoryMigrationSource{
		Migrations: sqliteMigrations[:2],
	}
	n, err = Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 1)

	// Can use column now
	_, err = s.DbMap.Exec("SELECT first_name FROM people")
	c.Assert(err, IsNil)
}

func (s *SqliteMigrateSuite) TestFileMigrate(c *C) {
	migrations := &FileMigrationSource{
		Dir: "test-migrations",
	}

	// Executes one migration
	n, err := Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 2)

	// Has data
	id, err := s.DbMap.SelectInt("SELECT id FROM people")
	c.Assert(err, IsNil)
	c.Assert(id, Equals, int64(1))
}

func (s *SqliteMigrateSuite) TestAssetMigrate(c *C) {
	migrations := &AssetMigrationSource{
		Asset:    Asset,
		AssetDir: AssetDir,
		Dir:      "test-migrations",
	}

	// Executes one migration
	n, err := Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 2)

	// Has data
	id, err := s.DbMap.SelectInt("SELECT id FROM people")
	c.Assert(err, IsNil)
	c.Assert(id, Equals, int64(1))
}
<commit_msg>Test ExecMax.<commit_after>package migrate

import (
	"database/sql"
	"os"

	"github.com/coopernurse/gorp"
	_ "github.com/mattn/go-sqlite3"
	. "gopkg.in/check.v1"
)

var filename = "/tmp/gorp-migrate-sqlite.db"
var sqliteMigrations = []*Migration{
	&Migration{
		Id:   "123",
		Up:   []string{"CREATE TABLE people (id int)"},
		Down: []string{"DROP TABLE people"},
	},
	&Migration{
		Id:   "124",
		Up:   []string{"ALTER TABLE people ADD COLUMN first_name text"},
		Down: []string{"SELECT 0"}, // Not really supported
	},
}

type SqliteMigrateSuite struct {
	DbMap *gorp.DbMap
}

var _ = Suite(&SqliteMigrateSuite{})

func (s *SqliteMigrateSuite) SetUpTest(c *C) {
	db, err := sql.Open("sqlite3", filename)
	c.Assert(err, IsNil)

	s.DbMap = &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
}

func (s *SqliteMigrateSuite) TearDownTest(c *C) {
	err := os.Remove(filename)
	c.Assert(err, IsNil)
}

func (s *SqliteMigrateSuite) TestRunMigration(c *C) {
	migrations := &MemoryMigrationSource{
		Migrations: sqliteMigrations[:1],
	}

	// Executes one migration
	n, err := Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 1)

	// Can use table now
	_, err = s.DbMap.Exec("SELECT * FROM people")
	c.Assert(err, IsNil)

	// Shouldn't apply migration again
	n, err = Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 0)
}

func (s *SqliteMigrateSuite) TestMigrateMultiple(c *C) {
	migrations := &MemoryMigrationSource{
		Migrations: sqliteMigrations[:2],
	}

	// Executes one migration
	n, err := Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 2)

	// Can use column now
	_, err = s.DbMap.Exec("SELECT first_name FROM people")
	c.Assert(err, IsNil)
}

func (s *SqliteMigrateSuite) TestMigrateIncremental(c *C) {
	migrations := &MemoryMigrationSource{
		Migrations: sqliteMigrations[:1],
	}

	// Executes one migration
	n, err := Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 1)

	// Execute a new migration
	migrations = &MemoryMigrationSource{
		Migrations: sqliteMigrations[:2],
	}
	n, err = Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 1)

	// Can use column now
	_, err = s.DbMap.Exec("SELECT first_name FROM people")
	c.Assert(err, IsNil)
}

func (s *SqliteMigrateSuite) TestFileMigrate(c *C) {
	migrations := &FileMigrationSource{
		Dir: "test-migrations",
	}

	// Executes one migration
	n, err := Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 2)

	// Has data
	id, err := s.DbMap.SelectInt("SELECT id FROM people")
	c.Assert(err, IsNil)
	c.Assert(id, Equals, int64(1))
}

func (s *SqliteMigrateSuite) TestAssetMigrate(c *C) {
	migrations := &AssetMigrationSource{
		Asset:    Asset,
		AssetDir: AssetDir,
		Dir:      "test-migrations",
	}

	// Executes one migration
	n, err := Exec(s.DbMap, migrations, Up)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 2)

	// Has data
	id, err := s.DbMap.SelectInt("SELECT id FROM people")
	c.Assert(err, IsNil)
	c.Assert(id, Equals, int64(1))
}

func (s *SqliteMigrateSuite) TestMigrateMax(c *C) {
	migrations := &FileMigrationSource{
		Dir: "test-migrations",
	}

	// Executes one migration
	n, err := ExecMax(s.DbMap, migrations, Up, 1)
	c.Assert(err, IsNil)
	c.Assert(n, Equals, 1)

	// Has data
	id, err := s.DbMap.SelectInt("SELECT COUNT(*) FROM people")
	c.Assert(err, IsNil)
	c.Assert(id, Equals, int64(0))
}
<|endoftext|>


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

package account

import (
	"fmt"

	"github.com/TheThingsNetwork/ttn/core/account/util"
	"github.com/TheThingsNetwork/ttn/core/types"
)

// ListApplications list all applications
func (a *Account) ListApplications() (apps []Application, err error) {
	err = util.GET(a.server, a.accessToken, "/applications", &apps)
	return apps, err
}

// FindApplication gets a specific application from the account server
func (a *Account) FindApplication(appID string) (app Application, err error) {
	err = util.GET(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID), &app)
	return app, err
}

type createApplicationReq struct {
	Name  string         `json:"name" valid:"required"`
	AppID string         `json:"id"   valid:"required"`
	EUIs  []types.AppEUI `json:"euis"`
}

// CreateApplication creates a new application on the account server
func (a *Account) CreateApplication(appID string, name string, EUIs []types.AppEUI) (app Application, err error) {
	body := createApplicationReq{
		Name:  name,
		AppID: appID,
		EUIs:  EUIs,
	}

	err = util.POST(a.server, a.accessToken, "/applications", &body, &app)
	return app, err
}

// DeleteApplication deletes an application
func (a *Account) DeleteApplication(appID string) error {
	return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID))
}

// Grant adds a collaborator to the application
func (a *Account) Grant(appID string, username string, rights []types.Right) error {
	return util.PUT(a.server, a.accessToken, fmt.Sprintf("/applications/%s/collaborators/%s", appID, username), rights, nil)
}

// Retract removes rights from a collaborator of the application
func (a *Account) Retract(appID string, username string) error {
	return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/collaborators/%s", appID, username))
}

type addAccessKeyReq struct {
	Name   string        `json:"name"   valid:"required"`
	Rights []types.Right `json:"rights" valid:"required"`
}

// AddAccessKey adds an access key to the application with the specified name
// and rights
func (a *Account) AddAccessKey(appID string, name string, rights []types.Right) (key types.AccessKey, err error) {
	body := addAccessKeyReq{
		Name:   name,
		Rights: rights,
	}
	err = util.POST(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys", appID), body, &key)
	return key, err
}

// RemoveAccessKey removes the specified access key from the application
func (a *Account) RemoveAccessKey(appID string, name string) error {
	return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys/%s", appID, name))
}

type editAppReq struct {
	Name string `json:"name,omitempty"`
}

// ChangeName changes the application name
func (a *Account) ChangeName(appID string, name string) (app Application, err error) {
	body := editAppReq{
		Name: name,
	}
	err = util.PATCH(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID), body, &app)
	return app, err
}

// AddEUI adds an EUI to the applications list of EUIs
func (a *Account) AddEUI(appID string, eui types.AppEUI) error {
	return util.POST(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()), nil, nil)
}

// RemoveEUI removes the specified EUI from the application
func (a *Account) RemoveEUI(appID string, eui types.AppEUI) error {
	return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()))
}
<commit_msg>fix errors against api<commit_after>// Copyright © 2016 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package account

import (
	"fmt"

	"github.com/TheThingsNetwork/ttn/core/account/util"
	"github.com/TheThingsNetwork/ttn/core/types"
)

// ListApplications list all applications
func (a *Account) ListApplications() (apps []Application, err error) {
	err = util.GET(a.server, a.accessToken, "/applications", &apps)
	return apps, err
}

// FindApplication gets a specific application from the account server
func (a *Account) FindApplication(appID string) (app Application, err error) {
	err = util.GET(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID), &app)
	return app, err
}

type createApplicationReq struct {
	Name  string         `json:"name" valid:"required"`
	AppID string         `json:"id"   valid:"required"`
	EUIs  []types.AppEUI `json:"euis"`
}

// CreateApplication creates a new application on the account server
func (a *Account) CreateApplication(appID string, name string, EUIs []types.AppEUI) (app Application, err error) {
	body := createApplicationReq{
		Name:  name,
		AppID: appID,
		EUIs:  EUIs,
	}

	err = util.POST(a.server, a.accessToken, "/applications", &body, &app)
	return app, err
}

// DeleteApplication deletes an application
func (a *Account) DeleteApplication(appID string) error {
	return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID))
}

type grantReq struct {
	Rights []types.Right `json:"rights"`
}

// Grant adds a collaborator to the application
func (a *Account) Grant(appID string, username string, rights []types.Right) error {
	req := grantReq{
		Rights: rights,
	}
	return util.PUT(a.server, a.accessToken, fmt.Sprintf("/applications/%s/collaborators/%s", appID, username), req, nil)
}

// Retract removes rights from a collaborator of the application
func (a *Account) Retract(appID string, username string) error {
	return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/collaborators/%s", appID, username))
}

type addAccessKeyReq struct {
	Name   string        `json:"name"   valid:"required"`
	Rights []types.Right `json:"rights" valid:"required"`
}

// AddAccessKey adds an access key to the application with the specified name
// and rights
func (a *Account) AddAccessKey(appID string, name string, rights []types.Right) (key types.AccessKey, err error) {
	body := addAccessKeyReq{
		Name:   name,
		Rights: rights,
	}
	err = util.POST(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys", appID), body, &key)
	return key, err
}

// RemoveAccessKey removes the specified access key from the application
func (a *Account) RemoveAccessKey(appID string, name string) error {
	return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys/%s", appID, name))
}

type editAppReq struct {
	Name string `json:"name,omitempty"`
}

// ChangeName changes the application name
func (a *Account) ChangeName(appID string, name string) (app Application, err error) {
	body := editAppReq{
		Name: name,
	}
	err = util.PATCH(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID), body, &app)
	return app, err
}

// AddEUI adds an EUI to the applications list of EUIs
func (a *Account) AddEUI(appID string, eui types.AppEUI) error {
	return util.PUT(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()), nil, nil)
}

// RemoveEUI removes the specified EUI from the application
func (a *Account) RemoveEUI(appID string, eui types.AppEUI) error {
	return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()))
}
<|endoftext|>


<commit_before>package datastore

import "github.com/iron-io/functions/api/models"

type Mock struct{}

func (m *Mock) GetApp(app string) (*models.App, error) {
	return nil, nil
}

func (m *Mock) GetApps(appFilter *models.AppFilter) ([]*models.App, error) {
	return nil, nil
}

func (m *Mock) StoreApp(app *models.App) (*models.App, error) {
	return nil, nil
}

func (m *Mock) RemoveApp(app string) error {
	return nil
}

func (m *Mock) GetRoute(app, route string) (*models.Route, error) {
	return nil, nil
}

func (m *Mock) GetRoutes(routeFilter *models.RouteFilter) ([]*models.Route, error) {
	return nil, nil
}

func (m *Mock) StoreRoute(route *models.Route) (*models.Route, error) {
	return nil, nil
}

func (m *Mock) RemoveRoute(app, route string) error {
	return nil
}
<commit_msg>added fake app and routes properties to mock<commit_after>package datastore

import "github.com/iron-io/functions/api/models"

type Mock struct {
	FakeApp    *models.App
	FakeApps   []*models.App
	FakeRoute  *models.Route
	FakeRoutes []*models.Route
}

func (m *Mock) GetApp(app string) (*models.App, error) {
	return m.FakeApp, nil
}

func (m *Mock) GetApps(appFilter *models.AppFilter) ([]*models.App, error) {
	return m.FakeApps, nil
}

func (m *Mock) StoreApp(app *models.App) (*models.App, error) {
	return m.FakeApp, nil
}

func (m *Mock) RemoveApp(app string) error {
	return nil
}

func (m *Mock) GetRoute(app, route string) (*models.Route, error) {
	return m.FakeRoute, nil
}

func (m *Mock) GetRoutes(routeFilter *models.RouteFilter) ([]*models.Route, error) {
	return m.FakeRoutes, nil
}

func (m *Mock) StoreRoute(route *models.Route) (*models.Route, error) {
	return m.FakeRoute, nil
}

func (m *Mock) RemoveRoute(app, route string) error {
	return nil
}
<|endoftext|>


<commit_before>/* The Cookoo `fmt` package provides utility wrappers for formatting text.

The commands in this package provide simple string formatting and printing
at the command level.

For convenience, a basic "text/template" wrapper is included in this library,
though a more robust "html/template" set of commands are provided in
"github.com/Masterminds/cookoo/web".

Example usage:

	reg.Route("test", "Test").
	Does(Template, "out").
	Using("template").WithDefault("{{.Hello}} {{.one}}").
	Using("Hello").WithDefault("Hello").
	Using("one").WithDefault(1)

Or

	reg.Route("test", "Test").
	Does(Sprintf, "out").
	Using("format").WithDefault("%s %d").
	Using("0").WithDefault("Hello").
	Using("1").WithDefault(1)

*/
package fmt

import (
	"github.com/Masterminds/cookoo"
	"text/template"
	"crypto/md5"
	"bytes"
	"fmt"
)

// Template is a template-based text formatter.
//
// This uses the core `text/template` to process a given string template.
//
// Params
// 	- template (string): A template string.
// 	- template.Context (bool): If true, the context will be placed into the
// 		template renderer as 'Cxt', and can be used as `{{.Cxt.Foo}}`. False
// 		by default.
// 	- ... (interface{}): Values passed into the template.
//
// Conventionally, template variables should start with an initial capital.
//
// Returns a formatted string.
func Template(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
	format := cookoo.GetString("template", "", p)
	withCxt := cookoo.GetBool("template.Context", false, p)

	name := fmt.Sprintf("%x", md5.Sum([]byte(format)))

	//c.Logf("debug", "Template %s is '%s'\n", name, format)

	tpl, err := template.New(name).Parse(format)
	if err != nil {
		return "", err
	}

	data := p.AsMap()
	if withCxt {
		//c.Logf("debug", "Adding context.")
		data["Cxt"] = c.AsMap()
	}

	var out bytes.Buffer
	if err := tpl.Execute(&out, data); err != nil {
		return "", err
	}

	return out.String(), nil
}

// Printf formats a string and then sends it to standard out.
//
// This is a command wrapper for the core `fmt.Printf` function, but tooled
// to work the Cookoo way.
//
// The following prints 'Hello' to standard out.
//
// 	//...
// 	Does(Printf, "out").
// 	Using("format").WithDefault("%s\n")
// 	Using("0").WithDefault("Hello")
//
// Params:
// 	- format (string): The format string
// 	- "0"... (string): String representation of an integer ascending from 0.
// 	  These are treated as positional.
func Printf(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) {
	msg := params.Get("format", "").(string)

	maxP := len(params.AsMap())
	vals := make([]interface{}, 0, maxP - 1)

	var istr string
	var i = 0
	for i < maxP {
		istr = fmt.Sprintf("%d", i) // FIXME
		if v, ok := params.Has(istr); ok {
			//fmt.Printf("%d: Found %v\n", i, v)
			vals = append(vals, v)
			i++
		} else {
			break
		}
	}
	fmt.Printf(msg, vals...)
	return true, nil
}
// Sprintf formats a string and then returns the result to the context.
//
// This is a command wrapper for the core `fmt.Sprintf` function, but tooled
// to work the Cookoo way.
//
// The following returns 'Hello World' to the context.
//
// 	//...
// 	Does(Sprintf, "out").
// 	Using("format").WithDefault("%s %s\n")
// 	Using("0").WithDefault("Hello")
// 	Using("1").WithDefault("World")
//
// Params:
// 	- format (string): The format string
// 	- "0"... (string): String representation of an integer ascending from 0.
// 	  These are treated as positional.
func Sprintf(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) {
	msg := params.Get("format", "").(string)

	maxP := len(params.AsMap())
	vals := make([]interface{}, 0, maxP - 1)

	var istr string
	var i = 0
	for i < maxP {
		istr = fmt.Sprintf("%d", i) // FIXME
		if v, ok := params.Has(istr); ok {
			//fmt.Printf("%d: Found %v\n", i, v)
			vals = append(vals, v)
			i++
		} else {
			break
		}
	}

	return fmt.Sprintf(msg, vals...), nil
}

<commit_msg>Added fmt.Println command.<commit_after>/* The Cookoo `fmt` package provides utility wrappers for formatting text.

The commands in this package provide simple string formatting and printing
at the command level.

For convenience, a basic "text/template" wrapper is included in this library,
though a more robust "html/template" set of commands are provided in
"github.com/Masterminds/cookoo/web".

Example usage:

	reg.Route("test", "Test").
	Does(Template, "out").
	Using("template").WithDefault("{{.Hello}} {{.one}}").
	Using("Hello").WithDefault("Hello").
	Using("one").WithDefault(1)

Or

	reg.Route("test", "Test").
	Does(Sprintf, "out").
	Using("format").WithDefault("%s %d").
	Using("0").WithDefault("Hello").
	Using("1").WithDefault(1)

*/
package fmt

import (
	"github.com/Masterminds/cookoo"
	"text/template"
	"crypto/md5"
	"bytes"
	"fmt"
)

// Template is a template-based text formatter.
//
// This uses the core `text/template` to process a given string template.
//
// Params
// 	- template (string): A template string.
// 	- template.Context (bool): If true, the context will be placed into the
// 		template renderer as 'Cxt', and can be used as `{{.Cxt.Foo}}`. False
// 		by default.
// 	- ... (interface{}): Values passed into the template.
//
// Conventionally, template variables should start with an initial capital.
//
// Returns a formatted string.
func Template(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
	format := cookoo.GetString("template", "", p)
	withCxt := cookoo.GetBool("template.Context", false, p)

	name := fmt.Sprintf("%x", md5.Sum([]byte(format)))

	//c.Logf("debug", "Template %s is '%s'\n", name, format)

	tpl, err := template.New(name).Parse(format)
	if err != nil {
		return "", err
	}

	data := p.AsMap()
	if withCxt {
		//c.Logf("debug", "Adding context.")
		data["Cxt"] = c.AsMap()
	}

	var out bytes.Buffer
	if err := tpl.Execute(&out, data); err != nil {
		return "", err
	}

	return out.String(), nil
}

// Println prints a string to standard output, and appends a newline.
//
// Also see web.Flush.
//
// Params:
// 	- contents (string): The string to print.
func Println(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
	msg := cookoo.GetString("contents", "", p)
	fmt.Println(msg)
	return msg, nil
}

// Printf formats a string and then sends it to standard out.
//
// This is a command wrapper for the core `fmt.Printf` function, but tooled
// to work the Cookoo way.
//
// The following prints 'Hello' to standard out.
//
// 	//...
// 	Does(Printf, "out").
// 	Using("format").WithDefault("%s\n")
// 	Using("0").WithDefault("Hello")
//
// Params:
// 	- format (string): The format string
// 	- "0"... (string): String representation of an integer ascending from 0.
// 	  These are treated as positional.
func Printf(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) {
	msg := params.Get("format", "").(string)

	maxP := len(params.AsMap())
	vals := make([]interface{}, 0, maxP - 1)

	var istr string
	var i = 0
	for i < maxP {
		istr = fmt.Sprintf("%d", i) // FIXME
		if v, ok := params.Has(istr); ok {
			//fmt.Printf("%d: Found %v\n", i, v)
			vals = append(vals, v)
			i++
		} else {
			break
		}
	}
	fmt.Printf(msg, vals...)
	return true, nil
}
// Sprintf formats a string and then returns the result to the context.
//
// This is a command wrapper for the core `fmt.Sprintf` function, but tooled
// to work the Cookoo way.
//
// The following returns 'Hello World' to the context.
//
// 	//...
// 	Does(Sprintf, "out").
// 	Using("format").WithDefault("%s %s\n")
// 	Using("0").WithDefault("Hello")
// 	Using("1").WithDefault("World")
//
// Params:
// 	- format (string): The format string
// 	- "0"... (string): String representation of an integer ascending from 0.
// 	  These are treated as positional.
func Sprintf(c cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) {
	msg := params.Get("format", "").(string)

	maxP := len(params.AsMap())
	vals := make([]interface{}, 0, maxP - 1)

	var istr string
	var i = 0
	for i < maxP {
		istr = fmt.Sprintf("%d", i) // FIXME
		if v, ok := params.Has(istr); ok {
			//fmt.Printf("%d: Found %v\n", i, v)
			vals = append(vals, v)
			i++
		} else {
			break
		}
	}

	return fmt.Sprintf(msg, vals...), nil
}

<|endoftext|>


<commit_before>package protocols

import (
	"bytes"
	"context"
	"encoding/binary"
	"net"

	"go.uber.org/zap"
)

type bittorrentMsg struct {
	Length             uint8
	ProtocolIdentifier [19]uint8
	Reserved           [8]uint8
	InfoHash           [20]uint8
	PeerID             [20]uint8
}

// HandleBittorrent handles a Bittorrent connection
func HandleBittorrent(ctx context.Context, conn net.Conn, logger Logger, h Honeypot) error {
	var err error
	defer func() {
		if err = conn.Close(); err != nil {
			logger.Error("failed to close connection", zap.Error(err), zap.String("handler", "bittorrent"))
			return
		}
	}()
	buffer := make([]byte, 1024)
	var r *bytes.Reader
	msg := bittorrentMsg{}
	md, err := h.MetadataByConnection(conn)
	if err != nil {
		return err
	}
	for {
		n, err := conn.Read(buffer)
		if err == nil || n > 0 {
			r = bytes.NewReader(buffer)
			if err := binary.Read(r, binary.BigEndian, &msg); err != nil {
				logger.Error("failed to read message", zap.Error(err), zap.String("handler", "bittorrent"))
				break
			}
			if err = h.Produce(conn, md, buffer); err != nil {
				logger.Error("failed to produce message", zap.Error(err), zap.String("handler", "bittorrent"))
			}

			logger.Info(
				"telnet send",
				zap.String("handler", "bittorrent"),
				zap.Uint8s("peer_id", msg.PeerID[:]),
				zap.Uint8s("inf_hash", msg.InfoHash[:]),
			)

			if err = binary.Write(conn, binary.BigEndian, msg); err != nil {
				logger.Error("failed to write message", zap.Error(err), zap.String("handler", "bittorrent"))
				break
			}
		}
	}
	return nil
}
<commit_msg>more error handling<commit_after>package protocols

import (
	"bytes"
	"context"
	"encoding/binary"
	"net"

	"go.uber.org/zap"
)

type bittorrentMsg struct {
	Length             uint8
	ProtocolIdentifier [19]uint8
	Reserved           [8]uint8
	InfoHash           [20]uint8
	PeerID             [20]uint8
}

// HandleBittorrent handles a Bittorrent connection
func HandleBittorrent(ctx context.Context, conn net.Conn, logger Logger, h Honeypot) error {
	var err error
	defer func() {
		if err = conn.Close(); err != nil {
			logger.Error("failed to close connection", zap.Error(err), zap.String("handler", "bittorrent"))
			return
		}
	}()

	buffer := make([]byte, 1024)
	md, err := h.MetadataByConnection(conn)
	if err != nil {
		return err
	}

	for {
		n, err := conn.Read(buffer)
		if err == nil || n > 0 {
			msg := bittorrentMsg{}
			if err := binary.Read(bytes.NewReader(buffer), binary.BigEndian, &msg); err != nil {
				logger.Error("failed to read message", zap.Error(err), zap.String("handler", "bittorrent"))
				break
			}
			if err = h.Produce(conn, md, buffer); err != nil {
				logger.Error("failed to produce message", zap.Error(err), zap.String("handler", "bittorrent"))
			}

			logger.Info(
				"telnet send",
				zap.String("handler", "bittorrent"),
				zap.Uint8s("peer_id", msg.PeerID[:]),
				zap.Uint8s("inf_hash", msg.InfoHash[:]),
			)

			if err = binary.Write(conn, binary.BigEndian, msg); err != nil {
				logger.Error("failed to write message", zap.Error(err), zap.String("handler", "bittorrent"))
				break
			}
		} else {
			logger.Info("failed to read from connection", zap.String("handler", "bittorrent"))
			if err != nil {
				logger.Error("failed to read data", zap.Error(err), zap.String("handler", "bittorrent"))
				break
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 storagebackend

import (
	"time"

	"go.opentelemetry.io/otel/trace"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/server/egressselector"
	"k8s.io/apiserver/pkg/storage/etcd3"
	"k8s.io/apiserver/pkg/storage/value"
	flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
)

const (
	StorageTypeUnset = ""
	StorageTypeETCD2 = "etcd2"
	StorageTypeETCD3 = "etcd3"

	DefaultCompactInterval      = 5 * time.Minute
	DefaultDBMetricPollInterval = 30 * time.Second
	DefaultHealthcheckTimeout   = 2 * time.Second
)

// TransportConfig holds all connection related info,  i.e. equal TransportConfig means equal servers we talk to.
type TransportConfig struct {
	// ServerList is the list of storage servers to connect with.
	ServerList []string
	// TLS credentials
	KeyFile       string
	CertFile      string
	TrustedCAFile string
	// function to determine the egress dialer. (i.e. konnectivity server dialer)
	EgressLookup egressselector.Lookup
	// The TracerProvider can add tracing the connection
	TracerProvider *trace.TracerProvider
}

// Config is configuration for creating a storage backend.
type Config struct {
	// Type defines the type of storage backend. Default ("") is "etcd3".
	Type string
	// Prefix is the prefix to all keys passed to storage.Interface methods.
	Prefix string
	// Transport holds all connection related info, i.e. equal TransportConfig means equal servers we talk to.
	Transport TransportConfig
	// Paging indicates whether the server implementation should allow paging (if it is
	// supported). This is generally configured by feature gating, or by a specific
	// resource type not wishing to allow paging, and is not intended for end users to
	// set.
	Paging bool

	Codec runtime.Codec
	// EncodeVersioner is the same groupVersioner used to build the
	// storage encoder. Given a list of kinds the input object might belong
	// to, the EncodeVersioner outputs the gvk the object will be
	// converted to before persisted in etcd.
	EncodeVersioner runtime.GroupVersioner
	// Transformer allows the value to be transformed prior to persisting into etcd.
	Transformer value.Transformer

	// CompactionInterval is an interval of requesting compaction from apiserver.
	// If the value is 0, no compaction will be issued.
	CompactionInterval time.Duration
	// CountMetricPollPeriod specifies how often should count metric be updated
	CountMetricPollPeriod time.Duration
	// DBMetricPollInterval specifies how often should storage backend metric be updated.
	DBMetricPollInterval time.Duration
	// HealthcheckTimeout specifies the timeout used when checking health
	HealthcheckTimeout time.Duration

	LeaseManagerConfig etcd3.LeaseManagerConfig

	// StorageObjectCountTracker is used to keep track of the total
	// number of objects in the storage per resource.
	StorageObjectCountTracker flowcontrolrequest.StorageObjectCountTracker
}

// ConfigForResource is a Config specialized to a particular `schema.GroupResource`
type ConfigForResource struct {
	// Config is the resource-independent configuration
	Config

	// GroupResource is the relevant one
	GroupResource schema.GroupResource
}

// ForResource specializes to the given resource
func (config *Config) ForResource(resource schema.GroupResource) *ConfigForResource {
	return &ConfigForResource{
		Config:        *config,
		GroupResource: resource,
	}
}

func NewDefaultConfig(prefix string, codec runtime.Codec) *Config {
	return &Config{
		Paging:               true,
		Prefix:               prefix,
		Codec:                codec,
		CompactionInterval:   DefaultCompactInterval,
		DBMetricPollInterval: DefaultDBMetricPollInterval,
		HealthcheckTimeout:   DefaultHealthcheckTimeout,
		LeaseManagerConfig:   etcd3.NewDefaultLeaseManagerConfig(),
	}
}
<commit_msg>UPSTREAM: <carry>: Bug 1852056: change etcd health check timeout to 10s<commit_after>/*
Copyright 2016 The Kubernetes 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 storagebackend

import (
	"time"

	"go.opentelemetry.io/otel/trace"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/server/egressselector"
	"k8s.io/apiserver/pkg/storage/etcd3"
	"k8s.io/apiserver/pkg/storage/value"
	flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
)

const (
	StorageTypeUnset = ""
	StorageTypeETCD2 = "etcd2"
	StorageTypeETCD3 = "etcd3"

	DefaultCompactInterval      = 5 * time.Minute
	DefaultDBMetricPollInterval = 30 * time.Second
	DefaultHealthcheckTimeout   = 10 * time.Second
)

// TransportConfig holds all connection related info,  i.e. equal TransportConfig means equal servers we talk to.
type TransportConfig struct {
	// ServerList is the list of storage servers to connect with.
	ServerList []string
	// TLS credentials
	KeyFile       string
	CertFile      string
	TrustedCAFile string
	// function to determine the egress dialer. (i.e. konnectivity server dialer)
	EgressLookup egressselector.Lookup
	// The TracerProvider can add tracing the connection
	TracerProvider *trace.TracerProvider
}

// Config is configuration for creating a storage backend.
type Config struct {
	// Type defines the type of storage backend. Default ("") is "etcd3".
	Type string
	// Prefix is the prefix to all keys passed to storage.Interface methods.
	Prefix string
	// Transport holds all connection related info, i.e. equal TransportConfig means equal servers we talk to.
	Transport TransportConfig
	// Paging indicates whether the server implementation should allow paging (if it is
	// supported). This is generally configured by feature gating, or by a specific
	// resource type not wishing to allow paging, and is not intended for end users to
	// set.
	Paging bool

	Codec runtime.Codec
	// EncodeVersioner is the same groupVersioner used to build the
	// storage encoder. Given a list of kinds the input object might belong
	// to, the EncodeVersioner outputs the gvk the object will be
	// converted to before persisted in etcd.
	EncodeVersioner runtime.GroupVersioner
	// Transformer allows the value to be transformed prior to persisting into etcd.
	Transformer value.Transformer

	// CompactionInterval is an interval of requesting compaction from apiserver.
	// If the value is 0, no compaction will be issued.
	CompactionInterval time.Duration
	// CountMetricPollPeriod specifies how often should count metric be updated
	CountMetricPollPeriod time.Duration
	// DBMetricPollInterval specifies how often should storage backend metric be updated.
	DBMetricPollInterval time.Duration
	// HealthcheckTimeout specifies the timeout used when checking health
	HealthcheckTimeout time.Duration

	LeaseManagerConfig etcd3.LeaseManagerConfig

	// StorageObjectCountTracker is used to keep track of the total
	// number of objects in the storage per resource.
	StorageObjectCountTracker flowcontrolrequest.StorageObjectCountTracker
}

// ConfigForResource is a Config specialized to a particular `schema.GroupResource`
type ConfigForResource struct {
	// Config is the resource-independent configuration
	Config

	// GroupResource is the relevant one
	GroupResource schema.GroupResource
}

// ForResource specializes to the given resource
func (config *Config) ForResource(resource schema.GroupResource) *ConfigForResource {
	return &ConfigForResource{
		Config:        *config,
		GroupResource: resource,
	}
}

func NewDefaultConfig(prefix string, codec runtime.Codec) *Config {
	return &Config{
		Paging:               true,
		Prefix:               prefix,
		Codec:                codec,
		CompactionInterval:   DefaultCompactInterval,
		DBMetricPollInterval: DefaultDBMetricPollInterval,
		HealthcheckTimeout:   DefaultHealthcheckTimeout,
		LeaseManagerConfig:   etcd3.NewDefaultLeaseManagerConfig(),
	}
}
<|endoftext|>