<commit_before>/*
 * // Copyright (c) 2018 Cisco and/or its affiliates.
 * //
 * // 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 processor

import (
	"net"

	"github.com/ligato/cn-infra/logging"

	epmodel "github.com/contiv/vpp/plugins/ksr/model/endpoints"
	podmodel "github.com/contiv/vpp/plugins/ksr/model/pod"
	svcmodel "github.com/contiv/vpp/plugins/ksr/model/service"
	"github.com/contiv/vpp/plugins/service/configurator"
)

// Service is used to combine data from the service model with the endpoints.
type Service struct {
	sp            *ServiceProcessor
	meta          *svcmodel.Service
	endpoints     *epmodel.Endpoints
	contivSvc     *configurator.ContivService
	localBackends []podmodel.ID
	refreshed     bool
}

// NewService is a constructor for Service.
func NewService(sp *ServiceProcessor) *Service {
	return &Service{
		sp:            sp,
		localBackends: []podmodel.ID{},
	}
}

// SetMetadata initializes or changes metadata for the service.
func (s *Service) SetMetadata(meta *svcmodel.Service) {
	s.meta = meta
	s.refreshed = false
}

// SetEndpoints initializes or changes endpoints for the service.
func (s *Service) SetEndpoints(endpoints *epmodel.Endpoints) {
	s.endpoints = endpoints
	s.refreshed = false
}

// GetContivService returns the service data represented as ContivService.
// Returns nil if there are not enough available data.
func (s *Service) GetContivService() *configurator.ContivService {
	if !s.refreshed {
		s.Refresh()
	}
	return s.contivSvc
}

// GetLocalBackends returns the set of IDs od all local backends of this service.
// Returns empty array if there are not enough available data.
func (s *Service) GetLocalBackends() []podmodel.ID {
	if !s.refreshed {
		s.Refresh()
	}
	return s.localBackends
}

// Refresh combines metadata with endpoints to get ContivService representation
// and the list of local backends.
func (s *Service) Refresh() {
	if s.meta == nil || s.endpoints == nil {
		s.contivSvc = nil
		s.localBackends = []podmodel.ID{}
		s.refreshed = true
		return
	}

	s.contivSvc = configurator.NewContivService()
	s.localBackends = []podmodel.ID{}

	s.contivSvc.ID = svcmodel.GetID(s.meta)
	if s.meta.ExternalTrafficPolicy != "Local" {
		s.contivSvc.SNAT = true
	}

	// Collect all IP addresses on which the service should be exposed.
	if s.meta.ClusterIp != "" && s.meta.ClusterIp != "None" {
		clusterIP := net.ParseIP(s.meta.ClusterIp)
		if clusterIP != nil {
			s.contivSvc.ExternalIPs = append(s.contivSvc.ExternalIPs, clusterIP)
		} else {
			s.sp.Log.WithFields(logging.Fields{
				"service":   s.contivSvc.ID,
				"clusterIP": clusterIP,
			}).Warn("Failed to parse clusterIP")
		}
	}

	for _, externalIPStr := range s.meta.ExternalIps {
		externalIP := net.ParseIP(externalIPStr)
		if externalIP != nil {
			s.contivSvc.ExternalIPs = append(s.contivSvc.ExternalIPs, externalIP)
		} else {
			s.sp.Log.WithFields(logging.Fields{
				"service":    s.contivSvc.ID,
				"externalIP": externalIPStr,
			}).Warn("Failed to parse external IP")
		}
	}

	// Fill up the map of service ports.
	for _, port := range s.meta.Port {
		sp := &configurator.ServicePort{
			Port:     uint16(port.GetPort()),
			NodePort: uint16(port.GetNodePort()),
		}
		if port.GetProtocol() == "TCP" {
			sp.Protocol = configurator.TCP
		} else {
			sp.Protocol = configurator.UDP
		}
		s.contivSvc.Ports[port.Name] = sp
	}

	// Fill up the map of service backends.
	for port := range s.contivSvc.Ports {
		s.contivSvc.Backends[port] = []*configurator.ServiceBackend{}
	}
	for _, epSubSet := range s.endpoints.GetEndpointSubsets() {
		epPorts := epSubSet.GetPorts()
		epAddrs := epSubSet.GetAddresses()
		for _, epAddr := range epAddrs {
			var local bool
			epIP := net.ParseIP(epAddr.GetIp())
			if epIP == nil {
				s.sp.Log.WithFields(logging.Fields{
					"service":    s.contivSvc.ID,
					"endpointIP": epAddr.GetIp(),
				}).Warn("Failed to parse endpoint IP")
				continue
			}
			if epAddr.GetNodeName() == "" || epAddr.GetNodeName() == s.sp.ServiceLabel.GetAgentLabel() {
				local = true
			}
			for _, epPort := range epPorts {
				if _, exposedPort := s.contivSvc.Ports[epPort.GetName()]; exposedPort {
					sb := &configurator.ServiceBackend{}
					sb.IP = epIP
					sb.Port = uint16(epPort.GetPort())
					sb.Local = local
					s.contivSvc.Backends[epPort.GetName()] = append(s.contivSvc.Backends[epPort.GetName()], sb)
				}
			}
			if local {
				// Get interface name and add it to the set of local backends.
				targetPod := epAddr.GetTargetRef()
				if targetPod.Kind == "Pod" {
					s.localBackends = append(s.localBackends,
						podmodel.ID{Name: targetPod.GetName(), Namespace: targetPod.GetNamespace()})
				}
			}
		}
	}

	s.refreshed = true
}
<commit_msg>Use getter instead of direct access.<commit_after>/*
 * // Copyright (c) 2018 Cisco and/or its affiliates.
 * //
 * // 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 processor

import (
	"net"

	"github.com/ligato/cn-infra/logging"

	epmodel "github.com/contiv/vpp/plugins/ksr/model/endpoints"
	podmodel "github.com/contiv/vpp/plugins/ksr/model/pod"
	svcmodel "github.com/contiv/vpp/plugins/ksr/model/service"
	"github.com/contiv/vpp/plugins/service/configurator"
)

// Service is used to combine data from the service model with the endpoints.
type Service struct {
	sp            *ServiceProcessor
	meta          *svcmodel.Service
	endpoints     *epmodel.Endpoints
	contivSvc     *configurator.ContivService
	localBackends []podmodel.ID
	refreshed     bool
}

// NewService is a constructor for Service.
func NewService(sp *ServiceProcessor) *Service {
	return &Service{
		sp:            sp,
		localBackends: []podmodel.ID{},
	}
}

// SetMetadata initializes or changes metadata for the service.
func (s *Service) SetMetadata(meta *svcmodel.Service) {
	s.meta = meta
	s.refreshed = false
}

// SetEndpoints initializes or changes endpoints for the service.
func (s *Service) SetEndpoints(endpoints *epmodel.Endpoints) {
	s.endpoints = endpoints
	s.refreshed = false
}

// GetContivService returns the service data represented as ContivService.
// Returns nil if there are not enough available data.
func (s *Service) GetContivService() *configurator.ContivService {
	if !s.refreshed {
		s.Refresh()
	}
	return s.contivSvc
}

// GetLocalBackends returns the set of IDs od all local backends of this service.
// Returns empty array if there are not enough available data.
func (s *Service) GetLocalBackends() []podmodel.ID {
	if !s.refreshed {
		s.Refresh()
	}
	return s.localBackends
}

// Refresh combines metadata with endpoints to get ContivService representation
// and the list of local backends.
func (s *Service) Refresh() {
	if s.meta == nil || s.endpoints == nil {
		s.contivSvc = nil
		s.localBackends = []podmodel.ID{}
		s.refreshed = true
		return
	}

	s.contivSvc = configurator.NewContivService()
	s.localBackends = []podmodel.ID{}

	s.contivSvc.ID = svcmodel.GetID(s.meta)
	if s.meta.ExternalTrafficPolicy != "Local" {
		s.contivSvc.SNAT = true
	}

	// Collect all IP addresses on which the service should be exposed.
	if s.meta.ClusterIp != "" && s.meta.ClusterIp != "None" {
		clusterIP := net.ParseIP(s.meta.ClusterIp)
		if clusterIP != nil {
			s.contivSvc.ExternalIPs = append(s.contivSvc.ExternalIPs, clusterIP)
		} else {
			s.sp.Log.WithFields(logging.Fields{
				"service":   s.contivSvc.ID,
				"clusterIP": clusterIP,
			}).Warn("Failed to parse clusterIP")
		}
	}

	for _, externalIPStr := range s.meta.ExternalIps {
		externalIP := net.ParseIP(externalIPStr)
		if externalIP != nil {
			s.contivSvc.ExternalIPs = append(s.contivSvc.ExternalIPs, externalIP)
		} else {
			s.sp.Log.WithFields(logging.Fields{
				"service":    s.contivSvc.ID,
				"externalIP": externalIPStr,
			}).Warn("Failed to parse external IP")
		}
	}

	// Fill up the map of service ports.
	for _, port := range s.meta.Port {
		sp := &configurator.ServicePort{
			Port:     uint16(port.GetPort()),
			NodePort: uint16(port.GetNodePort()),
		}
		if port.GetProtocol() == "TCP" {
			sp.Protocol = configurator.TCP
		} else {
			sp.Protocol = configurator.UDP
		}
		s.contivSvc.Ports[port.Name] = sp
	}

	// Fill up the map of service backends.
	for port := range s.contivSvc.Ports {
		s.contivSvc.Backends[port] = []*configurator.ServiceBackend{}
	}
	for _, epSubSet := range s.endpoints.GetEndpointSubsets() {
		epPorts := epSubSet.GetPorts()
		epAddrs := epSubSet.GetAddresses()
		for _, epAddr := range epAddrs {
			var local bool
			epIP := net.ParseIP(epAddr.GetIp())
			if epIP == nil {
				s.sp.Log.WithFields(logging.Fields{
					"service":    s.contivSvc.ID,
					"endpointIP": epAddr.GetIp(),
				}).Warn("Failed to parse endpoint IP")
				continue
			}
			if epAddr.GetNodeName() == "" || epAddr.GetNodeName() == s.sp.ServiceLabel.GetAgentLabel() {
				local = true
			}
			for _, epPort := range epPorts {
				if _, exposedPort := s.contivSvc.Ports[epPort.GetName()]; exposedPort {
					sb := &configurator.ServiceBackend{}
					sb.IP = epIP
					sb.Port = uint16(epPort.GetPort())
					sb.Local = local
					s.contivSvc.Backends[epPort.GetName()] = append(s.contivSvc.Backends[epPort.GetName()], sb)
				}
			}
			if local {
				// Get interface name and add it to the set of local backends.
				targetPod := epAddr.GetTargetRef()
				if targetPod.GetKind() == "Pod" {
					s.localBackends = append(s.localBackends,
						podmodel.ID{Name: targetPod.GetName(), Namespace: targetPod.GetNamespace()})
				}
			}
		}
	}

	s.refreshed = true
}
<|endoftext|>


<commit_before>package connectors

import (
	"bytes"
	"log"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"github.com/projectjane/jane/models"
	"github.com/projectjane/jane/parse"
	"golang.org/x/crypto/ssh"
)

type Exec struct {
}

func (x Exec) Listen(commandMsgs chan<- models.Message, connector models.Connector) {
	defer Recovery(connector)
	for _, command := range connector.Commands {
		if command.RunCheck {
			if connector.Debug {
				log.Print("Starting Listener for " + connector.ID + " " + command.Name)
			}
			go check(commandMsgs, command, connector)
		}
	}
}

func (x Exec) Command(message models.Message, publishMsgs chan<- models.Message, connector models.Connector) {
	for _, command := range connector.Commands {
		if match, tokens := parse.Match(command.Match, message.In.Text); match {
			args := parse.Substitute(command.Args, tokens)
			tokens["STDOUT"] = callCmd(command.Cmd, args, connector)
			var color = "NONE"
			var match = false
			if match, _ = parse.Match(command.Green, tokens["STDOUT"]); match {
				color = "SUCCESS"
			}
			if match, _ = parse.Match(command.Yellow, tokens["STDOUT"]); match {
				color = "WARN"
			}
			if match, _ = parse.Match(command.Red, tokens["STDOUT"]); match {
				color = "FAIL"
			}
			message.In.Tags = parse.TagAppend(message.In.Tags, connector.Tags)
			message.Out.Text = connector.ID + " " + command.Name
			message.Out.Detail = parse.Substitute(command.Output, tokens)
			message.Out.Status = color
			publishMsgs <- message
		}
	}
}

func (x Exec) Publish(publishMsgs <-chan models.Message, connector models.Connector) {
	return
}

func (x Exec) Help(connector models.Connector) (help []string) {
	help = make([]string, 0)
	for _, command := range connector.Commands {
		if !command.HideHelp {
			if command.Help != "" {
				help = append(help, command.Help)
			} else {
				help = append(help, command.Match)
			}
		}
	}
	return help
}

func check(commandMsgs chan<- models.Message, command models.Command, connector models.Connector) {
	var state = command.Green
	var interval = 1
	var remind = 0
	if command.Interval > 0 {
		interval = command.Interval
	}
	if command.Remind > 0 {
		remind = command.Remind
	}
	var counter = 0
	for {
		var color = "NONE"
		var match = false
		var newstate = ""
		counter += 1
		out := callCmd(command.Cmd, command.Args, connector)
		if match, _ = parse.Match(command.Green, out); match {
			newstate = command.Green
			color = "SUCCESS"
		}
		if match, _ = parse.Match(command.Yellow, out); match {
			newstate = command.Yellow
			color = "WARN"
		}
		if match, _ = parse.Match(command.Red, out); match {
			newstate = command.Red
			color = "FAIL"
		}
		if newstate != state || (newstate != command.Green && counter == remind && remind != 0) {
			var message models.Message
			message.In.ConnectorType = connector.Type
			message.In.ConnectorID = connector.ID
			message.In.Tags = connector.Tags
			message.In.Process = false
			message.Out.Text = connector.ID + " " + command.Name
			message.Out.Detail = strings.Replace(command.Output, "${STDOUT}", out, -1)
			message.Out.Status = color
			commandMsgs <- message
			state = newstate
		}
		if counter >= remind {
			counter = 0
		}
		time.Sleep(time.Duration(interval) * time.Minute)
	}
}

func callCmd(cmd string, args string, connector models.Connector) (out string) {
	if connector.Server != "" {
		out = callRemote(cmd, args, connector)
	} else {
		out = callLocal(cmd, args, connector)
	}
	return out
}

func callLocal(cmd string, args string, connector models.Connector) (out string) {
	ca := cmd + " " + args
	if connector.Debug {
		log.Print("Executing: " + cmd + " " + args)
	}
	var o bytes.Buffer
	var e bytes.Buffer
	c := exec.Command("/bin/sh", "-c", ca)
	c.Stdout = &o
	c.Stderr = &e
	err := c.Run()
	if err != nil {
		log.Print(cmd + " " + args)
		log.Print(err)
		log.Print(e.String())
	}
	out = o.String()
	if connector.Debug {
		log.Print(out)
	}
	return out
}

func callRemote(cmd string, args string, connector models.Connector) (out string) {
	serverconn := true
	clientconn := &ssh.ClientConfig{
		User: connector.Login,
		Auth: []ssh.AuthMethod{
			ssh.Password(connector.Pass),
		},
	}
	port := "22"
	if connector.Port != "" {
		port = connector.Port
	}
	if connector.Debug {
		log.Print("Starting ssh connection for " + connector.Server + ":" + port)
	}
	retries := 3
	retryCounter := retries
	for retryCounter > 0 {
		client, err := ssh.Dial("tcp", connector.Server+":"+port, clientconn)
		if err != nil {
			log.Print(err)
		}
		if client == nil {
			serverconn = false
		} else {
			defer client.Close()
			session, err := client.NewSession()
			if err != nil {
				log.Print(err)
			}
			if session == nil {
				serverconn = false
			} else {
				defer session.Close()
				b, err := session.CombinedOutput(cmd + " " + args)
				if err != nil && connector.Debug {
					log.Print(err)
				}
				out = string(b[:])
				if connector.Debug {
					log.Print("Exec results for " + connector.Server + " " + cmd + " " + args + ": " + out)
				}
			}
		}
		if serverconn {
			retryCounter = 0
		} else {
			if connector.Debug {
				log.Print("Cannot connect to server " + connector.Server + " (try #" + strconv.Itoa(retries-retryCounter) + ")")
			}
			retryCounter -= 1
			time.Sleep(3 * time.Second)
		}
	}
	if !serverconn {
		out = "ERROR - Cannot connect to server " + connector.Server
	}
	return out
}
<commit_msg>adding extra deleay between ssh retry<commit_after>package connectors

import (
	"bytes"
	"log"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"github.com/projectjane/jane/models"
	"github.com/projectjane/jane/parse"
	"golang.org/x/crypto/ssh"
)

type Exec struct {
}

func (x Exec) Listen(commandMsgs chan<- models.Message, connector models.Connector) {
	defer Recovery(connector)
	for _, command := range connector.Commands {
		if command.RunCheck {
			if connector.Debug {
				log.Print("Starting Listener for " + connector.ID + " " + command.Name)
			}
			go check(commandMsgs, command, connector)
		}
	}
}

func (x Exec) Command(message models.Message, publishMsgs chan<- models.Message, connector models.Connector) {
	for _, command := range connector.Commands {
		if match, tokens := parse.Match(command.Match, message.In.Text); match {
			args := parse.Substitute(command.Args, tokens)
			tokens["STDOUT"] = callCmd(command.Cmd, args, connector)
			var color = "NONE"
			var match = false
			if match, _ = parse.Match(command.Green, tokens["STDOUT"]); match {
				color = "SUCCESS"
			}
			if match, _ = parse.Match(command.Yellow, tokens["STDOUT"]); match {
				color = "WARN"
			}
			if match, _ = parse.Match(command.Red, tokens["STDOUT"]); match {
				color = "FAIL"
			}
			message.In.Tags = parse.TagAppend(message.In.Tags, connector.Tags)
			message.Out.Text = connector.ID + " " + command.Name
			message.Out.Detail = parse.Substitute(command.Output, tokens)
			message.Out.Status = color
			publishMsgs <- message
		}
	}
}

func (x Exec) Publish(publishMsgs <-chan models.Message, connector models.Connector) {
	return
}

func (x Exec) Help(connector models.Connector) (help []string) {
	help = make([]string, 0)
	for _, command := range connector.Commands {
		if !command.HideHelp {
			if command.Help != "" {
				help = append(help, command.Help)
			} else {
				help = append(help, command.Match)
			}
		}
	}
	return help
}

func check(commandMsgs chan<- models.Message, command models.Command, connector models.Connector) {
	var state = command.Green
	var interval = 1
	var remind = 0
	if command.Interval > 0 {
		interval = command.Interval
	}
	if command.Remind > 0 {
		remind = command.Remind
	}
	var counter = 0
	for {
		var color = "NONE"
		var match = false
		var newstate = ""
		counter += 1
		out := callCmd(command.Cmd, command.Args, connector)
		if match, _ = parse.Match(command.Green, out); match {
			newstate = command.Green
			color = "SUCCESS"
		}
		if match, _ = parse.Match(command.Yellow, out); match {
			newstate = command.Yellow
			color = "WARN"
		}
		if match, _ = parse.Match(command.Red, out); match {
			newstate = command.Red
			color = "FAIL"
		}
		if newstate != state || (newstate != command.Green && counter == remind && remind != 0) {
			var message models.Message
			message.In.ConnectorType = connector.Type
			message.In.ConnectorID = connector.ID
			message.In.Tags = connector.Tags
			message.In.Process = false
			message.Out.Text = connector.ID + " " + command.Name
			message.Out.Detail = strings.Replace(command.Output, "${STDOUT}", out, -1)
			message.Out.Status = color
			commandMsgs <- message
			state = newstate
		}
		if counter >= remind {
			counter = 0
		}
		time.Sleep(time.Duration(interval) * time.Minute)
	}
}

func callCmd(cmd string, args string, connector models.Connector) (out string) {
	if connector.Server != "" {
		out = callRemote(cmd, args, connector)
	} else {
		out = callLocal(cmd, args, connector)
	}
	return out
}

func callLocal(cmd string, args string, connector models.Connector) (out string) {
	ca := cmd + " " + args
	if connector.Debug {
		log.Print("Executing: " + cmd + " " + args)
	}
	var o bytes.Buffer
	var e bytes.Buffer
	c := exec.Command("/bin/sh", "-c", ca)
	c.Stdout = &o
	c.Stderr = &e
	err := c.Run()
	if err != nil {
		log.Print(cmd + " " + args)
		log.Print(err)
		log.Print(e.String())
	}
	out = o.String()
	if connector.Debug {
		log.Print(out)
	}
	return out
}

func callRemote(cmd string, args string, connector models.Connector) (out string) {
	serverconn := true
	clientconn := &ssh.ClientConfig{
		User: connector.Login,
		Auth: []ssh.AuthMethod{
			ssh.Password(connector.Pass),
		},
	}
	port := "22"
	if connector.Port != "" {
		port = connector.Port
	}
	if connector.Debug {
		log.Print("Starting ssh connection for " + connector.Server + ":" + port)
	}
	retryCounter := 1
	for retryCounter <= 3 {
		client, err := ssh.Dial("tcp", connector.Server+":"+port, clientconn)
		if err != nil {
			log.Print(err)
		}
		if client == nil {
			serverconn = false
		} else {
			defer client.Close()
			session, err := client.NewSession()
			if err != nil {
				log.Print(err)
			}
			if session == nil {
				serverconn = false
			} else {
				defer session.Close()
				b, err := session.CombinedOutput(cmd + " " + args)
				if err != nil && connector.Debug {
					log.Print(err)
				}
				out = string(b[:])
				if connector.Debug {
					log.Print("Exec results for " + connector.Server + " " + cmd + " " + args + ": " + out)
				}
			}
		}
		if serverconn {
			retryCounter = 999
		} else {
			if connector.Debug {
				log.Print("Cannot connect to server " + connector.Server + " (try #" + strconv.Itoa(retryCounter) + ")")
			}
			time.Sleep(time.Duration(3*retryCounter) * time.Second)
			retryCounter += 1
		}
	}
	if !serverconn {
		out = "ERROR - Cannot connect to server " + connector.Server
	}
	return out
}
<|endoftext|>


<commit_before>// This file implements a cluster state machine.  It relies on a cluster
// wide key-value store for coordinating the state of the cluster.
// It also stores the state of the cluster in this key-value store.
package cluster

import (
	"container/list"
	"errors"
	"net"
	"time"

	"github.com/samalba/dockerclient"

	"github.com/libopenstorage/gossip"
	gossiptypes "github.com/libopenstorage/gossip/types"
	"github.com/libopenstorage/openstorage/api"

	log "github.com/Sirupsen/logrus"

	kv "github.com/portworx/kvdb"
	"github.com/portworx/systemutils"
)

const (
	dockerHost   = "unix:///var/run/docker.sock"
	heartbeatKey = "heartbeat"
)

type ClusterManager struct {
	listeners *list.List
	config    Config
	kv        kv.Kvdb
	status    api.Status
	nodeCache map[string]api.Node // Cached info on the nodes in the cluster.
	docker    *dockerclient.DockerClient
	g         gossip.Gossiper
}

func externalIp() (string, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ip = ip.To4()
			if ip == nil {
				continue // not an ipv4 address
			}
			return ip.String(), nil
		}
	}

	return "", errors.New("Node not connected to the network.")
}

func (c *ClusterManager) AddEventListener(listener ClusterListener) error {
	log.Printf("Adding cluster event listener: %s", listener.String())
	c.listeners.PushBack(listener)
	return nil
}

func (c *ClusterManager) getSelf() *api.Node {
	var node = api.Node{}
	s := systemutils.New()

	// Get physical node info.
	node.Id = c.config.NodeId
	node.Status = api.StatusOk
	node.Ip, _ = externalIp()

	node.Cpu, _, _ = s.CpuUsage()
	node.Memory = s.MemUsage()
	node.Luns = s.Luns()

	node.Timestamp = time.Now()

	// Get containers running on this system.
	node.Containers, _ = c.docker.ListContainers(true, true, "")

	return &node
}

func (c *ClusterManager) initNode(db *Database) (*api.Node, bool) {
	node := c.getSelf()
	c.nodeCache[node.Id] = *node

	_, exists := db.NodeEntries[node.Id]

	// Add us into the database.
	db.NodeEntries[c.config.NodeId] = NodeEntry{Id: node.Id, Ip: node.Ip}

	log.Infof("Node %s joining cluster... \n\tCluster ID: %s\n\tIP: %s",
		c.config.NodeId, c.config.ClusterId, node.Ip)

	return node, exists
}

// Initialize node and alert listeners that we are joining the cluster.
func (c *ClusterManager) joinCluster(db *Database, self *api.Node, exist bool) error {
	var err error

	// If I am already in the cluster map, don't add me again.
	if exist {
		goto found
	}

	// Alert all listeners that we are a new node joining an existing cluster.
	for e := c.listeners.Front(); e != nil; e = e.Next() {
		err = e.Value.(ClusterListener).Init(self, db)
		if err != nil {
			log.Warnf("Failed to initialize %s: %v\n",
				e.Value.(ClusterListener).String(), err)
			goto done
		}
	}

found:
	// Alert all listeners that we are joining the cluster.
	for e := c.listeners.Front(); e != nil; e = e.Next() {
		err = e.Value.(ClusterListener).Join(self, db)
		if err != nil {
			log.Warnf("Failed to initialize %s: %v\n",
				e.Value.(ClusterListener).String(), err)
			goto done
		}
	}

	for id, n := range db.NodeEntries {
		if id != c.config.NodeId {
			// Check to see if the IP is the same.  If it is, then we have a stale entry.
			if n.Ip == self.Ip {
				log.Warnf("Warning, Detected node %s with the same IP %s in the database.  Will not connect to this node.",
					id, n.Ip)
			} else {
				// Gossip with this node.
				c.g.AddNode(n.Ip + ":9002")

				// Assume this node is OK.  We will catch any problems during heartbeating.
				c.nodeCache[id] = api.Node{Status: api.StatusOk, Timestamp: time.Now()}
			}
		}
	}

done:
	return err
}

func (c *ClusterManager) initCluster(db *Database, self *api.Node, exist bool) error {
	err := error(nil)

	// Alert all listeners that we are initializing a new cluster.
	for e := c.listeners.Front(); e != nil; e = e.Next() {
		err = e.Value.(ClusterListener).ClusterInit(self, db)
		if err != nil {
			log.Printf("Failed to initialize %s\n",
				e.Value.(ClusterListener).String())
			goto done
		}
	}

	err = c.joinCluster(db, self, exist)
	if err != nil {
		log.Printf("Failed to join new cluster\n")
		goto done
	}

done:
	return err
}

func (c *ClusterManager) heartBeat() {
	for {
		node := c.getSelf()
		c.nodeCache[node.Id] = *node

		c.g.UpdateSelf(heartbeatKey, *node)

		// Process heartbeats from other nodes...
		gossipValues := c.g.GetStoreKeyValue(heartbeatKey)

		for _, nodeInfo := range gossipValues {
			n, ok := nodeInfo.Value.(api.Node)

			if !ok {
				log.Warn("Received a bad broadcast packet: %v", nodeInfo.Value)
				continue
			}

			if n.Id == node.Id {
				continue
			}

			_, ok = c.nodeCache[n.Id]
			if ok {
				if n.Status != api.StatusOk {
					log.Warn("Detected node ", n.Id, " to be unhealthy.")

					for e := c.listeners.Front(); e != nil; e = e.Next() {
						err := e.Value.(ClusterListener).Update(&n)
						if err != nil {
							log.Warn("Failed to notify ", e.Value.(ClusterListener).String())
						}
					}

					delete(c.nodeCache, n.Id)
				} else if time.Since(n.Timestamp) > 10*time.Second {
					log.Warn("Detected node ", n.Id, " to be offline.")

					n.Status = api.StatusOffline

					for e := c.listeners.Front(); e != nil; e = e.Next() {
						err := e.Value.(ClusterListener).Update(&n)
						if err != nil {
							log.Warn("Failed to notify ", e.Value.(ClusterListener).String())
						}
					}

					delete(c.nodeCache, n.Id)
				}
			} else if time.Since(n.Timestamp) <= 10*time.Second {
				// A node joined the cluster.
				log.Warn("Detected node ", n.Id, " to be online.")

				c.nodeCache[n.Id] = n
				for e := c.listeners.Front(); e != nil; e = e.Next() {
					err := e.Value.(ClusterListener).Update(&n)
					if err != nil {
						log.Warn("Failed to notify ", e.Value.(ClusterListener).String())
					}
				}
			}
		}

		// Process stale entries in our local cache.
		for _, n := range c.nodeCache {
			if n.Id == node.Id {
				continue
			}

			if time.Since(n.Timestamp) > 60*time.Second {
				log.Warn("Detected node ", n.Id, " to be offline.")

				n.Status = api.StatusOffline

				for e := c.listeners.Front(); e != nil; e = e.Next() {
					err := e.Value.(ClusterListener).Update(&n)
					if err != nil {
						log.Warn("Failed to notify ", e.Value.(ClusterListener).String())
					}
				}

				delete(c.nodeCache, n.Id)
			}
		}

		time.Sleep(2 * time.Second)
	}
}

func (c *ClusterManager) Start() error {
	log.Info("Cluster manager starting...")
	kvdb := kv.Instance()

	// Start the gossip protocol.
	// XXX make the port configurable.
	c.g = gossip.New("0.0.0.0:9002", gossiptypes.NodeId(c.config.NodeId))
	c.g.SetGossipInterval(2 * time.Second)

	kvlock, err := kvdb.Lock("cluster/lock", 60)
	if err != nil {
		log.Panic("Fatal, Unable to obtain cluster lock.", err)
	}

	db, err := readDatabase()
	if err != nil {
		log.Panic(err)
	}

	if db.Status == api.StatusInit {
		log.Info("Will initialize a new cluster.")

		c.status = api.StatusOk
		db.Status = api.StatusOk
		self, _ := c.initNode(&db)

		// Update the new state of the cluster in the KV Database
		err = writeDatabase(&db)
		if err != nil {
			log.Panic(err)
		}

		err = kvdb.Unlock(kvlock)
		if err != nil {
			log.Panic("Fatal, unable to unlock cluster... Did something take too long to initialize?", err)
		}

		err = c.initCluster(&db, self, false)
		if err != nil {
			log.Panic(err)
		}
	} else if db.Status&api.StatusOk > 0 {
		log.Info("Cluster state is OK... Joining the cluster.")

		c.status = api.StatusOk
		self, exist := c.initNode(&db)

		err = writeDatabase(&db)
		if err != nil {
			log.Panic(err)
		}

		err = kvdb.Unlock(kvlock)
		if err != nil {
			log.Panic("Fatal, unable to unlock cluster... Did something take too long to initialize?", err)
		}

		err = c.joinCluster(&db, self, exist)
		if err != nil {
			log.Panic(err)
		}
	} else {
		err = kvdb.Unlock(kvlock)
		err = errors.New("Fatal, Cluster is in an unexpected state.")
		log.Panic(err)
	}

	// Start heartbeating to other nodes.
	go c.heartBeat()

	return nil
}

func (c *ClusterManager) Init() error {
	docker, err := dockerclient.NewDockerClient(dockerHost, nil)
	if err != nil {
		log.Printf("Fatal, could not connect to Docker.")
		return err
	}

	c.listeners = list.New()
	c.nodeCache = make(map[string]api.Node)
	c.docker = docker

	return nil
}

func (c *ClusterManager) Enumerate() (api.Cluster, error) {
	i := 0

	cluster := api.Cluster{Id: c.config.ClusterId, Status: c.status}
	cluster.Nodes = make([]api.Node, len(c.nodeCache))
	for _, n := range c.nodeCache {
		cluster.Nodes[i] = n
		i++
	}

	return cluster, nil
}

func (c *ClusterManager) Remove(nodes []api.Node) error {
	// TODO
	return nil
}

func (c *ClusterManager) Shutdown(cluster bool, nodes []api.Node) error {
	// TODO
	return nil
}
<commit_msg>register the broadcast payload<commit_after>// This file implements a cluster state machine.  It relies on a cluster
// wide key-value store for coordinating the state of the cluster.
// It also stores the state of the cluster in this key-value store.
package cluster

import (
	"container/list"
	"encoding/gob"
	"errors"
	"net"
	"time"

	"github.com/samalba/dockerclient"

	"github.com/libopenstorage/gossip"
	gossiptypes "github.com/libopenstorage/gossip/types"
	"github.com/libopenstorage/openstorage/api"

	log "github.com/Sirupsen/logrus"

	kv "github.com/portworx/kvdb"
	"github.com/portworx/systemutils"
)

const (
	dockerHost   = "unix:///var/run/docker.sock"
	heartbeatKey = "heartbeat"
)

type ClusterManager struct {
	listeners *list.List
	config    Config
	kv        kv.Kvdb
	status    api.Status
	nodeCache map[string]api.Node // Cached info on the nodes in the cluster.
	docker    *dockerclient.DockerClient
	g         gossip.Gossiper
}

func externalIp() (string, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ip = ip.To4()
			if ip == nil {
				continue // not an ipv4 address
			}
			return ip.String(), nil
		}
	}

	return "", errors.New("Node not connected to the network.")
}

func (c *ClusterManager) AddEventListener(listener ClusterListener) error {
	log.Printf("Adding cluster event listener: %s", listener.String())
	c.listeners.PushBack(listener)
	return nil
}

func (c *ClusterManager) getSelf() *api.Node {
	var node = api.Node{}
	s := systemutils.New()

	// Get physical node info.
	node.Id = c.config.NodeId
	node.Status = api.StatusOk
	node.Ip, _ = externalIp()

	node.Cpu, _, _ = s.CpuUsage()
	node.Memory = s.MemUsage()
	node.Luns = s.Luns()

	node.Timestamp = time.Now()

	// Get containers running on this system.
	node.Containers, _ = c.docker.ListContainers(true, true, "")

	return &node
}

func (c *ClusterManager) initNode(db *Database) (*api.Node, bool) {
	node := c.getSelf()
	c.nodeCache[node.Id] = *node

	_, exists := db.NodeEntries[node.Id]

	// Add us into the database.
	db.NodeEntries[c.config.NodeId] = NodeEntry{Id: node.Id, Ip: node.Ip}

	log.Infof("Node %s joining cluster... \n\tCluster ID: %s\n\tIP: %s",
		c.config.NodeId, c.config.ClusterId, node.Ip)

	return node, exists
}

// Initialize node and alert listeners that we are joining the cluster.
func (c *ClusterManager) joinCluster(db *Database, self *api.Node, exist bool) error {
	var err error

	// If I am already in the cluster map, don't add me again.
	if exist {
		goto found
	}

	// Alert all listeners that we are a new node joining an existing cluster.
	for e := c.listeners.Front(); e != nil; e = e.Next() {
		err = e.Value.(ClusterListener).Init(self, db)
		if err != nil {
			log.Warnf("Failed to initialize %s: %v\n",
				e.Value.(ClusterListener).String(), err)
			goto done
		}
	}

found:
	// Alert all listeners that we are joining the cluster.
	for e := c.listeners.Front(); e != nil; e = e.Next() {
		err = e.Value.(ClusterListener).Join(self, db)
		if err != nil {
			log.Warnf("Failed to initialize %s: %v\n",
				e.Value.(ClusterListener).String(), err)
			goto done
		}
	}

	for id, n := range db.NodeEntries {
		if id != c.config.NodeId {
			// Check to see if the IP is the same.  If it is, then we have a stale entry.
			if n.Ip == self.Ip {
				log.Warnf("Warning, Detected node %s with the same IP %s in the database.  Will not connect to this node.",
					id, n.Ip)
			} else {
				// Gossip with this node.
				c.g.AddNode(n.Ip + ":9002")

				// Assume this node is OK.  We will catch any problems during heartbeating.
				c.nodeCache[id] = api.Node{Status: api.StatusOk, Timestamp: time.Now()}
			}
		}
	}

done:
	return err
}

func (c *ClusterManager) initCluster(db *Database, self *api.Node, exist bool) error {
	err := error(nil)

	// Alert all listeners that we are initializing a new cluster.
	for e := c.listeners.Front(); e != nil; e = e.Next() {
		err = e.Value.(ClusterListener).ClusterInit(self, db)
		if err != nil {
			log.Printf("Failed to initialize %s\n",
				e.Value.(ClusterListener).String())
			goto done
		}
	}

	err = c.joinCluster(db, self, exist)
	if err != nil {
		log.Printf("Failed to join new cluster\n")
		goto done
	}

done:
	return err
}

func (c *ClusterManager) heartBeat() {
	for {
		node := c.getSelf()
		c.nodeCache[node.Id] = *node

		c.g.UpdateSelf(heartbeatKey, *node)

		// Process heartbeats from other nodes...
		gossipValues := c.g.GetStoreKeyValue(heartbeatKey)

		for _, nodeInfo := range gossipValues {
			n, ok := nodeInfo.Value.(api.Node)

			if !ok {
				log.Warn("Received a bad broadcast packet: %v", nodeInfo.Value)
				continue
			}

			if n.Id == node.Id {
				continue
			}

			_, ok = c.nodeCache[n.Id]
			if ok {
				if n.Status != api.StatusOk {
					log.Warn("Detected node ", n.Id, " to be unhealthy.")

					for e := c.listeners.Front(); e != nil; e = e.Next() {
						err := e.Value.(ClusterListener).Update(&n)
						if err != nil {
							log.Warn("Failed to notify ", e.Value.(ClusterListener).String())
						}
					}

					delete(c.nodeCache, n.Id)
				} else if time.Since(n.Timestamp) > 10*time.Second {
					log.Warn("Detected node ", n.Id, " to be offline.")

					n.Status = api.StatusOffline

					for e := c.listeners.Front(); e != nil; e = e.Next() {
						err := e.Value.(ClusterListener).Update(&n)
						if err != nil {
							log.Warn("Failed to notify ", e.Value.(ClusterListener).String())
						}
					}

					delete(c.nodeCache, n.Id)
				}
			} else if time.Since(n.Timestamp) <= 10*time.Second {
				// A node joined the cluster.
				log.Warn("Detected node ", n.Id, " to be online.")

				c.nodeCache[n.Id] = n
				for e := c.listeners.Front(); e != nil; e = e.Next() {
					err := e.Value.(ClusterListener).Update(&n)
					if err != nil {
						log.Warn("Failed to notify ", e.Value.(ClusterListener).String())
					}
				}
			}
		}

		// Process stale entries in our local cache.
		for _, n := range c.nodeCache {
			if n.Id == node.Id {
				continue
			}

			if time.Since(n.Timestamp) > 60*time.Second {
				log.Warn("Detected node ", n.Id, " to be offline.")

				n.Status = api.StatusOffline

				for e := c.listeners.Front(); e != nil; e = e.Next() {
					err := e.Value.(ClusterListener).Update(&n)
					if err != nil {
						log.Warn("Failed to notify ", e.Value.(ClusterListener).String())
					}
				}

				delete(c.nodeCache, n.Id)
			}
		}

		time.Sleep(2 * time.Second)
	}
}

func (c *ClusterManager) Start() error {
	log.Info("Cluster manager starting...")
	kvdb := kv.Instance()

	// Start the gossip protocol.
	// XXX make the port configurable.
	gob.Register(api.Node{})
	c.g = gossip.New("0.0.0.0:9002", gossiptypes.NodeId(c.config.NodeId))
	c.g.SetGossipInterval(2 * time.Second)

	kvlock, err := kvdb.Lock("cluster/lock", 60)
	if err != nil {
		log.Panic("Fatal, Unable to obtain cluster lock.", err)
	}

	db, err := readDatabase()
	if err != nil {
		log.Panic(err)
	}

	if db.Status == api.StatusInit {
		log.Info("Will initialize a new cluster.")

		c.status = api.StatusOk
		db.Status = api.StatusOk
		self, _ := c.initNode(&db)

		// Update the new state of the cluster in the KV Database
		err = writeDatabase(&db)
		if err != nil {
			log.Panic(err)
		}

		err = kvdb.Unlock(kvlock)
		if err != nil {
			log.Panic("Fatal, unable to unlock cluster... Did something take too long to initialize?", err)
		}

		err = c.initCluster(&db, self, false)
		if err != nil {
			log.Panic(err)
		}
	} else if db.Status&api.StatusOk > 0 {
		log.Info("Cluster state is OK... Joining the cluster.")

		c.status = api.StatusOk
		self, exist := c.initNode(&db)

		err = writeDatabase(&db)
		if err != nil {
			log.Panic(err)
		}

		err = kvdb.Unlock(kvlock)
		if err != nil {
			log.Panic("Fatal, unable to unlock cluster... Did something take too long to initialize?", err)
		}

		err = c.joinCluster(&db, self, exist)
		if err != nil {
			log.Panic(err)
		}
	} else {
		err = kvdb.Unlock(kvlock)
		err = errors.New("Fatal, Cluster is in an unexpected state.")
		log.Panic(err)
	}

	// Start heartbeating to other nodes.
	go c.heartBeat()

	return nil
}

func (c *ClusterManager) Init() error {
	docker, err := dockerclient.NewDockerClient(dockerHost, nil)
	if err != nil {
		log.Printf("Fatal, could not connect to Docker.")
		return err
	}

	c.listeners = list.New()
	c.nodeCache = make(map[string]api.Node)
	c.docker = docker

	return nil
}

func (c *ClusterManager) Enumerate() (api.Cluster, error) {
	i := 0

	cluster := api.Cluster{Id: c.config.ClusterId, Status: c.status}
	cluster.Nodes = make([]api.Node, len(c.nodeCache))
	for _, n := range c.nodeCache {
		cluster.Nodes[i] = n
		i++
	}

	return cluster, nil
}

func (c *ClusterManager) Remove(nodes []api.Node) error {
	// TODO
	return nil
}

func (c *ClusterManager) Shutdown(cluster bool, nodes []api.Node) error {
	// TODO
	return nil
}
<|endoftext|>


<commit_before>package console

import (
	"fmt"
	"github.com/getcarina/carina/common"
	"github.com/pkg/errors"
	"os"
	"strconv"
	"strings"
	"text/tabwriter"
)

// WriteRow writes a row of tabular data to the console
func WriteRow(fields []string) {
	output := new(tabwriter.Writer)
	output.Init(os.Stdout, 20, 1, 3, ' ', 0)

	s := strings.Join(fields, "\t")
	b := []byte(s + "\n")
	_, err := output.Write(b)
	if err == nil {
		_ = output.Flush()
	} else {
		err = errors.Wrap(err, "Unable to write to console.")
		fmt.Println(err.Error())
	}
}

// WriteClusterHeader writes the cluster table header to the console
func WriteClusterHeader() {
	headerFields := []string{
		"Name",
		"Type",
		"Flavor",
		"Nodes",
		"Status",
	}
	WriteRow(headerFields)
}

// WriteCluster writes the cluster data to the console
func WriteCluster(cluster common.Cluster) {
	fields := []string{
		cluster.GetName(),
		cluster.GetCOE(),
		cluster.GetFlavor(),
		strconv.Itoa(cluster.GetNodes()),
		cluster.GetStatus(),
	}
	WriteRow(fields)
}
<commit_msg>Hide cluster flavor<commit_after>package console

import (
	"fmt"
	"github.com/getcarina/carina/common"
	"github.com/pkg/errors"
	"os"
	"strconv"
	"strings"
	"text/tabwriter"
)

// WriteRow writes a row of tabular data to the console
func WriteRow(fields []string) {
	output := new(tabwriter.Writer)
	output.Init(os.Stdout, 20, 1, 3, ' ', 0)

	s := strings.Join(fields, "\t")
	b := []byte(s + "\n")
	_, err := output.Write(b)
	if err == nil {
		_ = output.Flush()
	} else {
		err = errors.Wrap(err, "Unable to write to console.")
		fmt.Println(err.Error())
	}
}

// WriteClusterHeader writes the cluster table header to the console
func WriteClusterHeader() {
	headerFields := []string{
		"Name",
		"Type",
		"Nodes",
		"Status",
	}
	WriteRow(headerFields)
}

// WriteCluster writes the cluster data to the console
func WriteCluster(cluster common.Cluster) {
	fields := []string{
		cluster.GetName(),
		cluster.GetCOE(),
		strconv.Itoa(cluster.GetNodes()),
		cluster.GetStatus(),
	}
	WriteRow(fields)
}
<|endoftext|>


<commit_before>package dijkstra

import "testing"
import "reflect"

func TestDijkstra(t *testing.T) {
	graph := [][][2]int{
		{ // 0
			{1, 7},
			{2, 9},
			{5, 14},
		},
		{ // 1
			{0, 7},
			{2, 10},
			{3, 15},
		},
		{ // 2
			{0, 9},
			{1, 10},
			{5, 2},
			{3, 11},
		},
		{ // 3
			{1, 15},
			{4, 6},
			{2, 11},
		},
		{ // 4
			{5, 9},
			{3, 6},
		},
		{ // 5
			{0, 14},
			{2, 2},
			{4, 9},
		},
	}

	result := Dijkstra(graph, 0)
	expected := []int{0, 7, 9, 20, 20, 11}

	if !reflect.DeepEqual(expected, result) {
		t.Fatalf("Expected: %v\nGot: %v", expected, result)
	}

	result = Dijkstra(graph, 1)
	expected = []int{7, 0, 10, 15, 21, 12}

	if !reflect.DeepEqual(expected, result) {
		t.Fatalf("Expected: %v\nGot: %v", expected, result)
	}

}
<commit_msg>formatting change<commit_after>package dijkstra

import (
	"reflect"
	"testing"
)

func TestDijkstra(t *testing.T) {
	graph := [][][2]int{
		{ // 0
			{1, 7},
			{2, 9},
			{5, 14},
		},
		{ // 1
			{0, 7},
			{2, 10},
			{3, 15},
		},
		{ // 2
			{0, 9},
			{1, 10},
			{5, 2},
			{3, 11},
		},
		{ // 3
			{1, 15},
			{4, 6},
			{2, 11},
		},
		{ // 4
			{5, 9},
			{3, 6},
		},
		{ // 5
			{0, 14},
			{2, 2},
			{4, 9},
		},
	}

	result := Dijkstra(graph, 0)
	expected := []int{0, 7, 9, 20, 20, 11}

	if !reflect.DeepEqual(expected, result) {
		t.Fatalf("Expected: %v\nGot: %v", expected, result)
	}

	result = Dijkstra(graph, 1)
	expected = []int{7, 0, 10, 15, 21, 12}

	if !reflect.DeepEqual(expected, result) {
		t.Fatalf("Expected: %v\nGot: %v", expected, result)
	}

}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"net/http"
	_ "net/http/pprof"
	"regexp"
	"strconv"
	"sync"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/log"

	consul_api "github.com/hashicorp/consul/api"
	consul "github.com/hashicorp/consul/consul/structs"
)

const (
	namespace = "consul"
)

// Exporter collects Consul stats from the given server and exports them using
// the prometheus metrics package.
type Exporter struct {
	URI string
	sync.RWMutex

	up, clusterServers                                            prometheus.Gauge
	nodeCount, serviceCount                                       prometheus.Counter
	serviceNodesTotal, serviceNodesHealthy, nodeChecks, keyValues *prometheus.GaugeVec
	client                                                        *consul_api.Client
	kvPrefix                                                      string
	kvFilter                                                      *regexp.Regexp
}

// NewExporter returns an initialized Exporter.
func NewExporter(uri string, kvPrefix string, kvFilter string) *Exporter {
	// Set up our Consul client connection.
	client, _ := consul_api.NewClient(&consul_api.Config{
		Address: uri,
	})

	// Init our exporter.
	return &Exporter{
		URI: uri,
		up: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "up",
			Help:      "Was the last query of Consul successful.",
		}),

		clusterServers: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "raft_peers",
			Help:      "How many peers (servers) are in the Raft cluster.",
		}),

		nodeCount: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "serf_lan_members",
			Help:      "How many members are in the cluster.",
		}),

		serviceCount: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "catalog_services",
			Help:      "How many services are in the cluster.",
		}),

		serviceNodesTotal: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "catalog_service_nodes",
				Help:      "Number of nodes currently registered for this service.",
			},
			[]string{"service"},
		),

		serviceNodesHealthy: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "catalog_service_node_healthy",
				Help:      "Is this service healthy on this node?",
			},
			[]string{"service", "node"},
		),

		nodeChecks: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "agent_check",
				Help:      "Is this check passing on this node?",
			},
			[]string{"check", "node"},
		),

		keyValues: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "catalog_kv",
				Help:      "The values for selected keys in Consul's key/value catalog. Keys with non-numeric values are omitted.",
			},
			[]string{"key"},
		),

		client:   client,
		kvPrefix: kvPrefix,
		kvFilter: regexp.MustCompile(kvFilter),
	}
}

// Describe describes all the metrics ever exported by the Consul exporter. It
// implements prometheus.Collector.
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	ch <- e.up.Desc()
	ch <- e.nodeCount.Desc()
	ch <- e.serviceCount.Desc()
	ch <- e.clusterServers.Desc()

	e.serviceNodesTotal.Describe(ch)
	e.serviceNodesHealthy.Describe(ch)
	e.keyValues.Describe(ch)
}

// Collect fetches the stats from configured Consul location and delivers them
// as Prometheus metrics. It implements prometheus.Collector.
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
	services := make(chan []*consul_api.ServiceEntry)
	checks := make(chan []*consul_api.HealthCheck)

	go e.queryClient(services, checks)

	e.Lock() // To protect metrics from concurrent collects.
	defer e.Unlock()

	// Reset metrics.
	e.serviceNodesTotal.Reset()
	e.serviceNodesHealthy.Reset()
	e.nodeChecks.Reset()

	e.setMetrics(services, checks)

	ch <- e.up
	ch <- e.clusterServers
	ch <- e.nodeCount
	ch <- e.serviceCount

	e.serviceNodesTotal.Collect(ch)
	e.serviceNodesHealthy.Collect(ch)
	e.nodeChecks.Collect(ch)

	e.keyValues.Reset()
	e.setKeyValues()
	e.keyValues.Collect(ch)
}

func (e *Exporter) queryClient(services chan<- []*consul_api.ServiceEntry, checks chan<- []*consul_api.HealthCheck) {
	defer close(services)
	defer close(checks)

	// How many peers are in the Consul cluster?
	peers, err := e.client.Status().Peers()
	if err != nil {
		e.up.Set(0)
		log.Errorf("Query error is %v", err)
		return
	}

	// We'll use peers to decide that we're up.
	e.up.Set(1)
	e.clusterServers.Set(float64(len(peers)))

	// How many nodes are registered?
	nodes, _, err := e.client.Catalog().Nodes(&consul_api.QueryOptions{})
	if err != nil {
		// FIXME: How should we handle a partial failure like this?
	} else {
		e.nodeCount.Set(float64(len(nodes)))
	}

	// Query for the full list of services.
	serviceNames, _, err := e.client.Catalog().Services(&consul_api.QueryOptions{})
	if err != nil {
		// FIXME: How should we handle a partial failure like this?
		return
	}

	e.serviceCount.Set(float64(len(serviceNames)))

	for s := range serviceNames {
		sEntries, _, err := e.client.Health().Service(s, "", false, &consul_api.QueryOptions{})
		if err != nil {
			log.Errorf("Failed to query service health: %v", err)
			continue
		}
		services <- sEntries
	}

	cEntries, _, err := e.client.Health().State("any", &consul_api.QueryOptions{})
	if err != nil {
		log.Errorf("Failed to query service health: %v", err)
	} else {
		checks <- cEntries
	}
}

func (e *Exporter) setMetrics(services <-chan []*consul_api.ServiceEntry, checks <-chan []*consul_api.HealthCheck) {
	// Each service will be an array of ServiceEntry structs.
	running := true
	for running {
		select {
		case service, b := <-services:
			running = b
			if len(service) == 0 {
				// Not sure this should ever happen, but catch it just in case...
				continue
			}

			// We should have one ServiceEntry per node, so use that for total nodes.
			e.serviceNodesTotal.WithLabelValues(service[0].Service.Service).Set(float64(len(service)))

			for _, entry := range service {
				// We have a Node, a Service, and one or more Checks. Our
				// service-node combo is passing if all checks have a `status`
				// of "passing."

				passing := 1

				for _, hc := range entry.Checks {
					if hc.Status != consul.HealthPassing {
						passing = 0
						break
					}
				}

				log.Infof("%v/%v status is %v", entry.Service.Service, entry.Node.Node, passing)

				e.serviceNodesHealthy.WithLabelValues(entry.Service.Service, entry.Node.Node).Set(float64(passing))
			}
		case entry, b := <-checks:
			running = b
			for _, hc := range entry {
				passing := 1
				if hc.ServiceID == "" {
					if hc.Status != consul.HealthPassing {
						passing = 0
					}
					e.nodeChecks.WithLabelValues(hc.CheckID, hc.Node).Set(float64(passing))
					log.Infof("CHECKS: %v/%v status is %d", hc.CheckID, hc.Node, passing)
				}
			}
		}
	}

}

func (e *Exporter) setKeyValues() {
	if e.kvPrefix == "" {
		return
	}

	kv := e.client.KV()
	pairs, _, err := kv.List(e.kvPrefix, &consul_api.QueryOptions{})
	if err != nil {
		log.Errorf("Error fetching key/values: %s", err)
		return
	}

	for _, pair := range pairs {
		if e.kvFilter.MatchString(pair.Key) {
			val, err := strconv.ParseFloat(string(pair.Value), 64)
			if err == nil {
				e.keyValues.WithLabelValues(pair.Key).Set(val)
			}
		}
	}
}

func main() {
	var (
		listenAddress = flag.String("web.listen-address", ":9107", "Address to listen on for web interface and telemetry.")
		metricsPath   = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
		consulServer  = flag.String("consul.server", "localhost:8500", "HTTP API address of a Consul server or agent.")
		kvPrefix      = flag.String("kv.prefix", "", "Prefix from which to expose key/value pairs.")
		kvFilter      = flag.String("kv.filter", ".*", "Regex that determines which keys to expose.")
	)
	flag.Parse()

	exporter := NewExporter(*consulServer, *kvPrefix, *kvFilter)
	prometheus.MustRegister(exporter)

	log.Infof("Starting Server: %s", *listenAddress)
	http.Handle(*metricsPath, prometheus.Handler())
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
             <title>Consul Exporter</title>

Consul Exporter

<a href='` + *metricsPath + `'>Metrics

             `))
	})
	log.Fatal(http.ListenAndServe(*listenAddress, nil))
}
<commit_msg>Fix and simplify collect code<commit_after>package main

import (
	"flag"
	"net/http"
	_ "net/http/pprof"
	"regexp"
	"strconv"
	"sync"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/log"

	consul_api "github.com/hashicorp/consul/api"
	consul "github.com/hashicorp/consul/consul/structs"
)

const (
	namespace = "consul"
)

// Exporter collects Consul stats from the given server and exports them using
// the prometheus metrics package.
type Exporter struct {
	URI string
	sync.RWMutex

	up, clusterServers                                            prometheus.Gauge
	nodeCount, serviceCount                                       prometheus.Counter
	serviceNodesTotal, serviceNodesHealthy, nodeChecks, keyValues *prometheus.GaugeVec
	client                                                        *consul_api.Client
	kvPrefix                                                      string
	kvFilter                                                      *regexp.Regexp
}

// NewExporter returns an initialized Exporter.
func NewExporter(uri string, kvPrefix string, kvFilter string) *Exporter {
	// Set up our Consul client connection.
	client, _ := consul_api.NewClient(&consul_api.Config{
		Address: uri,
	})

	// Init our exporter.
	return &Exporter{
		URI: uri,
		up: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "up",
			Help:      "Was the last query of Consul successful.",
		}),

		clusterServers: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "raft_peers",
			Help:      "How many peers (servers) are in the Raft cluster.",
		}),

		nodeCount: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "serf_lan_members",
			Help:      "How many members are in the cluster.",
		}),

		serviceCount: prometheus.NewGauge(prometheus.GaugeOpts{
			Namespace: namespace,
			Name:      "catalog_services",
			Help:      "How many services are in the cluster.",
		}),

		serviceNodesTotal: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "catalog_service_nodes",
				Help:      "Number of nodes currently registered for this service.",
			},
			[]string{"service"},
		),

		serviceNodesHealthy: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "catalog_service_node_healthy",
				Help:      "Is this service healthy on this node?",
			},
			[]string{"service", "node"},
		),

		nodeChecks: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "agent_check",
				Help:      "Is this check passing on this node?",
			},
			[]string{"check", "node"},
		),

		keyValues: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "catalog_kv",
				Help:      "The values for selected keys in Consul's key/value catalog. Keys with non-numeric values are omitted.",
			},
			[]string{"key"},
		),

		client:   client,
		kvPrefix: kvPrefix,
		kvFilter: regexp.MustCompile(kvFilter),
	}
}

// Describe describes all the metrics ever exported by the Consul exporter. It
// implements prometheus.Collector.
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	ch <- e.up.Desc()
	ch <- e.nodeCount.Desc()
	ch <- e.serviceCount.Desc()
	ch <- e.clusterServers.Desc()

	e.serviceNodesTotal.Describe(ch)
	e.serviceNodesHealthy.Describe(ch)
	e.keyValues.Describe(ch)
}

// Collect fetches the stats from configured Consul location and delivers them
// as Prometheus metrics. It implements prometheus.Collector.
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
	e.Lock() // To protect metrics from concurrent collects.
	defer e.Unlock()

	// Reset metrics.
	e.serviceNodesTotal.Reset()
	e.serviceNodesHealthy.Reset()
	e.nodeChecks.Reset()

	e.collect()

	ch <- e.up
	ch <- e.clusterServers
	ch <- e.nodeCount
	ch <- e.serviceCount

	e.serviceNodesTotal.Collect(ch)
	e.serviceNodesHealthy.Collect(ch)
	e.nodeChecks.Collect(ch)

	e.keyValues.Reset()
	e.setKeyValues()
	e.keyValues.Collect(ch)
}

func (e *Exporter) collect() {
	// How many peers are in the Consul cluster?
	peers, err := e.client.Status().Peers()
	if err != nil {
		e.up.Set(0)
		log.Errorf("Query error is %v", err)
		return
	}

	// We'll use peers to decide that we're up.
	e.up.Set(1)
	e.clusterServers.Set(float64(len(peers)))

	// How many nodes are registered?
	nodes, _, err := e.client.Catalog().Nodes(&consul_api.QueryOptions{})
	if err != nil {
		// FIXME: How should we handle a partial failure like this?
	} else {
		e.nodeCount.Set(float64(len(nodes)))
	}

	// Query for the full list of services.
	serviceNames, _, err := e.client.Catalog().Services(&consul_api.QueryOptions{})
	if err != nil {
		// FIXME: How should we handle a partial failure like this?
		return
	}

	e.serviceCount.Set(float64(len(serviceNames)))

	for s := range serviceNames {
		service, _, err := e.client.Health().Service(s, "", false, &consul_api.QueryOptions{})
		if err != nil {
			log.Errorf("Failed to query service health: %v", err)
			continue
		}

		if len(service) == 0 {
			// Not sure this should ever happen, but catch it just in case...
			continue
		}

		// We should have one ServiceEntry per node, so use that for total nodes.
		e.serviceNodesTotal.WithLabelValues(service[0].Service.Service).Set(float64(len(service)))

		for _, entry := range service {
			// We have a Node, a Service, and one or more Checks. Our
			// service-node combo is passing if all checks have a `status`
			// of "passing."
			passing := 1
			for _, hc := range entry.Checks {
				if hc.Status != consul.HealthPassing {
					passing = 0
					break
				}
			}
			log.Infof("%v/%v status is %v", entry.Service.Service, entry.Node.Node, passing)
			e.serviceNodesHealthy.WithLabelValues(entry.Service.Service, entry.Node.Node).Set(float64(passing))
		}
	}

	checks, _, err := e.client.Health().State("any", &consul_api.QueryOptions{})
	if err != nil {
		log.Errorf("Failed to query service health: %v", err)
		return
	}

	for _, hc := range checks {
		if hc.ServiceID == "" {
			passing := 1
			if hc.Status != consul.HealthPassing {
				passing = 0
			}
			log.Infof("CHECKS: %v/%v status is %d", hc.CheckID, hc.Node, passing)
			e.nodeChecks.WithLabelValues(hc.CheckID, hc.Node).Set(float64(passing))
		}
	}
}

func (e *Exporter) setKeyValues() {
	if e.kvPrefix == "" {
		return
	}

	kv := e.client.KV()
	pairs, _, err := kv.List(e.kvPrefix, &consul_api.QueryOptions{})
	if err != nil {
		log.Errorf("Error fetching key/values: %s", err)
		return
	}

	for _, pair := range pairs {
		if e.kvFilter.MatchString(pair.Key) {
			val, err := strconv.ParseFloat(string(pair.Value), 64)
			if err == nil {
				e.keyValues.WithLabelValues(pair.Key).Set(val)
			}
		}
	}
}

func main() {
	var (
		listenAddress = flag.String("web.listen-address", ":9107", "Address to listen on for web interface and telemetry.")
		metricsPath   = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
		consulServer  = flag.String("consul.server", "localhost:8500", "HTTP API address of a Consul server or agent.")
		kvPrefix      = flag.String("kv.prefix", "", "Prefix from which to expose key/value pairs.")
		kvFilter      = flag.String("kv.filter", ".*", "Regex that determines which keys to expose.")
	)
	flag.Parse()

	exporter := NewExporter(*consulServer, *kvPrefix, *kvFilter)
	prometheus.MustRegister(exporter)

	log.Infof("Starting Server: %s", *listenAddress)
	http.Handle(*metricsPath, prometheus.Handler())
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
             <title>Consul Exporter</title>

Consul Exporter

<a href='` + *metricsPath + `'>Metrics

             `))
	})
	log.Fatal(http.ListenAndServe(*listenAddress, nil))
}
<|endoftext|>


<commit_before>/*
 * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc.
 *
 * 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 cmd

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"net/http"
	"strconv"
	"time"
)

// Returns a hexadecimal representation of time at the
// time response is sent to the client.
func mustGetRequestID(t time.Time) string {
	return fmt.Sprintf("%X", t.UnixNano())
}

// Write http common headers
func setCommonHeaders(w http.ResponseWriter) {
	// Set unique request ID for each reply.
	w.Header().Set(responseRequestIDKey, mustGetRequestID(UTCNow()))
	w.Header().Set("Server", globalServerUserAgent)
	w.Header().Set("X-Amz-Bucket-Region", serverConfig.GetRegion())
	w.Header().Set("Accept-Ranges", "bytes")
}

// Encodes the response headers into XML format.
func encodeResponse(response interface{}) []byte {
	var bytesBuffer bytes.Buffer
	bytesBuffer.WriteString(xml.Header)
	e := xml.NewEncoder(&bytesBuffer)
	e.Encode(response)
	return bytesBuffer.Bytes()
}

// Write object header
func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, contentRange *httpRange) {
	// set common headers
	setCommonHeaders(w)

	// Set content length.
	w.Header().Set("Content-Length", strconv.FormatInt(objInfo.Size, 10))

	// Set last modified time.
	lastModified := objInfo.ModTime.UTC().Format(http.TimeFormat)
	w.Header().Set("Last-Modified", lastModified)

	// Set Etag if available.
	if objInfo.ETag != "" {
		w.Header().Set("ETag", "\""+objInfo.ETag+"\"")
	}

	// Set all other user defined metadata.
	for k, v := range objInfo.UserDefined {
		w.Header().Set(k, v)
	}

	// for providing ranged content
	if contentRange != nil && contentRange.offsetBegin > -1 {
		// Override content-length
		w.Header().Set("Content-Length", strconv.FormatInt(contentRange.getLength(), 10))
		w.Header().Set("Content-Range", contentRange.String())
		w.WriteHeader(http.StatusPartialContent)
	}
}
<commit_msg>api: No need to set x-amz-bucket-region if region is empty. (#4586)<commit_after>/*
 * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc.
 *
 * 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 cmd

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"net/http"
	"strconv"
	"time"
)

// Returns a hexadecimal representation of time at the
// time response is sent to the client.
func mustGetRequestID(t time.Time) string {
	return fmt.Sprintf("%X", t.UnixNano())
}

// Write http common headers
func setCommonHeaders(w http.ResponseWriter) {
	// Set unique request ID for each reply.
	w.Header().Set(responseRequestIDKey, mustGetRequestID(UTCNow()))
	w.Header().Set("Server", globalServerUserAgent)
	// Set `x-amz-bucket-region` only if region is set on the server
	// by default minio uses an empty region.
	if region := serverConfig.GetRegion(); region != "" {
		w.Header().Set("X-Amz-Bucket-Region", region)
	}
	w.Header().Set("Accept-Ranges", "bytes")
}

// Encodes the response headers into XML format.
func encodeResponse(response interface{}) []byte {
	var bytesBuffer bytes.Buffer
	bytesBuffer.WriteString(xml.Header)
	e := xml.NewEncoder(&bytesBuffer)
	e.Encode(response)
	return bytesBuffer.Bytes()
}

// Write object header
func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, contentRange *httpRange) {
	// set common headers
	setCommonHeaders(w)

	// Set content length.
	w.Header().Set("Content-Length", strconv.FormatInt(objInfo.Size, 10))

	// Set last modified time.
	lastModified := objInfo.ModTime.UTC().Format(http.TimeFormat)
	w.Header().Set("Last-Modified", lastModified)

	// Set Etag if available.
	if objInfo.ETag != "" {
		w.Header().Set("ETag", "\""+objInfo.ETag+"\"")
	}

	// Set all other user defined metadata.
	for k, v := range objInfo.UserDefined {
		w.Header().Set(k, v)
	}

	// for providing ranged content
	if contentRange != nil && contentRange.offsetBegin > -1 {
		// Override content-length
		w.Header().Set("Content-Length", strconv.FormatInt(contentRange.getLength(), 10))
		w.Header().Set("Content-Range", contentRange.String())
		w.WriteHeader(http.StatusPartialContent)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"
	"reflect"

	"github.com/docker/go-units"
	"github.com/go-debos/debos"
	"github.com/go-debos/debos/recipe"
	"github.com/go-debos/fakemachine"
	"github.com/jessevdk/go-flags"
)

func checkError(context *debos.DebosContext, err error, a debos.Action, stage string) int {
	if err == nil {
		return 0
	}

	context.State = debos.Failed
	log.Printf("Action `%s` failed at stage %s, error: %s", a, stage, err)
	debos.DebugShell(*context)
	return 1
}

func do_run(r recipe.Recipe, context *debos.DebosContext) int {
	for _, a := range r.Actions {
		err := a.Run(context)

		// This does not stop the call of stacked Cleanup methods for other Actions
		// Stack Cleanup methods
		defer a.Cleanup(context)

		// Check the state of Run method
		if exitcode := checkError(context, err, a, "Run"); exitcode != 0 {
			return exitcode
		}
	}

	return 0
}

func warnLocalhost(variable string, value string) {
	message := `WARNING: Environment variable %[1]s contains a reference to
		    localhost. This may not work when running from fakemachine.
		    Consider using an address that is valid on your network.`

	if strings.Contains(value, "localhost") ||
	   strings.Contains(value, "127.0.0.1") ||
	   strings.Contains(value, "::1") {
		log.Printf(message, variable)
	}
}

func DumpActionStruct(iface interface{}) string {
	var a []string

	s := reflect.ValueOf(iface)
	t := reflect.TypeOf(iface)

	for i := 0; i < t.NumField(); i++ {
		f := s.Field(i)
		// Dump only exported entries
		if f.CanInterface() {
			str := fmt.Sprintf("%s: %v", s.Type().Field(i).Name, f.Interface())
			a = append(a, str)
		}
	}

	return strings.Join(a, ", ")
}

func DumpActionFields(iface interface{}) {
	entries := reflect.ValueOf(iface).Elem()

	for i := 0; i < entries.NumField(); i++ {
		f := entries.Field(i)
		// Dump only exported entries
		if f.CanInterface() {
			switch f.Kind() {
			case reflect.Struct:
				// BaseAction is the only struct embbed in Action ActionFields
				// dump it at the same level
				log.Printf("  - %s", DumpActionStruct(f.Interface()))

			case reflect.Slice:
				s := reflect.ValueOf(f.Interface())
				if s.Index(0).Kind() == reflect.Struct {
					log.Printf("    %s:\n", entries.Type().Field(i).Name)
					for j := 0; j < s.Len(); j++ {
						if s.Index(j).Kind() == reflect.Struct {
							log.Printf("    { %s }", DumpActionStruct(s.Index(j).Interface()))
						}
					}
				} else {
					log.Printf("    %s: %s\n", entries.Type().Field(i).Name, f)
				}

			default:
				log.Printf("    %s: %v\n", entries.Type().Field(i).Name, f.Interface())
			}
		}
	}
}

func main() {
	var context debos.DebosContext
	var options struct {
		ArtifactDir   string            `long:"artifactdir" description:"Directory for packed archives and ostree repositories (default: current directory)"`
		InternalImage string            `long:"internal-image" hidden:"true"`
		TemplateVars  map[string]string `short:"t" long:"template-var" description:"Template variables (use -t VARIABLE:VALUE syntax)"`
		DebugShell    bool              `long:"debug-shell" description:"Fall into interactive shell on error"`
		Shell         string            `short:"s" long:"shell" description:"Redefine interactive shell binary (default: bash)" optionsl:"" default:"/bin/bash"`
		ScratchSize   string            `long:"scratchsize" description:"Size of disk backed scratch space"`
		CPUs          int               `short:"c" long:"cpus" description:"Number of CPUs to use for build VM (default: 2)"`
		Memory        string            `short:"m" long:"memory" description:"Amount of memory for build VM (default: 2048MB)"`
		ShowBoot      bool              `long:"show-boot" description:"Show boot/console messages from the fake machine"`
		EnvironVars   map[string]string `short:"e" long:"environ-var" description:"Environment variables (use -e VARIABLE:VALUE syntax)"`
		Verbose       bool              `short:"v" long:"verbose" description:"Verbose output"`
	}

	// These are the environment variables that will be detected on the
	// host and propagated to fakemachine. These are listed lower case, but
	// they are detected and configured in both lower case and upper case.
	var environ_vars = [...]string {
		"http_proxy",
		"https_proxy",
		"ftp_proxy",
		"rsync_proxy",
		"all_proxy",
		"no_proxy",
	}

	var exitcode int = 0
	// Allow to run all deferred calls prior to os.Exit()
	defer func() {
		os.Exit(exitcode)
	}()

	parser := flags.NewParser(&options, flags.Default)
	args, err := parser.Parse()

	if err != nil {
		flagsErr, ok := err.(*flags.Error)
		if ok && flagsErr.Type == flags.ErrHelp {
			return
		} else {
			fmt.Printf("%v\n", flagsErr)
			exitcode = 1
			return
		}
	}

	if len(args) != 1 {
		log.Println("No recipe given!")
		exitcode = 1
		return
	}

	// Set interactive shell binary only if '--debug-shell' options passed
	if options.DebugShell {
		context.DebugShell = options.Shell
	}

	file := args[0]
	file = debos.CleanPath(file)

	r := recipe.Recipe{}
	if _, err := os.Stat(file); os.IsNotExist(err) {
		log.Println(err)
		exitcode = 1
		return
	}
	if err := r.Parse(file, options.TemplateVars); err != nil {
		log.Println(err)
		exitcode = 1
		return
	}

	if options.Verbose {
		log.Printf("Architecture: %s\n", r.Architecture)
		log.Println("Actions:")
		for _, a := range r.Actions {
			actions := reflect.ValueOf(&a).Elem()
			for i := 0; i < actions.NumField(); i++ {
				DumpActionFields(actions.Field(i).Interface())
			}
		}
	}

	/* If fakemachine is supported the outer fake machine will never use the
	 * scratchdir, so just set it to /scratch as a dummy to prevent the
	 * outer debos creating a temporary direction */
	if fakemachine.InMachine() || fakemachine.Supported() {
		context.Scratchdir = "/scratch"
	} else {
		log.Printf("fakemachine not supported, running on the host!")
		cwd, _ := os.Getwd()
		context.Scratchdir, err = ioutil.TempDir(cwd, ".debos-")
		defer os.RemoveAll(context.Scratchdir)
	}

	context.Rootdir = path.Join(context.Scratchdir, "root")
	context.Image = options.InternalImage
	context.RecipeDir = path.Dir(file)

	context.Artifactdir = options.ArtifactDir
	if context.Artifactdir == "" {
		context.Artifactdir, _ = os.Getwd()
	}
	context.Artifactdir = debos.CleanPath(context.Artifactdir)

	// Initialise origins map
	context.Origins = make(map[string]string)
	context.Origins["artifacts"] = context.Artifactdir
	context.Origins["filesystem"] = context.Rootdir
	context.Origins["recipe"] = context.RecipeDir

	context.Architecture = r.Architecture

	context.State = debos.Success

	// Initialize environment variables map
	context.EnvironVars = make(map[string]string)

	// First add variables from host
	for _, e := range environ_vars {
		lowerVar := strings.ToLower(e) // lowercase not really needed
		lowerVal := os.Getenv(lowerVar)
		if lowerVal != "" {
			context.EnvironVars[lowerVar] = lowerVal
		}

		upperVar := strings.ToUpper(e)
		upperVal := os.Getenv(upperVar)
		if upperVal != "" {
			context.EnvironVars[upperVar] = upperVal
		}
	}

	// Then add/overwrite with variables from command line
	for k, v := range options.EnvironVars {
		// Allows the user to unset environ variables with -e
		if v == "" {
			delete(context.EnvironVars, k)
		} else {
			context.EnvironVars[k] = v
		}
	}

	for _, a := range r.Actions {
		err = a.Verify(&context)
		if exitcode = checkError(&context, err, a, "Verify"); exitcode != 0 {
			return
		}
	}

	if !fakemachine.InMachine() && fakemachine.Supported() {
		m := fakemachine.NewMachine()
		var args []string

		if options.Memory == "" {
			// Set default memory size for fakemachine
			options.Memory = "2Gb"
		}
		memsize, err := units.RAMInBytes(options.Memory)
		if err != nil {
			fmt.Printf("Couldn't parse memory size: %v\n", err)
			exitcode = 1
			return
		}
		m.SetMemory(int(memsize / 1024 / 1024))

		if options.CPUs == 0 {
			// Set default CPU count for fakemachine
			options.CPUs = 2
		}
		m.SetNumCPUs(options.CPUs)

		if options.ScratchSize != "" {
			size, err := units.FromHumanSize(options.ScratchSize)
			if err != nil {
				fmt.Printf("Couldn't parse scratch size: %v\n", err)
				exitcode = 1
				return
			}
			m.SetScratch(size, "")
		}

		m.SetShowBoot(options.ShowBoot)

		// Puts in a format that is compatible with output of os.Environ()
		if context.EnvironVars != nil {
			EnvironString := []string{}
			for k, v := range context.EnvironVars {
				warnLocalhost(k, v)
				EnvironString = append(EnvironString, fmt.Sprintf("%s=%s", k, v))
			}
			m.SetEnviron(EnvironString) // And save the resulting environ vars on m
		}

		m.AddVolume(context.Artifactdir)
		args = append(args, "--artifactdir", context.Artifactdir)

		for k, v := range options.TemplateVars {
			args = append(args, "--template-var", fmt.Sprintf("%s:\"%s\"", k, v))
		}

		for k, v := range options.EnvironVars {
			args = append(args, "--environ-var", fmt.Sprintf("%s:\"%s\"", k, v))
		}

		m.AddVolume(context.RecipeDir)
		args = append(args, file)

		if options.DebugShell {
			args = append(args, "--debug-shell")
			args = append(args, "--shell", fmt.Sprintf("%s", options.Shell))
		}

		for _, a := range r.Actions {
			// Stack PostMachineCleanup methods
			defer a.PostMachineCleanup(&context)

			err = a.PreMachine(&context, m, &args)
			if exitcode = checkError(&context, err, a, "PreMachine"); exitcode != 0 {
				return
			}
		}

		exitcode, err = m.RunInMachineWithArgs(args)
		if err != nil {
			fmt.Println(err)
			return
		}

		if exitcode != 0 {
			context.State = debos.Failed
			return
		}

		for _, a := range r.Actions {
			err = a.PostMachine(&context)
			if exitcode = checkError(&context, err, a, "Postmachine"); exitcode != 0 {
				return
			}
		}

		log.Printf("==== Recipe done ====")
		return
	}

	if !fakemachine.InMachine() {
		for _, a := range r.Actions {
			// Stack PostMachineCleanup methods
			defer a.PostMachineCleanup(&context)

			err = a.PreNoMachine(&context)
			if exitcode = checkError(&context, err, a, "PreNoMachine"); exitcode != 0 {
				return
			}
		}
	}

	// Create Rootdir
	if _, err = os.Stat(context.Rootdir); os.IsNotExist(err) {
		err = os.Mkdir(context.Rootdir, 0755)
		if err != nil && os.IsNotExist(err) {
			exitcode = 1
			return
		}
	}

	exitcode = do_run(r, &context)
	if exitcode != 0 {
		return
	}

	if !fakemachine.InMachine() {
		for _, a := range r.Actions {
			err = a.PostMachine(&context)
			if exitcode = checkError(&context, err, a, "PostMachine"); exitcode != 0 {
				return
			}
		}
		log.Printf("==== Recipe done ====")
	}
}
<commit_msg>cmd: Make recipe dump recursive<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"
	"reflect"

	"github.com/docker/go-units"
	"github.com/go-debos/debos"
	"github.com/go-debos/debos/recipe"
	"github.com/go-debos/fakemachine"
	"github.com/jessevdk/go-flags"
)

func checkError(context *debos.DebosContext, err error, a debos.Action, stage string) int {
	if err == nil {
		return 0
	}

	context.State = debos.Failed
	log.Printf("Action `%s` failed at stage %s, error: %s", a, stage, err)
	debos.DebugShell(*context)
	return 1
}

func do_run(r recipe.Recipe, context *debos.DebosContext) int {
	for _, a := range r.Actions {
		err := a.Run(context)

		// This does not stop the call of stacked Cleanup methods for other Actions
		// Stack Cleanup methods
		defer a.Cleanup(context)

		// Check the state of Run method
		if exitcode := checkError(context, err, a, "Run"); exitcode != 0 {
			return exitcode
		}
	}

	return 0
}

func warnLocalhost(variable string, value string) {
	message := `WARNING: Environment variable %[1]s contains a reference to
		    localhost. This may not work when running from fakemachine.
		    Consider using an address that is valid on your network.`

	if strings.Contains(value, "localhost") ||
	   strings.Contains(value, "127.0.0.1") ||
	   strings.Contains(value, "::1") {
		log.Printf(message, variable)
	}
}

func DumpActionStruct(iface interface{}) string {
	var a []string

	s := reflect.ValueOf(iface)
	t := reflect.TypeOf(iface)

	for i := 0; i < t.NumField(); i++ {
		f := s.Field(i)
		// Dump only exported entries
		if f.CanInterface() {
			str := fmt.Sprintf("%s: %v", s.Type().Field(i).Name, f.Interface())
			a = append(a, str)
		}
	}

	return strings.Join(a, ", ")
}

const tabs = 2

func DumpActions(iface interface{}, depth int) {
	tab := strings.Repeat(" ", depth * tabs)
	entries := reflect.ValueOf(iface)

	for i := 0; i < entries.NumField(); i++ {
		if entries.Type().Field(i).Name == "Actions" {
			log.Printf("%s  %s:\n", tab, entries.Type().Field(i).Name)
			actions := reflect.ValueOf(entries.Field(i).Interface())
			for j := 0; j < actions.Len(); j++ {
				yaml := reflect.ValueOf(actions.Index(j).Interface())
				DumpActionFields(yaml.Field(0).Interface(), depth + 1)
			}
		} else {
			log.Printf("%s  %s: %v\n", tab, entries.Type().Field(i).Name, entries.Field(i).Interface())
		}
	}
}

func DumpActionFields(iface interface{}, depth int) {
	tab := strings.Repeat(" ", depth * tabs)
	entries := reflect.ValueOf(iface).Elem()

	for i := 0; i < entries.NumField(); i++ {
		f := entries.Field(i)
		// Dump only exported entries
		if f.CanInterface() {
			switch f.Kind() {
			case reflect.Struct:
				// BaseAction is the only struct embbed in Action ActionFields
				// dump it at the same level
				log.Printf("  - %s", DumpActionStruct(f.Interface()))

			case reflect.Slice:
				s := reflect.ValueOf(f.Interface())
				if s.Index(0).Kind() == reflect.Struct {
					log.Printf("    %s:\n", entries.Type().Field(i).Name)
					for j := 0; j < s.Len(); j++ {
						if s.Index(j).Kind() == reflect.Struct {
							log.Printf("%s  { %s }", tab, DumpActionStruct(s.Index(j).Interface()))
						}
					}
				} else {
					log.Printf("%s  %s: %s\n", tab, entries.Type().Field(i).Name, f)
				}

			default:
				log.Printf("%s  %s: %v\n", tab, entries.Type().Field(i).Name, f.Interface())
			}
		}
	}
}

func main() {
	var context debos.DebosContext
	var options struct {
		ArtifactDir   string            `long:"artifactdir" description:"Directory for packed archives and ostree repositories (default: current directory)"`
		InternalImage string            `long:"internal-image" hidden:"true"`
		TemplateVars  map[string]string `short:"t" long:"template-var" description:"Template variables (use -t VARIABLE:VALUE syntax)"`
		DebugShell    bool              `long:"debug-shell" description:"Fall into interactive shell on error"`
		Shell         string            `short:"s" long:"shell" description:"Redefine interactive shell binary (default: bash)" optionsl:"" default:"/bin/bash"`
		ScratchSize   string            `long:"scratchsize" description:"Size of disk backed scratch space"`
		CPUs          int               `short:"c" long:"cpus" description:"Number of CPUs to use for build VM (default: 2)"`
		Memory        string            `short:"m" long:"memory" description:"Amount of memory for build VM (default: 2048MB)"`
		ShowBoot      bool              `long:"show-boot" description:"Show boot/console messages from the fake machine"`
		EnvironVars   map[string]string `short:"e" long:"environ-var" description:"Environment variables (use -e VARIABLE:VALUE syntax)"`
		Verbose       bool              `short:"v" long:"verbose" description:"Verbose output"`
	}

	// These are the environment variables that will be detected on the
	// host and propagated to fakemachine. These are listed lower case, but
	// they are detected and configured in both lower case and upper case.
	var environ_vars = [...]string {
		"http_proxy",
		"https_proxy",
		"ftp_proxy",
		"rsync_proxy",
		"all_proxy",
		"no_proxy",
	}

	var exitcode int = 0
	// Allow to run all deferred calls prior to os.Exit()
	defer func() {
		os.Exit(exitcode)
	}()

	parser := flags.NewParser(&options, flags.Default)
	args, err := parser.Parse()

	if err != nil {
		flagsErr, ok := err.(*flags.Error)
		if ok && flagsErr.Type == flags.ErrHelp {
			return
		} else {
			fmt.Printf("%v\n", flagsErr)
			exitcode = 1
			return
		}
	}

	if len(args) != 1 {
		log.Println("No recipe given!")
		exitcode = 1
		return
	}

	// Set interactive shell binary only if '--debug-shell' options passed
	if options.DebugShell {
		context.DebugShell = options.Shell
	}

	file := args[0]
	file = debos.CleanPath(file)

	r := recipe.Recipe{}
	if _, err := os.Stat(file); os.IsNotExist(err) {
		log.Println(err)
		exitcode = 1
		return
	}
	if err := r.Parse(file, options.TemplateVars); err != nil {
		log.Println(err)
		exitcode = 1
		return
	}

	if options.Verbose {
		log.Println("Internal actions dump:")
		DumpActions(reflect.ValueOf(r).Interface(), 0)
	}

	/* If fakemachine is supported the outer fake machine will never use the
	 * scratchdir, so just set it to /scratch as a dummy to prevent the
	 * outer debos creating a temporary direction */
	if fakemachine.InMachine() || fakemachine.Supported() {
		context.Scratchdir = "/scratch"
	} else {
		log.Printf("fakemachine not supported, running on the host!")
		cwd, _ := os.Getwd()
		context.Scratchdir, err = ioutil.TempDir(cwd, ".debos-")
		defer os.RemoveAll(context.Scratchdir)
	}

	context.Rootdir = path.Join(context.Scratchdir, "root")
	context.Image = options.InternalImage
	context.RecipeDir = path.Dir(file)

	context.Artifactdir = options.ArtifactDir
	if context.Artifactdir == "" {
		context.Artifactdir, _ = os.Getwd()
	}
	context.Artifactdir = debos.CleanPath(context.Artifactdir)

	// Initialise origins map
	context.Origins = make(map[string]string)
	context.Origins["artifacts"] = context.Artifactdir
	context.Origins["filesystem"] = context.Rootdir
	context.Origins["recipe"] = context.RecipeDir

	context.Architecture = r.Architecture

	context.State = debos.Success

	// Initialize environment variables map
	context.EnvironVars = make(map[string]string)

	// First add variables from host
	for _, e := range environ_vars {
		lowerVar := strings.ToLower(e) // lowercase not really needed
		lowerVal := os.Getenv(lowerVar)
		if lowerVal != "" {
			context.EnvironVars[lowerVar] = lowerVal
		}

		upperVar := strings.ToUpper(e)
		upperVal := os.Getenv(upperVar)
		if upperVal != "" {
			context.EnvironVars[upperVar] = upperVal
		}
	}

	// Then add/overwrite with variables from command line
	for k, v := range options.EnvironVars {
		// Allows the user to unset environ variables with -e
		if v == "" {
			delete(context.EnvironVars, k)
		} else {
			context.EnvironVars[k] = v
		}
	}

	for _, a := range r.Actions {
		err = a.Verify(&context)
		if exitcode = checkError(&context, err, a, "Verify"); exitcode != 0 {
			return
		}
	}

	if !fakemachine.InMachine() && fakemachine.Supported() {
		m := fakemachine.NewMachine()
		var args []string

		if options.Memory == "" {
			// Set default memory size for fakemachine
			options.Memory = "2Gb"
		}
		memsize, err := units.RAMInBytes(options.Memory)
		if err != nil {
			fmt.Printf("Couldn't parse memory size: %v\n", err)
			exitcode = 1
			return
		}
		m.SetMemory(int(memsize / 1024 / 1024))

		if options.CPUs == 0 {
			// Set default CPU count for fakemachine
			options.CPUs = 2
		}
		m.SetNumCPUs(options.CPUs)

		if options.ScratchSize != "" {
			size, err := units.FromHumanSize(options.ScratchSize)
			if err != nil {
				fmt.Printf("Couldn't parse scratch size: %v\n", err)
				exitcode = 1
				return
			}
			m.SetScratch(size, "")
		}

		m.SetShowBoot(options.ShowBoot)

		// Puts in a format that is compatible with output of os.Environ()
		if context.EnvironVars != nil {
			EnvironString := []string{}
			for k, v := range context.EnvironVars {
				warnLocalhost(k, v)
				EnvironString = append(EnvironString, fmt.Sprintf("%s=%s", k, v))
			}
			m.SetEnviron(EnvironString) // And save the resulting environ vars on m
		}

		m.AddVolume(context.Artifactdir)
		args = append(args, "--artifactdir", context.Artifactdir)

		for k, v := range options.TemplateVars {
			args = append(args, "--template-var", fmt.Sprintf("%s:\"%s\"", k, v))
		}

		for k, v := range options.EnvironVars {
			args = append(args, "--environ-var", fmt.Sprintf("%s:\"%s\"", k, v))
		}

		m.AddVolume(context.RecipeDir)
		args = append(args, file)

		if options.DebugShell {
			args = append(args, "--debug-shell")
			args = append(args, "--shell", fmt.Sprintf("%s", options.Shell))
		}

		for _, a := range r.Actions {
			// Stack PostMachineCleanup methods
			defer a.PostMachineCleanup(&context)

			err = a.PreMachine(&context, m, &args)
			if exitcode = checkError(&context, err, a, "PreMachine"); exitcode != 0 {
				return
			}
		}

		exitcode, err = m.RunInMachineWithArgs(args)
		if err != nil {
			fmt.Println(err)
			return
		}

		if exitcode != 0 {
			context.State = debos.Failed
			return
		}

		for _, a := range r.Actions {
			err = a.PostMachine(&context)
			if exitcode = checkError(&context, err, a, "Postmachine"); exitcode != 0 {
				return
			}
		}

		log.Printf("==== Recipe done ====")
		return
	}

	if !fakemachine.InMachine() {
		for _, a := range r.Actions {
			// Stack PostMachineCleanup methods
			defer a.PostMachineCleanup(&context)

			err = a.PreNoMachine(&context)
			if exitcode = checkError(&context, err, a, "PreNoMachine"); exitcode != 0 {
				return
			}
		}
	}

	// Create Rootdir
	if _, err = os.Stat(context.Rootdir); os.IsNotExist(err) {
		err = os.Mkdir(context.Rootdir, 0755)
		if err != nil && os.IsNotExist(err) {
			exitcode = 1
			return
		}
	}

	exitcode = do_run(r, &context)
	if exitcode != 0 {
		return
	}

	if !fakemachine.InMachine() {
		for _, a := range r.Actions {
			err = a.PostMachine(&context)
			if exitcode = checkError(&context, err, a, "PostMachine"); exitcode != 0 {
				return
			}
		}
		log.Printf("==== Recipe done ====")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"go/build"
	"go/format"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/vincent-petithory/dispel"
)

var (
	templateName        string
	defaultImplName     string
	prefix              string
	handlerReceiverType string
	pkgpath             string
)

func init() {
	flag.StringVar(&templateName, "template", "all", fmt.Sprintf(`execute this template only.
        It must be one of %q. If empty, noone is executed.
        If set to the special value all (the default), all templates are executed.`, dispel.TemplateNames()))
	flag.StringVar(&defaultImplName, "default-impl", "all", fmt.Sprintf(`execute this default impl only.
        It must be one of %q. If empty, noone is executed.
        If set to the special value all, all default impls are executed.`, dispel.DefaultNames()))
	flag.StringVar(&prefix, "prefix", "dispel_", `the prefix to use for each generated template file.
        This doesn't apply to default implementations, which have fixed names.`)
	flag.StringVar(&handlerReceiverType, "handler-receiver-type", "", "the type which will receive the handler funcs.")
	flag.StringVar(&pkgpath, "pkgpath", "", `Generate and analyze code in this package. It is mandatory to set a value if not invoked with go:generate.
        If set when the program is invoked by go:generate, it overrides the package path resolved from $GOFILE.`)
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage: dispel SCHEMA")
		fmt.Fprintln(os.Stderr)
		flag.PrintDefaults()
	}
}

func parseSchema(path string) (*dispel.SchemaParser, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = f.Close()
	}()

	var schema dispel.Schema
	if err := json.NewDecoder(f).Decode(&schema); err != nil {
		return nil, err
	}

	return &dispel.SchemaParser{RootSchema: &schema}, nil
}

func main() {
	flag.Parse()
	prgmName := filepath.Base(os.Args[0])
	log.SetFlags(0)
	log.SetPrefix(prgmName + ": ")

	// Check envvars from go:generate are set
	var pkgAbsPath string
	switch {
	case pkgpath != "":
		p, err := filepath.Abs(pkgpath)
		if err != nil {
			log.Fatal(err)
		}
		pkgAbsPath = p
	case os.Getenv("GOFILE") != "":
		p, err := filepath.Abs(os.Getenv("GOFILE"))
		if err != nil {
			log.Fatal(err)
		}
		pkgAbsPath = filepath.Dir(p)
	default:
		log.Fatal("no package found: $GOFILE or --pkgpath must be set")
	}

	// Setting the json schema path is mandatory
	if schemaFilepath == "" {
		log.Fatal("no jsonschema file provided")
	}
	// Abort if the generated files' prefix is empty
	if prefix == "" {
		log.Fatal("generated files need a non-empty prefix")
	}

	genPathFn := func(name string) string {
		return filepath.Join(pkgAbsPath, fmt.Sprintf("%s%s.go", prefix, strings.ToLower(name)))
	}

	// Setting the json schema path is mandatory
	if flag.NArg() < 1 {
		flag.Usage()
		log.Fatal("no jsonschema file provided")
	}
	schemaFilepath := flag.Arg(0)

	// Parse JSON Schema
	schemaParser, err := parseSchema(schemaFilepath)
	if err != nil {
		log.Fatal(err)
	}

	// Create dispel template using the parser
	t, err := dispel.NewTemplate(schemaParser)
	if err != nil {
		log.Fatal(err)
	}

	// Create the list of generated file names
	var genFiles []string
	for _, tmplName := range t.Names() {
		genFiles = append(genFiles, genPathFn(tmplName))
	}

	// Resolve the pkg from the pkg path
	goPkg, err := build.ImportDir(pkgAbsPath, build.FindOnly)
	if err != nil {
		log.Fatal(err)
	}

	// Find methods whose receiver's type is the one defined as holding handler funcs implementations
	// We exclude the ones we auto-generate.
	handlerFuncDecls, err := dispel.FindTypesFuncs(pkgAbsPath, goPkg.Name, []string{strings.Replace(handlerReceiverType, "*", "", -1)}, genFiles)
	if err != nil {
		log.Fatal(err)
	}
	var existingHandlers []string
	for name := range handlerFuncDecls {
		existingHandlers = append(existingHandlers, name)
	}

	// Parse the routes in the schema
	routes, err := schemaParser.ParseRoutes()
	if err != nil {
		switch t := err.(type) {
		case dispel.InvalidSchemaError:
			log.Fatalf("Schema: %#v\nMsg: %s", t.Schema, t.Msg)
		default:
			log.Fatal(err)
		}
	}

	// Prepare context for template
	ctx := &dispel.TemplateContext{
		Prgm:                strings.Join(append([]string{prgmName}, os.Args[1:]...), " "),
		PkgName:             goPkg.Name,
		Routes:              routes,
		HandlerReceiverType: handlerReceiverType,
		ExistingHandlers:    existingHandlers,
	}

	// Exec templates
	var buf bytes.Buffer
	for _, name := range t.Names() {
		if templateName != "all" && templateName != name {
			continue
		}
		if err := t.ExecuteTemplate(&buf, name, ctx); err != nil {
			log.Fatal(err)
		}
		// Format source with gofmt
		src, err := format.Source(buf.Bytes())
		if err != nil {
			log.Fatalf("%s\n\ngofmt: %s", buf.Bytes(), err)
		}

		// Write file to disk
		if err := ioutil.WriteFile(genPathFn(name), src, 0666); err != nil {
			log.Fatal(err)
		}
		buf.Reset()
	}

	defaultImpl, err := dispel.NewDefaultImpl()
	if err != nil {
		log.Fatal(err)
	}
	buf.Reset()
	for _, name := range defaultImpl.Names() {
		if defaultImplName != "all" && defaultImplName != name {
			continue
		}
		if err := defaultImpl.ExecuteTemplate(&buf, name, ctx.PkgName); err != nil {
			log.Fatal(err)
		}
		// Format source with gofmt
		src, err := format.Source(buf.Bytes())
		if err != nil {
			log.Fatalf("%s\n\ngofmt: %s", buf.Bytes(), err)
		}

		// Write file to disk
		destpath := filepath.Join(pkgAbsPath, name+".go")
		if err := ioutil.WriteFile(destpath, src, 0666); err != nil {
			log.Fatal(err)
		}
		buf.Reset()
	}
}
<commit_msg>dispel: require to set the package name if not using go:generate<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"go/format"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/vincent-petithory/dispel"
)

var (
	templateName        string
	defaultImplName     string
	prefix              string
	handlerReceiverType string
	pkgpath             string
	pkgname             string
)

func init() {
	flag.StringVar(&templateName, "template", "all", fmt.Sprintf(`execute this template only.
        It must be one of %q. If empty, noone is executed.
        If set to the special value all (the default), all templates are executed.`, dispel.TemplateNames()))
	flag.StringVar(&defaultImplName, "default-impl", "all", fmt.Sprintf(`execute this default impl only.
        It must be one of %q. If empty, noone is executed.
        If set to the special value all, all default impls are executed.`, dispel.DefaultNames()))
	flag.StringVar(&prefix, "prefix", "dispel_", `the prefix to use for each generated template file.
        This doesn't apply to default implementations, which have fixed names.`)
	flag.StringVar(&handlerReceiverType, "handler-receiver-type", "", "the type which will receive the handler funcs.")
	flag.StringVar(&pkgpath, "pkgpath", "", `Generate and analyze code in this package. It is mandatory to set a value if not invoked with go:generate.
        If set when the program is invoked by go:generate, it overrides the package path resolved from $GOFILE.`)
	flag.StringVar(&pkgname, "pkgname", "", `The package name to use at the pkgpath location. It is mandatory to set a value if not invoked with go:generate.
        If set when the program is invoked by go:generate, it overrides the value of $GOPACKAGE`)
	flag.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage: dispel SCHEMA")
		fmt.Fprintln(os.Stderr)
		flag.PrintDefaults()
	}
}

func parseSchema(path string) (*dispel.SchemaParser, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = f.Close()
	}()

	var schema dispel.Schema
	if err := json.NewDecoder(f).Decode(&schema); err != nil {
		return nil, err
	}

	return &dispel.SchemaParser{RootSchema: &schema}, nil
}

func main() {
	flag.Parse()
	prgmName := filepath.Base(os.Args[0])
	log.SetFlags(0)
	log.SetPrefix(prgmName + ": ")

	// Check envvars from go:generate are set
	var pkgAbsPath string
	switch {
	case pkgpath != "":
		p, err := filepath.Abs(pkgpath)
		if err != nil {
			log.Fatal(err)
		}
		pkgAbsPath = p
	case os.Getenv("GOFILE") != "":
		p, err := filepath.Abs(os.Getenv("GOFILE"))
		if err != nil {
			log.Fatal(err)
		}
		pkgAbsPath = filepath.Dir(p)
	default:
		flag.Usage()
		log.Fatal("no package found: $GOFILE or --pkgpath must be set")
	}
	switch {
	case pkgname != "":
	case os.Getenv("GOPACKAGE") != "":
		pkgname = os.Getenv("GOPACKAGE")
	default:
		flag.Usage()
		log.Fatal("no package name found: $GOPACKAGE or --pkgname must be set")
	}

	// Abort if the generated files' prefix is empty
	if prefix == "" {
		flag.Usage()
		log.Fatal("generated files need a non-empty prefix")
	}

	genPathFn := func(name string) string {
		return filepath.Join(pkgAbsPath, fmt.Sprintf("%s%s.go", prefix, strings.ToLower(name)))
	}

	// Setting the json schema path is mandatory
	if flag.NArg() < 1 {
		flag.Usage()
		log.Fatal("no jsonschema file provided")
	}
	schemaFilepath := flag.Arg(0)

	// Parse JSON Schema
	schemaParser, err := parseSchema(schemaFilepath)
	if err != nil {
		log.Fatal(err)
	}

	// Create dispel template using the parser
	t, err := dispel.NewTemplate(schemaParser)
	if err != nil {
		log.Fatal(err)
	}

	// Create the list of generated file names
	var genFiles []string
	for _, tmplName := range t.Names() {
		genFiles = append(genFiles, genPathFn(tmplName))
	}

	// Find methods whose receiver's type is the one defined as holding handler funcs implementations
	// We exclude the ones we auto-generate.
	handlerFuncDecls, err := dispel.FindTypesFuncs(pkgAbsPath, pkgname, []string{strings.Replace(handlerReceiverType, "*", "", -1)}, genFiles)
	if err != nil {
		log.Fatal(err)
	}
	var existingHandlers []string
	for name := range handlerFuncDecls {
		existingHandlers = append(existingHandlers, name)
	}

	// Parse the routes in the schema
	routes, err := schemaParser.ParseRoutes()
	if err != nil {
		switch t := err.(type) {
		case dispel.InvalidSchemaError:
			log.Fatalf("Schema: %#v\nMsg: %s", t.Schema, t.Msg)
		default:
			log.Fatal(err)
		}
	}

	// Prepare context for template
	ctx := &dispel.TemplateContext{
		Prgm:                strings.Join(append([]string{prgmName}, os.Args[1:]...), " "),
		PkgName:             pkgname,
		Routes:              routes,
		HandlerReceiverType: handlerReceiverType,
		ExistingHandlers:    existingHandlers,
	}

	// Exec templates
	var buf bytes.Buffer
	for _, name := range t.Names() {
		if templateName != "all" && templateName != name {
			continue
		}
		if err := t.ExecuteTemplate(&buf, name, ctx); err != nil {
			log.Fatal(err)
		}
		// Format source with gofmt
		src, err := format.Source(buf.Bytes())
		if err != nil {
			log.Fatalf("%s\n\ngofmt: %s", buf.Bytes(), err)
		}

		// Write file to disk
		if err := ioutil.WriteFile(genPathFn(name), src, 0666); err != nil {
			log.Fatal(err)
		}
		buf.Reset()
	}

	defaultImpl, err := dispel.NewDefaultImpl()
	if err != nil {
		log.Fatal(err)
	}
	buf.Reset()
	for _, name := range defaultImpl.Names() {
		if defaultImplName != "all" && defaultImplName != name {
			continue
		}
		if err := defaultImpl.ExecuteTemplate(&buf, name, ctx.PkgName); err != nil {
			log.Fatal(err)
		}
		// Format source with gofmt
		src, err := format.Source(buf.Bytes())
		if err != nil {
			log.Fatalf("%s\n\ngofmt: %s", buf.Bytes(), err)
		}

		// Write file to disk
		destpath := filepath.Join(pkgAbsPath, name+".go")
		if err := ioutil.WriteFile(destpath, src, 0666); err != nil {
			log.Fatal(err)
		}
		buf.Reset()
	}
}
<|endoftext|>