<commit_before>package goutility

import (
	"errors"
	"net"
)

// GetFirstNonLoopbackIP get first non-loopback ip address of the current machine
func GetFirstNonLoopbackIP() (string, error) {

	// https://gist.github.com/mowings/017c80c188d1024ba3e7
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return "", err
	}

	for _, addr := range addrs {

		ip := GetAvailableIPFromAddress(addr)

		if ip != nil && !ip.IsLoopback() {

			// TODO: clarify function name, we're only looking for IPv4s here
			ip = ip.To4()
			if ip != nil {
				return ip.String(), nil
			}
		}
	}
	return "", errors.New("Unable to find external ip address")
}

// GetAvailableIPFromAddress retrieves the IP from a net.Addr if one is available
func GetAvailableIPFromAddress(address net.Addr) net.IP {

	var ip net.IP
	switch v := address.(type) {
	case *net.IPNet:
		ip = v.IP
	case *net.IPAddr:
		ip = v.IP
	}
	return ip
}
<commit_msg>separate out loop contents<commit_after>package goutility

import (
	"errors"
	"net"
)

// GetFirstNonLoopbackIP get first non-loopback ip address of the current machine
func GetFirstNonLoopbackIP() (string, error) {

	// https://gist.github.com/mowings/017c80c188d1024ba3e7
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return "", err
	}

	for _, addr := range addrs {

		ip := GetAvailableIPFromAddress(addr)

		// TODO: clarify function name, we're only looking for IPv4s here
		// TODO: clarify function name, we're returning a string, not a net.IP
		ipString := retreiveNonLoopbackV4IPString(ip)
		if ipString != nil {
			return *ipString, nil
		}
	}
	return "", errors.New("Unable to find external ip address")
}

func retreiveNonLoopbackV4IPString(ip net.IP) *string {

	if ip == nil {
		return nil
	}

	if ip.IsLoopback() {
		return nil
	}

	ipv4 := ip.To4()
	if ipv4 == nil {
		return nil
	}

	ipString := ipv4.String()
	return &ipString
}

// GetAvailableIPFromAddress retrieves the IP from a net.Addr if one is available
func GetAvailableIPFromAddress(address net.Addr) net.IP {

	var ip net.IP
	switch v := address.(type) {
	case *net.IPNet:
		ip = v.IP
	case *net.IPAddr:
		ip = v.IP
	}
	return ip
}
<|endoftext|>


<commit_before>/*
memberlist is a library that manages cluster
membership and member failure detection using a gossip based protocol.

The use cases for such a library are far-reaching: all distributed systems
require membership, and memberlist is a re-usable solution to managing
cluster membership and node failure detection.

memberlist is eventually consistent but converges quickly on average.
The speed at which it converges can be heavily tuned via various knobs
on the protocol. Node failures are detected and network partitions are partially
tolerated by attempting to communicate to potentially dead nodes through
multiple routes.
*/
package memberlist

import (
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"sync"
	"time"
)

type Config struct {
	// The name of this node. This must be unique in the cluster.
	Name string

	// Configuration related to what address to bind to and ports to
	// listen on. The ports used must match every node in the cluster,
	// since they'll be used for connecting as well as listening.
	BindAddr string
	UDPPort  int
	TCPPort  int

	// TCPTimeout is the timeout for establishing a TCP connection with
	// a remote node for a full state sync.
	TCPTimeout time.Duration

	// IndirectChecks is the number of nodes that will be asked to perform
	// an indirect probe of a node in the case a direct probe fails. Memberlist
	// waits for an ack from any single indirect node, so increasing this
	// number will increase the likelihood that an indirect probe will succeed
	// at the expense of bandwidth.
	IndirectChecks int

	// RetransmitMult is the multiplier for the number of retransmissions
	// that are attempted for messages broadcasted over gossip. The actual
	// count of retransmissions is calculated using the formula:
	//
	//   Retransmits = RetransmitMult * log(N+1)
	//
	// This allows the retransmits to scale properly with cluster size. The
	// higher the multiplier, the more likely a failed broadcast is to converge
	// at the expense of increased bandwidth.
	RetransmitMult int

	// SuspicionMult is the multiplier for determining the time an
	// inaccessible node is considered suspect before declaring it dead.
	// The actual timeout is calculated using the formula:
	//
	//   SuspicionTimeout = SuspicionMult * log(N+1) * ProbeInterval
	//
	// This allows the timeout to scale properly with expected propagation
	// delay with a larger cluster size. The higher the multiplier, the longer
	// an inaccessible node is considered part of the cluster before declaring
	// it dead, giving that suspect node more time to refute if it is indeed
	// still alive.
	SuspicionMult int

	// PushPullInterval is the interval between complete state syncs.
	// Complete state syncs are done with a single node over TCP and are
	// quite expensive relative to standard gossiped messages. Setting this
	// to zero will disable state push/pull syncs completely.
	//
	// Setting this interval lower (more frequent) will increase convergence
	// speeds across larger clusters at the expense of increased bandwidth
	// usage.
	PushPullInterval time.Duration

	// ProbeInterval and ProbeTimeout are used to configure probing
	// behavior for memberlist.
	//
	// ProbeInterval is the interval between random node probes. Setting
	// this lower (more frequent) will cause the memberlist cluster to detect
	// failed nodes more quickly at the expense of increased bandwidth usage.
	//
	// ProbeTimeout is the timeout to wait for an ack from a probed node
	// before assuming it is unhealthy. This should be set to 99-percentile
	// of RTT (round-trip time) on your network.
	ProbeInterval time.Duration
	ProbeTimeout  time.Duration

	// GossipInterval and GossipNodes are used to configure the gossip
	// behavior of memberlist.
	//
	// GossipInterval is the interval between sending messages that need
	// to be gossiped that haven't been able to piggyback on probing messages.
	// If this is set to zero, non-piggyback gossip is disabled. By lowering
	// this value (more frequent) gossip messages are propagated across
	// the cluster more quickly at the expense of increased bandwidth.
	//
	// GossipNodes is the number of random nodes to send gossip messages to
	// per GossipInterval. Increasing this number causes the gossip messages
	// to propagate across the cluster more quickly at the expense of
	// increased bandwidth.
	GossipInterval time.Duration
	GossipNodes    int

	// Delegate and Events are delegates for receiving and providing
	// data to memberlist via callback mechanisms. For Delegate, see
	// the Delegate interface. For Events, see the EventDelegate interface.
	Delegate Delegate
	Events   EventDelegate

	// LogOutput is the writer where logs should be sent. If this is not
	// set, logging will go to stderr by default.
	LogOutput io.Writer
}

type Memberlist struct {
	config         *Config
	shutdown       bool
	leave          bool
	leaveBroadcast chan struct{}

	udpListener *net.UDPConn
	tcpListener *net.TCPListener

	sequenceNum uint32 // Local sequence number
	incarnation uint32 // Local incarnation number

	nodeLock sync.RWMutex
	nodes    []*nodeState          // Known nodes
	nodeMap  map[string]*nodeState // Maps Addr.String() -> NodeState

	tickerLock sync.Mutex
	tickers    []*time.Ticker
	stopTick   chan struct{}
	probeIndex int

	ackLock     sync.Mutex
	ackHandlers map[uint32]*ackHandler

	broadcasts *TransmitLimitedQueue

	startStopLock sync.Mutex

	logger *log.Logger
}

// DefaultConfig returns a sane set of configurations for Memberlist.
// It uses the hostname as the node name, and otherwise sets very conservative
// values that are sane for most LAN environments. The default configuration
// errs on the side on the side of caution, choosing values that are optimized
// for higher convergence at the cost of higher bandwidth usage. Regardless,
// these values are a good starting point when getting started with memberlist.
func DefaultConfig() *Config {
	hostname, _ := os.Hostname()
	return &Config{
		Name:             hostname,
		BindAddr:         "0.0.0.0",
		UDPPort:          7946,
		TCPPort:          7946,
		TCPTimeout:       30 * time.Second,       // Timeout after 10 seconds
		IndirectChecks:   3,                      // Use 3 nodes for the indirect ping
		RetransmitMult:   4,                      // Retransmit a message 4 * log(N+1) nodes
		SuspicionMult:    5,                      // Suspect a node for 5 * log(N+1) * Interval
		PushPullInterval: 30 * time.Second,       // Low frequency
		ProbeTimeout:     500 * time.Millisecond, // Reasonable RTT time for LAN
		ProbeInterval:    1 * time.Second,        // Failure check every second

		GossipNodes:    3,                      // Gossip to 3 nodes
		GossipInterval: 200 * time.Millisecond, // Gossip more rapidly
	}
}

// newMemberlist creates the network listeners.
// Does not schedule execution of background maintenence.
func newMemberlist(conf *Config) (*Memberlist, error) {
	tcpAddr := fmt.Sprintf("%s:%d", conf.BindAddr, conf.TCPPort)
	tcpLn, err := net.Listen("tcp", tcpAddr)
	if err != nil {
		return nil, fmt.Errorf("Failed to start TCP listener. Err: %s", err)
	}

	udpAddr := fmt.Sprintf("%s:%d", conf.BindAddr, conf.UDPPort)
	udpLn, err := net.ListenPacket("udp", udpAddr)
	if err != nil {
		tcpLn.Close()
		return nil, fmt.Errorf("Failed to start UDP listener. Err: %s", err)
	}

	// Set the UDP receive window size
	setUDPRecvBuf(udpLn.(*net.UDPConn))

	if conf.LogOutput == nil {
		conf.LogOutput = os.Stderr
	}

	m := &Memberlist{
		config:         conf,
		leaveBroadcast: make(chan struct{}, 1),
		udpListener:    udpLn.(*net.UDPConn),
		tcpListener:    tcpLn.(*net.TCPListener),
		nodeMap:        make(map[string]*nodeState),
		ackHandlers:    make(map[uint32]*ackHandler),
		broadcasts:     &TransmitLimitedQueue{RetransmitMult: conf.RetransmitMult},
		logger:         log.New(conf.LogOutput, "", log.LstdFlags),
	}
	m.broadcasts.NumNodes = func() int { return len(m.nodes) }
	go m.tcpListen()
	go m.udpListen()
	return m, nil
}

// Create will create a new Memberlist using the given configuration.
// This will not connect to any other node (see Join) yet, but will start
// all the listeners to allow other nodes to join this memberlist.
// After creating a Memberlist, the configuration given should not be
// modified by the user anymore.
func Create(conf *Config) (*Memberlist, error) {
	m, err := newMemberlist(conf)
	if err != nil {
		return nil, err
	}
	if err := m.setAlive(); err != nil {
		m.Shutdown()
		return nil, err
	}
	m.schedule()
	return m, nil
}

// Join is used to take an existing Memberlist and attempt to join a cluster
// by contacting all the given hosts and performing a state sync. Initially,
// the Memberlist only contains our own state, so doing this will cause
// remote nodes to become aware of the existence of this node, effectively
// joining the cluster.
//
// This returns the number of hosts successfully contacted and an error if
// none could be reached. If an error is returned, the node did not successfully
// join the cluster.
func (m *Memberlist) Join(existing []string) (int, error) {
	// Attempt to join any of them
	numSuccess := 0
	var retErr error
	for _, exist := range existing {
		addr, err := net.ResolveIPAddr("ip", exist)
		if err != nil {
			retErr = err
			continue
		}

		if err := m.pushPullNode(addr.IP); err != nil {
			retErr = err
			continue
		}

		numSuccess++
	}

	if numSuccess > 0 {
		retErr = nil
	}

	return numSuccess, retErr
}

// setAlive is used to mark this node as being alive. This is the same
// as if we received an alive notification our own network channel for
// ourself.
func (m *Memberlist) setAlive() error {
	// Pick a private IP address
	var ipAddr []byte
	if m.config.BindAddr == "0.0.0.0" {
		// We're not bound to a specific IP, so let's list the interfaces
		// on this machine and use the first private IP we find.
		addresses, err := net.InterfaceAddrs()
		if err != nil {
			return fmt.Errorf("Failed to get interface addresses! Err: %vn", err)
		}

		// Find private IPv4 address
		for _, addr := range addresses {
			ip, ok := addr.(*net.IPNet)
			if !ok {
				continue
			}
			if ip.IP.To4() == nil {
				continue
			}
			if !isPrivateIP(ip.IP.String()) {
				continue
			}
			ipAddr = ip.IP
			break
		}

		// Failed to find private IP, use loopback
		if ipAddr == nil {
			ipAddr = []byte{127, 0, 0, 1}
		}
	} else {
		// Use the IP that we're bound to.
		addr := m.tcpListener.Addr().(*net.TCPAddr)
		ipAddr = addr.IP
	}

	// Get the node meta data
	var meta []byte
	if m.config.Delegate != nil {
		meta = m.config.Delegate.NodeMeta(metaMaxSize)
		if len(meta) > metaMaxSize {
			panic("Node meta data provided is longer than the limit")
		}
	}

	a := alive{
		Incarnation: m.nextIncarnation(),
		Node:        m.config.Name,
		Addr:        ipAddr,
		Meta:        meta,
	}
	m.aliveNode(&a)

	return nil
}

// Members returns a list of all known live nodes. The node structures
// returned must not be modified. If you wish to modify a Node, make a
// copy first.
func (m *Memberlist) Members() []*Node {
	m.nodeLock.RLock()
	defer m.nodeLock.RUnlock()

	nodes := make([]*Node, 0, len(m.nodes))
	for _, n := range m.nodes {
		if n.State != stateDead {
			nodes = append(nodes, &n.Node)
		}
	}

	return nodes
}

// NumMembers returns the number of alive nodes currently known. Between
// the time of calling this and calling Members, the number of alive nodes
// may have changed, so this shouldn't be used to determine how many
// members will be returned by Members.
func (m *Memberlist) NumMembers() (alive int) {
	m.nodeLock.RLock()
	defer m.nodeLock.RUnlock()

	for _, n := range m.nodes {
		if n.State != stateDead {
			alive++
		}
	}

	return
}

// Leave will broadcast a leave message but will not shutdown the background
// listeners, meaning the node will continue participating in gossip and state
// updates.
//
// This will block until the leave message is successfully broadcasted to
// a member of the cluster, if any exist or until a specified timeout
// is reached.
//
// This method is safe to call multiple times, but must not be called
// after the cluster is already shut down.
func (m *Memberlist) Leave(timeout time.Duration) error {
	m.startStopLock.Lock()
	defer m.startStopLock.Unlock()

	if m.shutdown {
		panic("leave after shutdown")
	}

	if !m.leave {
		m.leave = true

		state, ok := m.nodeMap[m.config.Name]
		if !ok {
			m.logger.Println("[WARN] Leave but we're not in the node map.")
			return nil
		}

		d := dead{
			Incarnation: state.Incarnation,
			Node:        state.Name,
		}
		m.deadNode(&d)

		// Check for any other alive node
		anyAlive := false
		for _, n := range m.nodes {
			if n.State != stateDead {
				anyAlive = true
				break
			}
		}

		// Block until the broadcast goes out
		if anyAlive {
			var timeoutCh <-chan time.Time
			if timeout > 0 {
				timeoutCh = time.After(timeout)
			}
			select {
			case <-m.leaveBroadcast:
			case <-timeoutCh:
				return fmt.Errorf("timeout waiting for leave broadcast")
			}
		}
	}

	return nil
}

// Shutdown will stop any background maintanence of network activity
// for this memberlist, causing it to appear "dead". A leave message
// will not be broadcasted prior, so the cluster being left will have
// to detect this node's shutdown using probing. If you wish to more
// gracefully exit the cluster, call Leave prior to shutting down.
//
// This method is safe to call multiple times.
func (m *Memberlist) Shutdown() error {
	m.startStopLock.Lock()
	defer m.startStopLock.Unlock()

	if !m.shutdown {
		m.shutdown = true
		m.deschedule()
		m.udpListener.Close()
		m.tcpListener.Close()
	}

	return nil
}
<commit_msg>reduce the tcp timeout<commit_after>/*
memberlist is a library that manages cluster
membership and member failure detection using a gossip based protocol.

The use cases for such a library are far-reaching: all distributed systems
require membership, and memberlist is a re-usable solution to managing
cluster membership and node failure detection.

memberlist is eventually consistent but converges quickly on average.
The speed at which it converges can be heavily tuned via various knobs
on the protocol. Node failures are detected and network partitions are partially
tolerated by attempting to communicate to potentially dead nodes through
multiple routes.
*/
package memberlist

import (
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"sync"
	"time"
)

type Config struct {
	// The name of this node. This must be unique in the cluster.
	Name string

	// Configuration related to what address to bind to and ports to
	// listen on. The ports used must match every node in the cluster,
	// since they'll be used for connecting as well as listening.
	BindAddr string
	UDPPort  int
	TCPPort  int

	// TCPTimeout is the timeout for establishing a TCP connection with
	// a remote node for a full state sync.
	TCPTimeout time.Duration

	// IndirectChecks is the number of nodes that will be asked to perform
	// an indirect probe of a node in the case a direct probe fails. Memberlist
	// waits for an ack from any single indirect node, so increasing this
	// number will increase the likelihood that an indirect probe will succeed
	// at the expense of bandwidth.
	IndirectChecks int

	// RetransmitMult is the multiplier for the number of retransmissions
	// that are attempted for messages broadcasted over gossip. The actual
	// count of retransmissions is calculated using the formula:
	//
	//   Retransmits = RetransmitMult * log(N+1)
	//
	// This allows the retransmits to scale properly with cluster size. The
	// higher the multiplier, the more likely a failed broadcast is to converge
	// at the expense of increased bandwidth.
	RetransmitMult int

	// SuspicionMult is the multiplier for determining the time an
	// inaccessible node is considered suspect before declaring it dead.
	// The actual timeout is calculated using the formula:
	//
	//   SuspicionTimeout = SuspicionMult * log(N+1) * ProbeInterval
	//
	// This allows the timeout to scale properly with expected propagation
	// delay with a larger cluster size. The higher the multiplier, the longer
	// an inaccessible node is considered part of the cluster before declaring
	// it dead, giving that suspect node more time to refute if it is indeed
	// still alive.
	SuspicionMult int

	// PushPullInterval is the interval between complete state syncs.
	// Complete state syncs are done with a single node over TCP and are
	// quite expensive relative to standard gossiped messages. Setting this
	// to zero will disable state push/pull syncs completely.
	//
	// Setting this interval lower (more frequent) will increase convergence
	// speeds across larger clusters at the expense of increased bandwidth
	// usage.
	PushPullInterval time.Duration

	// ProbeInterval and ProbeTimeout are used to configure probing
	// behavior for memberlist.
	//
	// ProbeInterval is the interval between random node probes. Setting
	// this lower (more frequent) will cause the memberlist cluster to detect
	// failed nodes more quickly at the expense of increased bandwidth usage.
	//
	// ProbeTimeout is the timeout to wait for an ack from a probed node
	// before assuming it is unhealthy. This should be set to 99-percentile
	// of RTT (round-trip time) on your network.
	ProbeInterval time.Duration
	ProbeTimeout  time.Duration

	// GossipInterval and GossipNodes are used to configure the gossip
	// behavior of memberlist.
	//
	// GossipInterval is the interval between sending messages that need
	// to be gossiped that haven't been able to piggyback on probing messages.
	// If this is set to zero, non-piggyback gossip is disabled. By lowering
	// this value (more frequent) gossip messages are propagated across
	// the cluster more quickly at the expense of increased bandwidth.
	//
	// GossipNodes is the number of random nodes to send gossip messages to
	// per GossipInterval. Increasing this number causes the gossip messages
	// to propagate across the cluster more quickly at the expense of
	// increased bandwidth.
	GossipInterval time.Duration
	GossipNodes    int

	// Delegate and Events are delegates for receiving and providing
	// data to memberlist via callback mechanisms. For Delegate, see
	// the Delegate interface. For Events, see the EventDelegate interface.
	Delegate Delegate
	Events   EventDelegate

	// LogOutput is the writer where logs should be sent. If this is not
	// set, logging will go to stderr by default.
	LogOutput io.Writer
}

type Memberlist struct {
	config         *Config
	shutdown       bool
	leave          bool
	leaveBroadcast chan struct{}

	udpListener *net.UDPConn
	tcpListener *net.TCPListener

	sequenceNum uint32 // Local sequence number
	incarnation uint32 // Local incarnation number

	nodeLock sync.RWMutex
	nodes    []*nodeState          // Known nodes
	nodeMap  map[string]*nodeState // Maps Addr.String() -> NodeState

	tickerLock sync.Mutex
	tickers    []*time.Ticker
	stopTick   chan struct{}
	probeIndex int

	ackLock     sync.Mutex
	ackHandlers map[uint32]*ackHandler

	broadcasts *TransmitLimitedQueue

	startStopLock sync.Mutex

	logger *log.Logger
}

// DefaultConfig returns a sane set of configurations for Memberlist.
// It uses the hostname as the node name, and otherwise sets very conservative
// values that are sane for most LAN environments. The default configuration
// errs on the side on the side of caution, choosing values that are optimized
// for higher convergence at the cost of higher bandwidth usage. Regardless,
// these values are a good starting point when getting started with memberlist.
func DefaultConfig() *Config {
	hostname, _ := os.Hostname()
	return &Config{
		Name:             hostname,
		BindAddr:         "0.0.0.0",
		UDPPort:          7946,
		TCPPort:          7946,
		TCPTimeout:       10 * time.Second,       // Timeout after 10 seconds
		IndirectChecks:   3,                      // Use 3 nodes for the indirect ping
		RetransmitMult:   4,                      // Retransmit a message 4 * log(N+1) nodes
		SuspicionMult:    5,                      // Suspect a node for 5 * log(N+1) * Interval
		PushPullInterval: 30 * time.Second,       // Low frequency
		ProbeTimeout:     500 * time.Millisecond, // Reasonable RTT time for LAN
		ProbeInterval:    1 * time.Second,        // Failure check every second

		GossipNodes:    3,                      // Gossip to 3 nodes
		GossipInterval: 200 * time.Millisecond, // Gossip more rapidly
	}
}

// newMemberlist creates the network listeners.
// Does not schedule execution of background maintenence.
func newMemberlist(conf *Config) (*Memberlist, error) {
	tcpAddr := fmt.Sprintf("%s:%d", conf.BindAddr, conf.TCPPort)
	tcpLn, err := net.Listen("tcp", tcpAddr)
	if err != nil {
		return nil, fmt.Errorf("Failed to start TCP listener. Err: %s", err)
	}

	udpAddr := fmt.Sprintf("%s:%d", conf.BindAddr, conf.UDPPort)
	udpLn, err := net.ListenPacket("udp", udpAddr)
	if err != nil {
		tcpLn.Close()
		return nil, fmt.Errorf("Failed to start UDP listener. Err: %s", err)
	}

	// Set the UDP receive window size
	setUDPRecvBuf(udpLn.(*net.UDPConn))

	if conf.LogOutput == nil {
		conf.LogOutput = os.Stderr
	}

	m := &Memberlist{
		config:         conf,
		leaveBroadcast: make(chan struct{}, 1),
		udpListener:    udpLn.(*net.UDPConn),
		tcpListener:    tcpLn.(*net.TCPListener),
		nodeMap:        make(map[string]*nodeState),
		ackHandlers:    make(map[uint32]*ackHandler),
		broadcasts:     &TransmitLimitedQueue{RetransmitMult: conf.RetransmitMult},
		logger:         log.New(conf.LogOutput, "", log.LstdFlags),
	}
	m.broadcasts.NumNodes = func() int { return len(m.nodes) }
	go m.tcpListen()
	go m.udpListen()
	return m, nil
}

// Create will create a new Memberlist using the given configuration.
// This will not connect to any other node (see Join) yet, but will start
// all the listeners to allow other nodes to join this memberlist.
// After creating a Memberlist, the configuration given should not be
// modified by the user anymore.
func Create(conf *Config) (*Memberlist, error) {
	m, err := newMemberlist(conf)
	if err != nil {
		return nil, err
	}
	if err := m.setAlive(); err != nil {
		m.Shutdown()
		return nil, err
	}
	m.schedule()
	return m, nil
}

// Join is used to take an existing Memberlist and attempt to join a cluster
// by contacting all the given hosts and performing a state sync. Initially,
// the Memberlist only contains our own state, so doing this will cause
// remote nodes to become aware of the existence of this node, effectively
// joining the cluster.
//
// This returns the number of hosts successfully contacted and an error if
// none could be reached. If an error is returned, the node did not successfully
// join the cluster.
func (m *Memberlist) Join(existing []string) (int, error) {
	// Attempt to join any of them
	numSuccess := 0
	var retErr error
	for _, exist := range existing {
		addr, err := net.ResolveIPAddr("ip", exist)
		if err != nil {
			retErr = err
			continue
		}

		if err := m.pushPullNode(addr.IP); err != nil {
			retErr = err
			continue
		}

		numSuccess++
	}

	if numSuccess > 0 {
		retErr = nil
	}

	return numSuccess, retErr
}

// setAlive is used to mark this node as being alive. This is the same
// as if we received an alive notification our own network channel for
// ourself.
func (m *Memberlist) setAlive() error {
	// Pick a private IP address
	var ipAddr []byte
	if m.config.BindAddr == "0.0.0.0" {
		// We're not bound to a specific IP, so let's list the interfaces
		// on this machine and use the first private IP we find.
		addresses, err := net.InterfaceAddrs()
		if err != nil {
			return fmt.Errorf("Failed to get interface addresses! Err: %vn", err)
		}

		// Find private IPv4 address
		for _, addr := range addresses {
			ip, ok := addr.(*net.IPNet)
			if !ok {
				continue
			}
			if ip.IP.To4() == nil {
				continue
			}
			if !isPrivateIP(ip.IP.String()) {
				continue
			}
			ipAddr = ip.IP
			break
		}

		// Failed to find private IP, use loopback
		if ipAddr == nil {
			ipAddr = []byte{127, 0, 0, 1}
		}
	} else {
		// Use the IP that we're bound to.
		addr := m.tcpListener.Addr().(*net.TCPAddr)
		ipAddr = addr.IP
	}

	// Get the node meta data
	var meta []byte
	if m.config.Delegate != nil {
		meta = m.config.Delegate.NodeMeta(metaMaxSize)
		if len(meta) > metaMaxSize {
			panic("Node meta data provided is longer than the limit")
		}
	}

	a := alive{
		Incarnation: m.nextIncarnation(),
		Node:        m.config.Name,
		Addr:        ipAddr,
		Meta:        meta,
	}
	m.aliveNode(&a)

	return nil
}

// Members returns a list of all known live nodes. The node structures
// returned must not be modified. If you wish to modify a Node, make a
// copy first.
func (m *Memberlist) Members() []*Node {
	m.nodeLock.RLock()
	defer m.nodeLock.RUnlock()

	nodes := make([]*Node, 0, len(m.nodes))
	for _, n := range m.nodes {
		if n.State != stateDead {
			nodes = append(nodes, &n.Node)
		}
	}

	return nodes
}

// NumMembers returns the number of alive nodes currently known. Between
// the time of calling this and calling Members, the number of alive nodes
// may have changed, so this shouldn't be used to determine how many
// members will be returned by Members.
func (m *Memberlist) NumMembers() (alive int) {
	m.nodeLock.RLock()
	defer m.nodeLock.RUnlock()

	for _, n := range m.nodes {
		if n.State != stateDead {
			alive++
		}
	}

	return
}

// Leave will broadcast a leave message but will not shutdown the background
// listeners, meaning the node will continue participating in gossip and state
// updates.
//
// This will block until the leave message is successfully broadcasted to
// a member of the cluster, if any exist or until a specified timeout
// is reached.
//
// This method is safe to call multiple times, but must not be called
// after the cluster is already shut down.
func (m *Memberlist) Leave(timeout time.Duration) error {
	m.startStopLock.Lock()
	defer m.startStopLock.Unlock()

	if m.shutdown {
		panic("leave after shutdown")
	}

	if !m.leave {
		m.leave = true

		state, ok := m.nodeMap[m.config.Name]
		if !ok {
			m.logger.Println("[WARN] Leave but we're not in the node map.")
			return nil
		}

		d := dead{
			Incarnation: state.Incarnation,
			Node:        state.Name,
		}
		m.deadNode(&d)

		// Check for any other alive node
		anyAlive := false
		for _, n := range m.nodes {
			if n.State != stateDead {
				anyAlive = true
				break
			}
		}

		// Block until the broadcast goes out
		if anyAlive {
			var timeoutCh <-chan time.Time
			if timeout > 0 {
				timeoutCh = time.After(timeout)
			}
			select {
			case <-m.leaveBroadcast:
			case <-timeoutCh:
				return fmt.Errorf("timeout waiting for leave broadcast")
			}
		}
	}

	return nil
}

// Shutdown will stop any background maintanence of network activity
// for this memberlist, causing it to appear "dead". A leave message
// will not be broadcasted prior, so the cluster being left will have
// to detect this node's shutdown using probing. If you wish to more
// gracefully exit the cluster, call Leave prior to shutting down.
//
// This method is safe to call multiple times.
func (m *Memberlist) Shutdown() error {
	m.startStopLock.Lock()
	defer m.startStopLock.Unlock()

	if !m.shutdown {
		m.shutdown = true
		m.deschedule()
		m.udpListener.Close()
		m.tcpListener.Close()
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"eaciit/gdrj/model"
	"eaciit/gdrj/modules"
	"os"

	"sync"
	"time"

	"github.com/eaciit/dbox"
	"github.com/eaciit/orm/v1"
	"github.com/eaciit/toolkit"
)

var conn dbox.IConnection
var count int
var wg *sync.WaitGroup
var mtx *sync.Mutex

func setinitialconnection() {
	var err error
	conn, err = modules.GetDboxIConnection("db_godrej")

	if err != nil {
		toolkit.Println("Initial connection found : ", err)
		os.Exit(1)
	}

	err = gdrj.SetDb(conn)
	if err != nil {
		toolkit.Println("Initial connection found : ", err)
		os.Exit(1)
	}
}

type alloc struct {
	Total  float64
	Ratios []*gdrj.SalesRatio
}

var (
	//ledgers = toolkit.M{}
	plmodels = toolkit.M{}
	pcs      = toolkit.M{}
	ccs      = toolkit.M{}
	prods    = toolkit.M{}
	custs    = toolkit.M{}
	ratios   = map[string]*alloc{}
)

func getCursor(obj orm.IModel) dbox.ICursor {
	c, e := gdrj.Find(obj, nil, nil)
	if e != nil {
		return nil
	}
	return c
}

func prepMaster() {
	var e error

	pc := new(gdrj.ProfitCenter)
	cc := new(gdrj.CostCenter)
	prod := new(gdrj.Product)

	toolkit.Println("--> PC")
	cpc := getCursor(pc)
	defer cpc.Close()
	for e = cpc.Fetch(pc, 1, false); e == nil; {
		pcs.Set(pc.ID, pc)
		pc = new(gdrj.ProfitCenter)
		e = cpc.Fetch(pc, 1, false)
	}

	toolkit.Println("--> CC")
	ccc := getCursor(cc)
	defer ccc.Close()
	for e = ccc.Fetch(cc, 1, false); e == nil; {
		ccs.Set(cc.ID, cc)
		cc = new(gdrj.CostCenter)
		e = ccc.Fetch(cc, 1, false)
	}

	toolkit.Println("--> SKU")
	cprod := getCursor(prod)
	defer cprod.Close()
	for e = cprod.Fetch(prod, 1, false); e == nil; {
		prods.Set(prod.ID, prod)
		prod = new(gdrj.Product)
		e = cprod.Fetch(prod, 1, false)
	}

	toolkit.Println("--> Outlet")
	cust := new(gdrj.Customer)
	ccust := getCursor(cust)
	defer ccust.Close()
	for e = ccust.Fetch(cust, 1, false); e == nil; {
		custs.Set(cust.ID, cust)
		cust = new(gdrj.Customer)
		e = ccust.Fetch(cust, 1, false)
	}

	/*
		   sku:=new(gdrj.MappingInventory)
		   cskus:=getCursor(sku)
		   defer cskus.Close()
		   for e=cskus.Fetch(sku,1,false);e==nil;{
		       vdistskus.Set(sku.SKUID_VDIST,sku.ID)
		       sku=new(gdrj.MappingInventory)
		       e=cskus.Fetch(sku,1,false)
		   }

		   sh := new(gdrj.SalesHeader)
		   cshs:=getCursor(sh)
		   defer cshs.Close()
		   for e=cshs.Fetch(sh,1,false);e==nil;{
		       //sh.SalesGrossAmount=0
		       //sh.SalesNetAmount=0
		       //sh.SalesLine=0
		       shs.Set(sh.ID,sh)
		       sh = new(gdrj.SalesHeader)
		       e=cshs.Fetch(sh,1,false)
		   }

		ledger := new(gdrj.LedgerMaster)
		cledger := getCursor(ledger)
		defer cledger.Close()
		for e = cledger.Fetch(ledger, 1, false); e == nil; {
			ledgers.Set(ledger.ID, ledger)
			ledger = new(gdrj.LedgerMaster)
			e = cledger.Fetch(ledger, 1, false)
		}
	*/

	toolkit.Println("--> PL Model")
	plmodel := new(gdrj.PLModel)
	cplmodel := getCursor(plmodel)
	defer cplmodel.Close()
	bmodels := []gdrj.PLModel{}
	cplmodel.Fetch(&bmodels, 0, false)
	for _, plm := range bmodels {
		plmodels.Set(plm.ID, plm)
	}

	toolkit.Println("--> Sales Ratio")
	ratio := new(gdrj.SalesRatio)
	cratios := getCursor(ratio)
	defer cratios.Close()
	for {
		efetch := cratios.Fetch(ratio, 1, false)
		if efetch != nil {
			break
		}
		ratioid := toolkit.Sprintf("%d_%d_%s", ratio.Year, ratio.Month, ratio.SKUID)
		a, exist := ratios[ratioid]
		if !exist {
			a = new(alloc)
		}
		a.Total = ratio.Ratio
		a.Ratios = append(a.Ratios, ratio)
		ratios[ratioid] = a
	}
}

type cogs struct {
	Year, Month                                                  int
	SKUID                                                        string
	COGS, RM, Labour, Energy, OtherCost, FixedCost, Depreciation float64
}

func (cogs *cogs) ID() string {
	return toolkit.Sprintf("%d_%d_%s",
		cogs.Year, cogs.Month,
		cogs.SKUID)
}

//var cogss = map[string]*cogs{}
func main() {
	setinitialconnection()
	defer gdrj.CloseDb()

	toolkit.Println("Reading Master")
	prepMaster()

	toolkit.Printfn("Delete existing")
	conn.NewQuery().From(new(gdrj.PLDataModel).TableName()).Where(dbox.Eq("source", "COGS")).Delete().Exec(nil)

	toolkit.Println("START...")
	crx, err := conn.NewQuery().From("cogs_import").
		Take(1000).
		Cursor(nil)
	if err != nil {
		toolkit.Println("Error Found : ", err.Error())
		os.Exit(1)
	}
	defer crx.Close()

	count := crx.Count()
	jobs := make(chan *cogs, count)
	result := make(chan string, count)

	for wi := 1; wi < 10; wi++ {
		go worker(wi, jobs, result)
	}

	i := 0
	step := count / 100
	limit := step
	t0 := time.Now()
	for {
		//st := new(gdrj.SalesTrx)
		acogs := []toolkit.M{}
		err = crx.Fetch(&acogs, 1000, false)
		if err != nil {
			toolkit.Printfn("Exit loop: %s", err.Error())
			break
		}

		if len(acogs) > 0 {
			for _, mcogs := range acogs {
				i++
				ocogs := new(cogs)
				ocogs.Year = mcogs.GetInt("Year")
				ocogs.Month = mcogs.GetInt("Month")
				ocogs.SKUID = toolkit.ToString(mcogs.GetInt("SAPCode"))
				ocogs.COGS = mcogs.GetFloat64("COGS_Amount")
				ocogs.RM = mcogs.GetFloat64("RM_Amount")
				ocogs.RM = mcogs.GetFloat64("RM_Amount")
				ocogs.Labour = mcogs.GetFloat64("LC_Amount")
				ocogs.Energy = mcogs.GetFloat64("PF_Amount")
				ocogs.Depreciation = mcogs.GetFloat64("Depre_Amount")
				jobs <- ocogs
			}
		}

		if i >= limit {
			toolkit.Printfn("Calc %d of %d (%dpct) in %s", i, count, i*100/count,
				time.Since(t0).String())
			limit += step
		}

		if len(acogs) < 1000 {
			break
		}
	}
	close(jobs)

	toolkit.Println("Saving")
	limit = step
	for ri := 0; ri < count; ri++ {
		<-result
		if ri >= limit {
			toolkit.Printfn("Saving %d of %d (%dpct) in %s", ri, count, ri*100/count,
				time.Since(t0).String())
			limit += step
		}
	}
	toolkit.Printfn("Done %s", time.Since(t0).String())
}

func worker(wi int, jobs <-chan *cogs, r chan<- string) {
	workerConn, _ := modules.GetDboxIConnection("db_godrej")
	defer workerConn.Close()

	for m := range jobs {
		id := m.ID()
		a, exist := ratios[id]
		rs := []*gdrj.SalesRatio{}
		total := float64(0)
		if exist {
			rs = a.Ratios
			total = a.Total
		} else {
			newratio := new(gdrj.SalesRatio)
			newratio.Year = m.Year
			newratio.Month = m.Month
			newratio.OutletID = "ZZZ"
			newratio.SKUID = m.SKUID
			newratio.Ratio = 1
			total = 1
			rs = append(rs, newratio)
		}

		toolkit.Printfn("Allocating %d-%d %s to %d outlets",
			m.Year, m.Month, m.SKUID, len(rs))
		for _, r := range rs {
			m.CreatePLModel(r.OutletID, r.Ratio/total, workerConn)
		}
		if len(rs)>0{
			r <- "OK"
		} else {
			r <- "NOK"
		}
	}
}

func (c *cogs) CreatePLModel(outletid string, ratio float64, conn dbox.IConnection) error {
	ratio = -ratio
	if c.RM != 0 {
		gdrj.GetPLModel("PL9", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.RM, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	if c.Labour != 0 {
		gdrj.GetPLModel("PL14", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.Labour, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	if c.FixedCost != 0 {
		gdrj.GetPLModel("PL19", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.FixedCost, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	if c.Energy != 0 {
		gdrj.GetPLModel("PL74", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.Energy, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	if c.Depreciation != 0 {
		gdrj.GetPLModel("PL21", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.Energy, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	c.OtherCost = c.COGS - c.RM - c.Labour - c.Energy - c.FixedCost - c.Depreciation
	if c.Depreciation != 0 {
		gdrj.GetPLModel("PL20", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.Energy, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	return nil
}
<commit_msg>remove only 1000<commit_after>package main

import (
	"eaciit/gdrj/model"
	"eaciit/gdrj/modules"
	"os"

	"sync"
	"time"

	"github.com/eaciit/dbox"
	"github.com/eaciit/orm/v1"
	"github.com/eaciit/toolkit"
)

var conn dbox.IConnection
var count int
var wg *sync.WaitGroup
var mtx *sync.Mutex

func setinitialconnection() {
	var err error
	conn, err = modules.GetDboxIConnection("db_godrej")

	if err != nil {
		toolkit.Println("Initial connection found : ", err)
		os.Exit(1)
	}

	err = gdrj.SetDb(conn)
	if err != nil {
		toolkit.Println("Initial connection found : ", err)
		os.Exit(1)
	}
}

type alloc struct {
	Total  float64
	Ratios []*gdrj.SalesRatio
}

var (
	//ledgers = toolkit.M{}
	plmodels = toolkit.M{}
	pcs      = toolkit.M{}
	ccs      = toolkit.M{}
	prods    = toolkit.M{}
	custs    = toolkit.M{}
	ratios   = map[string]*alloc{}
)

func getCursor(obj orm.IModel) dbox.ICursor {
	c, e := gdrj.Find(obj, nil, nil)
	if e != nil {
		return nil
	}
	return c
}

func prepMaster() {
	var e error

	pc := new(gdrj.ProfitCenter)
	cc := new(gdrj.CostCenter)
	prod := new(gdrj.Product)

	toolkit.Println("--> PC")
	cpc := getCursor(pc)
	defer cpc.Close()
	for e = cpc.Fetch(pc, 1, false); e == nil; {
		pcs.Set(pc.ID, pc)
		pc = new(gdrj.ProfitCenter)
		e = cpc.Fetch(pc, 1, false)
	}

	toolkit.Println("--> CC")
	ccc := getCursor(cc)
	defer ccc.Close()
	for e = ccc.Fetch(cc, 1, false); e == nil; {
		ccs.Set(cc.ID, cc)
		cc = new(gdrj.CostCenter)
		e = ccc.Fetch(cc, 1, false)
	}

	toolkit.Println("--> SKU")
	cprod := getCursor(prod)
	defer cprod.Close()
	for e = cprod.Fetch(prod, 1, false); e == nil; {
		prods.Set(prod.ID, prod)
		prod = new(gdrj.Product)
		e = cprod.Fetch(prod, 1, false)
	}

	toolkit.Println("--> Outlet")
	cust := new(gdrj.Customer)
	ccust := getCursor(cust)
	defer ccust.Close()
	for e = ccust.Fetch(cust, 1, false); e == nil; {
		custs.Set(cust.ID, cust)
		cust = new(gdrj.Customer)
		e = ccust.Fetch(cust, 1, false)
	}

	/*
		   sku:=new(gdrj.MappingInventory)
		   cskus:=getCursor(sku)
		   defer cskus.Close()
		   for e=cskus.Fetch(sku,1,false);e==nil;{
		       vdistskus.Set(sku.SKUID_VDIST,sku.ID)
		       sku=new(gdrj.MappingInventory)
		       e=cskus.Fetch(sku,1,false)
		   }

		   sh := new(gdrj.SalesHeader)
		   cshs:=getCursor(sh)
		   defer cshs.Close()
		   for e=cshs.Fetch(sh,1,false);e==nil;{
		       //sh.SalesGrossAmount=0
		       //sh.SalesNetAmount=0
		       //sh.SalesLine=0
		       shs.Set(sh.ID,sh)
		       sh = new(gdrj.SalesHeader)
		       e=cshs.Fetch(sh,1,false)
		   }

		ledger := new(gdrj.LedgerMaster)
		cledger := getCursor(ledger)
		defer cledger.Close()
		for e = cledger.Fetch(ledger, 1, false); e == nil; {
			ledgers.Set(ledger.ID, ledger)
			ledger = new(gdrj.LedgerMaster)
			e = cledger.Fetch(ledger, 1, false)
		}
	*/

	toolkit.Println("--> PL Model")
	plmodel := new(gdrj.PLModel)
	cplmodel := getCursor(plmodel)
	defer cplmodel.Close()
	bmodels := []gdrj.PLModel{}
	cplmodel.Fetch(&bmodels, 0, false)
	for _, plm := range bmodels {
		plmodels.Set(plm.ID, plm)
	}

	toolkit.Println("--> Sales Ratio")
	ratio := new(gdrj.SalesRatio)
	cratios := getCursor(ratio)
	defer cratios.Close()
	for {
		efetch := cratios.Fetch(ratio, 1, false)
		if efetch != nil {
			break
		}
		ratioid := toolkit.Sprintf("%d_%d_%s", ratio.Year, ratio.Month, ratio.SKUID)
		a, exist := ratios[ratioid]
		if !exist {
			a = new(alloc)
		}
		a.Total = ratio.Ratio
		a.Ratios = append(a.Ratios, ratio)
		ratios[ratioid] = a
	}
}

type cogs struct {
	Year, Month                                                  int
	SKUID                                                        string
	COGS, RM, Labour, Energy, OtherCost, FixedCost, Depreciation float64
}

func (cogs *cogs) ID() string {
	return toolkit.Sprintf("%d_%d_%s",
		cogs.Year, cogs.Month,
		cogs.SKUID)
}

//var cogss = map[string]*cogs{}
func main() {
	setinitialconnection()
	defer gdrj.CloseDb()

	toolkit.Println("Reading Master")
	prepMaster()

	toolkit.Printfn("Delete existing")
	conn.NewQuery().From(new(gdrj.PLDataModel).TableName()).Where(dbox.Eq("source", "COGS")).Delete().Exec(nil)

	toolkit.Println("START...")
	crx, err := conn.NewQuery().From("cogs_import").
		//Take(1000).
		Cursor(nil)
	if err != nil {
		toolkit.Println("Error Found : ", err.Error())
		os.Exit(1)
	}
	defer crx.Close()

	count := crx.Count()
	jobs := make(chan *cogs, count)
	result := make(chan string, count)

	for wi := 1; wi < 10; wi++ {
		go worker(wi, jobs, result)
	}

	i := 0
	step := count / 100
	limit := step
	t0 := time.Now()
	for {
		//st := new(gdrj.SalesTrx)
		acogs := []toolkit.M{}
		err = crx.Fetch(&acogs, 1000, false)
		if err != nil {
			toolkit.Printfn("Exit loop: %s", err.Error())
			break
		}

		if len(acogs) > 0 {
			for _, mcogs := range acogs {
				i++
				ocogs := new(cogs)
				ocogs.Year = mcogs.GetInt("Year")
				ocogs.Month = mcogs.GetInt("Month")
				ocogs.SKUID = toolkit.ToString(mcogs.GetInt("SAPCode"))
				ocogs.COGS = mcogs.GetFloat64("COGS_Amount")
				ocogs.RM = mcogs.GetFloat64("RM_Amount")
				ocogs.RM = mcogs.GetFloat64("RM_Amount")
				ocogs.Labour = mcogs.GetFloat64("LC_Amount")
				ocogs.Energy = mcogs.GetFloat64("PF_Amount")
				ocogs.Depreciation = mcogs.GetFloat64("Depre_Amount")
				jobs <- ocogs
			}
		}

		if i >= limit {
			toolkit.Printfn("Calc %d of %d (%dpct) in %s", i, count, i*100/count,
				time.Since(t0).String())
			limit += step
		}

		if len(acogs) < 1000 {
			break
		}
	}
	close(jobs)

	toolkit.Println("Saving")
	limit = step
	for ri := 0; ri < count; ri++ {
		<-result
		if ri >= limit {
			toolkit.Printfn("Saving %d of %d (%dpct) in %s", ri, count, ri*100/count,
				time.Since(t0).String())
			limit += step
		}
	}
	toolkit.Printfn("Done %s", time.Since(t0).String())
}

func worker(wi int, jobs <-chan *cogs, r chan<- string) {
	workerConn, _ := modules.GetDboxIConnection("db_godrej")
	defer workerConn.Close()

	for m := range jobs {
		id := m.ID()
		a, exist := ratios[id]
		rs := []*gdrj.SalesRatio{}
		total := float64(0)
		if exist {
			rs = a.Ratios
			total = a.Total
		} else {
			newratio := new(gdrj.SalesRatio)
			newratio.Year = m.Year
			newratio.Month = m.Month
			newratio.OutletID = "ZZZ"
			newratio.SKUID = m.SKUID
			newratio.Ratio = 1
			total = 1
			rs = append(rs, newratio)
		}

		toolkit.Printfn("Allocating %d-%d %s to %d outlets",
			m.Year, m.Month, m.SKUID, len(rs))
		for _, r := range rs {
			m.CreatePLModel(r.OutletID, r.Ratio/total, workerConn)
		}
		if len(rs)>0{
			r <- "OK"
		} else {
			r <- "NOK"
		}
	}
}

func (c *cogs) CreatePLModel(outletid string, ratio float64, conn dbox.IConnection) error {
	ratio = -ratio
	if c.RM != 0 {
		gdrj.GetPLModel("PL9", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.RM, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	if c.Labour != 0 {
		gdrj.GetPLModel("PL14", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.Labour, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	if c.FixedCost != 0 {
		gdrj.GetPLModel("PL19", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.FixedCost, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	if c.Energy != 0 {
		gdrj.GetPLModel("PL74", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.Energy, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	if c.Depreciation != 0 {
		gdrj.GetPLModel("PL21", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.Energy, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	c.OtherCost = c.COGS - c.RM - c.Labour - c.Energy - c.FixedCost - c.Depreciation
	if c.Depreciation != 0 {
		gdrj.GetPLModel("PL20", "ID11",
			c.Year, c.Month, outletid, c.SKUID, "", "",
			ratio*c.Energy, 0, 0, "COGS", conn,
			custs, prods, pcs, ccs, plmodels,
			false, true)
	}

	return nil
}
<|endoftext|>


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

package main

import (
	"fmt"
	"github.com/mclarkson/obdi/external/jinzhu/gorm"
	_ "github.com/mclarkson/obdi/external/mattn/go-sqlite3"
	"log"
	"time"
)

// Maps to the users table
type User struct {
	Id         int64
	Login      string `sql:"not null"`
	Forename   string
	Surname    string
	Passhash   string
	Enabled    bool
	CreatedAt  time.Time
	UpdatedAt  time.Time
	DeletedAt  time.Time
	Email      string
	MultiLogin bool

	//Session     Session
	//SessionId   sql.NullInt64
	//Uemap     []Uemap
}

// Maps to the sessions table
type Session struct {
	Id        int64
	Guid      string
	UserId    int64 `sql:"not null"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

// Application log
type Activity struct {
	Id         int64
	Session_id int64
	Message    string
	CreatedAt  time.Time
	UpdatedAt  time.Time
	DeletedAt  time.Time
}

// Data Centre environment, e.g. Peer1 - Dev, or Newman St. - Prod
type Env struct {
	Id       int64
	DispName string // Display name
	SysName  string // System name (Salt environment name)
	/*Dc          Dc*/ // only for creating Env and substruct
	DcId               int64
	//WorkerIp    string      // Hostname or IP address of worker
	//WorkerPort  string      // Port the worker listens on
	WorkerUrl string // Worker URL Prefix
	WorkerKey string // Key (password) for worker
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

type Dc struct {
	Id        int64
	DispName  string // Display name
	SysName   string // System name (Salt name)
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

type DcCap struct {
	Id   int64
	Code string
	Desc string
}

type DcCapMap struct {
	Id      int64
	DcId    int64
	DcCapId int64
}

type EnvCap struct {
	Id              int64
	Code            string
	Desc            string
	IsWorkerDef     bool // Can it have a worker definition
	IsJsonObjectDef bool // Can it have a json object definition
}

type Worker struct {
	Id        int64
	EnvId     int64
	EnvCapId  int64
	WorkerUrl string // Worker URL Prefix
	WorkerKey string // Key (password) for worker
}

type JsonObject struct {
	Id       int64
	EnvId    int64
	EnvCapId int64
	Json     string
}

type EnvCapMap struct {
	Id       int64
	EnvId    int64
	EnvCapId int64
}

// Permissions
type Perm struct {
	Id        int64
	UserId    int64
	EnvId     int64
	Writeable bool
	Enabled   bool
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

type Script struct {
	Id        int64
	Name      string
	Desc      string
	Source    []byte
	Type      string
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

type Job struct {
	Id            int64
	ScriptId      int64
	Args          string // E.g. `-a -f "bob 1" name`
	EnvVars       string // E.g. `A:1 B:"Hi there" C:3`
	Status        int64
	StatusReason  string
	StatusPercent int64
	CreatedAt     time.Time
	UpdatedAt     time.Time
	DeletedAt     time.Time
	UserLogin     string
	Errors        int64
	EnvId         int64  // For WorkerUrl and WorkerKey, overridden by:
	EnvCapDesc    string // E.g. "EZBACKUP_WORKER"
	Type          int64  // 1 - user job, 2 - system job
	WorkerUrl     string // Worker URL Prefix
	WorkerKey     string // Key (password) for worker
}

type OutputLine struct {
	Id     int64
	Serial int64
	JobId  int64
	Text   string
	//Type            int64       // 0 - output, 1 - error output
}

type Plugin struct {
	Id        int64
	Name      string
	Desc      string
	Parent    string // Parents Name
	HasView   int64  // 1 - has a view, 2 - has not
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

type File struct {
	Id       int64
	Name     string
	Desc     string
	Url      string
	Type     int64 // 1 - JS, 2 - HTML, 3 - GO, 4 - CSS
	PluginId int64
}

type Repo struct {
	Id        int64
	Url       string
	CreatedAt time.Time
}

type Database struct {
	dB gorm.DB
}

func (db *Database) InitDB() {
	var err error
	dbname := config.DBPath()

	db.dB, err = gorm.Open("sqlite3", dbname)
	if err != nil {
		logit("Could not open sqlite3 database, '" + dbname + "'")
		log.Fatal(fmt.Sprintf(
			"Got error when connect database, the error is '%v'", err))
	}

	if err := db.dB.AutoMigrate(User{}).Error; err != nil {
		txt := "AutoMigrate Users table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Session{}).Error; err != nil {
		txt := "AutoMigrate Sessions table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Activity{}).Error; err != nil {
		txt := "AutoMigrate Activity table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Env{}).Error; err != nil {
		txt := "AutoMigrate Env table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Dc{}).Error; err != nil {
		txt := "AutoMigrate Dc table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Perm{}).Error; err != nil {
		txt := "AutoMigrate Dc table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(DcCap{}).Error; err != nil {
		txt := "AutoMigrate DcCap table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(DcCapMap{}).Error; err != nil {
		txt := "AutoMigrate DcCapMap table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(EnvCap{}).Error; err != nil {
		txt := "AutoMigrate EnvCap table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(EnvCapMap{}).Error; err != nil {
		txt := "AutoMigrate EnvCapMap table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Perm{}).Error; err != nil {
		txt := "AutoMigrate Dc table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Job{}).Error; err != nil {
		txt := "AutoMigrate Job table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(OutputLine{}).Error; err != nil {
		txt := "AutoMigrate OutputLine table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Script{}).Error; err != nil {
		txt := "AutoMigrate Script table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Plugin{}).Error; err != nil {
		txt := "AutoMigrate Plugin table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(File{}).Error; err != nil {
		txt := "AutoMigrate File table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Repo{}).Error; err != nil {
		txt := "AutoMigrate Repos table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Worker{}).Error; err != nil {
		txt := "AutoMigrate Workers table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(JsonObject{}).Error; err != nil {
		txt := "AutoMigrate JsonObjects table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}

	// Unique index is also a constraint. So these are forced to be unique
	db.dB.Model(User{}).AddUniqueIndex("idx_login", "login")
	db.dB.Model(Plugin{}).AddIndex("idx_name", "name")
	db.dB.Model(Session{}).AddIndex("idx_user_id", "user_id")
	db.dB.Model(Activity{}).AddIndex("idx_session_id", "session_id")
	db.dB.Model(Script{}).AddIndex("idx_script_name", "name")
	db.dB.Model(Worker{}).AddIndex("idx_worker_env_id", "env_id")
	db.dB.Model(Worker{}).AddIndex("idx_worker_env_cap_id", "env_cap_id")
	db.dB.Model(JsonObject{}).AddIndex("idx_jsonobject_env_id", "env_id")
	db.dB.Model(JsonObject{}).AddIndex("idx_jsonobject_env_cap_id", "env_cap_id")
	// TODO: OutputLines table should be in a separate DB file if
	// TODO: performance drops.
	db.dB.Model(OutputLine{}).AddIndex("idx_id_serial", "job_id", "serial")

	logit("Sqlite3 database " + dbname + " opened")
}

func (db *Database) CreateAdminAccount() {

	user := User{}
	user.Login = "admin"
	user.Enabled = true

	if db.dB.Where("login = ?", "admin").
		First(&user).RecordNotFound() {

		c := &Crypt{}
		c.Pass = []byte("admin")
		c.Crypt()

		db.dB.Where(User{Login: "admin"}).
			Attrs(User{
				Passhash: string(c.Hash),
				Forename: "Admin",
				Surname:  "User",
				Email:    "admin@invalid",
			}).FirstOrCreate(&user)

		logit("Admin user created")
	}
}

func (db *Database) DB() *gorm.DB {
	return &db.dB
}

func NewDB() *Database {
	db := &Database{}
	db.InitDB()
	return db
}
<commit_msg>Remove Unique constraint<commit_after>// Copyright 2016 Mark Clarkson
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"github.com/mclarkson/obdi/external/jinzhu/gorm"
	_ "github.com/mclarkson/obdi/external/mattn/go-sqlite3"
	"log"
	"time"
)

// Maps to the users table
type User struct {
	Id         int64
	Login      string `sql:"not null"`
	Forename   string
	Surname    string
	Passhash   string
	Enabled    bool
	CreatedAt  time.Time
	UpdatedAt  time.Time
	DeletedAt  time.Time
	Email      string
	MultiLogin bool

	//Session     Session
	//SessionId   sql.NullInt64
	//Uemap     []Uemap
}

// Maps to the sessions table
type Session struct {
	Id        int64
	Guid      string
	UserId    int64 `sql:"not null"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

// Application log
type Activity struct {
	Id         int64
	Session_id int64
	Message    string
	CreatedAt  time.Time
	UpdatedAt  time.Time
	DeletedAt  time.Time
}

// Data Centre environment, e.g. Peer1 - Dev, or Newman St. - Prod
type Env struct {
	Id       int64
	DispName string // Display name
	SysName  string // System name (Salt environment name)
	/*Dc          Dc*/ // only for creating Env and substruct
	DcId               int64
	//WorkerIp    string      // Hostname or IP address of worker
	//WorkerPort  string      // Port the worker listens on
	WorkerUrl string // Worker URL Prefix
	WorkerKey string // Key (password) for worker
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

type Dc struct {
	Id        int64
	DispName  string // Display name
	SysName   string // System name (Salt name)
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

type DcCap struct {
	Id   int64
	Code string
	Desc string
}

type DcCapMap struct {
	Id      int64
	DcId    int64
	DcCapId int64
}

type EnvCap struct {
	Id              int64
	Code            string
	Desc            string
	IsWorkerDef     bool // Can it have a worker definition
	IsJsonObjectDef bool // Can it have a json object definition
}

type Worker struct {
	Id        int64
	EnvId     int64
	EnvCapId  int64
	WorkerUrl string // Worker URL Prefix
	WorkerKey string // Key (password) for worker
}

type JsonObject struct {
	Id       int64
	EnvId    int64
	EnvCapId int64
	Json     string
}

type EnvCapMap struct {
	Id       int64
	EnvId    int64
	EnvCapId int64
}

// Permissions
type Perm struct {
	Id        int64
	UserId    int64
	EnvId     int64
	Writeable bool
	Enabled   bool
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

type Script struct {
	Id        int64
	Name      string
	Desc      string
	Source    []byte
	Type      string
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

type Job struct {
	Id            int64
	ScriptId      int64
	Args          string // E.g. `-a -f "bob 1" name`
	EnvVars       string // E.g. `A:1 B:"Hi there" C:3`
	Status        int64
	StatusReason  string
	StatusPercent int64
	CreatedAt     time.Time
	UpdatedAt     time.Time
	DeletedAt     time.Time
	UserLogin     string
	Errors        int64
	EnvId         int64  // For WorkerUrl and WorkerKey, overridden by:
	EnvCapDesc    string // E.g. "EZBACKUP_WORKER"
	Type          int64  // 1 - user job, 2 - system job
	WorkerUrl     string // Worker URL Prefix
	WorkerKey     string // Key (password) for worker
}

type OutputLine struct {
	Id     int64
	Serial int64
	JobId  int64
	Text   string
	//Type            int64       // 0 - output, 1 - error output
}

type Plugin struct {
	Id        int64
	Name      string
	Desc      string
	Parent    string // Parents Name
	HasView   int64  // 1 - has a view, 2 - has not
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

type File struct {
	Id       int64
	Name     string
	Desc     string
	Url      string
	Type     int64 // 1 - JS, 2 - HTML, 3 - GO, 4 - CSS
	PluginId int64
}

type Repo struct {
	Id        int64
	Url       string
	CreatedAt time.Time
}

type Database struct {
	dB gorm.DB
}

func (db *Database) InitDB() {
	var err error
	dbname := config.DBPath()

	db.dB, err = gorm.Open("sqlite3", dbname)
	if err != nil {
		logit("Could not open sqlite3 database, '" + dbname + "'")
		log.Fatal(fmt.Sprintf(
			"Got error when connect database, the error is '%v'", err))
	}

	if err := db.dB.AutoMigrate(User{}).Error; err != nil {
		txt := "AutoMigrate Users table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Session{}).Error; err != nil {
		txt := "AutoMigrate Sessions table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Activity{}).Error; err != nil {
		txt := "AutoMigrate Activity table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Env{}).Error; err != nil {
		txt := "AutoMigrate Env table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Dc{}).Error; err != nil {
		txt := "AutoMigrate Dc table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Perm{}).Error; err != nil {
		txt := "AutoMigrate Dc table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(DcCap{}).Error; err != nil {
		txt := "AutoMigrate DcCap table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(DcCapMap{}).Error; err != nil {
		txt := "AutoMigrate DcCapMap table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(EnvCap{}).Error; err != nil {
		txt := "AutoMigrate EnvCap table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(EnvCapMap{}).Error; err != nil {
		txt := "AutoMigrate EnvCapMap table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Perm{}).Error; err != nil {
		txt := "AutoMigrate Dc table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Job{}).Error; err != nil {
		txt := "AutoMigrate Job table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(OutputLine{}).Error; err != nil {
		txt := "AutoMigrate OutputLine table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Script{}).Error; err != nil {
		txt := "AutoMigrate Script table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Plugin{}).Error; err != nil {
		txt := "AutoMigrate Plugin table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(File{}).Error; err != nil {
		txt := "AutoMigrate File table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Repo{}).Error; err != nil {
		txt := "AutoMigrate Repos table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(Worker{}).Error; err != nil {
		txt := "AutoMigrate Workers table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}
	if err := db.dB.AutoMigrate(JsonObject{}).Error; err != nil {
		txt := "AutoMigrate JsonObjects table failed"
		log.Fatal(fmt.Sprintf("%s: %s", txt, err))
	}

	db.dB.Model(User{}).AddIndex("idx_login", "login")
	db.dB.Model(Plugin{}).AddIndex("idx_name", "name")
	db.dB.Model(Session{}).AddIndex("idx_user_id", "user_id")
	db.dB.Model(Activity{}).AddIndex("idx_session_id", "session_id")
	db.dB.Model(Script{}).AddIndex("idx_script_name", "name")
	db.dB.Model(Worker{}).AddIndex("idx_worker_env_id", "env_id")
	db.dB.Model(Worker{}).AddIndex("idx_worker_env_cap_id", "env_cap_id")
	db.dB.Model(JsonObject{}).AddIndex("idx_jsonobject_env_id", "env_id")
	db.dB.Model(JsonObject{}).AddIndex("idx_jsonobject_env_cap_id", "env_cap_id")
	// TODO: OutputLines table should be in a separate DB file if
	// TODO: performance drops.
	db.dB.Model(OutputLine{}).AddIndex("idx_id_serial", "job_id", "serial")

	logit("Sqlite3 database " + dbname + " opened")
}

func (db *Database) CreateAdminAccount() {

	user := User{}
	user.Login = "admin"
	user.Enabled = true

	if db.dB.Where("login = ?", "admin").
		First(&user).RecordNotFound() {

		c := &Crypt{}
		c.Pass = []byte("admin")
		c.Crypt()

		db.dB.Where(User{Login: "admin"}).
			Attrs(User{
				Passhash: string(c.Hash),
				Forename: "Admin",
				Surname:  "User",
				Email:    "admin@invalid",
			}).FirstOrCreate(&user)

		logit("Admin user created")
	}
}

func (db *Database) DB() *gorm.DB {
	return &db.dB
}

func NewDB() *Database {
	db := &Database{}
	db.InitDB()
	return db
}
<|endoftext|>


<commit_before>package gitgo

import (
	"bufio"
	"bytes"
	"encoding/hex"
	"fmt"
	"io"
	"strings"
)

type GitObject interface {
	Type() string
}

type gitObject struct {
	Type string

	// Commit fields
	Tree      string
	Parents   []string
	Author    string
	Committer string
	Message   string
	Size      string

	// Tree
	Blobs []objectMeta
	Trees []objectMeta

	// Blob
	Contents string
}

type Blob struct {
	_type    string
	size     string
	Contents string
}

func (b Blob) Type() string {
	return b._type

}

type Commit struct {
	_type     string
	Tree      string
	Parents   []string
	Author    string
	Committer string
	Message   string
	size      string
}

func (c Commit) Type() string {
	return c._type
}

type Tree struct {
	_type string
	Blobs []objectMeta
	Trees []objectMeta
	size  string
}

func (t Tree) Type() string {
	return t._type
}

// objectMeta contains the metadata
// (hash, permissions, and filename)
// corresponding either to a blob (leaf) or another tree
type objectMeta struct {
	Hash     SHA
	Perms    string
	filename string
}

func NewObject(input SHA) (obj GitObject, err error) {
	str, err := CatFile(input)
	if err != nil {
		return
	}
	return parseObj(str)
}

func normalizePerms(perms string) string {
	// TODO don't store permissions as a string
	for len(perms) < 6 {
		perms = "0" + perms
	}
	return perms
}

func parseObj(obj string) (result GitObject, err error) {

	parts := strings.Split(obj, "\x00")
	parts = strings.Fields(parts[0])
	resultType := parts[0]
	resultSize := parts[1]
	nullIndex := strings.Index(obj, "\x00")

	lines := strings.Split(obj[nullIndex+1:], "\n")

	switch resultType {
	case "commit":
		var commit = Commit{_type: resultType, size: resultSize}
		for i, line := range lines {
			// The next line is the commit message
			if len(strings.Fields(line)) == 0 {
				commit.Message = strings.Join(lines[i+1:], "\n")
				break
			}
			parts := strings.Fields(line)
			key := parts[0]
			switch KeyType(key) {
			case TreeKey:
				commit.Tree = parts[1]
			case ParentKey:
				commit.Parents = append(commit.Parents, parts[1])
			case AuthorKey:
				commit.Author = strings.Join(parts[1:], " ")
			case CommitterKey:
				commit.Committer = strings.Join(parts[1:], " ")
			default:
				err = fmt.Errorf("Encounterd unknown field in commit: %s", key)
				return
			}
		}
		result = commit
	case "tree":
		var tree = Tree{_type: resultType, size: resultSize}

		scanner := bufio.NewScanner(bytes.NewBuffer([]byte(obj)))
		scanner.Split(ScanNullLines)

		var tmp objectMeta

		var resultObjs []objectMeta

		for count := 0; ; count++ {
			done := !scanner.Scan()
			if done {
				break
			}

			txt := scanner.Text()

			if count == 0 {
				// the first time through, scanner.Text() will be
				// "tree <size>"
				continue
			}
			if count == 1 {
				// the second time through, scanner.Text() will be
				// <perms> <filename>
				// separated by a space
				fields := strings.Fields(txt)
				tmp.Perms = normalizePerms(fields[0])
				tmp.filename = fields[1]
				continue
			}

			// after the second time through, scanner.Text() will be
			// <sha><perms2> <file2>
			// where perms2 and file2 refer to the permissions and filename (respectively)
			// of the NEXT object, and <sha> is the first 20 bytes exactly.
			// If there is no next object (this is the last object)
			// then scanner.Text() will yield exactly 20 bytes.

			// decode the next 20 bytes to get the SHA
			tmp.Hash = SHA(hex.EncodeToString([]byte(txt[:20])))
			resultObjs = append(resultObjs, tmp)
			if len(txt) <= 20 {
				// We've read the last line
				break
			}

			// Now, tmp points to the next object in the tree listing
			tmp = objectMeta{}
			remainder := txt[20:]
			fields := strings.Fields(remainder)
			tmp.Perms = normalizePerms(fields[0])
			tmp.filename = fields[1]
		}

		if err := scanner.Err(); err != nil && err != io.EOF {
			return tree, err
		}

		for _, part := range resultObjs {
			obj, err := NewObject(part.Hash)
			if err != nil {
				return tree, err
			}
			switch obj.Type() {
			case "tree":
				tree.Trees = append(tree.Trees, part)
			case "blob":
				tree.Blobs = append(tree.Blobs, part)
			default:
				return tree, fmt.Errorf("Unknown type found: %s", obj.Type)
			}
		}
		result = tree
	case "blob":
		var blob = Blob{_type: resultType, size: resultSize}
		blob.Contents = obj[nullIndex+1:]
		result = blob
	default:
		err = fmt.Errorf("Received unknown object type %s", resultType)
	}

	return
}

// ScanNullLines is like bufio.ScanLines, except it uses the null character as the delimiter
// instead of a newline
func ScanNullLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.IndexByte(data, '\x00'); i >= 0 {
		// We have a full null-terminated line.
		return i + 1, data[0:i], nil
	}
	// If we're at EOF, we have a final, non-terminated "line". Return it.
	if atEOF {
		return len(data), data, nil
	}
	// Request more data.
	return 0, nil, nil
}
<commit_msg>Fix string formatting type<commit_after>package gitgo

import (
	"bufio"
	"bytes"
	"encoding/hex"
	"fmt"
	"io"
	"strings"
)

type GitObject interface {
	Type() string
}

type gitObject struct {
	Type string

	// Commit fields
	Tree      string
	Parents   []string
	Author    string
	Committer string
	Message   string
	Size      string

	// Tree
	Blobs []objectMeta
	Trees []objectMeta

	// Blob
	Contents string
}

type Blob struct {
	_type    string
	size     string
	Contents string
}

func (b Blob) Type() string {
	return b._type

}

type Commit struct {
	_type     string
	Tree      string
	Parents   []string
	Author    string
	Committer string
	Message   string
	size      string
}

func (c Commit) Type() string {
	return c._type
}

type Tree struct {
	_type string
	Blobs []objectMeta
	Trees []objectMeta
	size  string
}

func (t Tree) Type() string {
	return t._type
}

// objectMeta contains the metadata
// (hash, permissions, and filename)
// corresponding either to a blob (leaf) or another tree
type objectMeta struct {
	Hash     SHA
	Perms    string
	filename string
}

func NewObject(input SHA) (obj GitObject, err error) {
	str, err := CatFile(input)
	if err != nil {
		return
	}
	return parseObj(str)
}

func normalizePerms(perms string) string {
	// TODO don't store permissions as a string
	for len(perms) < 6 {
		perms = "0" + perms
	}
	return perms
}

func parseObj(obj string) (result GitObject, err error) {

	parts := strings.Split(obj, "\x00")
	parts = strings.Fields(parts[0])
	resultType := parts[0]
	resultSize := parts[1]
	nullIndex := strings.Index(obj, "\x00")

	lines := strings.Split(obj[nullIndex+1:], "\n")

	switch resultType {
	case "commit":
		var commit = Commit{_type: resultType, size: resultSize}
		for i, line := range lines {
			// The next line is the commit message
			if len(strings.Fields(line)) == 0 {
				commit.Message = strings.Join(lines[i+1:], "\n")
				break
			}
			parts := strings.Fields(line)
			key := parts[0]
			switch KeyType(key) {
			case TreeKey:
				commit.Tree = parts[1]
			case ParentKey:
				commit.Parents = append(commit.Parents, parts[1])
			case AuthorKey:
				commit.Author = strings.Join(parts[1:], " ")
			case CommitterKey:
				commit.Committer = strings.Join(parts[1:], " ")
			default:
				err = fmt.Errorf("Encounterd unknown field in commit: %s", key)
				return
			}
		}
		result = commit
	case "tree":
		var tree = Tree{_type: resultType, size: resultSize}

		scanner := bufio.NewScanner(bytes.NewBuffer([]byte(obj)))
		scanner.Split(ScanNullLines)

		var tmp objectMeta

		var resultObjs []objectMeta

		for count := 0; ; count++ {
			done := !scanner.Scan()
			if done {
				break
			}

			txt := scanner.Text()

			if count == 0 {
				// the first time through, scanner.Text() will be
				// "tree <size>"
				continue
			}
			if count == 1 {
				// the second time through, scanner.Text() will be
				// <perms> <filename>
				// separated by a space
				fields := strings.Fields(txt)
				tmp.Perms = normalizePerms(fields[0])
				tmp.filename = fields[1]
				continue
			}

			// after the second time through, scanner.Text() will be
			// <sha><perms2> <file2>
			// where perms2 and file2 refer to the permissions and filename (respectively)
			// of the NEXT object, and <sha> is the first 20 bytes exactly.
			// If there is no next object (this is the last object)
			// then scanner.Text() will yield exactly 20 bytes.

			// decode the next 20 bytes to get the SHA
			tmp.Hash = SHA(hex.EncodeToString([]byte(txt[:20])))
			resultObjs = append(resultObjs, tmp)
			if len(txt) <= 20 {
				// We've read the last line
				break
			}

			// Now, tmp points to the next object in the tree listing
			tmp = objectMeta{}
			remainder := txt[20:]
			fields := strings.Fields(remainder)
			tmp.Perms = normalizePerms(fields[0])
			tmp.filename = fields[1]
		}

		if err := scanner.Err(); err != nil && err != io.EOF {
			return tree, err
		}

		for _, part := range resultObjs {
			obj, err := NewObject(part.Hash)
			if err != nil {
				return tree, err
			}
			switch obj.Type() {
			case "tree":
				tree.Trees = append(tree.Trees, part)
			case "blob":
				tree.Blobs = append(tree.Blobs, part)
			default:
				return tree, fmt.Errorf("Unknown type found: %s", obj.Type())
			}
		}
		result = tree
	case "blob":
		var blob = Blob{_type: resultType, size: resultSize}
		blob.Contents = obj[nullIndex+1:]
		result = blob
	default:
		err = fmt.Errorf("Received unknown object type %s", resultType)
	}

	return
}

// ScanNullLines is like bufio.ScanLines, except it uses the null character as the delimiter
// instead of a newline
func ScanNullLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.IndexByte(data, '\x00'); i >= 0 {
		// We have a full null-terminated line.
		return i + 1, data[0:i], nil
	}
	// If we're at EOF, we have a final, non-terminated "line". Return it.
	if atEOF {
		return len(data), data, nil
	}
	// Request more data.
	return 0, nil, nil
}
<|endoftext|>


<commit_before>// Copyright 2018 The Terraformer 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 gcp

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/GoogleCloudPlatform/terraformer/terraform_utils"

	"google.golang.org/api/dns/v1"
)

var cloudDNSAllowEmptyValues = []string{}

var cloudDNSAdditionalFields = map[string]string{}

type CloudDNSGenerator struct {
	GCPService
}

func (g CloudDNSGenerator) createZonesResources(ctx context.Context, svc *dns.Service, project string) []terraform_utils.Resource {
	resources := []terraform_utils.Resource{}
	managedZonesListCall := svc.ManagedZones.List(project)
	err := managedZonesListCall.Pages(ctx, func(listDNS *dns.ManagedZonesListResponse) error {
		for _, zone := range listDNS.ManagedZones {
			resources = append(resources, terraform_utils.NewResource(
				zone.Name,
				zone.Name,
				"google_dns_managed_zone",
				"google",
				map[string]string{
					"name":    zone.Name,
					"project": project,
				},
				cloudDNSAllowEmptyValues,
				cloudDNSAdditionalFields,
			))
			records := g.createRecordsResources(ctx, svc, project, zone.Name)
			resources = append(resources, records...)
		}
		return nil
	})
	if err != nil {
		log.Println(err)
		return []terraform_utils.Resource{}
	}
	return resources
}
func (CloudDNSGenerator) createRecordsResources(ctx context.Context, svc *dns.Service, project, zoneName string) []terraform_utils.Resource {
	resources := []terraform_utils.Resource{}
	managedRecordsListCall := svc.ResourceRecordSets.List(project, zoneName)
	err := managedRecordsListCall.Pages(ctx, func(listDNS *dns.ResourceRecordSetsListResponse) error {
		for _, record := range listDNS.Rrsets {
			resources = append(resources, terraform_utils.NewResource(
				fmt.Sprintf("%s/%s/%s", zoneName, record.Name, record.Type),
				strings.TrimSuffix(record.Name+"-"+record.Type, "."),
				"google_dns_record_set",
				"google",
				map[string]string{
					"name":         record.Name,
					"managed_zone": zoneName,
					"type":         record.Type,
					"project":      project,
				},
				cloudDNSAllowEmptyValues,
				cloudDNSAdditionalFields,
			))
		}
		return nil
	})
	if err != nil {
		log.Println(err)
		return []terraform_utils.Resource{}
	}
	return resources
}

// Generate TerraformResources from GCP API,
// create terraform resource for each zone + each record
func (g *CloudDNSGenerator) InitResources() error {
	project := g.GetArgs()["project"].(string)
	ctx := context.Background()
	svc, err := dns.NewService(ctx)
	if err != nil {
		log.Fatal(err)
	}

	g.Resources = g.createZonesResources(ctx, svc, project)
	g.PopulateIgnoreKeys()
	return nil
}

func (g *CloudDNSGenerator) PostConvertHook() error {
	for i, resourceRecord := range g.Resources {
		if resourceRecord.InstanceInfo.Type == "google_dns_managed_zone" {
			continue
		}
		item := resourceRecord.Item
		zoneID := item["managed_zone"].(string)
		for _, resourceZone := range g.Resources {
			if resourceZone.InstanceInfo.Type != "google_dns_managed_zone" {
				continue
			}
			if zoneID == resourceZone.InstanceState.ID {
				g.Resources[i].Item["managed_zone"] = "${google_dns_managed_zone." + resourceZone.ResourceName + ".name}"
				name := g.Resources[i].Item["name"].(string)
				name = strings.Replace(name, resourceZone.Item["dns_name"].(string), "", -1)
				g.Resources[i].Item["name"] = name + "${google_dns_managed_zone." + resourceZone.ResourceName + ".dns_name}"
			}
		}
	}
	return nil
}
<commit_msg>added zoneName preceding google_dns_record_set<commit_after>// Copyright 2018 The Terraformer 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 gcp

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/GoogleCloudPlatform/terraformer/terraform_utils"

	"google.golang.org/api/dns/v1"
)

var cloudDNSAllowEmptyValues = []string{}

var cloudDNSAdditionalFields = map[string]string{}

type CloudDNSGenerator struct {
	GCPService
}

func (g CloudDNSGenerator) createZonesResources(ctx context.Context, svc *dns.Service, project string) []terraform_utils.Resource {
	resources := []terraform_utils.Resource{}
	managedZonesListCall := svc.ManagedZones.List(project)
	err := managedZonesListCall.Pages(ctx, func(listDNS *dns.ManagedZonesListResponse) error {
		for _, zone := range listDNS.ManagedZones {
			resources = append(resources, terraform_utils.NewResource(
				zone.Name,
				zone.Name,
				"google_dns_managed_zone",
				"google",
				map[string]string{
					"name":    zone.Name,
					"project": project,
				},
				cloudDNSAllowEmptyValues,
				cloudDNSAdditionalFields,
			))
			records := g.createRecordsResources(ctx, svc, project, zone.Name)
			resources = append(resources, records...)
		}
		return nil
	})
	if err != nil {
		log.Println(err)
		return []terraform_utils.Resource{}
	}
	return resources
}
func (CloudDNSGenerator) createRecordsResources(ctx context.Context, svc *dns.Service, project, zoneName string) []terraform_utils.Resource {
	resources := []terraform_utils.Resource{}
	managedRecordsListCall := svc.ResourceRecordSets.List(project, zoneName)
	err := managedRecordsListCall.Pages(ctx, func(listDNS *dns.ResourceRecordSetsListResponse) error {
		for _, record := range listDNS.Rrsets {
			resources = append(resources, terraform_utils.NewResource(
				fmt.Sprintf("%s/%s/%s", zoneName, record.Name, record.Type),
				zoneName+"_"+strings.TrimSuffix(record.Name+"-"+record.Type, "."),
				"google_dns_record_set",
				"google",
				map[string]string{
					"name":         record.Name,
					"managed_zone": zoneName,
					"type":         record.Type,
					"project":      project,
				},
				cloudDNSAllowEmptyValues,
				cloudDNSAdditionalFields,
			))
		}
		return nil
	})
	if err != nil {
		log.Println(err)
		return []terraform_utils.Resource{}
	}
	return resources
}

// Generate TerraformResources from GCP API,
// create terraform resource for each zone + each record
func (g *CloudDNSGenerator) InitResources() error {
	project := g.GetArgs()["project"].(string)
	ctx := context.Background()
	svc, err := dns.NewService(ctx)
	if err != nil {
		log.Fatal(err)
	}

	g.Resources = g.createZonesResources(ctx, svc, project)
	g.PopulateIgnoreKeys()
	return nil
}

func (g *CloudDNSGenerator) PostConvertHook() error {
	for i, resourceRecord := range g.Resources {
		if resourceRecord.InstanceInfo.Type == "google_dns_managed_zone" {
			continue
		}
		item := resourceRecord.Item
		zoneID := item["managed_zone"].(string)
		for _, resourceZone := range g.Resources {
			if resourceZone.InstanceInfo.Type != "google_dns_managed_zone" {
				continue
			}
			if zoneID == resourceZone.InstanceState.ID {
				g.Resources[i].Item["managed_zone"] = "${google_dns_managed_zone." + resourceZone.ResourceName + ".name}"
				name := g.Resources[i].Item["name"].(string)
				name = strings.Replace(name, resourceZone.Item["dns_name"].(string), "", -1)
				g.Resources[i].Item["name"] = name + "${google_dns_managed_zone." + resourceZone.ResourceName + ".dns_name}"
			}
		}
	}
	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 lgtm

import (
	"regexp"

	"github.com/Sirupsen/logrus"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/plugins"
)

const pluginName = "lgtm"

var (
	lgtmLabel    = "lgtm"
	lgtmRe       = regexp.MustCompile(`(?mi)^\/lgtm\r?$`)
	lgtmCancelRe = regexp.MustCompile(`(?mi)^\/lgtm cancel\r?$`)
)

func init() {
	plugins.RegisterIssueCommentHandler(pluginName, handleIssueComment)
}

type githubClient interface {
	CreateComment(owner, repo string, number int, comment string) error
	AddLabel(owner, repo string, number int, label string) error
	RemoveLabel(owner, repo string, number int, label string) error
}

func handleIssueComment(pc plugins.PluginClient, ic github.IssueCommentEvent) error {
	return handle(pc.GitHubClient, pc.Logger, ic)
}

func handle(gc githubClient, log *logrus.Entry, ic github.IssueCommentEvent) error {
	// Only consider open PRs.
	if !ic.Issue.IsPullRequest() || ic.Issue.State != "open" || ic.Action != "created" {
		return nil
	}

	org := ic.Repo.Owner.Login
	repo := ic.Repo.Name
	number := ic.Issue.Number

	// If we create an "/lgtm" comment, add lgtm if necessary.
	// If we create a "/lgtm cancel" comment, remove lgtm if necessary.
	wantLGTM := false
	if lgtmRe.MatchString(ic.Comment.Body) {
		wantLGTM = true
	} else if lgtmCancelRe.MatchString(ic.Comment.Body) {
		wantLGTM = false
	} else {
		return nil
	}

	// Allow authors to cancel LGTM. Do not allow authors to LGTM, and do not
	// accept commands from any other user.
	commentAuthor := ic.Comment.User.Login
	isAssignee := ic.Issue.IsAssignee(commentAuthor)
	isAuthor := ic.Issue.IsAuthor(commentAuthor)
	if isAuthor && wantLGTM {
		resp := "you can't LGTM your own PR"
		log.Infof("Commenting with \"%s\".", resp)
		return gc.CreateComment(org, repo, number, plugins.FormatResponse(ic.Comment, resp))
	} else if !isAuthor {
		if !isAssignee && wantLGTM {
			resp := "you can't LGTM a PR unless you are assigned as a reviewer"
			log.Infof("Commenting with \"%s\".", resp)
			return gc.CreateComment(org, repo, number, plugins.FormatResponse(ic.Comment, resp))
		} else if !isAssignee && !wantLGTM {
			resp := "you can't remove LGTM from a PR unless you are assigned as a reviewer"
			log.Infof("Commenting with \"%s\".", resp)
			return gc.CreateComment(org, repo, number, plugins.FormatResponse(ic.Comment, resp))
		}
	}

	// Only add the label if it doesn't have it, and vice versa.
	hasLGTM := ic.Issue.HasLabel(lgtmLabel)
	if hasLGTM && !wantLGTM {
		log.Info("Removing LGTM label.")
		return gc.RemoveLabel(org, repo, number, lgtmLabel)
	} else if !hasLGTM && wantLGTM {
		log.Info("Adding LGTM label.")
		return gc.AddLabel(org, repo, number, lgtmLabel)
	}
	return nil
}
<commit_msg>Update LGTM message to not say 'reviewer'.<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 lgtm

import (
	"regexp"

	"github.com/Sirupsen/logrus"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/plugins"
)

const pluginName = "lgtm"

var (
	lgtmLabel    = "lgtm"
	lgtmRe       = regexp.MustCompile(`(?mi)^\/lgtm\r?$`)
	lgtmCancelRe = regexp.MustCompile(`(?mi)^\/lgtm cancel\r?$`)
)

func init() {
	plugins.RegisterIssueCommentHandler(pluginName, handleIssueComment)
}

type githubClient interface {
	CreateComment(owner, repo string, number int, comment string) error
	AddLabel(owner, repo string, number int, label string) error
	RemoveLabel(owner, repo string, number int, label string) error
}

func handleIssueComment(pc plugins.PluginClient, ic github.IssueCommentEvent) error {
	return handle(pc.GitHubClient, pc.Logger, ic)
}

func handle(gc githubClient, log *logrus.Entry, ic github.IssueCommentEvent) error {
	// Only consider open PRs.
	if !ic.Issue.IsPullRequest() || ic.Issue.State != "open" || ic.Action != "created" {
		return nil
	}

	org := ic.Repo.Owner.Login
	repo := ic.Repo.Name
	number := ic.Issue.Number

	// If we create an "/lgtm" comment, add lgtm if necessary.
	// If we create a "/lgtm cancel" comment, remove lgtm if necessary.
	wantLGTM := false
	if lgtmRe.MatchString(ic.Comment.Body) {
		wantLGTM = true
	} else if lgtmCancelRe.MatchString(ic.Comment.Body) {
		wantLGTM = false
	} else {
		return nil
	}

	// Allow authors to cancel LGTM. Do not allow authors to LGTM, and do not
	// accept commands from any other user.
	commentAuthor := ic.Comment.User.Login
	isAssignee := ic.Issue.IsAssignee(commentAuthor)
	isAuthor := ic.Issue.IsAuthor(commentAuthor)
	if isAuthor && wantLGTM {
		resp := "you can't LGTM your own PR"
		log.Infof("Commenting with \"%s\".", resp)
		return gc.CreateComment(org, repo, number, plugins.FormatResponse(ic.Comment, resp))
	} else if !isAuthor {
		if !isAssignee && wantLGTM {
			resp := "you can't LGTM a PR unless you are an assignee"
			log.Infof("Commenting with \"%s\".", resp)
			return gc.CreateComment(org, repo, number, plugins.FormatResponse(ic.Comment, resp))
		} else if !isAssignee && !wantLGTM {
			resp := "you can't remove LGTM from a PR unless you are an assignee"
			log.Infof("Commenting with \"%s\".", resp)
			return gc.CreateComment(org, repo, number, plugins.FormatResponse(ic.Comment, resp))
		}
	}

	// Only add the label if it doesn't have it, and vice versa.
	hasLGTM := ic.Issue.HasLabel(lgtmLabel)
	if hasLGTM && !wantLGTM {
		log.Info("Removing LGTM label.")
		return gc.RemoveLabel(org, repo, number, lgtmLabel)
	} else if !hasLGTM && wantLGTM {
		log.Info("Adding LGTM label.")
		return gc.AddLabel(org, repo, number, lgtmLabel)
	}
	return nil
}
<|endoftext|>