<commit_before>package scamp

import "encoding/json"
import "encoding/pem"
import "crypto/x509"
import "crypto/rsa"

import "fmt"
import "errors"
import "strings"

type ServiceProxy struct {
	version int
	ident string
	sector string
	weight int
	announceInterval int
	connspec string
	protocols []string
	actions []ServiceProxyClass

	rawClassRecords []byte
	rawCert []byte
	rawSig []byte

	timestamp HighResTimestamp

	conn *Connection
}

type ServiceProxyClass struct {
	className string
	actions []actionDescription
}

type actionDescription struct {
	actionName string
	crudTags string
	version int
}

// type Service struct {
// 	serviceSpec   string
// 	name          string

// 	listener      net.Listener

// 	actions       map[string]ServiceAction
// 	sessChan      (chan *Session)
// 	isRunning     bool
// 	openConns     []*Connection

// 	cert          tls.Certificate
// }
func ServiceAsServiceProxy(serv *Service) (proxy *ServiceProxy) {
	proxy = new(ServiceProxy)
	proxy.version = 3
	proxy.ident = serv.name
	proxy.sector = "sector"
	proxy.weight = 1
	proxy.announceInterval = defaultAnnounceInterval * 500
	proxy.connspec = fmt.Sprintf("beepish+tls://%s:%d", serv.listenerIP.To4().String(), serv.listenerPort)
	proxy.protocols = make([]string, 1, 1)
	proxy.protocols[0] = "json"
	proxy.actions = make([]ServiceProxyClass, 0)
	proxy.rawClassRecords = []byte("rawClassRecords")
	proxy.rawCert = []byte("rawCert")
	proxy.rawSig = []byte("rawSig")

	// { "Logger.info": [{ "name": "blah", "callback": foo() }] }
	for classAndActionName, serviceAction := range serv.actions {
		actionDotIndex := strings.LastIndex(classAndActionName, ".")
		// TODO: this is the only spot that could fail? shouldn't happen in any usage...
		if actionDotIndex == -1 {
			panic("bad action name")
		}
		className := classAndActionName[0:actionDotIndex]
		actionName := classAndActionName[actionDotIndex+1:len(classAndActionName)]

		newServiceProxyClass := ServiceProxyClass {
			className: className,
			actions: make([]actionDescription, 0),
		}
		newServiceProxyClass.actions = append(newServiceProxyClass.actions, actionDescription {
			actionName: actionName,
			crudTags: serviceAction.crudTags,
			version: serviceAction.version,
		})

		proxy.actions = append(proxy.actions, newServiceProxyClass)
	}

	timestamp,err := Gettimeofday()
	if err != nil {
		Error.Printf("error with high-res timestamp: `%s`", err)
		return nil
	}
	proxy.timestamp = timestamp

	return
}

func NewServiceProxy(classRecordsRaw []byte, certRaw []byte, sigRaw []byte) (proxy *ServiceProxy, err error) {
	proxy = new(ServiceProxy)
	proxy.rawClassRecords = classRecordsRaw
	proxy.rawCert = certRaw
	proxy.rawSig = sigRaw

	var classRecords []json.RawMessage
	err = json.Unmarshal(classRecordsRaw, &classRecords)
	if err != nil {
		return
	}
	if len(classRecords) != 9 {
		err = errors.New( fmt.Sprintf("expected 9 entries in class record. got %d", len(classRecords)) )
	}

	// OMG, position-based, heterogenously typed values in an array suck to deal with.
	err = json.Unmarshal(classRecords[0], &proxy.version)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[1], &proxy.ident)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[2], &proxy.sector)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[3], &proxy.weight)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[4], &proxy.announceInterval)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[5], &proxy.connspec)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[6], &proxy.protocols)
	if err != nil {
		return
	}

	var rawClasses [][]json.RawMessage
	err = json.Unmarshal(classRecords[7], &rawClasses)
	if err != nil {
		return
	}
	classes := make([]ServiceProxyClass, len(rawClasses), len(rawClasses))
	proxy.actions = classes

	for i,rawClass := range rawClasses {
		if len(rawClass) < 2 {
			err = errors.New( fmt.Sprintf("expected rawClass to have at least 2 entries. was: `%s`", rawClass) )
			return nil, err
		}

		err = json.Unmarshal(rawClass[0], &classes[i].className)
		if err != nil {
			return nil, err
		}

		rawActionsSlice := rawClass[1:]
		classes[i].actions = make([]actionDescription, len(rawActionsSlice), len(rawActionsSlice))

		for j,rawActionSpec := range rawActionsSlice {
			var actionsRawMessages []json.RawMessage
			err = json.Unmarshal(rawActionSpec, &actionsRawMessages)
			if err != nil {
				Error.Printf("could not parse: %s", rawActionSpec)
				return nil, err
			} else if len(actionsRawMessages) != 2 && len(actionsRawMessages) != 3 {
				err = errors.New( fmt.Sprintf("expected action spec to have 2 or 3 entries. got `%s` (%d)", actionsRawMessages, len(actionsRawMessages) ) )
			}

			err = json.Unmarshal(actionsRawMessages[0], &classes[i].actions[j].actionName)
			if err != nil {
				return nil, err
			}

			err = json.Unmarshal(actionsRawMessages[1], &classes[i].actions[j].crudTags)
			if err != nil {
				return nil, err
			}
		}
	}

	proxy.conn = nil // we connect on demand
	return
}

// 1) Verify signature of classRecords
// 2) Make sure the fingerprint is in authorized_services
// 3) Filter announced actions against authorized actions
func (proxy *ServiceProxy)Validate() (err error) {
	_, err = proxy.validateSignature()
	if err != nil {
		return
	}

	// See if we have this fingerprint in our authorized_services
	// TODO

	return
}

func (proxy *ServiceProxy)validateSignature() (hexSha1 string, err error) {
	decoded,_ := pem.Decode(proxy.rawCert)
	if decoded == nil {
		err = errors.New( fmt.Sprintf("could not find valid cert in `%s`", proxy.rawCert) )
		return
	}

	// Put pem in form useful for fingerprinting
	cert,err := x509.ParseCertificate(decoded.Bytes)
	if err != nil {
		return "", err
	}

	pkixInterface := cert.PublicKey
	rsaPubKey, ok := pkixInterface.(*rsa.PublicKey)
	if !ok {
		err = errors.New("could not cast parsed value to rsa.PublicKey")
		return
	}

	valid,err := VerifySHA256(proxy.rawClassRecords, rsaPubKey, proxy.rawSig, false)
	if !valid {
		return
	}

	hexSha1 = sha1FingerPrint(cert)
	return
}

func (proxy *ServiceProxy)GetConnection() (conn *Connection, err error) {
	if proxy.conn != nil {
		conn = proxy.conn
		return
	}

	proxy.conn, err = Connect(proxy.connspec)
	if err != nil {
		return
	}

	return
}

func (proxy *ServiceProxy)MarshalJSON() (b []byte, err error) {
	// var arr []json.RawMessage
	// arr = make([]json.RawMessage, 1, 1)

	// b,err = json.Marshal(arr)
	// err = json.Marshal(arr[0], &proxy.version)
	arr := make([]interface{},9)
	arr[0] = &proxy.version
  arr[1] = &proxy.ident
  arr[2] = &proxy.sector
  arr[3] = &proxy.weight
  arr[4] = &proxy.announceInterval
  arr[5] = &proxy.connspec
  arr[6] = &proxy.protocols

  // TODO: move this to two MarshalJSON interfaces for `ServiceProxyClass` and `actionDescription`
  // doing so should remove manual copies and separate concerns
  //
  // Serialize actions in this format:
  // 	["bgdispatcher",["poll","",1],["reboot","",1],["report","",1]]
  classSpecs := make([][]interface{}, len(proxy.actions), len(proxy.actions))
  for i,class := range proxy.actions {
  	entry := make([]interface{}, 1+len(class.actions), 1+len(class.actions))
  	entry[0] = &class.className
  	for j,action := range class.actions {
  		actions := make([]interface{},3,3)

  		actionNameCopy := make([]byte, len(action.actionName))
  		copy(actionNameCopy, action.actionName)
  		actions[0] = string(actionNameCopy)
  		actions[1] = &action.crudTags
  		actions[2] = &action.version
  		entry[j+1] = &actions
  	}

  	classSpecs[i] = entry
  }
  arr[7] = &classSpecs

  arr[8] = &proxy.timestamp

	return json.Marshal(arr)
}<commit_msg>Wrong sector<commit_after>package scamp

import "encoding/json"
import "encoding/pem"
import "crypto/x509"
import "crypto/rsa"

import "fmt"
import "errors"
import "strings"

type ServiceProxy struct {
	version int
	ident string
	sector string
	weight int
	announceInterval int
	connspec string
	protocols []string
	actions []ServiceProxyClass

	rawClassRecords []byte
	rawCert []byte
	rawSig []byte

	timestamp HighResTimestamp

	conn *Connection
}

type ServiceProxyClass struct {
	className string
	actions []actionDescription
}

type actionDescription struct {
	actionName string
	crudTags string
	version int
}

// type Service struct {
// 	serviceSpec   string
// 	name          string

// 	listener      net.Listener

// 	actions       map[string]ServiceAction
// 	sessChan      (chan *Session)
// 	isRunning     bool
// 	openConns     []*Connection

// 	cert          tls.Certificate
// }
func ServiceAsServiceProxy(serv *Service) (proxy *ServiceProxy) {
	proxy = new(ServiceProxy)
	proxy.version = 3
	proxy.ident = serv.name
	proxy.sector = "main"
	proxy.weight = 1
	proxy.announceInterval = defaultAnnounceInterval * 500
	proxy.connspec = fmt.Sprintf("beepish+tls://%s:%d", serv.listenerIP.To4().String(), serv.listenerPort)
	proxy.protocols = make([]string, 1, 1)
	proxy.protocols[0] = "json"
	proxy.actions = make([]ServiceProxyClass, 0)
	proxy.rawClassRecords = []byte("rawClassRecords")
	proxy.rawCert = []byte("rawCert")
	proxy.rawSig = []byte("rawSig")

	// { "Logger.info": [{ "name": "blah", "callback": foo() }] }
	for classAndActionName, serviceAction := range serv.actions {
		actionDotIndex := strings.LastIndex(classAndActionName, ".")
		// TODO: this is the only spot that could fail? shouldn't happen in any usage...
		if actionDotIndex == -1 {
			panic("bad action name")
		}
		className := classAndActionName[0:actionDotIndex]
		actionName := classAndActionName[actionDotIndex+1:len(classAndActionName)]

		newServiceProxyClass := ServiceProxyClass {
			className: className,
			actions: make([]actionDescription, 0),
		}
		newServiceProxyClass.actions = append(newServiceProxyClass.actions, actionDescription {
			actionName: actionName,
			crudTags: serviceAction.crudTags,
			version: serviceAction.version,
		})

		proxy.actions = append(proxy.actions, newServiceProxyClass)
	}

	timestamp,err := Gettimeofday()
	if err != nil {
		Error.Printf("error with high-res timestamp: `%s`", err)
		return nil
	}
	proxy.timestamp = timestamp

	return
}

func NewServiceProxy(classRecordsRaw []byte, certRaw []byte, sigRaw []byte) (proxy *ServiceProxy, err error) {
	proxy = new(ServiceProxy)
	proxy.rawClassRecords = classRecordsRaw
	proxy.rawCert = certRaw
	proxy.rawSig = sigRaw

	var classRecords []json.RawMessage
	err = json.Unmarshal(classRecordsRaw, &classRecords)
	if err != nil {
		return
	}
	if len(classRecords) != 9 {
		err = errors.New( fmt.Sprintf("expected 9 entries in class record. got %d", len(classRecords)) )
	}

	// OMG, position-based, heterogenously typed values in an array suck to deal with.
	err = json.Unmarshal(classRecords[0], &proxy.version)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[1], &proxy.ident)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[2], &proxy.sector)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[3], &proxy.weight)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[4], &proxy.announceInterval)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[5], &proxy.connspec)
	if err != nil {
		return
	}

	err = json.Unmarshal(classRecords[6], &proxy.protocols)
	if err != nil {
		return
	}

	var rawClasses [][]json.RawMessage
	err = json.Unmarshal(classRecords[7], &rawClasses)
	if err != nil {
		return
	}
	classes := make([]ServiceProxyClass, len(rawClasses), len(rawClasses))
	proxy.actions = classes

	for i,rawClass := range rawClasses {
		if len(rawClass) < 2 {
			err = errors.New( fmt.Sprintf("expected rawClass to have at least 2 entries. was: `%s`", rawClass) )
			return nil, err
		}

		err = json.Unmarshal(rawClass[0], &classes[i].className)
		if err != nil {
			return nil, err
		}

		rawActionsSlice := rawClass[1:]
		classes[i].actions = make([]actionDescription, len(rawActionsSlice), len(rawActionsSlice))

		for j,rawActionSpec := range rawActionsSlice {
			var actionsRawMessages []json.RawMessage
			err = json.Unmarshal(rawActionSpec, &actionsRawMessages)
			if err != nil {
				Error.Printf("could not parse: %s", rawActionSpec)
				return nil, err
			} else if len(actionsRawMessages) != 2 && len(actionsRawMessages) != 3 {
				err = errors.New( fmt.Sprintf("expected action spec to have 2 or 3 entries. got `%s` (%d)", actionsRawMessages, len(actionsRawMessages) ) )
			}

			err = json.Unmarshal(actionsRawMessages[0], &classes[i].actions[j].actionName)
			if err != nil {
				return nil, err
			}

			err = json.Unmarshal(actionsRawMessages[1], &classes[i].actions[j].crudTags)
			if err != nil {
				return nil, err
			}
		}
	}

	proxy.conn = nil // we connect on demand
	return
}

// 1) Verify signature of classRecords
// 2) Make sure the fingerprint is in authorized_services
// 3) Filter announced actions against authorized actions
func (proxy *ServiceProxy)Validate() (err error) {
	_, err = proxy.validateSignature()
	if err != nil {
		return
	}

	// See if we have this fingerprint in our authorized_services
	// TODO

	return
}

func (proxy *ServiceProxy)validateSignature() (hexSha1 string, err error) {
	decoded,_ := pem.Decode(proxy.rawCert)
	if decoded == nil {
		err = errors.New( fmt.Sprintf("could not find valid cert in `%s`", proxy.rawCert) )
		return
	}

	// Put pem in form useful for fingerprinting
	cert,err := x509.ParseCertificate(decoded.Bytes)
	if err != nil {
		return "", err
	}

	pkixInterface := cert.PublicKey
	rsaPubKey, ok := pkixInterface.(*rsa.PublicKey)
	if !ok {
		err = errors.New("could not cast parsed value to rsa.PublicKey")
		return
	}

	valid,err := VerifySHA256(proxy.rawClassRecords, rsaPubKey, proxy.rawSig, false)
	if !valid {
		return
	}

	hexSha1 = sha1FingerPrint(cert)
	return
}

func (proxy *ServiceProxy)GetConnection() (conn *Connection, err error) {
	if proxy.conn != nil {
		conn = proxy.conn
		return
	}

	proxy.conn, err = Connect(proxy.connspec)
	if err != nil {
		return
	}

	return
}

func (proxy *ServiceProxy)MarshalJSON() (b []byte, err error) {
	// var arr []json.RawMessage
	// arr = make([]json.RawMessage, 1, 1)

	// b,err = json.Marshal(arr)
	// err = json.Marshal(arr[0], &proxy.version)
	arr := make([]interface{},9)
	arr[0] = &proxy.version
  arr[1] = &proxy.ident
  arr[2] = &proxy.sector
  arr[3] = &proxy.weight
  arr[4] = &proxy.announceInterval
  arr[5] = &proxy.connspec
  arr[6] = &proxy.protocols

  // TODO: move this to two MarshalJSON interfaces for `ServiceProxyClass` and `actionDescription`
  // doing so should remove manual copies and separate concerns
  //
  // Serialize actions in this format:
  // 	["bgdispatcher",["poll","",1],["reboot","",1],["report","",1]]
  classSpecs := make([][]interface{}, len(proxy.actions), len(proxy.actions))
  for i,class := range proxy.actions {
  	entry := make([]interface{}, 1+len(class.actions), 1+len(class.actions))
  	entry[0] = &class.className
  	for j,action := range class.actions {
  		actions := make([]interface{},3,3)

  		actionNameCopy := make([]byte, len(action.actionName))
  		copy(actionNameCopy, action.actionName)
  		actions[0] = string(actionNameCopy)
  		actions[1] = &action.crudTags
  		actions[2] = &action.version
  		entry[j+1] = &actions
  	}

  	classSpecs[i] = entry
  }
  arr[7] = &classSpecs

  arr[8] = &proxy.timestamp

	return json.Marshal(arr)
}<|endoftext|>


<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 vppcalls_test

import (
	"net"
	"testing"

	"github.com/ligato/vpp-agent/plugins/vpp/binapi/dhcp"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/interfaces"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/ip"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/memif"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/tap"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/tapv2"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/vpe"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/vxlan"
	interfaces2 "github.com/ligato/vpp-agent/plugins/vpp/model/interfaces"
	"github.com/ligato/vpp-agent/tests/vppcallmock"
	. "github.com/onsi/gomega"
)

// Test dump of interfaces with vxlan type
func TestDumpInterfacesVxLan(t *testing.T) {
	ctx, ifHandler := ifTestSetup(t)
	defer ctx.TeardownTestCtx()

	ipv61Parse := net.ParseIP("dead:beef:feed:face:cafe:babe:baad:c0de").To16()
	ipv62Parse := net.ParseIP("d3ad:beef:feed:face:cafe:babe:baad:c0de").To16()

	ctx.MockReplies([]*vppcallmock.HandleReplies{
		{
			Name: (&interfaces.SwInterfaceDump{}).GetMessageName(),
			Ping: true,
			Message: &interfaces.SwInterfaceDetails{
				InterfaceName: []byte("vxlan1"),
			},
		},
		{
			Name:    (&interfaces.SwInterfaceGetTable{}).GetMessageName(),
			Ping:    false,
			Message: &interfaces.SwInterfaceGetTableReply{},
		},
		{
			Name:    (&ip.IPAddressDump{}).GetMessageName(),
			Ping:    true,
			Message: &ip.IPAddressDetails{},
		},
		{
			Name:    (&memif.MemifSocketFilenameDump{}).GetMessageName(),
			Ping:    true,
			Message: &memif.MemifSocketFilenameDetails{},
		},
		{
			Name:    (&memif.MemifDump{}).GetMessageName(),
			Ping:    true,
			Message: &memif.MemifDetails{},
		},
		{
			Name:    (&tap.SwInterfaceTapDump{}).GetMessageName(),
			Ping:    true,
			Message: &tap.SwInterfaceTapDetails{},
		},
		{
			Name:    (&tapv2.SwInterfaceTapV2Dump{}).GetMessageName(),
			Ping:    true,
			Message: &tapv2.SwInterfaceTapV2Details{},
		},
		{
			Name: (&vxlan.VxlanTunnelDump{}).GetMessageName(),
			Ping: true,
			Message: &vxlan.VxlanTunnelDetails{
				IsIPv6:     1,
				SwIfIndex:  0,
				SrcAddress: ipv61Parse,
				DstAddress: ipv62Parse,
			},
		},
	})

	intfs, err := ifHandler.DumpInterfaces()
	Expect(err).To(BeNil())
	Expect(intfs).To(HaveLen(1))
	intface := intfs[0].Interface

	// Check vxlan
	Expect(intface.Vxlan.SrcAddress).To(Equal("dead:beef:feed:face:cafe:babe:baad:c0de"))
	Expect(intface.Vxlan.DstAddress).To(Equal("d3ad:beef:feed:face:cafe:babe:baad:c0de"))
}

// Test dump of interfaces with host type
func TestDumpInterfacesHost(t *testing.T) {
	ctx, ifHandler := ifTestSetup(t)
	defer ctx.TeardownTestCtx()

	ctx.MockReplies([]*vppcallmock.HandleReplies{
		{
			Name: (&interfaces.SwInterfaceDump{}).GetMessageName(),
			Ping: true,
			Message: &interfaces.SwInterfaceDetails{
				InterfaceName: []byte("host-localhost"),
			},
		},
		{
			Name:    (&interfaces.SwInterfaceGetTable{}).GetMessageName(),
			Ping:    false,
			Message: &interfaces.SwInterfaceGetTableReply{},
		},
		{
			Name:    (&ip.IPAddressDump{}).GetMessageName(),
			Ping:    true,
			Message: &ip.IPAddressDetails{},
		},
		{
			Name:    (&memif.MemifSocketFilenameDump{}).GetMessageName(),
			Ping:    true,
			Message: &memif.MemifSocketFilenameDetails{},
		},
		{
			Name:    (&memif.MemifDump{}).GetMessageName(),
			Ping:    true,
			Message: &memif.MemifDetails{},
		},
		{
			Name:    (&tap.SwInterfaceTapDump{}).GetMessageName(),
			Ping:    true,
			Message: &tap.SwInterfaceTapDetails{},
		},
		{
			Name:    (&tapv2.SwInterfaceTapV2Dump{}).GetMessageName(),
			Ping:    true,
			Message: &tapv2.SwInterfaceTapV2Details{},
		},
		{
			Name:    (&vxlan.VxlanTunnelDump{}).GetMessageName(),
			Ping:    true,
			Message: &vxlan.VxlanTunnelDetails{},
		},
	})

	intfs, err := ifHandler.DumpInterfaces()
	Expect(err).To(BeNil())
	Expect(intfs).To(HaveLen(1))
	intface := intfs[0].Interface

	// Check interface data
	Expect(intface.Afpacket.HostIfName).To(Equal("localhost"))
}

// Test dump of interfaces with memif type
func TestDumpInterfacesMemif(t *testing.T) {
	ctx, ifHandler := ifTestSetup(t)
	defer ctx.TeardownTestCtx()

	ctx.MockReplies([]*vppcallmock.HandleReplies{
		{
			Name: (&interfaces.SwInterfaceDump{}).GetMessageName(),
			Ping: true,
			Message: &interfaces.SwInterfaceDetails{
				InterfaceName: []byte("memif1"),
			},
		},
		{
			Name:    (&interfaces.SwInterfaceGetTable{}).GetMessageName(),
			Ping:    false,
			Message: &interfaces.SwInterfaceGetTableReply{},
		},
		{
			Name:    (&ip.IPAddressDump{}).GetMessageName(),
			Ping:    true,
			Message: &ip.IPAddressDetails{},
		},
		{
			Name: (&memif.MemifSocketFilenameDump{}).GetMessageName(),
			Ping: true,
			Message: &memif.MemifSocketFilenameDetails{
				SocketID:       1,
				SocketFilename: []byte("test"),
			},
		},
		{
			Name: (&memif.MemifDump{}).GetMessageName(),
			Ping: true,
			Message: &memif.MemifDetails{
				ID:         2,
				SwIfIndex:  0,
				Role:       1, // Slave
				Mode:       1, // IP
				SocketID:   1,
				RingSize:   0,
				BufferSize: 0,
			},
		},
		{
			Name:    (&tap.SwInterfaceTapDump{}).GetMessageName(),
			Ping:    true,
			Message: &tap.SwInterfaceTapDetails{},
		},
		{
			Name:    (&tapv2.SwInterfaceTapV2Dump{}).GetMessageName(),
			Ping:    true,
			Message: &tapv2.SwInterfaceTapV2Details{},
		},
		{
			Name:    (&vxlan.VxlanTunnelDump{}).GetMessageName(),
			Ping:    true,
			Message: &vxlan.VxlanTunnelDetails{},
		},
	})

	intfs, err := ifHandler.DumpInterfaces()
	Expect(err).To(BeNil())
	Expect(intfs).To(HaveLen(1))
	intface := intfs[0].Interface

	// Check memif
	Expect(intface.Memif.SocketFilename).To(Equal("test"))
	Expect(intface.Memif.Id).To(Equal(uint32(2)))
	Expect(intface.Memif.Mode).To(Equal(interfaces2.Interfaces_Interface_Memif_IP))
	Expect(intface.Memif.Master).To(BeFalse())
}

// Test dump of interfaces using custom mock reply handler to avoid issues with ControlPingMessageReply
// not being properly recognized
func TestDumpInterfacesFull(t *testing.T) {
	ctx, ifHandler := ifTestSetup(t)
	defer ctx.TeardownTestCtx()

	hwAddr1Parse, err := net.ParseMAC("01:23:45:67:89:ab")
	Expect(err).To(BeNil())

	ctx.MockReplies([]*vppcallmock.HandleReplies{
		{
			Name: (&interfaces.SwInterfaceDump{}).GetMessageName(),
			Ping: true,
			Message: &interfaces.SwInterfaceDetails{
				SwIfIndex:       0,
				InterfaceName:   []byte("memif1"),
				Tag:             []byte("interface2"),
				AdminUpDown:     1,
				LinkMtu:         9216, // Default MTU
				L2Address:       hwAddr1Parse,
				L2AddressLength: uint32(len(hwAddr1Parse)),
			},
		},
		{
			Name: (&interfaces.SwInterfaceGetTable{}).GetMessageName(),
			Ping: false,
			Message: &interfaces.SwInterfaceGetTableReply{
				Retval: 0,
				VrfID:  42,
			},
		},
		{
			Name:    (&ip.IPAddressDump{}).GetMessageName(),
			Ping:    true,
			Message: &ip.IPAddressDetails{},
		},
		{
			Name: (&dhcp.DHCPClientDump{}).GetMessageName(),
			Ping: true,
			Message: &dhcp.DHCPClientDetails{
				Client: dhcp.DHCPClient{
					SwIfIndex: 0,
				},
			},
		},
		{
			Name: (&memif.MemifSocketFilenameDump{}).GetMessageName(),
			Ping: true,
			Message: &memif.MemifSocketFilenameDetails{
				SocketID:       1,
				SocketFilename: []byte("test"),
			},
		},
		{
			Name: (&memif.MemifDump{}).GetMessageName(),
			Ping: true,
			Message: &memif.MemifDetails{
				ID:         2,
				SwIfIndex:  0,
				Role:       0, // Master
				Mode:       0, // Ethernet
				SocketID:   1,
				RingSize:   0,
				BufferSize: 0,
			},
		},
		{
			Name: (&tap.SwInterfaceTapDump{}).GetMessageName(),
			Ping: true,
			Message: &tap.SwInterfaceTapDetails{
				SwIfIndex: 0,
				DevName:   []byte("taptap"),
			},
		},
		{
			Name: (&tapv2.SwInterfaceTapV2Dump{}).GetMessageName(),
			Ping: true,
			Message: &tapv2.SwInterfaceTapV2Details{
				SwIfIndex:  0,
				HostIfName: []byte("taptap2"), // This will overwrite the v1 tap name
			},
		},
		{
			Name: (&vxlan.VxlanTunnelDump{}).GetMessageName(),
			Ping: true,
			Message: &vxlan.VxlanTunnelDetails{
				SwIfIndex:  0,
				SrcAddress: []byte{192, 168, 0, 1},
				DstAddress: []byte{192, 168, 0, 2},
			},
		},
	})

	intfs, err := ifHandler.DumpInterfaces()
	Expect(err).To(BeNil())
	Expect(intfs).To(HaveLen(1))

	intface := intfs[0].Interface
	intMeta := intfs[0].Meta

	// This is last checked type, so it will be equal to that
	Expect(intface.Type).To(Equal(interfaces2.InterfaceType_VXLAN_TUNNEL))
	Expect(intface.PhysAddress).To(Equal("01:23:45:67:89:ab"))
	Expect(intface.Name).To(Equal("interface2"))
	Expect(intface.Mtu).To(Equal(uint32(0))) // default mtu
	Expect(intface.Enabled).To(BeTrue())
	Expect(intface.Vrf).To(Equal(uint32(42)))
	Expect(intface.SetDhcpClient).To(BeTrue())
	Expect(intMeta.VrfIPv4).To(Equal(uint32(42)))
	Expect(intMeta.VrfIPv4).To(Equal(uint32(0)))

	// Check memif
	Expect(intface.Memif.SocketFilename).To(Equal("test"))
	Expect(intface.Memif.Id).To(Equal(uint32(2)))
	Expect(intface.Memif.Mode).To(Equal(interfaces2.Interfaces_Interface_Memif_ETHERNET))
	Expect(intface.Memif.Master).To(BeTrue())

	// Check tap
	Expect(intface.Tap.HostIfName).To(Equal("taptap2"))
	Expect(intface.Tap.Version).To(Equal(uint32(2)))

	// Check vxlan
	Expect(intface.Vxlan.SrcAddress).To(Equal("192.168.0.1"))
	Expect(intface.Vxlan.DstAddress).To(Equal("192.168.0.2"))
}

// Test dump of memif socket details using standard reply mocking
func TestDumpMemifSocketDetails(t *testing.T) {
	ctx, ifHandler := ifTestSetup(t)
	defer ctx.TeardownTestCtx()

	ctx.MockVpp.MockReply(&memif.MemifSocketFilenameDetails{
		SocketID:       1,
		SocketFilename: []byte("test"),
	})

	ctx.MockVpp.MockReply(&vpe.ControlPingReply{})

	result, err := ifHandler.DumpMemifSocketDetails()
	Expect(err).To(BeNil())
	Expect(result).To(Not(BeEmpty()))

	socketID, ok := result["test"]
	Expect(ok).To(BeTrue())
	Expect(socketID).To(Equal(uint32(1)))
}
<commit_msg>fix test<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 vppcalls_test

import (
	"net"
	"testing"

	"github.com/ligato/vpp-agent/plugins/vpp/binapi/dhcp"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/interfaces"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/ip"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/memif"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/tap"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/tapv2"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/vpe"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/vxlan"
	interfaces2 "github.com/ligato/vpp-agent/plugins/vpp/model/interfaces"
	"github.com/ligato/vpp-agent/tests/vppcallmock"
	. "github.com/onsi/gomega"
)

// Test dump of interfaces with vxlan type
func TestDumpInterfacesVxLan(t *testing.T) {
	ctx, ifHandler := ifTestSetup(t)
	defer ctx.TeardownTestCtx()

	ipv61Parse := net.ParseIP("dead:beef:feed:face:cafe:babe:baad:c0de").To16()
	ipv62Parse := net.ParseIP("d3ad:beef:feed:face:cafe:babe:baad:c0de").To16()

	ctx.MockReplies([]*vppcallmock.HandleReplies{
		{
			Name: (&interfaces.SwInterfaceDump{}).GetMessageName(),
			Ping: true,
			Message: &interfaces.SwInterfaceDetails{
				InterfaceName: []byte("vxlan1"),
			},
		},
		{
			Name:    (&interfaces.SwInterfaceGetTable{}).GetMessageName(),
			Ping:    false,
			Message: &interfaces.SwInterfaceGetTableReply{},
		},
		{
			Name:    (&ip.IPAddressDump{}).GetMessageName(),
			Ping:    true,
			Message: &ip.IPAddressDetails{},
		},
		{
			Name:    (&memif.MemifSocketFilenameDump{}).GetMessageName(),
			Ping:    true,
			Message: &memif.MemifSocketFilenameDetails{},
		},
		{
			Name:    (&memif.MemifDump{}).GetMessageName(),
			Ping:    true,
			Message: &memif.MemifDetails{},
		},
		{
			Name:    (&tap.SwInterfaceTapDump{}).GetMessageName(),
			Ping:    true,
			Message: &tap.SwInterfaceTapDetails{},
		},
		{
			Name:    (&tapv2.SwInterfaceTapV2Dump{}).GetMessageName(),
			Ping:    true,
			Message: &tapv2.SwInterfaceTapV2Details{},
		},
		{
			Name: (&vxlan.VxlanTunnelDump{}).GetMessageName(),
			Ping: true,
			Message: &vxlan.VxlanTunnelDetails{
				IsIPv6:     1,
				SwIfIndex:  0,
				SrcAddress: ipv61Parse,
				DstAddress: ipv62Parse,
			},
		},
	})

	intfs, err := ifHandler.DumpInterfaces()
	Expect(err).To(BeNil())
	Expect(intfs).To(HaveLen(1))
	intface := intfs[0].Interface

	// Check vxlan
	Expect(intface.Vxlan.SrcAddress).To(Equal("dead:beef:feed:face:cafe:babe:baad:c0de"))
	Expect(intface.Vxlan.DstAddress).To(Equal("d3ad:beef:feed:face:cafe:babe:baad:c0de"))
}

// Test dump of interfaces with host type
func TestDumpInterfacesHost(t *testing.T) {
	ctx, ifHandler := ifTestSetup(t)
	defer ctx.TeardownTestCtx()

	ctx.MockReplies([]*vppcallmock.HandleReplies{
		{
			Name: (&interfaces.SwInterfaceDump{}).GetMessageName(),
			Ping: true,
			Message: &interfaces.SwInterfaceDetails{
				InterfaceName: []byte("host-localhost"),
			},
		},
		{
			Name:    (&interfaces.SwInterfaceGetTable{}).GetMessageName(),
			Ping:    false,
			Message: &interfaces.SwInterfaceGetTableReply{},
		},
		{
			Name:    (&ip.IPAddressDump{}).GetMessageName(),
			Ping:    true,
			Message: &ip.IPAddressDetails{},
		},
		{
			Name:    (&memif.MemifSocketFilenameDump{}).GetMessageName(),
			Ping:    true,
			Message: &memif.MemifSocketFilenameDetails{},
		},
		{
			Name:    (&memif.MemifDump{}).GetMessageName(),
			Ping:    true,
			Message: &memif.MemifDetails{},
		},
		{
			Name:    (&tap.SwInterfaceTapDump{}).GetMessageName(),
			Ping:    true,
			Message: &tap.SwInterfaceTapDetails{},
		},
		{
			Name:    (&tapv2.SwInterfaceTapV2Dump{}).GetMessageName(),
			Ping:    true,
			Message: &tapv2.SwInterfaceTapV2Details{},
		},
		{
			Name:    (&vxlan.VxlanTunnelDump{}).GetMessageName(),
			Ping:    true,
			Message: &vxlan.VxlanTunnelDetails{},
		},
	})

	intfs, err := ifHandler.DumpInterfaces()
	Expect(err).To(BeNil())
	Expect(intfs).To(HaveLen(1))
	intface := intfs[0].Interface

	// Check interface data
	Expect(intface.Afpacket.HostIfName).To(Equal("localhost"))
}

// Test dump of interfaces with memif type
func TestDumpInterfacesMemif(t *testing.T) {
	ctx, ifHandler := ifTestSetup(t)
	defer ctx.TeardownTestCtx()

	ctx.MockReplies([]*vppcallmock.HandleReplies{
		{
			Name: (&interfaces.SwInterfaceDump{}).GetMessageName(),
			Ping: true,
			Message: &interfaces.SwInterfaceDetails{
				InterfaceName: []byte("memif1"),
			},
		},
		{
			Name:    (&interfaces.SwInterfaceGetTable{}).GetMessageName(),
			Ping:    false,
			Message: &interfaces.SwInterfaceGetTableReply{},
		},
		{
			Name:    (&ip.IPAddressDump{}).GetMessageName(),
			Ping:    true,
			Message: &ip.IPAddressDetails{},
		},
		{
			Name: (&memif.MemifSocketFilenameDump{}).GetMessageName(),
			Ping: true,
			Message: &memif.MemifSocketFilenameDetails{
				SocketID:       1,
				SocketFilename: []byte("test"),
			},
		},
		{
			Name: (&memif.MemifDump{}).GetMessageName(),
			Ping: true,
			Message: &memif.MemifDetails{
				ID:         2,
				SwIfIndex:  0,
				Role:       1, // Slave
				Mode:       1, // IP
				SocketID:   1,
				RingSize:   0,
				BufferSize: 0,
			},
		},
		{
			Name:    (&tap.SwInterfaceTapDump{}).GetMessageName(),
			Ping:    true,
			Message: &tap.SwInterfaceTapDetails{},
		},
		{
			Name:    (&tapv2.SwInterfaceTapV2Dump{}).GetMessageName(),
			Ping:    true,
			Message: &tapv2.SwInterfaceTapV2Details{},
		},
		{
			Name:    (&vxlan.VxlanTunnelDump{}).GetMessageName(),
			Ping:    true,
			Message: &vxlan.VxlanTunnelDetails{},
		},
	})

	intfs, err := ifHandler.DumpInterfaces()
	Expect(err).To(BeNil())
	Expect(intfs).To(HaveLen(1))
	intface := intfs[0].Interface

	// Check memif
	Expect(intface.Memif.SocketFilename).To(Equal("test"))
	Expect(intface.Memif.Id).To(Equal(uint32(2)))
	Expect(intface.Memif.Mode).To(Equal(interfaces2.Interfaces_Interface_Memif_IP))
	Expect(intface.Memif.Master).To(BeFalse())
}

// Test dump of interfaces using custom mock reply handler to avoid issues with ControlPingMessageReply
// not being properly recognized
func TestDumpInterfacesFull(t *testing.T) {
	ctx, ifHandler := ifTestSetup(t)
	defer ctx.TeardownTestCtx()

	hwAddr1Parse, err := net.ParseMAC("01:23:45:67:89:ab")
	Expect(err).To(BeNil())

	ctx.MockReplies([]*vppcallmock.HandleReplies{
		{
			Name: (&interfaces.SwInterfaceDump{}).GetMessageName(),
			Ping: true,
			Message: &interfaces.SwInterfaceDetails{
				SwIfIndex:       0,
				InterfaceName:   []byte("memif1"),
				Tag:             []byte("interface2"),
				AdminUpDown:     1,
				LinkMtu:         9216, // Default MTU
				L2Address:       hwAddr1Parse,
				L2AddressLength: uint32(len(hwAddr1Parse)),
			},
		},
		{
			Name: (&interfaces.SwInterfaceGetTable{}).GetMessageName(),
			Ping: false,
			Message: &interfaces.SwInterfaceGetTableReply{
				Retval: 0,
				VrfID:  42,
			},
		},
		{
			Name:    (&ip.IPAddressDump{}).GetMessageName(),
			Ping:    true,
			Message: &ip.IPAddressDetails{},
		},
		{
			Name: (&dhcp.DHCPClientDump{}).GetMessageName(),
			Ping: true,
			Message: &dhcp.DHCPClientDetails{
				Client: dhcp.DHCPClient{
					SwIfIndex: 0,
				},
			},
		},
		{
			Name: (&memif.MemifSocketFilenameDump{}).GetMessageName(),
			Ping: true,
			Message: &memif.MemifSocketFilenameDetails{
				SocketID:       1,
				SocketFilename: []byte("test"),
			},
		},
		{
			Name: (&memif.MemifDump{}).GetMessageName(),
			Ping: true,
			Message: &memif.MemifDetails{
				ID:         2,
				SwIfIndex:  0,
				Role:       0, // Master
				Mode:       0, // Ethernet
				SocketID:   1,
				RingSize:   0,
				BufferSize: 0,
			},
		},
		{
			Name: (&tap.SwInterfaceTapDump{}).GetMessageName(),
			Ping: true,
			Message: &tap.SwInterfaceTapDetails{
				SwIfIndex: 0,
				DevName:   []byte("taptap"),
			},
		},
		{
			Name: (&tapv2.SwInterfaceTapV2Dump{}).GetMessageName(),
			Ping: true,
			Message: &tapv2.SwInterfaceTapV2Details{
				SwIfIndex:  0,
				HostIfName: []byte("taptap2"), // This will overwrite the v1 tap name
			},
		},
		{
			Name: (&vxlan.VxlanTunnelDump{}).GetMessageName(),
			Ping: true,
			Message: &vxlan.VxlanTunnelDetails{
				SwIfIndex:  0,
				SrcAddress: []byte{192, 168, 0, 1},
				DstAddress: []byte{192, 168, 0, 2},
			},
		},
	})

	intfs, err := ifHandler.DumpInterfaces()
	Expect(err).To(BeNil())
	Expect(intfs).To(HaveLen(1))

	intface := intfs[0].Interface
	intMeta := intfs[0].Meta

	// This is last checked type, so it will be equal to that
	Expect(intface.Type).To(Equal(interfaces2.InterfaceType_VXLAN_TUNNEL))
	Expect(intface.PhysAddress).To(Equal("01:23:45:67:89:ab"))
	Expect(intface.Name).To(Equal("interface2"))
	Expect(intface.Mtu).To(Equal(uint32(0))) // default mtu
	Expect(intface.Enabled).To(BeTrue())
	Expect(intface.Vrf).To(Equal(uint32(42)))
	Expect(intface.SetDhcpClient).To(BeTrue())
	Expect(intMeta.VrfIPv4).To(Equal(uint32(42)))
	Expect(intMeta.VrfIPv6).To(Equal(uint32(42)))

	// Check memif
	Expect(intface.Memif.SocketFilename).To(Equal("test"))
	Expect(intface.Memif.Id).To(Equal(uint32(2)))
	Expect(intface.Memif.Mode).To(Equal(interfaces2.Interfaces_Interface_Memif_ETHERNET))
	Expect(intface.Memif.Master).To(BeTrue())

	// Check tap
	Expect(intface.Tap.HostIfName).To(Equal("taptap2"))
	Expect(intface.Tap.Version).To(Equal(uint32(2)))

	// Check vxlan
	Expect(intface.Vxlan.SrcAddress).To(Equal("192.168.0.1"))
	Expect(intface.Vxlan.DstAddress).To(Equal("192.168.0.2"))
}

// Test dump of memif socket details using standard reply mocking
func TestDumpMemifSocketDetails(t *testing.T) {
	ctx, ifHandler := ifTestSetup(t)
	defer ctx.TeardownTestCtx()

	ctx.MockVpp.MockReply(&memif.MemifSocketFilenameDetails{
		SocketID:       1,
		SocketFilename: []byte("test"),
	})

	ctx.MockVpp.MockReply(&vpe.ControlPingReply{})

	result, err := ifHandler.DumpMemifSocketDetails()
	Expect(err).To(BeNil())
	Expect(result).To(Not(BeEmpty()))

	socketID, ok := result["test"]
	Expect(ok).To(BeTrue())
	Expect(socketID).To(Equal(uint32(1)))
}
<|endoftext|>


<commit_before>package web_test

import (
	"fmt"
	"time"

	yaml "gopkg.in/yaml.v2"

	"github.com/concourse/atc"
	"github.com/concourse/testflight/gitserver"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/sclevine/agouti/matchers"
)

var _ = Describe("BuildsView", func() {
	var build atc.Build

	Context("with a job in the configuration", func() {
		var originGitServer *gitserver.Server

		BeforeEach(func() {
			originGitServer = gitserver.Start(client)
			originGitServer.CommitResource()

			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "some-job",
						Plan: atc.PlanSequence{
							{
								Get: "some-input-resource",
							},
							{
								Task: "some-task",
								TaskConfig: &atc.TaskConfig{
									Platform: "linux",
									ImageResource: &atc.ImageResource{
										Type:   "docker-image",
										Source: atc.Source{"repository": "busybox"},
									},
									Inputs: []atc.TaskInputConfig{
										{Name: "some-input-resource"},
									},
									Outputs: []atc.TaskOutputConfig{
										{Name: "some-output-src"},
									},
									Run: atc.TaskRunConfig{
										Path: "cp",
										Args: []string{"-r", "some-input-resource/.", "some-output-src"},
									},
								},
							},
							{
								Put:    "some-output-resource",
								Params: atc.Params{"repository": "some-output-src"},
							},
						},
					},
				},
				Resources: []atc.ResourceConfig{
					{
						Name: "some-input-resource",
						Type: "git",
						Source: atc.Source{
							"branch": "master",
							"uri":    originGitServer.URI(),
						},
						CheckEvery: "",
					},
					{
						Name: "some-output-resource",
						Type: "git",
						Source: atc.Source{
							"branch": "master",
							"uri":    originGitServer.URI(),
						},
						CheckEvery: "",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			build, err = team.CreateJobBuild(pipelineName, "some-job")
			Expect(err).NotTo(HaveOccurred())
		})

		AfterEach(func() {
			originGitServer.Stop()
		})

		It("can view resource information of a job build", func() {
			url := atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s/jobs/some-job", teamName, pipelineName))

			Expect(page.Navigate(url)).To(Succeed())
			Eventually(page.Find(".build-header.succeeded")).Should(BeFound())

			Eventually(page.All(".builds-list li")).Should(HaveCount(1))

			Expect(page.Find(".builds-list li:first-child a")).To(HaveText("#1"))
			Eventually(page.Find(".builds-list li:first-child a.succeeded"), 10*time.Second).Should(BeFound())

			Eventually(page.Find(".builds-list li:first-child .inputs .resource-name"), 10*time.Second).Should(BeFound())
			Expect(page.Find(".builds-list li:first-child .inputs .resource-name")).To(HaveText("some-input-resource"))
			Expect(page.Find(".builds-list li:first-child .inputs .resource-version .dict-key")).To(HaveText("ref"))
			Expect(page.Find(".builds-list li:first-child .inputs .resource-version .dict-value")).To(MatchText("[0-9a-f]{40}"))

			Expect(page.Find(".builds-list li:first-child .outputs .resource-name")).To(HaveText("some-output-resource"))
			Expect(page.Find(".builds-list li:first-child .outputs .resource-version .dict-key")).To(HaveText("ref"))
			Expect(page.Find(".builds-list li:first-child .outputs .resource-version .dict-value")).To(MatchText("[0-9a-z]{40}"))

			// button should not have the boolean attribute "disabled" set. agouti currently returns
			// an empty string in that case.
			Expect(page.Find("button.build-action")).ToNot(BeNil())
			Expect(page.Find("button.build-action")).To(HaveAttribute("disabled", ""))
		})

		Describe("paused pipeline", func() {
			BeforeEach(func() {
				_, err := team.PausePipeline(pipelineName)
				Expect(err).NotTo(HaveOccurred())
			})

			It("displays a blue header", func() {
				Expect(page.Navigate(atcRoute(build.URL))).To(Succeed())

				Expect(page.Navigate(atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s/jobs/some-job/builds/%s", teamName, pipelineName, build.Name)))).To(Succeed())

				// top bar should show the pipeline is paused
				Eventually(page.Find(".top-bar.test.paused"), 10*time.Second).Should(BeFound())
			})
		})
	})

	Context("when a job and a resource in the configuration have names that need be escaped", func() {
		var pipelineUrl string
		var buildsUrl string
		var resourceUrl string

		BeforeEach(func() {
			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "some/job",
						Plan: atc.PlanSequence{
							{
								Get: "some/resource",
							},
						},
					},
				},
				Resources: []atc.ResourceConfig{
					{
						Name: "some/resource",
						Type: "time",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			pipelineUrl = atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s", teamName, pipelineName))
			Expect(page.Navigate(pipelineUrl)).To(Succeed())

			buildsUrl = fmt.Sprintf("%s/jobs/some%%2Fjob", pipelineUrl)
			resourceUrl = fmt.Sprintf("%s/resources/some%%2Fresource", pipelineUrl)
		})

		It("displays the unescaped names in the pipeline view", func() {
			Expect(page.Find(".job")).To(HaveText("some/job"))
			Expect(page.Find(".input")).To(HaveText("some/resource"))
		})

		It("can navigate to the escaped links", func() {
			Eventually(page.FindByLink("some/job")).Should(BeFound())
			Expect(page.FindByLink("some/job").Click()).To(Succeed())
			Eventually(page.URL).Should(Equal(buildsUrl))
			Expect(page.Navigate(atcURL)).Should(Succeed())
			Eventually(page.FindByLink("some/resource")).Should(BeFound())
			Expect(page.FindByLink("some/resource").Click()).To(Succeed())
			Eventually(page.URL).Should(Equal(resourceUrl))
		})
	})

	Context("when manual triggering of the job is disabled", func() {
		var manualTriggerDisabledBuild atc.Build

		BeforeEach(func() {
			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "job-manual-trigger-disabled",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			manualTriggerDisabledBuild, err = team.CreateJobBuild(pipelineName, "job-manual-trigger-disabled")
			Expect(err).NotTo(HaveOccurred())

			_, _, pipelineVersion, _, err := team.PipelineConfig(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			config = atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name:                 "job-manual-trigger-disabled",
						DisableManualTrigger: true,
					},
				},
			}

			byteConfig, err = yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, pipelineVersion, byteConfig)
			Expect(err).NotTo(HaveOccurred())
		})

		It("should have a disabled button in the build details view", func() {
			Expect(page.Navigate(atcRoute(manualTriggerDisabledBuild.URL))).To(Succeed())

			// job detail w/build info -> job detail
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled/builds/%s",
				teamName,
				pipelineName,
				manualTriggerDisabledBuild.Name,
			))))
			Eventually(page.Find("span.build-action"), 10*time.Second).Should(HaveAttribute("disabled", "true"))
		})

		It("should have a disabled button in the job details view", func() {
			Expect(page.Navigate(atcRoute(manualTriggerDisabledBuild.URL))).To(Succeed())

			// job detail w/build info -> job detail
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled/builds/%s",
				teamName,
				pipelineName,
				manualTriggerDisabledBuild.Name,
			))))

			Eventually(page.Find("h1 a"), 10*time.Second).Should(BeFound())
			Expect(page.Find("h1 a").Click()).To(Succeed())
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled",
				teamName,
				pipelineName,
			))))

			Eventually(page.Find("button.build-action")).Should(BeFound())
			Expect(page.Find("button.build-action")).To(HaveAttribute("disabled", "true"))
		})
	})
})
<commit_msg>nuke web test; migrating to testpilot<commit_after>package web_test

import (
	"fmt"
	"time"

	yaml "gopkg.in/yaml.v2"

	"github.com/concourse/atc"
	"github.com/concourse/testflight/gitserver"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/sclevine/agouti/matchers"
)

var _ = Describe("BuildsView", func() {
	var build atc.Build

	Context("with a job in the configuration", func() {
		var originGitServer *gitserver.Server

		BeforeEach(func() {
			originGitServer = gitserver.Start(client)
			originGitServer.CommitResource()

			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "some-job",
						Plan: atc.PlanSequence{
							{
								Get: "some-input-resource",
							},
							{
								Task: "some-task",
								TaskConfig: &atc.TaskConfig{
									Platform: "linux",
									ImageResource: &atc.ImageResource{
										Type:   "docker-image",
										Source: atc.Source{"repository": "busybox"},
									},
									Inputs: []atc.TaskInputConfig{
										{Name: "some-input-resource"},
									},
									Outputs: []atc.TaskOutputConfig{
										{Name: "some-output-src"},
									},
									Run: atc.TaskRunConfig{
										Path: "cp",
										Args: []string{"-r", "some-input-resource/.", "some-output-src"},
									},
								},
							},
							{
								Put:    "some-output-resource",
								Params: atc.Params{"repository": "some-output-src"},
							},
						},
					},
				},
				Resources: []atc.ResourceConfig{
					{
						Name: "some-input-resource",
						Type: "git",
						Source: atc.Source{
							"branch": "master",
							"uri":    originGitServer.URI(),
						},
						CheckEvery: "",
					},
					{
						Name: "some-output-resource",
						Type: "git",
						Source: atc.Source{
							"branch": "master",
							"uri":    originGitServer.URI(),
						},
						CheckEvery: "",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			build, err = team.CreateJobBuild(pipelineName, "some-job")
			Expect(err).NotTo(HaveOccurred())
		})

		AfterEach(func() {
			originGitServer.Stop()
		})

		It("can view resource information of a job build", func() {
			url := atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s/jobs/some-job", teamName, pipelineName))

			Expect(page.Navigate(url)).To(Succeed())
			Eventually(page.Find(".build-header.succeeded")).Should(BeFound())

			Eventually(page.All(".builds-list li")).Should(HaveCount(1))

			Expect(page.Find(".builds-list li:first-child a")).To(HaveText("#1"))
			Eventually(page.Find(".builds-list li:first-child a.succeeded"), 10*time.Second).Should(BeFound())

			Eventually(page.Find(".builds-list li:first-child .inputs .resource-name"), 10*time.Second).Should(BeFound())
			Expect(page.Find(".builds-list li:first-child .inputs .resource-name")).To(HaveText("some-input-resource"))
			Expect(page.Find(".builds-list li:first-child .inputs .resource-version .dict-key")).To(HaveText("ref"))
			Expect(page.Find(".builds-list li:first-child .inputs .resource-version .dict-value")).To(MatchText("[0-9a-f]{40}"))

			Expect(page.Find(".builds-list li:first-child .outputs .resource-name")).To(HaveText("some-output-resource"))
			Expect(page.Find(".builds-list li:first-child .outputs .resource-version .dict-key")).To(HaveText("ref"))
			Expect(page.Find(".builds-list li:first-child .outputs .resource-version .dict-value")).To(MatchText("[0-9a-z]{40}"))

			// button should not have the boolean attribute "disabled" set. agouti currently returns
			// an empty string in that case.
			Expect(page.Find("button.build-action")).ToNot(BeNil())
			Expect(page.Find("button.build-action")).To(HaveAttribute("disabled", ""))
		})

		Describe("paused pipeline", func() {
			BeforeEach(func() {
				_, err := team.PausePipeline(pipelineName)
				Expect(err).NotTo(HaveOccurred())
			})

			It("displays a blue header", func() {
				Expect(page.Navigate(atcRoute(build.URL))).To(Succeed())

				Expect(page.Navigate(atcRoute(fmt.Sprintf("/teams/%s/pipelines/%s/jobs/some-job/builds/%s", teamName, pipelineName, build.Name)))).To(Succeed())

				// top bar should show the pipeline is paused
				Eventually(page.Find(".top-bar.test.paused"), 10*time.Second).Should(BeFound())
			})
		})
	})

	Context("when manual triggering of the job is disabled", func() {
		var manualTriggerDisabledBuild atc.Build

		BeforeEach(func() {
			config := atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name: "job-manual-trigger-disabled",
					},
				},
			}

			byteConfig, err := yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, "0", byteConfig)
			Expect(err).NotTo(HaveOccurred())

			_, err = team.UnpausePipeline(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			manualTriggerDisabledBuild, err = team.CreateJobBuild(pipelineName, "job-manual-trigger-disabled")
			Expect(err).NotTo(HaveOccurred())

			_, _, pipelineVersion, _, err := team.PipelineConfig(pipelineName)
			Expect(err).NotTo(HaveOccurred())

			config = atc.Config{
				Jobs: []atc.JobConfig{
					{
						Name:                 "job-manual-trigger-disabled",
						DisableManualTrigger: true,
					},
				},
			}

			byteConfig, err = yaml.Marshal(config)
			Expect(err).NotTo(HaveOccurred())

			_, _, _, err = team.CreateOrUpdatePipelineConfig(pipelineName, pipelineVersion, byteConfig)
			Expect(err).NotTo(HaveOccurred())
		})

		It("should have a disabled button in the build details view", func() {
			Expect(page.Navigate(atcRoute(manualTriggerDisabledBuild.URL))).To(Succeed())

			// job detail w/build info -> job detail
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled/builds/%s",
				teamName,
				pipelineName,
				manualTriggerDisabledBuild.Name,
			))))
			Eventually(page.Find("span.build-action"), 10*time.Second).Should(HaveAttribute("disabled", "true"))
		})

		It("should have a disabled button in the job details view", func() {
			Expect(page.Navigate(atcRoute(manualTriggerDisabledBuild.URL))).To(Succeed())

			// job detail w/build info -> job detail
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled/builds/%s",
				teamName,
				pipelineName,
				manualTriggerDisabledBuild.Name,
			))))

			Eventually(page.Find("h1 a"), 10*time.Second).Should(BeFound())
			Expect(page.Find("h1 a").Click()).To(Succeed())
			Eventually(page, 10*time.Second).Should(HaveURL(atcRoute(fmt.Sprintf(
				"/teams/%s/pipelines/%s/jobs/job-manual-trigger-disabled",
				teamName,
				pipelineName,
			))))

			Eventually(page.Find("button.build-action")).Should(BeFound())
			Expect(page.Find("button.build-action")).To(HaveAttribute("disabled", "true"))
		})
	})
})
<|endoftext|>


<commit_before>package cli

import (
	"github.com/mehrdadrad/mylg/banner"
	"gopkg.in/readline.v1"
)

type Readline struct {
	instance *readline.Instance
}

func Init(prompt string) *Readline {
	var (
		r   Readline
		err error
	)
	r.instance, err = readline.NewEx(&readline.Config{
		Prompt:          prompt + "> ",
		HistoryFile:     "/tmp/myping",
		InterruptPrompt: "^C",
		EOFPrompt:       "exit",
	})
	if err != nil {
		panic(err)
	}
	banner.Println()
	return &r
}

func (r *Readline) SetPrompt(p string) {
	r.instance.SetPrompt(p + "> ")
}

func (r *Readline) Run(cmd chan<- string, next chan struct{}) {
	func() {
		for {
			line, err := r.instance.Readline()
			if err != nil { // io.EOF, readline.ErrInterrupt
				break
			}
			cmd <- line
			<-next
		}
	}()
}
<commit_msg>init basic autocomplete<commit_after>package cli

import (
	"github.com/mehrdadrad/mylg/banner"
	"gopkg.in/readline.v1"
)

type Readline struct {
	instance *readline.Instance
}

func Init(prompt string) *Readline {
	var (
		r         Readline
		err       error
		completer = readline.NewPrefixCompleter(
			readline.PcItem("ping"),
			readline.PcItem("connect",
				readline.PcItem("telia"),
				readline.PcItem("level3"),
			),
			readline.PcItem("node"),
			readline.PcItem("local"),
			readline.PcItem("help"),
		)
	)
	r.instance, err = readline.NewEx(&readline.Config{
		Prompt:          prompt + "> ",
		HistoryFile:     "/tmp/myping",
		InterruptPrompt: "^C",
		EOFPrompt:       "exit",
		AutoComplete:    completer,
	})
	if err != nil {
		panic(err)
	}
	banner.Println()
	return &r
}

func (r *Readline) SetPrompt(p string) {
	r.instance.SetPrompt(p + "> ")
}

func (r *Readline) Run(cmd chan<- string, next chan struct{}) {
	func() {
		for {
			line, err := r.instance.Readline()
			if err != nil { // io.EOF, readline.ErrInterrupt
				break
			}
			cmd <- line
			<-next
		}
	}()
}
<|endoftext|>


<commit_before>// Package cli provides all methods to control command line functions
package cli

import (
	"encoding/json"
	"fmt"
	"github.com/mehrdadrad/mylg/banner"
	"gopkg.in/readline.v1"
	"io/ioutil"
	"net/http"
	"regexp"
	"strconv"
	"strings"
)

const usage = `Usage:
	The myLG tool developed to troubleshoot networking situations.
	The vi/emacs mode,almost all basic features is supported. press tab to see what options are available.

	connect <provider name>     connects to external looking glass, press tab to see the menu
	node <city/country name>    connects to specific node at current looking glass, press tab to see the available nodes
	local                       back to local
	lg                          change mode to extenal looking glass
	ns                          change mode to name server looking up
	ping                        ping ip address or domain name
	dig                         name server looking up
	whois                       resolve AS number/IP/CIDR to holder (provides by ripe ncc)
	hping                       Ping through HTTP/HTTPS w/ GET/HEAD methods
	scan                        scan tcp ports (you can provide range >scan host minport maxport)
	dump                        prints out a description of the contents of packets on a network interface
	disc                        discover all the devices on a LAN
	peering                     peering information (provides by peeringdb.com)
	web                         web dashboard - opens dashboard at your default browser
	`

// Readline structure
type Readline struct {
	instance  *readline.Instance
	completer *readline.PrefixCompleter
	prompt    string
	next      chan struct{}
}

var (
	// validation command regex
	CMDReg, _ = regexp.Compile(
		`(ping|trace|bgp|lg|ns|dig|dump|disc|whois|peering|scan|hping|connect|node|local|mode|help|web|exit|quit)\s{0,1}(.*)`)
)

// Init set readline imain items
func Init(prompt, version string) *Readline {
	var (
		r         Readline
		err       error
		completer = readline.NewPrefixCompleter(
			readline.PcItem("ping"),
			readline.PcItem("trace"),
			readline.PcItem("bgp"),
			readline.PcItem("hping"),
			readline.PcItem("connect"),
			readline.PcItem("node"),
			readline.PcItem("local"),
			readline.PcItem("lg"),
			readline.PcItem("ns"),
			readline.PcItem("dig"),
			readline.PcItem("whois"),
			readline.PcItem("scan"),
			readline.PcItem("dump"),
			readline.PcItem("disc"),
			readline.PcItem("peering"),
			readline.PcItem("help"),
			readline.PcItem("web"),
			readline.PcItem("exit"),
		)
	)
	r.completer = completer
	r.instance, err = readline.NewEx(&readline.Config{
		Prompt:          prompt + "> ",
		HistoryFile:     "/tmp/myping",
		InterruptPrompt: "^C",
		EOFPrompt:       "exit",
		AutoComplete:    completer,
	})
	if err != nil {
		panic(err)
	}
	banner.Println(version) // print banner
	go checkUpdate(version) // check update version
	r.prompt = prompt       // init local prompt
	return &r
}

// RemoveItemCompleter removes subitem(s) from a specific main item
func (r *Readline) RemoveItemCompleter(pcItem string) {
	child := []readline.PrefixCompleterInterface{}
	for _, p := range r.completer.Children {
		if strings.TrimSpace(string(p.GetName())) != pcItem {
			child = append(child, p)
		}
	}

	r.completer.Children = child

}

// AddCompleter updates subitem(s) from a specific main item
func (r *Readline) AddCompleter(pcItem string, pcSubItems []string) {
	var pc readline.PrefixCompleter
	c := []readline.PrefixCompleterInterface{}
	for _, item := range pcSubItems {
		c = append(c, readline.PcItem(item))
	}
	pc.Name = []rune(pcItem + " ")
	pc.Children = c
	r.completer.Children = append(r.completer.Children, &pc)
}

// UpdateCompleter updates subitem(s) from a specific main item
func (r *Readline) UpdateCompleter(pcItem string, pcSubItems []string) {
	child := []readline.PrefixCompleterInterface{}
	var pc readline.PrefixCompleter
	for _, p := range r.completer.Children {
		if strings.TrimSpace(string(p.GetName())) == pcItem {
			c := []readline.PrefixCompleterInterface{}
			for _, item := range pcSubItems {
				c = append(c, readline.PcItem(item))
			}
			pc.Name = []rune(pcItem + " ")
			pc.Children = c
			child = append(child, &pc)
		} else {
			child = append(child, p)
		}
	}
	if len(pc.Name) < 1 {
		// todo adding new
	}
	r.completer.Children = child
}

// SetPrompt set readline prompt and store it
func (r *Readline) SetPrompt(p string) {
	p = strings.ToLower(p)
	r.prompt = p
	r.instance.SetPrompt(p + "> ")
}

// UpdatePromptN appends readline prompt
func (r *Readline) UpdatePromptN(p string, n int) {
	var parts []string
	p = strings.ToLower(p)
	parts = strings.SplitAfterN(r.prompt, "/", n)
	if n <= len(parts) && n > -1 {
		parts[n-1] = p
		r.prompt = strings.Join(parts, "")
	} else {
		r.prompt += "/" + p
	}
	r.instance.SetPrompt(r.prompt + "> ")
}

// GetPrompt returns the current prompt string
func (r *Readline) GetPrompt() string {
	return r.prompt
}

// Refresh prompt
func (r *Readline) Refresh() {
	r.instance.Refresh()
}

// SetVim set mode to vim
func (r *Readline) SetVim() {
	if !r.instance.IsVimMode() {
		r.instance.SetVimMode(true)
		println("mode changed to vim")
	} else {
		println("mode already is vim")
	}
}

// SetEmacs set mode to emacs
func (r *Readline) SetEmacs() {
	if r.instance.IsVimMode() {
		r.instance.SetVimMode(false)
		println("mode changed to emacs")
	} else {
		println("mode already is emacs")
	}
}

// Next trigers to read next line
func (r *Readline) Next() {
	r.next <- struct{}{}
}

// Run the main loop
func (r *Readline) Run(cmd chan<- string, next chan struct{}) {
	r.next = next
	func() {
		for {
			line, err := r.instance.Readline()
			if err != nil { // io.EOF, readline.ErrInterrupt
				break
			}
			cmd <- line
			if _, ok := <-next; !ok {
				break
			}
		}
	}()
}

// Close the readline instance
func (r *Readline) Close(next chan struct{}) {
	r.instance.Close()
}

// Help print out the main help
func (r *Readline) Help() {
	fmt.Println(usage)
}

// checkUpdate checks if any new version is available
func checkUpdate(version string) {
	type mylg struct {
		Version string
	}
	var appCtl mylg

	if version == "test" {
		return
	}

	resp, err := http.Get("http://mylg.io/appctl/mylg")
	if err != nil {
		println("error: check update has been failed ")
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		println("error: check update has been failed (2)" + err.Error())
		return
	}
	err = json.Unmarshal(body, &appCtl)
	if err != nil {
		println(err.Error())
		return
	}
	if version != appCtl.Version {
		fmt.Printf("New version is available (v%s) at http://mylg.io/download\n", appCtl.Version)
	}
}

//Flag parses the command arguments syntax:
// -flag=x
// -flag x
// help
func Flag(args string) (string, map[string]interface{}) {
	var (
		r   = make(map[string]interface{}, 10)
		err error
	)
	args = strings.TrimSpace(args)
	re := regexp.MustCompile(`(?i)-([a-z]+)={0,1}\s{0,1}([0-9|a-z|\-|'"{}:\/]*)`)
	f := re.FindAllStringSubmatch(args, -1)
	for _, kv := range f {
		if len(kv) > 1 {
			// trim extra characters (' and ") from value
			kv[2] = strings.Trim(kv[2], "'")
			kv[2] = strings.Trim(kv[2], `"`)
			r[kv[1]], err = strconv.Atoi(kv[2])
			if err != nil {
				r[kv[1]] = kv[2]
			}
			args = strings.Replace(args, kv[0], "", -1)
		}
	}
	if m, _ := regexp.MatchString(`(?i)help$`, args); m {
		r["help"] = true
	}
	args = strings.TrimSpace(args)
	return args, r
}

// SetFlag returns command option(s)
func SetFlag(flag map[string]interface{}, option string, v interface{}) interface{} {
	if sValue, ok := flag[option]; ok {
		switch v.(type) {
		case int:
			return sValue.(int)
		case string:
			switch sValue.(type) {
			case string:
				return sValue.(string)
			case int:
				str := strconv.FormatInt(int64(sValue.(int)), 10)
				return str
			case float64:
				str := strconv.FormatFloat(sValue.(float64), 'f', -1, 64)
				return str
			}
		case bool:
			return true
		default:
			return sValue.(string)
		}
	}
	return v
}
<commit_msg>added number to regex to support pure number switch<commit_after>// Package cli provides all methods to control command line functions
package cli

import (
	"encoding/json"
	"fmt"
	"github.com/mehrdadrad/mylg/banner"
	"gopkg.in/readline.v1"
	"io/ioutil"
	"net/http"
	"regexp"
	"strconv"
	"strings"
)

const usage = `Usage:
	The myLG tool developed to troubleshoot networking situations.
	The vi/emacs mode,almost all basic features is supported. press tab to see what options are available.

	connect <provider name>     connects to external looking glass, press tab to see the menu
	node <city/country name>    connects to specific node at current looking glass, press tab to see the available nodes
	local                       back to local
	lg                          change mode to extenal looking glass
	ns                          change mode to name server looking up
	ping                        ping ip address or domain name
	dig                         name server looking up
	whois                       resolve AS number/IP/CIDR to holder (provides by ripe ncc)
	hping                       Ping through HTTP/HTTPS w/ GET/HEAD methods
	scan                        scan tcp ports (you can provide range >scan host minport maxport)
	dump                        prints out a description of the contents of packets on a network interface
	disc                        discover all the devices on a LAN
	peering                     peering information (provides by peeringdb.com)
	web                         web dashboard - opens dashboard at your default browser
	`

// Readline structure
type Readline struct {
	instance  *readline.Instance
	completer *readline.PrefixCompleter
	prompt    string
	next      chan struct{}
}

var (
	// validation command regex
	CMDReg, _ = regexp.Compile(
		`(ping|trace|bgp|lg|ns|dig|dump|disc|whois|peering|scan|hping|connect|node|local|mode|help|web|exit|quit)\s{0,1}(.*)`)
)

// Init set readline imain items
func Init(prompt, version string) *Readline {
	var (
		r         Readline
		err       error
		completer = readline.NewPrefixCompleter(
			readline.PcItem("ping"),
			readline.PcItem("trace"),
			readline.PcItem("bgp"),
			readline.PcItem("hping"),
			readline.PcItem("connect"),
			readline.PcItem("node"),
			readline.PcItem("local"),
			readline.PcItem("lg"),
			readline.PcItem("ns"),
			readline.PcItem("dig"),
			readline.PcItem("whois"),
			readline.PcItem("scan"),
			readline.PcItem("dump"),
			readline.PcItem("disc"),
			readline.PcItem("peering"),
			readline.PcItem("help"),
			readline.PcItem("web"),
			readline.PcItem("exit"),
		)
	)
	r.completer = completer
	r.instance, err = readline.NewEx(&readline.Config{
		Prompt:          prompt + "> ",
		HistoryFile:     "/tmp/myping",
		InterruptPrompt: "^C",
		EOFPrompt:       "exit",
		AutoComplete:    completer,
	})
	if err != nil {
		panic(err)
	}
	banner.Println(version) // print banner
	go checkUpdate(version) // check update version
	r.prompt = prompt       // init local prompt
	return &r
}

// RemoveItemCompleter removes subitem(s) from a specific main item
func (r *Readline) RemoveItemCompleter(pcItem string) {
	child := []readline.PrefixCompleterInterface{}
	for _, p := range r.completer.Children {
		if strings.TrimSpace(string(p.GetName())) != pcItem {
			child = append(child, p)
		}
	}

	r.completer.Children = child

}

// AddCompleter updates subitem(s) from a specific main item
func (r *Readline) AddCompleter(pcItem string, pcSubItems []string) {
	var pc readline.PrefixCompleter
	c := []readline.PrefixCompleterInterface{}
	for _, item := range pcSubItems {
		c = append(c, readline.PcItem(item))
	}
	pc.Name = []rune(pcItem + " ")
	pc.Children = c
	r.completer.Children = append(r.completer.Children, &pc)
}

// UpdateCompleter updates subitem(s) from a specific main item
func (r *Readline) UpdateCompleter(pcItem string, pcSubItems []string) {
	child := []readline.PrefixCompleterInterface{}
	var pc readline.PrefixCompleter
	for _, p := range r.completer.Children {
		if strings.TrimSpace(string(p.GetName())) == pcItem {
			c := []readline.PrefixCompleterInterface{}
			for _, item := range pcSubItems {
				c = append(c, readline.PcItem(item))
			}
			pc.Name = []rune(pcItem + " ")
			pc.Children = c
			child = append(child, &pc)
		} else {
			child = append(child, p)
		}
	}
	if len(pc.Name) < 1 {
		// todo adding new
	}
	r.completer.Children = child
}

// SetPrompt set readline prompt and store it
func (r *Readline) SetPrompt(p string) {
	p = strings.ToLower(p)
	r.prompt = p
	r.instance.SetPrompt(p + "> ")
}

// UpdatePromptN appends readline prompt
func (r *Readline) UpdatePromptN(p string, n int) {
	var parts []string
	p = strings.ToLower(p)
	parts = strings.SplitAfterN(r.prompt, "/", n)
	if n <= len(parts) && n > -1 {
		parts[n-1] = p
		r.prompt = strings.Join(parts, "")
	} else {
		r.prompt += "/" + p
	}
	r.instance.SetPrompt(r.prompt + "> ")
}

// GetPrompt returns the current prompt string
func (r *Readline) GetPrompt() string {
	return r.prompt
}

// Refresh prompt
func (r *Readline) Refresh() {
	r.instance.Refresh()
}

// SetVim set mode to vim
func (r *Readline) SetVim() {
	if !r.instance.IsVimMode() {
		r.instance.SetVimMode(true)
		println("mode changed to vim")
	} else {
		println("mode already is vim")
	}
}

// SetEmacs set mode to emacs
func (r *Readline) SetEmacs() {
	if r.instance.IsVimMode() {
		r.instance.SetVimMode(false)
		println("mode changed to emacs")
	} else {
		println("mode already is emacs")
	}
}

// Next trigers to read next line
func (r *Readline) Next() {
	r.next <- struct{}{}
}

// Run the main loop
func (r *Readline) Run(cmd chan<- string, next chan struct{}) {
	r.next = next
	func() {
		for {
			line, err := r.instance.Readline()
			if err != nil { // io.EOF, readline.ErrInterrupt
				break
			}
			cmd <- line
			if _, ok := <-next; !ok {
				break
			}
		}
	}()
}

// Close the readline instance
func (r *Readline) Close(next chan struct{}) {
	r.instance.Close()
}

// Help print out the main help
func (r *Readline) Help() {
	fmt.Println(usage)
}

// checkUpdate checks if any new version is available
func checkUpdate(version string) {
	type mylg struct {
		Version string
	}
	var appCtl mylg

	if version == "test" {
		return
	}

	resp, err := http.Get("http://mylg.io/appctl/mylg")
	if err != nil {
		println("error: check update has been failed ")
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		println("error: check update has been failed (2)" + err.Error())
		return
	}
	err = json.Unmarshal(body, &appCtl)
	if err != nil {
		return
	}
	if version != appCtl.Version {
		fmt.Printf("New version is available (v%s) at http://mylg.io/download\n", appCtl.Version)
	}
}

//Flag parses the command arguments syntax:
// -flag=x
// -flag x
// help
func Flag(args string) (string, map[string]interface{}) {
	var (
		r   = make(map[string]interface{}, 10)
		err error
	)
	args = strings.TrimSpace(args)
	re := regexp.MustCompile(`(?i)-([a-z|0-9]+)={0,1}\s{0,1}([0-9|a-z|\-|'"{}:\/]*)`)
	f := re.FindAllStringSubmatch(args, -1)
	for _, kv := range f {
		if len(kv) > 1 {
			// trim extra characters (' and ") from value
			kv[2] = strings.Trim(kv[2], "'")
			kv[2] = strings.Trim(kv[2], `"`)
			r[kv[1]], err = strconv.Atoi(kv[2])
			if err != nil {
				r[kv[1]] = kv[2]
			}
			args = strings.Replace(args, kv[0], "", -1)
		}
	}
	if m, _ := regexp.MatchString(`(?i)help$`, args); m {
		r["help"] = true
	}
	args = strings.TrimSpace(args)
	return args, r
}

// SetFlag returns command option(s)
func SetFlag(flag map[string]interface{}, option string, v interface{}) interface{} {
	if sValue, ok := flag[option]; ok {
		switch v.(type) {
		case int:
			return sValue.(int)
		case string:
			switch sValue.(type) {
			case string:
				return sValue.(string)
			case int:
				str := strconv.FormatInt(int64(sValue.(int)), 10)
				return str
			case float64:
				str := strconv.FormatFloat(sValue.(float64), 'f', -1, 64)
				return str
			}
		case bool:
			return true
		default:
			return sValue.(string)
		}
	}
	return v
}
<|endoftext|>


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

package cmd

import (
	gerrors "errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"sort"
	"strconv"
	"strings"

	"github.com/tsuru/tsuru/errors"
	"github.com/tsuru/tsuru/fs"
	"launchpad.net/gnuflag"
)

var ErrAbortCommand = gerrors.New("")

type exiter interface {
	Exit(int)
}

type osExiter struct{}

func (e osExiter) Exit(code int) {
	os.Exit(code)
}

type Lookup func(context *Context) error

type Manager struct {
	Commands      map[string]Command
	topics        map[string]string
	name          string
	stdout        io.Writer
	stderr        io.Writer
	stdin         io.Reader
	version       string
	versionHeader string
	e             exiter
	original      string
	wrong         bool
	lookup        Lookup
}

func NewManager(name, ver, verHeader string, stdout, stderr io.Writer, stdin io.Reader, lookup Lookup) *Manager {
	manager := &Manager{name: name, version: ver, versionHeader: verHeader, stdout: stdout, stderr: stderr, stdin: stdin, lookup: lookup}
	manager.Register(&help{manager})
	manager.Register(&version{manager})
	return manager
}

func BuildBaseManager(name, version, versionHeader string, lookup Lookup) *Manager {
	m := NewManager(name, version, versionHeader, os.Stdout, os.Stderr, os.Stdin, lookup)
	m.Register(&login{})
	m.Register(&logout{})
	m.Register(&userCreate{})
	m.Register(&resetPassword{})
	m.Register(&userRemove{})
	m.Register(&teamCreate{})
	m.Register(&teamRemove{})
	m.Register(&teamList{})
	m.Register(&teamUserAdd{})
	m.Register(&teamUserRemove{})
	m.Register(teamUserList{})
	m.Register(&changePassword{})
	m.Register(&targetList{})
	m.Register(&targetAdd{})
	m.Register(&targetRemove{})
	m.Register(&targetSet{})
	m.Register(&showAPIToken{})
	m.Register(&regenerateAPIToken{})
	m.RegisterTopic("target", fmt.Sprintf(targetTopic, name))
	return m
}

func (m *Manager) Register(command Command) {
	if m.Commands == nil {
		m.Commands = make(map[string]Command)
	}
	name := command.Info().Name
	_, found := m.Commands[name]
	if found {
		panic(fmt.Sprintf("command already registered: %s", name))
	}
	m.Commands[name] = command
}

func (m *Manager) RegisterDeprecated(command Command, oldName string) {
	if m.Commands == nil {
		m.Commands = make(map[string]Command)
	}
	name := command.Info().Name
	_, found := m.Commands[name]
	if found {
		panic(fmt.Sprintf("command already registered: %s", name))
	}
	m.Commands[name] = command
	m.Commands[oldName] = &DeprecatedCommand{Command: command, oldName: oldName}
}

func (m *Manager) RegisterTopic(name, content string) {
	if m.topics == nil {
		m.topics = make(map[string]string)
	}
	_, found := m.topics[name]
	if found {
		panic(fmt.Sprintf("topic already registered: %s", name))
	}
	m.topics[name] = content
}

func (m *Manager) Run(args []string) {
	var status int
	if len(args) == 0 {
		args = append(args, "help")
	}
	name := args[0]
	command, ok := m.Commands[name]
	if !ok {
		if m.lookup != nil {
			context := Context{args, m.stdout, m.stderr, m.stdin}
			err := m.lookup(&context)
			if err != nil {
				msg := ""
				if os.IsNotExist(err) {
					msg = fmt.Sprintf("%s: %q is not a tsuru command. See %q.\n", os.Args[0], args[0], "tsuru help")
					msg = fmt.Sprintf("%s\nDid you mean?\n", msg)
					for key, _ := range m.Commands {
						if strings.HasPrefix(key, args[0]) {
							msg = fmt.Sprintf("%s	%s\n", msg, key)
						}
					}
				} else {
					msg = err.Error()
				}
				fmt.Fprint(m.stderr, msg)
				m.finisher().Exit(1)
			}
			return
		}
		fmt.Fprintf(m.stderr, "Error: command %q does not exist\n", args[0])
		m.finisher().Exit(1)
		return
	}
	args = args[1:]
	info := command.Info()
	if flagged, ok := command.(FlaggedCommand); ok {
		flagset := flagged.Flags()
		err := flagset.Parse(true, args)
		if err != nil {
			fmt.Fprint(m.stderr, err)
			m.finisher().Exit(1)
			return
		}
		args = flagset.Args()
	}
	if length := len(args); (length < info.MinArgs || (info.MaxArgs > 0 && length > info.MaxArgs)) &&
		name != "help" {
		m.wrong = true
		m.original = info.Name
		command = m.Commands["help"]
		args = []string{name}
		status = 1
	}
	context := Context{args, m.stdout, m.stderr, m.stdin}
	client := NewClient(&http.Client{}, &context, m)
	err := command.Run(&context, client)
	if err != nil {
		errorMsg := err.Error()
		httpErr, ok := err.(*errors.HTTP)
		if ok && httpErr.Code == http.StatusUnauthorized && name != "login" {
			errorMsg = `You're not authenticated or your session has expired. Please use "login" command for authentication.`
		}
		if !strings.HasSuffix(errorMsg, "\n") {
			errorMsg += "\n"
		}
		if err != ErrAbortCommand {
			io.WriteString(m.stderr, "Error: "+errorMsg)
		}
		status = 1
	}
	m.finisher().Exit(status)
}

func (m *Manager) finisher() exiter {
	if m.e == nil {
		m.e = osExiter{}
	}
	return m.e
}

type Command interface {
	Info() *Info
	Run(context *Context, client *Client) error
}

type FlaggedCommand interface {
	Command
	Flags() *gnuflag.FlagSet
}

type DeprecatedCommand struct {
	Command
	oldName string
}

func (c *DeprecatedCommand) Run(context *Context, client *Client) error {
	fmt.Fprintf(context.Stderr, "WARNING: %q has been deprecated, please use %q instead.\n\n", c.oldName, c.Command.Info().Name)
	return c.Command.Run(context, client)
}

func (c *DeprecatedCommand) Flags() *gnuflag.FlagSet {
	if cmd, ok := c.Command.(FlaggedCommand); ok {
		return cmd.Flags()
	}
	return gnuflag.NewFlagSet("", gnuflag.ContinueOnError)
}

type Context struct {
	Args   []string
	Stdout io.Writer
	Stderr io.Writer
	Stdin  io.Reader
}

type Info struct {
	Name    string
	MinArgs int
	MaxArgs int
	Usage   string
	Desc    string
}

// Implementing the Commandable interface allows extending
// the tsr command line interface
type Commandable interface {
	Commands() []Command
}

// Implementing the AdminCommandable interface allows extending
// the tsuru-admin command line interface
type AdminCommandable interface {
	AdminCommands() []Command
}

type help struct {
	manager *Manager
}

func (c *help) Info() *Info {
	return &Info{
		Name:  "help",
		Usage: "command [args]",
	}
}

func (c *help) Run(context *Context, client *Client) error {
	const deprecatedMsg = "WARNING: %q is deprecated. Showing help for %q instead.\n\n"
	output := fmt.Sprintf("%s version %s.\n\n", c.manager.name, c.manager.version)
	if c.manager.wrong {
		output += fmt.Sprint("ERROR: wrong number of arguments.\n\n")
	}
	if len(context.Args) > 0 {
		if cmd, ok := c.manager.Commands[context.Args[0]]; ok {
			if deprecated, ok := cmd.(*DeprecatedCommand); ok {
				fmt.Fprintf(context.Stderr, deprecatedMsg, deprecated.oldName, cmd.Info().Name)
			}
			info := cmd.Info()
			output += fmt.Sprintf("Usage: %s %s\n", c.manager.name, info.Usage)
			output += fmt.Sprintf("\n%s\n", info.Desc)
			if info.MinArgs > 0 {
				output += fmt.Sprintf("\nMinimum # of arguments: %d", info.MinArgs)
			}
			if info.MaxArgs > 0 {
				output += fmt.Sprintf("\nMaximum # of arguments: %d", info.MaxArgs)
			}
			output += fmt.Sprint("\n")
		} else if topic, ok := c.manager.topics[context.Args[0]]; ok {
			output += topic
		} else {
			return fmt.Errorf("command %q does not exist.", context.Args[0])
		}
	} else {
		output += fmt.Sprintf("Usage: %s %s\n\nAvailable commands:\n", c.manager.name, c.Info().Usage)
		var commands []string
		for name, cmd := range c.manager.Commands {
			if _, ok := cmd.(*DeprecatedCommand); !ok {
				commands = append(commands, name)
			}
		}
		sort.Strings(commands)
		for _, command := range commands {
			output += fmt.Sprintf("  %s\n", command)
		}
		output += fmt.Sprintf("\nUse %s help <commandname> to get more information about a command.\n", c.manager.name)
		if len(c.manager.topics) > 0 {
			output += fmt.Sprintln("\nAvailable topics:")
			for topic := range c.manager.topics {
				output += fmt.Sprintf("  %s\n", topic)
			}
			output += fmt.Sprintf("\nUse %s help <topicname> to get more information about a topic.\n", c.manager.name)
		}
	}
	io.WriteString(context.Stdout, output)
	return nil
}

type version struct {
	manager *Manager
}

func (c *version) Info() *Info {
	return &Info{
		Name:    "version",
		MinArgs: 0,
		Usage:   "version",
		Desc:    "display the current version",
	}
}

func (c *version) Run(context *Context, client *Client) error {
	fmt.Fprintf(context.Stdout, "%s version %s.\n", c.manager.name, c.manager.version)
	return nil
}

func ExtractProgramName(path string) string {
	parts := strings.Split(path, "/")
	return parts[len(parts)-1]
}

var fsystem fs.Fs

func filesystem() fs.Fs {
	if fsystem == nil {
		fsystem = fs.OsFs{}
	}
	return fsystem
}

// validateVersion checks whether current version is greater or equal to
// supported version.
func validateVersion(supported, current string) bool {
	var (
		bigger bool
		limit  int
	)
	if supported == "" || supported == current {
		return true
	}
	partsSupported := strings.Split(supported, ".")
	partsCurrent := strings.Split(current, ".")
	if len(partsSupported) > len(partsCurrent) {
		limit = len(partsCurrent)
		bigger = true
	} else {
		limit = len(partsSupported)
	}
	for i := 0; i < limit; i++ {
		current, err := strconv.Atoi(partsCurrent[i])
		if err != nil {
			return false
		}
		supported, err := strconv.Atoi(partsSupported[i])
		if err != nil {
			return false
		}
		if current < supported {
			return false
		}
		if current > supported {
			return true
		}
	}
	if bigger {
		return false
	}
	return true
}
<commit_msg>Fuzzy string match for unknown command<commit_after>// Copyright 2014 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cmd

import (
	gerrors "errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"sort"
	"strconv"
	"strings"

	"github.com/sajari/fuzzy"
	"github.com/tsuru/tsuru/errors"
	"github.com/tsuru/tsuru/fs"
	"launchpad.net/gnuflag"
)

var ErrAbortCommand = gerrors.New("")

type exiter interface {
	Exit(int)
}

type osExiter struct{}

func (e osExiter) Exit(code int) {
	os.Exit(code)
}

type Lookup func(context *Context) error

type Manager struct {
	Commands      map[string]Command
	topics        map[string]string
	name          string
	stdout        io.Writer
	stderr        io.Writer
	stdin         io.Reader
	version       string
	versionHeader string
	e             exiter
	original      string
	wrong         bool
	lookup        Lookup
}

func NewManager(name, ver, verHeader string, stdout, stderr io.Writer, stdin io.Reader, lookup Lookup) *Manager {
	manager := &Manager{name: name, version: ver, versionHeader: verHeader, stdout: stdout, stderr: stderr, stdin: stdin, lookup: lookup}
	manager.Register(&help{manager})
	manager.Register(&version{manager})
	return manager
}

func BuildBaseManager(name, version, versionHeader string, lookup Lookup) *Manager {
	m := NewManager(name, version, versionHeader, os.Stdout, os.Stderr, os.Stdin, lookup)
	m.Register(&login{})
	m.Register(&logout{})
	m.Register(&userCreate{})
	m.Register(&resetPassword{})
	m.Register(&userRemove{})
	m.Register(&teamCreate{})
	m.Register(&teamRemove{})
	m.Register(&teamList{})
	m.Register(&teamUserAdd{})
	m.Register(&teamUserRemove{})
	m.Register(teamUserList{})
	m.Register(&changePassword{})
	m.Register(&targetList{})
	m.Register(&targetAdd{})
	m.Register(&targetRemove{})
	m.Register(&targetSet{})
	m.Register(&showAPIToken{})
	m.Register(&regenerateAPIToken{})
	m.RegisterTopic("target", fmt.Sprintf(targetTopic, name))
	return m
}

func (m *Manager) Register(command Command) {
	if m.Commands == nil {
		m.Commands = make(map[string]Command)
	}
	name := command.Info().Name
	_, found := m.Commands[name]
	if found {
		panic(fmt.Sprintf("command already registered: %s", name))
	}
	m.Commands[name] = command
}

func (m *Manager) RegisterDeprecated(command Command, oldName string) {
	if m.Commands == nil {
		m.Commands = make(map[string]Command)
	}
	name := command.Info().Name
	_, found := m.Commands[name]
	if found {
		panic(fmt.Sprintf("command already registered: %s", name))
	}
	m.Commands[name] = command
	m.Commands[oldName] = &DeprecatedCommand{Command: command, oldName: oldName}
}

func (m *Manager) RegisterTopic(name, content string) {
	if m.topics == nil {
		m.topics = make(map[string]string)
	}
	_, found := m.topics[name]
	if found {
		panic(fmt.Sprintf("topic already registered: %s", name))
	}
	m.topics[name] = content
}

func (m *Manager) Run(args []string) {
	var status int
	if len(args) == 0 {
		args = append(args, "help")
	}
	name := args[0]
	command, ok := m.Commands[name]
	if !ok {
		if m.lookup != nil {
			context := Context{args, m.stdout, m.stderr, m.stdin}
			err := m.lookup(&context)
			if err != nil {
				msg := ""
				if os.IsNotExist(err) {
					msg = fmt.Sprintf("%s: %q is not a tsuru command. See %q.\n", os.Args[0], args[0], "tsuru help")
					msg += fmt.Sprintf("\nDid you mean?\n")
					var keys []string
					for key, _ := range m.Commands {
						keys = append(keys, key)
					}
					sort.Strings(keys)
					for _, key := range keys {
						levenshtein := fuzzy.Levenshtein(&key, &args[0])
						if levenshtein < 3 || strings.HasPrefix(key, args[0]) {
							msg += fmt.Sprintf("\t%s\n", key)
						}
					}
				} else {
					msg = err.Error()
				}
				fmt.Fprint(m.stderr, msg)
				m.finisher().Exit(1)
			}
			return
		}
		fmt.Fprintf(m.stderr, "Error: command %q does not exist\n", args[0])
		m.finisher().Exit(1)
		return
	}
	args = args[1:]
	info := command.Info()
	if flagged, ok := command.(FlaggedCommand); ok {
		flagset := flagged.Flags()
		err := flagset.Parse(true, args)
		if err != nil {
			fmt.Fprint(m.stderr, err)
			m.finisher().Exit(1)
			return
		}
		args = flagset.Args()
	}
	if length := len(args); (length < info.MinArgs || (info.MaxArgs > 0 && length > info.MaxArgs)) &&
		name != "help" {
		m.wrong = true
		m.original = info.Name
		command = m.Commands["help"]
		args = []string{name}
		status = 1
	}
	context := Context{args, m.stdout, m.stderr, m.stdin}
	client := NewClient(&http.Client{}, &context, m)
	err := command.Run(&context, client)
	if err != nil {
		errorMsg := err.Error()
		httpErr, ok := err.(*errors.HTTP)
		if ok && httpErr.Code == http.StatusUnauthorized && name != "login" {
			errorMsg = `You're not authenticated or your session has expired. Please use "login" command for authentication.`
		}
		if !strings.HasSuffix(errorMsg, "\n") {
			errorMsg += "\n"
		}
		if err != ErrAbortCommand {
			io.WriteString(m.stderr, "Error: "+errorMsg)
		}
		status = 1
	}
	m.finisher().Exit(status)
}

func (m *Manager) finisher() exiter {
	if m.e == nil {
		m.e = osExiter{}
	}
	return m.e
}

type Command interface {
	Info() *Info
	Run(context *Context, client *Client) error
}

type FlaggedCommand interface {
	Command
	Flags() *gnuflag.FlagSet
}

type DeprecatedCommand struct {
	Command
	oldName string
}

func (c *DeprecatedCommand) Run(context *Context, client *Client) error {
	fmt.Fprintf(context.Stderr, "WARNING: %q has been deprecated, please use %q instead.\n\n", c.oldName, c.Command.Info().Name)
	return c.Command.Run(context, client)
}

func (c *DeprecatedCommand) Flags() *gnuflag.FlagSet {
	if cmd, ok := c.Command.(FlaggedCommand); ok {
		return cmd.Flags()
	}
	return gnuflag.NewFlagSet("", gnuflag.ContinueOnError)
}

type Context struct {
	Args   []string
	Stdout io.Writer
	Stderr io.Writer
	Stdin  io.Reader
}

type Info struct {
	Name    string
	MinArgs int
	MaxArgs int
	Usage   string
	Desc    string
}

// Implementing the Commandable interface allows extending
// the tsr command line interface
type Commandable interface {
	Commands() []Command
}

// Implementing the AdminCommandable interface allows extending
// the tsuru-admin command line interface
type AdminCommandable interface {
	AdminCommands() []Command
}

type help struct {
	manager *Manager
}

func (c *help) Info() *Info {
	return &Info{
		Name:  "help",
		Usage: "command [args]",
	}
}

func (c *help) Run(context *Context, client *Client) error {
	const deprecatedMsg = "WARNING: %q is deprecated. Showing help for %q instead.\n\n"
	output := fmt.Sprintf("%s version %s.\n\n", c.manager.name, c.manager.version)
	if c.manager.wrong {
		output += fmt.Sprint("ERROR: wrong number of arguments.\n\n")
	}
	if len(context.Args) > 0 {
		if cmd, ok := c.manager.Commands[context.Args[0]]; ok {
			if deprecated, ok := cmd.(*DeprecatedCommand); ok {
				fmt.Fprintf(context.Stderr, deprecatedMsg, deprecated.oldName, cmd.Info().Name)
			}
			info := cmd.Info()
			output += fmt.Sprintf("Usage: %s %s\n", c.manager.name, info.Usage)
			output += fmt.Sprintf("\n%s\n", info.Desc)
			if info.MinArgs > 0 {
				output += fmt.Sprintf("\nMinimum # of arguments: %d", info.MinArgs)
			}
			if info.MaxArgs > 0 {
				output += fmt.Sprintf("\nMaximum # of arguments: %d", info.MaxArgs)
			}
			output += fmt.Sprint("\n")
		} else if topic, ok := c.manager.topics[context.Args[0]]; ok {
			output += topic
		} else {
			return fmt.Errorf("command %q does not exist.", context.Args[0])
		}
	} else {
		output += fmt.Sprintf("Usage: %s %s\n\nAvailable commands:\n", c.manager.name, c.Info().Usage)
		var commands []string
		for name, cmd := range c.manager.Commands {
			if _, ok := cmd.(*DeprecatedCommand); !ok {
				commands = append(commands, name)
			}
		}
		sort.Strings(commands)
		for _, command := range commands {
			output += fmt.Sprintf("  %s\n", command)
		}
		output += fmt.Sprintf("\nUse %s help <commandname> to get more information about a command.\n", c.manager.name)
		if len(c.manager.topics) > 0 {
			output += fmt.Sprintln("\nAvailable topics:")
			for topic := range c.manager.topics {
				output += fmt.Sprintf("  %s\n", topic)
			}
			output += fmt.Sprintf("\nUse %s help <topicname> to get more information about a topic.\n", c.manager.name)
		}
	}
	io.WriteString(context.Stdout, output)
	return nil
}

type version struct {
	manager *Manager
}

func (c *version) Info() *Info {
	return &Info{
		Name:    "version",
		MinArgs: 0,
		Usage:   "version",
		Desc:    "display the current version",
	}
}

func (c *version) Run(context *Context, client *Client) error {
	fmt.Fprintf(context.Stdout, "%s version %s.\n", c.manager.name, c.manager.version)
	return nil
}

func ExtractProgramName(path string) string {
	parts := strings.Split(path, "/")
	return parts[len(parts)-1]
}

var fsystem fs.Fs

func filesystem() fs.Fs {
	if fsystem == nil {
		fsystem = fs.OsFs{}
	}
	return fsystem
}

// validateVersion checks whether current version is greater or equal to
// supported version.
func validateVersion(supported, current string) bool {
	var (
		bigger bool
		limit  int
	)
	if supported == "" || supported == current {
		return true
	}
	partsSupported := strings.Split(supported, ".")
	partsCurrent := strings.Split(current, ".")
	if len(partsSupported) > len(partsCurrent) {
		limit = len(partsCurrent)
		bigger = true
	} else {
		limit = len(partsSupported)
	}
	for i := 0; i < limit; i++ {
		current, err := strconv.Atoi(partsCurrent[i])
		if err != nil {
			return false
		}
		supported, err := strconv.Atoi(partsSupported[i])
		if err != nil {
			return false
		}
		if current < supported {
			return false
		}
		if current > supported {
			return true
		}
	}
	if bigger {
		return false
	}
	return true
}
<|endoftext|>