<commit_before>package main

import (
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"github.com/Sirupsen/logrus"
	"github.com/hashicorp/golang-lru"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"
)

var (
	certMemCache      *lru.Cache
	DEFAULT_FILE_MODE os.FileMode = 0666
)

// Must return valid certificate with non nil cert.Leaf or return nil
func certificateCacheGet(domain string) *tls.Certificate {
	if certMemCache != nil {
		certP, ok := certMemCache.Get(domain)
		if ok {
			logrus.Debugf("Got certificate from memory cache for domain '%v'", domain)
			return certP.(*tls.Certificate)
		} else {
			logrus.Debugf("Havn't certificate for '%v' in memory cache", domain)
		}
	}

	if *certDir == "" {
		logrus.Debugf("Skip certificateCacheGet becouse certDir is empty")
		return nil
	}
	keyPath := filepath.Join(*certDir, domain+".key")
	certPath := filepath.Join(*certDir, domain+".crt")
	cert, err := tls.LoadX509KeyPair(certPath, keyPath)

	switch {
	case err == nil:
		logrus.Debugf("Certificate files readed for domain '%v'", domain)
	case os.IsNotExist(err):
		logrus.Debugf("Certificate cache path key: '%v', cert: '%v'", keyPath, certPath)
		logrus.Infof("Have no certificate/key in cert-dir for domain '%v'", domain)
		return nil
	default:
		logrus.Errorf("Can't certificate/key load from file for domain '%v': %v", domain)
		return nil
	}

	if len(cert.Certificate) == 0 {
		logrus.Errorf("No certificates in file for domain '%v', file '%v'", domain, certPath)
		return nil
	}
	cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
	if err == nil {
		logrus.Debugf("Certificate parsed for domain '%v'", domain)

		if certMemCache != nil {
			logrus.Debugf("Put certificate to memory cache '%v' while get from disk cache", domain)
			certMemCache.Add(domain, &cert)
		}
		return &cert
	} else {
		logrus.Errorf("Can't parse certificate for domain '%v': %v", domain, err)
		return nil
	}
}

func certificateCachePut(domain string, cert *tls.Certificate) {
	logrus.Infof("Certificate put to cache for domain '%v'", domain)

	if certMemCache != nil {
		logrus.Debugf("Put cert in memory cache for domain '%v'", domain)
		certMemCache.Add(domain, cert)
	}

	if *certDir == "" {
		logrus.Debugf("Skip certificateCachePut becouse certDir is empty")
		return
	}
	err := os.MkdirAll(*certDir, 0600)
	if err != nil {
		logrus.Errorf("Can't create dir for save cached cert '%v':%v", *certDir, err)
		return
	}

	keyPath := filepath.Join(*certDir, domain+".key")
	certPath := filepath.Join(*certDir, domain+".crt")
	jsonPath := filepath.Join(*certDir, domain+".json")

	keyFile, err := os.Create(keyPath)
	if keyFile != nil {
		defer keyFile.Close()
	}
	if err != nil {
		logrus.Errorf("Can't open file for save key '%v':%v", keyPath, err)
		return
	}

	switch key := cert.PrivateKey.(type) {
	case *rsa.PrivateKey:
		keyBytes := x509.MarshalPKCS1PrivateKey(key)
		pemBlock := pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}
		err = pem.Encode(keyFile, &pemBlock)
		if err != nil {
			logrus.Errorf("Error while write bytes to rsa-key file '%v': %v", keyPath, err)
			return
		}
	case *ecdsa.PrivateKey:
		keyBytes, err := x509.MarshalECPrivateKey(key)
		if err != nil {
			logrus.Errorf("Error while marshal ecdsa-key for domain '%v': %v", domain, err)
			return
		}
		pemBlock := pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}
		err = pem.Encode(keyFile, &pemBlock)
		if err != nil {
			logrus.Errorf("Error while write bytes to ecdsa-key file '%v': %v", keyPath, err)
			return
		}
	}

	certFile, err := os.Create(certPath)
	if certFile != nil {
		defer certFile.Close()
	}
	if err != nil {
		logrus.Errorf("Can't open file for write certificate '%v': %v", certPath, err)
		return
	}
	for _, certBytes := range cert.Certificate {
		pemBlock := pem.Block{Type: "CERTIFICATE", Bytes: certBytes}
		err = pem.Encode(certFile, &pemBlock)
		if err != nil {
			logrus.Errorf("Can't write pem block to certificate '%v': %v", certPath, err)
			return
		}
	}

	logrus.Infof("Save certificate for domain '%v' to files: %v, %v", domain, keyPath, certPath)

	if *certJsonInfo {
		if cert.Leaf != nil {
			info := struct {
				Domains    []string
				ExpireDate time.Time
			}{}
			info.Domains = cert.Leaf.DNSNames
			info.ExpireDate = cert.Leaf.NotAfter.UTC()
			jsInfoBytes, err := json.Marshal(info)
			if err == nil {
				err = ioutil.WriteFile(jsonPath, jsInfoBytes, DEFAULT_FILE_MODE)
				if err == nil {
					logrus.Debug("Save cert metadata to: ", jsonPath)
				} else {
					logrus.Errorf("Can't write file '%v': %v", jsonPath, err)
				}
			} else {
				logrus.Errorf("Can't marshal json cert info (%v): %v", cert.Leaf.DNSNames, err)
			}
		} else {
			logrus.Error("Certificate leaf is nil while save in cache")
		}
	}
}
<commit_msg>log improve<commit_after>package main

import (
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"github.com/Sirupsen/logrus"
	"github.com/hashicorp/golang-lru"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"
)

var (
	certMemCache      *lru.Cache
	DEFAULT_FILE_MODE os.FileMode = 0666
)

// Must return valid certificate with non nil cert.Leaf or return nil
func certificateCacheGet(domain string) *tls.Certificate {
	if certMemCache != nil {
		certP, ok := certMemCache.Get(domain)
		if ok {
			logrus.Debugf("Got certificate from memory cache for domain '%v'", domain)
			return certP.(*tls.Certificate)
		} else {
			logrus.Debugf("Havn't certificate for '%v' in memory cache", domain)
		}
	}

	if *certDir == "" {
		logrus.Debugf("Skip certificateCacheGet becouse certDir is empty")
		return nil
	}
	keyPath := filepath.Join(*certDir, domain+".key")
	certPath := filepath.Join(*certDir, domain+".crt")
	cert, err := tls.LoadX509KeyPair(certPath, keyPath)

	switch {
	case err == nil:
		logrus.Debugf("Certificate files readed for domain '%v'", domain)
	case os.IsNotExist(err):
		logrus.Debugf("Certificate cache path key: '%v', cert: '%v'", keyPath, certPath)
		logrus.Infof("Have no certificate/key in cert-dir for domain '%v'", domain)
		return nil
	default:
		logrus.Errorf("Can't certificate/key load from file for domain '%v': %v", domain, err)
		return nil
	}

	if len(cert.Certificate) == 0 {
		logrus.Errorf("No certificates in file for domain '%v', file '%v'", domain, certPath)
		return nil
	}
	cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
	if err == nil {
		logrus.Debugf("Certificate parsed for domain '%v'", domain)

		if certMemCache != nil {
			logrus.Debugf("Put certificate to memory cache '%v' while get from disk cache", domain)
			certMemCache.Add(domain, &cert)
		}
		return &cert
	} else {
		logrus.Errorf("Can't parse certificate for domain '%v': %v", domain, err)
		return nil
	}
}

func certificateCachePut(domain string, cert *tls.Certificate) {
	logrus.Infof("Certificate put to cache for domain '%v'", domain)

	if certMemCache != nil {
		logrus.Debugf("Put cert in memory cache for domain '%v'", domain)
		certMemCache.Add(domain, cert)
	}

	if *certDir == "" {
		logrus.Debugf("Skip certificateCachePut becouse certDir is empty")
		return
	}
	err := os.MkdirAll(*certDir, 0600)
	if err != nil {
		logrus.Errorf("Can't create dir for save cached cert '%v':%v", *certDir, err)
		return
	}

	keyPath := filepath.Join(*certDir, domain+".key")
	certPath := filepath.Join(*certDir, domain+".crt")
	jsonPath := filepath.Join(*certDir, domain+".json")

	keyFile, err := os.Create(keyPath)
	if keyFile != nil {
		defer keyFile.Close()
	}
	if err != nil {
		logrus.Errorf("Can't open file for save key '%v':%v", keyPath, err)
		return
	}

	switch key := cert.PrivateKey.(type) {
	case *rsa.PrivateKey:
		keyBytes := x509.MarshalPKCS1PrivateKey(key)
		pemBlock := pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}
		err = pem.Encode(keyFile, &pemBlock)
		if err != nil {
			logrus.Errorf("Error while write bytes to rsa-key file '%v': %v", keyPath, err)
			return
		}
	case *ecdsa.PrivateKey:
		keyBytes, err := x509.MarshalECPrivateKey(key)
		if err != nil {
			logrus.Errorf("Error while marshal ecdsa-key for domain '%v': %v", domain, err)
			return
		}
		pemBlock := pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}
		err = pem.Encode(keyFile, &pemBlock)
		if err != nil {
			logrus.Errorf("Error while write bytes to ecdsa-key file '%v': %v", keyPath, err)
			return
		}
	}

	certFile, err := os.Create(certPath)
	if certFile != nil {
		defer certFile.Close()
	}
	if err != nil {
		logrus.Errorf("Can't open file for write certificate '%v': %v", certPath, err)
		return
	}
	for _, certBytes := range cert.Certificate {
		pemBlock := pem.Block{Type: "CERTIFICATE", Bytes: certBytes}
		err = pem.Encode(certFile, &pemBlock)
		if err != nil {
			logrus.Errorf("Can't write pem block to certificate '%v': %v", certPath, err)
			return
		}
	}

	logrus.Infof("Save certificate for domain '%v' to files: %v, %v", domain, keyPath, certPath)

	if *certJsonInfo {
		if cert.Leaf != nil {
			info := struct {
				Domains    []string
				ExpireDate time.Time
			}{}
			info.Domains = cert.Leaf.DNSNames
			info.ExpireDate = cert.Leaf.NotAfter.UTC()
			jsInfoBytes, err := json.Marshal(info)
			if err == nil {
				err = ioutil.WriteFile(jsonPath, jsInfoBytes, DEFAULT_FILE_MODE)
				if err == nil {
					logrus.Debug("Save cert metadata to: ", jsonPath)
				} else {
					logrus.Errorf("Can't write file '%v': %v", jsonPath, err)
				}
			} else {
				logrus.Errorf("Can't marshal json cert info (%v): %v", cert.Leaf.DNSNames, err)
			}
		} else {
			logrus.Error("Certificate leaf is nil while save in cache")
		}
	}
}
<|endoftext|>


<commit_before>package descriptions

import "fmt"
import "log"
import "github.com/jbowtie/gokogiri/xml"
import "github.com/gosuri/uitable"

import "github.com/nlewo/contrail-introspect-cli/utils"
import "github.com/nlewo/contrail-introspect-cli/collection"

func CtrlIfmap() collection.DescCollection {
	return collection.DescCollection{
		BaseXpath: "IFMapTableShowResp/ifmap_db/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "node_name/text()",
			LongDetail:       collection.LongFormatFn(ifmapNeighbors),
		},
		PageArgs: []string{"controller-fqdn", "table-name", "search-string"},
		PageBuilder: func(args []string) collection.Sourcer {
			path := fmt.Sprintf("Snh_IFMapTableShowReq?table_name=%s&search_string=%s", args[1], args[2])
			return collection.Webui{Path: path, VrouterUrl: args[0], Port: 8083}
		},
		PrimaryField: "node_name",
	}
}

func ifmapNeighbors(t *uitable.Table, e collection.Element) {
	t.AddRow("Node name", "Neighbors")
	nodeName, _ := e.Node.Search("node_name/text()")
	t.AddRow(fmt.Sprintf("%s", nodeName[0]))
	neighbors, _ := e.Node.Search("neighbors/list/element/text()")
	for _, n := range neighbors {
		t.AddRow("", fmt.Sprintf("%s", n))
	}
	t.AddRow("", "")
}

func Interface() collection.DescCollection {
	return collection.DescCollection{
		BaseXpath: "__ItfResp_list/ItfResp/itf_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "name/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"uuid", "name", "vrf_name", "vm_uuid", "ip_addr", "mdata_ip_addr"}),
		},
		PageArgs: []string{"vrouter-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{Table: "db.interface.0", VrouterUrl: args[0], Port: 8085}
		},
		PrimaryField: "name",
	}
}

func Peering() collection.DescCollection {
	return collection.DescCollection{
		PageArgs:  []string{"vrouter-fqdn"},
		BaseXpath: "AgentXmppConnectionStatus/peer/list",
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Webui{Path: "Snh_AgentXmppConnectionStatusReq", VrouterUrl: args[0], Port: 8085}
		},
		DescElt: collection.DescElement{
			ShortDetailXpath: "controller_ip/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"controller_ip", "state", "flap_count", "cfg_controller"}),
		},
		PrimaryField: "name",
	}
}

func Si() collection.DescCollection {
	return collection.DescCollection{
		BaseXpath: "__ServiceInstanceResp_list/ServiceInstanceResp/service_instance_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "uuid/text()",
			// LongDetailHelp: []string{"Service instance uuid", "Type of service instance", "Virtual machine uuid"},
			LongDetail: collection.LongFormatXpaths([]string{"uuid", "service_type", "instance_id"}),
		},
		PageArgs: []string{"vrouter-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{Table: "db.service-instance.0", VrouterUrl: args[0], Port: 8085}
		},
		PrimaryField: "uuid",
	}
}

func Route() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"vrouter-fqdn", "vrf-name"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{VrouterUrl: args[0], Table: args[1] + ".uc.route.0,", Port: 8085}
		},
		BaseXpath: "__Inet4UcRouteResp_list/Inet4UcRouteResp/route_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "src_ip/text()",
			LongDetail:       collection.LongFormatFn(routeDetail)},
		PrimaryField: "src_ip",
	}
}
func Vrf() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"vrouter-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{Table: "db.vrf.0", VrouterUrl: args[0], Port: 8085}
		},
		BaseXpath: "__VrfListResp_list/VrfListResp/vrf_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "name/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"name", "ucindex"}),
		},
		PrimaryField: "name",
	}
}
func Vn() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"vrouter-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{Table: "db.vn.0", VrouterUrl: args[0], Port: 8085}
		},
		BaseXpath: "__VnListResp_list/VnListResp/vn_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "name/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"name", "vrf_name"}),
		},
		PrimaryField: "name",
	}
}

func RiSummary() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"controller-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Webui{Path: "Snh_ShowRoutingInstanceSummaryReq?search_string=", VrouterUrl: args[0], Port: 8083}
		},
		BaseXpath: "ShowRoutingInstanceSummaryResp/instances/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "name/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"name", "virtual_network"}),
		},
		PrimaryField: "name",
	}
}

func CtrlRouteSummary() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"controller-fqdn", "search"},
		PageBuilder: func(args []string) collection.Sourcer {
			path := fmt.Sprintf("Snh_ShowRouteSummaryReq?search_string=%s", args[1])
			return collection.Webui{Path: path, VrouterUrl: args[0], Port: 8083}
		},
		BaseXpath: "ShowRouteSummaryResp/tables/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "name/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"name", "prefixes", "paths", "primary_paths", "secondary_paths", "pending_updates"}),
		},
		PrimaryField: "name",
	}
}

func CtrlRoute() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"controller-fqdn", "routing-instance"},
		PageBuilder: func(args []string) collection.Sourcer {
			path := fmt.Sprintf("Snh_ShowRouteReq?x=%s.inet.0", args[1])
			return collection.Webui{Path: path, VrouterUrl: args[0], Port: 8083}
		},
		BaseXpath: "ShowRouteResp/tables/list/ShowRouteTable/routes/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "prefix/text()",
			LongDetail:       collection.LongFormatFn(controllerRoutePath),
		},
		PrimaryField: "prefix",
	}
}

func Mpls() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"vrouter-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{Table: "db.mpls.0", VrouterUrl: args[0], Port: 8085}
		},
		BaseXpath: "__MplsResp_list/MplsResp/mpls_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "label/text()",
			LongDetail:       collection.LongFormatFn(mplsDetail),
		},
		PrimaryField: "label",
	}
}

func routeDetail(t *uitable.Table, e collection.Element) {
	srcIp, _ := e.Node.Search("src_ip/text()")
	t.AddRow(fmt.Sprintf("Src %s", srcIp[0]))
	paths, _ := e.Node.Search("path_list/list/PathSandeshData")

	t.AddRow("    Dst", "Peers", "MPLS label", "Interface", "Dest VN")
	for _, path := range paths {
		nhs, _ := path.Search("nh/NhSandeshData//dip/text()")
		peers, _ := path.Search("peer/text()")
		label, _ := path.Search("label/text()")
		destvn, _ := path.Search("dest_vn/text()")
		itf, _ := path.Search("nh/NhSandeshData/itf/text()")
		t.AddRow("    "+utils.Pretty(nhs), utils.Pretty(peers), utils.Pretty(label), utils.Pretty(itf), utils.Pretty(destvn))
	}
	t.AddRow("")
}

func mplsDetail(t *uitable.Table, e collection.Element) {
	label, err := e.GetField("label")
	if err != nil {
		log.Fatal(err)
	}
	t.AddRow(fmt.Sprintf("Label: %s", label))
	nexthopDetail(t, e.Node)
	t.AddRow("")
}

func nexthopDetail(t *uitable.Table, node xml.Node) {
	t.AddRow("    Type", "Interface", "Nexthop index")
	nhs, _ := node.Search("nh/NhSandeshData/type/text()")
	itf, _ := node.Search("nh/NhSandeshData/itf/text()")
	nhIdx, _ := node.Search("nh/NhSandeshData/nh_index/text()")
	t.AddRow("    "+utils.Pretty(nhs), utils.Pretty(itf), utils.Pretty(nhIdx))
}

func controllerRoutePath(t *uitable.Table, e collection.Element) {
	srcIp, _ := e.Node.Search("prefix/text()")
	t.AddRow(fmt.Sprintf("Prefix %s", srcIp[0]))
	paths, _ := e.Node.Search("paths/list/ShowRoutePath")

	t.AddRow("    Protocol", "Nexthop", "Local Pref", "Peers", "MPLS label")
	for _, path := range paths {
		protocol, _ := path.Search("protocol/text()")
		nhs, _ := path.Search("next_hop/text()")
		peers, _ := path.Search("source/text()")
		label, _ := path.Search("label/text()")
		localPref, _ := path.Search("local_preference/text()")
		t.AddRow("    "+utils.Pretty(protocol), utils.Pretty(nhs), utils.Pretty(localPref), utils.Pretty(peers), utils.Pretty(label))
	}
	t.AddRow("")
}

func AgentPing() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"vrouter-fqdn", "vrf-name", "source-ip", "source-port", "dest-ip", "dest-port"},
		PageBuilder: func(args []string) collection.Sourcer {
			sourceIp := args[2]
			sourcePort := args[3]
			destIp := args[4]
			destPort := args[5]
			vrfName := args[1]

			path := fmt.Sprintf("Snh_PingReq?source_ip=%s&source_port=%s&dest_ip=%s&dest_port=%s&protocol=6&vrf_name=%s&packet_size=&count=1&interval=",
				sourceIp, sourcePort, destIp, destPort, vrfName)
			return collection.Webui{Path: path, VrouterUrl: args[0], Port: 8085}
		},
		BaseXpath: "__PingResp_list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "resp/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"resp", "rtt"}),
		},
	}
}
<commit_msg>agent-route: add prefix and local pref<commit_after>package descriptions

import "fmt"
import "log"
import "github.com/jbowtie/gokogiri/xml"
import "github.com/gosuri/uitable"

import "github.com/nlewo/contrail-introspect-cli/utils"
import "github.com/nlewo/contrail-introspect-cli/collection"

func CtrlIfmap() collection.DescCollection {
	return collection.DescCollection{
		BaseXpath: "IFMapTableShowResp/ifmap_db/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "node_name/text()",
			LongDetail:       collection.LongFormatFn(ifmapNeighbors),
		},
		PageArgs: []string{"controller-fqdn", "table-name", "search-string"},
		PageBuilder: func(args []string) collection.Sourcer {
			path := fmt.Sprintf("Snh_IFMapTableShowReq?table_name=%s&search_string=%s", args[1], args[2])
			return collection.Webui{Path: path, VrouterUrl: args[0], Port: 8083}
		},
		PrimaryField: "node_name",
	}
}

func ifmapNeighbors(t *uitable.Table, e collection.Element) {
	t.AddRow("Node name", "Neighbors")
	nodeName, _ := e.Node.Search("node_name/text()")
	t.AddRow(fmt.Sprintf("%s", nodeName[0]))
	neighbors, _ := e.Node.Search("neighbors/list/element/text()")
	for _, n := range neighbors {
		t.AddRow("", fmt.Sprintf("%s", n))
	}
	t.AddRow("", "")
}

func Interface() collection.DescCollection {
	return collection.DescCollection{
		BaseXpath: "__ItfResp_list/ItfResp/itf_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "name/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"uuid", "name", "vrf_name", "vm_uuid", "ip_addr", "mdata_ip_addr"}),
		},
		PageArgs: []string{"vrouter-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{Table: "db.interface.0", VrouterUrl: args[0], Port: 8085}
		},
		PrimaryField: "name",
	}
}

func Peering() collection.DescCollection {
	return collection.DescCollection{
		PageArgs:  []string{"vrouter-fqdn"},
		BaseXpath: "AgentXmppConnectionStatus/peer/list",
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Webui{Path: "Snh_AgentXmppConnectionStatusReq", VrouterUrl: args[0], Port: 8085}
		},
		DescElt: collection.DescElement{
			ShortDetailXpath: "controller_ip/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"controller_ip", "state", "flap_count", "cfg_controller"}),
		},
		PrimaryField: "name",
	}
}

func Si() collection.DescCollection {
	return collection.DescCollection{
		BaseXpath: "__ServiceInstanceResp_list/ServiceInstanceResp/service_instance_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "uuid/text()",
			// LongDetailHelp: []string{"Service instance uuid", "Type of service instance", "Virtual machine uuid"},
			LongDetail: collection.LongFormatXpaths([]string{"uuid", "service_type", "instance_id"}),
		},
		PageArgs: []string{"vrouter-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{Table: "db.service-instance.0", VrouterUrl: args[0], Port: 8085}
		},
		PrimaryField: "uuid",
	}
}

func Route() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"vrouter-fqdn", "vrf-name"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{VrouterUrl: args[0], Table: args[1] + ".uc.route.0,", Port: 8085}
		},
		BaseXpath: "__Inet4UcRouteResp_list/Inet4UcRouteResp/route_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "src_ip/text()",
			LongDetail:       collection.LongFormatFn(routeDetail)},
		PrimaryField: "src_ip",
	}
}
func Vrf() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"vrouter-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{Table: "db.vrf.0", VrouterUrl: args[0], Port: 8085}
		},
		BaseXpath: "__VrfListResp_list/VrfListResp/vrf_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "name/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"name", "ucindex"}),
		},
		PrimaryField: "name",
	}
}
func Vn() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"vrouter-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{Table: "db.vn.0", VrouterUrl: args[0], Port: 8085}
		},
		BaseXpath: "__VnListResp_list/VnListResp/vn_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "name/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"name", "vrf_name"}),
		},
		PrimaryField: "name",
	}
}

func RiSummary() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"controller-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Webui{Path: "Snh_ShowRoutingInstanceSummaryReq?search_string=", VrouterUrl: args[0], Port: 8083}
		},
		BaseXpath: "ShowRoutingInstanceSummaryResp/instances/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "name/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"name", "virtual_network"}),
		},
		PrimaryField: "name",
	}
}

func CtrlRouteSummary() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"controller-fqdn", "search"},
		PageBuilder: func(args []string) collection.Sourcer {
			path := fmt.Sprintf("Snh_ShowRouteSummaryReq?search_string=%s", args[1])
			return collection.Webui{Path: path, VrouterUrl: args[0], Port: 8083}
		},
		BaseXpath: "ShowRouteSummaryResp/tables/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "name/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"name", "prefixes", "paths", "primary_paths", "secondary_paths", "pending_updates"}),
		},
		PrimaryField: "name",
	}
}

func CtrlRoute() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"controller-fqdn", "routing-instance"},
		PageBuilder: func(args []string) collection.Sourcer {
			path := fmt.Sprintf("Snh_ShowRouteReq?x=%s.inet.0", args[1])
			return collection.Webui{Path: path, VrouterUrl: args[0], Port: 8083}
		},
		BaseXpath: "ShowRouteResp/tables/list/ShowRouteTable/routes/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "prefix/text()",
			LongDetail:       collection.LongFormatFn(controllerRoutePath),
		},
		PrimaryField: "prefix",
	}
}

func Mpls() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"vrouter-fqdn"},
		PageBuilder: func(args []string) collection.Sourcer {
			return collection.Remote{Table: "db.mpls.0", VrouterUrl: args[0], Port: 8085}
		},
		BaseXpath: "__MplsResp_list/MplsResp/mpls_list/list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "label/text()",
			LongDetail:       collection.LongFormatFn(mplsDetail),
		},
		PrimaryField: "label",
	}
}

func routeDetail(t *uitable.Table, e collection.Element) {
	srcIp, _ := e.Node.Search("src_ip/text()")
	srcPrefix, _ := e.Node.Search("src_plen/text()")
	t.AddRow(fmt.Sprintf("Src %s/%s", srcIp[0], srcPrefix[0]))
	paths, _ := e.Node.Search("path_list/list/PathSandeshData")

	t.AddRow("    Dst", "Peers", "MPLS label", "Local Pref", "Interface", "Dest VN")
	for _, path := range paths {
		nhs, _ := path.Search("nh/NhSandeshData//dip/text()")
		peers, _ := path.Search("peer/text()")
		label, _ := path.Search("label/text()")
		pref, _ := path.Search("path_preference_data/PathPreferenceSandeshData/preference/text()")
		destvn, _ := path.Search("dest_vn/text()")
		itf, _ := path.Search("nh/NhSandeshData/itf/text()")
		t.AddRow("    "+utils.Pretty(nhs), utils.Pretty(peers), utils.Pretty(label),
			 utils.Pretty(pref), utils.Pretty(itf), utils.Pretty(destvn))
	}
	t.AddRow("")
}

func mplsDetail(t *uitable.Table, e collection.Element) {
	label, err := e.GetField("label")
	if err != nil {
		log.Fatal(err)
	}
	t.AddRow(fmt.Sprintf("Label: %s", label))
	nexthopDetail(t, e.Node)
	t.AddRow("")
}

func nexthopDetail(t *uitable.Table, node xml.Node) {
	t.AddRow("    Type", "Interface", "Nexthop index")
	nhs, _ := node.Search("nh/NhSandeshData/type/text()")
	itf, _ := node.Search("nh/NhSandeshData/itf/text()")
	nhIdx, _ := node.Search("nh/NhSandeshData/nh_index/text()")
	t.AddRow("    "+utils.Pretty(nhs), utils.Pretty(itf), utils.Pretty(nhIdx))
}

func controllerRoutePath(t *uitable.Table, e collection.Element) {
	srcIp, _ := e.Node.Search("prefix/text()")
	t.AddRow(fmt.Sprintf("Prefix %s", srcIp[0]))
	paths, _ := e.Node.Search("paths/list/ShowRoutePath")

	t.AddRow("    Protocol", "Nexthop", "Local Pref", "Peers", "MPLS label")
	for _, path := range paths {
		protocol, _ := path.Search("protocol/text()")
		nhs, _ := path.Search("next_hop/text()")
		peers, _ := path.Search("source/text()")
		label, _ := path.Search("label/text()")
		localPref, _ := path.Search("local_preference/text()")
		t.AddRow("    "+utils.Pretty(protocol), utils.Pretty(nhs), utils.Pretty(localPref), utils.Pretty(peers), utils.Pretty(label))
	}
	t.AddRow("")
}

func AgentPing() collection.DescCollection {
	return collection.DescCollection{
		PageArgs: []string{"vrouter-fqdn", "vrf-name", "source-ip", "source-port", "dest-ip", "dest-port"},
		PageBuilder: func(args []string) collection.Sourcer {
			sourceIp := args[2]
			sourcePort := args[3]
			destIp := args[4]
			destPort := args[5]
			vrfName := args[1]

			path := fmt.Sprintf("Snh_PingReq?source_ip=%s&source_port=%s&dest_ip=%s&dest_port=%s&protocol=6&vrf_name=%s&packet_size=&count=1&interval=",
				sourceIp, sourcePort, destIp, destPort, vrfName)
			return collection.Webui{Path: path, VrouterUrl: args[0], Port: 8085}
		},
		BaseXpath: "__PingResp_list",
		DescElt: collection.DescElement{
			ShortDetailXpath: "resp/text()",
			LongDetail:       collection.LongFormatXpaths([]string{"resp", "rtt"}),
		},
	}
}
<|endoftext|>


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

package dfs

import (
	"fmt"

	"github.com/control-center/serviced/dfs/docker"
	"github.com/control-center/serviced/domain/service"
	"github.com/zenoss/glog"
)

// UpgradeRegistry loads images for each service into the docker registry
// index.  Also migrates images from a previous repository
func (dfs *DistributedFilesystem) UpgradeRegistry(svcs []service.Service, tenantID, registryHost string) error {
	imageIDs := make(map[string]struct{})
	for _, svc := range svcs {
		if svc.ImageID == "" {
			// no image, no migration needed
			continue
		}
		image := svc.ImageID
		if _, ok := imageIDs[image]; ok {
			// image has already been added
			continue
		}
		imageIDs[image] = struct{}{}
		// is image in registry?
		rImage, err := dfs.findImage(image, tenantID)
		if err != nil {
			return err
		} else if rImage != "" {
			// image is already in the registry
			glog.V(2).Infof("Image %s for service %s (%s) already present in docker registry", rImage, svc.Name, svc.ID)
			continue
		}
		// get registry image tag from image name
		rImage, err = dfs.parseRegistryImage(image, tenantID)
		if err != nil {
			glog.Warningf("Cannot parse image name %s under service %s (%s)", image, svc.Name, svc.ID)
			continue
		}
		// download image from old registry
		if registryHost != "" {
			glog.V(2).Info("Downloading image %s from %s registry", image, registryHost)
			oldImage := fmt.Sprintf("%s/%s", registryHost, rImage)
			if err := dfs.docker.PullImage(oldImage); err != nil {
				glog.V(2).Infof("Could not pull image %s from registry %s, falling back to local library: %s", image, registryHost, err)
			} else if err := dfs.docker.TagImage(oldImage, image); err != nil {
				glog.Errorf("Could not retag image %s as %s: %s", oldImage, image, err)
				return err
			}
		}
		// find image in docker library
		img, err := dfs.docker.FindImage(image)
		if docker.IsImageNotFound(err) {
			glog.Warningf("Could not find image %s for service %s (%s)", image, svc.Name, svc.ID)
			continue
		} else if err != nil {
			glog.Errorf("Error looking up image %s for service %s (%s): %s", image, svc.Name, svc.ID, err)
			return err
		}
		// write to registry index
		if err := dfs.index.PushImage(rImage, img.ID); err != nil {
			glog.Errorf("Could not write %s (%s) to registry index: %s", rImage, img.ID, err)
			return err
		}
		glog.Infof("Added image %s for service %s (%s) to the docker registry", rImage, svc.Name, svc.ID)
	}
	return nil
}
<commit_msg>comments per code review<commit_after>// Copyright 2015 The Serviced Authors.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dfs

import (
	"fmt"

	"github.com/control-center/serviced/dfs/docker"
	"github.com/control-center/serviced/domain/service"
	"github.com/zenoss/glog"
)

// UpgradeRegistry loads images for each service into the docker registry
// index.  Also migrates images from a previous (or V1) registry at
// registryHost (host:port).
func (dfs *DistributedFilesystem) UpgradeRegistry(svcs []service.Service, tenantID, registryHost string) error {
	imageIDs := make(map[string]struct{})
	for _, svc := range svcs {
		if svc.ImageID == "" {
			// no image, no migration needed
			continue
		}
		image := svc.ImageID
		if _, ok := imageIDs[image]; ok {
			// image has already been added
			continue
		}
		imageIDs[image] = struct{}{}
		// is image in registry?
		rImage, err := dfs.findImage(image, tenantID)
		if err != nil {
			return err
		} else if rImage != "" {
			// image is already in the registry
			glog.V(2).Infof("Image %s for service %s (%s) already present in docker registry", rImage, svc.Name, svc.ID)
			continue
		}
		// get registry image tag from image name
		rImage, err = dfs.parseRegistryImage(image, tenantID)
		if err != nil {
			glog.Warningf("Cannot parse image name %s under service %s (%s)", image, svc.Name, svc.ID)
			continue
		}
		// download image from old registry at registryHost defined at HOST:PORT
		// and retag it at the original registry path as defined by the service.
		if registryHost != "" {
			glog.Infof("Downloading image %s from %s registry", image, registryHost)
			oldImage := fmt.Sprintf("%s/%s", registryHost, rImage)
			if err := dfs.docker.PullImage(oldImage); err != nil {
				glog.Warningf("Could not pull image %s from registry %s, falling back to local library: %s", image, registryHost, err)
			} else if err := dfs.docker.TagImage(oldImage, image); err != nil {
				glog.Errorf("Could not retag image %s as %s: %s", oldImage, image, err)
				return err
			}
		}
		// find image in docker library
		img, err := dfs.docker.FindImage(image)
		if docker.IsImageNotFound(err) {
			glog.Warningf("Could not find image %s for service %s (%s)", image, svc.Name, svc.ID)
			continue
		} else if err != nil {
			glog.Errorf("Error looking up image %s for service %s (%s): %s", image, svc.Name, svc.ID, err)
			return err
		}
		// write to registry index
		if err := dfs.index.PushImage(rImage, img.ID); err != nil {
			glog.Errorf("Could not write %s (%s) to registry index: %s", rImage, img.ID, err)
			return err
		}
		glog.Infof("Added image %s for service %s (%s) to the docker registry", rImage, svc.Name, svc.ID)
	}
	return nil
}
<|endoftext|>


<commit_before>package sexp

import (
	"lang"
	"xtypes"
)

// IsStmt returns true for statement form.
func IsStmt(form Form) bool {
	return form.Type() == xtypes.TypVoid
}

func IsThrow(form Form) bool {
	switch form := form.(type) {
	case *Call:
		return lang.FuncIsThrowing(form.Fn.Name)
	case *LispCall:
		return lang.FuncIsThrowing(form.Fn.Sym)

	default:
		return false
	}
}

// IsReturning returns true for forms that unconditionally
// return from function.
func IsReturning(form Form) bool {
	// #REFS: 49.
	switch form := form.(type) {
	case *Return:
		return true

	case *Block:
		for _, form := range form.Forms {
			if IsReturning(form) {
				return true
			}
		}

	case *If:
		// If both branches return, whole statement returns.
		return IsReturning(form.Then) && IsReturning(form.Else)

	case *ExprStmt:
		return IsReturning(form.Expr)

	case *Call:
		return lang.FuncIsThrowing(form.Fn.Name)
	case *LispCall:
		return lang.FuncIsThrowing(form.Fn.Sym)
	}

	return false
}
<commit_msg>refs #49 - using sexp.Walk in sexp.IsReturning<commit_after>package sexp

import (
	"lang"
	"xtypes"
)

// IsStmt returns true for statement form.
func IsStmt(form Form) bool {
	return form.Type() == xtypes.TypVoid
}

func IsThrow(form Form) bool {
	switch form := form.(type) {
	case *Call:
		return lang.FuncIsThrowing(form.Fn.Name)
	case *LispCall:
		return lang.FuncIsThrowing(form.Fn.Sym)

	default:
		return false
	}
}

// IsReturning returns true for forms that unconditionally
// return from function.
func IsReturning(form Form) bool {
	found := false
	Walk(form, func(form Form) bool {
		switch form := form.(type) {
		case *Return:
			found = true
			return false

		case *Call:
			found = lang.FuncIsThrowing(form.Fn.Name)
			return !found
		case *LispCall:
			found = lang.FuncIsThrowing(form.Fn.Sym)
			return !found

		default:
			return true
		}
	})
	return found
}
<|endoftext|>


<commit_before>package rx

import (
	"fmt"

	"github.com/reactivego/subscriber"
)

//jig:template Subscriber

// Subscriber is an alias for the subscriber.Subscriber interface type.
type Subscriber subscriber.Subscriber

// Subscription is an alias for the subscriber.Subscription interface type.
type Subscription subscriber.Subscription

//jig:template SubscribeOption
//jig:needs Schedulers, Subscriber

// SubscribeOption is an option that can be passed to the Subscribe method.
type SubscribeOption func(options *subscribeOptions)

type subscribeOptions struct {
	scheduler Scheduler
	subscriber Subscriber
	onSubscribe func(subscription Subscription)
	onUnsubscribe func()
}

// SubscribeOn returns an option that can be passed to the Subscribe method.
// It takes the scheduler to subscribe the observable on. The tasks that
// actually perform the observable functionality are scheduled on this
// scheduler. The other options that can be passed here are applied after the
// scheduler was set so any schedulers passed in via other will override
// the scheduler passed here.
func SubscribeOn(scheduler Scheduler, other ...SubscribeOption) SubscribeOption {
	return func(options *subscribeOptions) {
		options.scheduler = scheduler
		for _, setter := range other {
			setter(options)
		}
	}
}

// WithSubscriber returns an option that can be passed to the Subscribe method.
// The Subscribe method will use the subscriber passed here instead of creating
// a new one.
func WithSubscriber(subscriber Subscriber) SubscribeOption {
	return func(options *subscribeOptions) {
		options.subscriber = subscriber
	}
}

// OnSubscribe returns an option that can be passed to the Subscribe method.
// It takes a callback that is called from the Subscribe method just before
// subscribing continues further.
func OnSubscribe(callback func(Subscription)) SubscribeOption {
	return func(options *subscribeOptions) { options.onSubscribe = callback }
}

// OnUnsubscribe returns an option that can be passed to the Subscribe method.
// It takes a callback that is called by the Subscribe method to notify the
// client that the subscription has been canceled.
func OnUnsubscribe(callback func()) SubscribeOption {
	return func(options *subscribeOptions) { options.onUnsubscribe = callback }
}

// newSchedulerAndSubscriber will return either return the scheduler and subscriber
// passed in through the SubscribeOn() and WithSubscriber() options or it will
// return newly created scheduler and subscriber. Before returning the callback
// passed in through OnSubscribe() will already have been called.
func newSchedulerAndSubscriber(setters []SubscribeOption) (Scheduler, Subscriber) {
	options := &subscribeOptions{scheduler: CurrentGoroutineScheduler()}
	for _, setter := range setters {
		setter(options)
	}
	if options.subscriber == nil {
		options.subscriber = subscriber.New()
	}
	options.subscriber.OnUnsubscribe(options.onUnsubscribe)
	if options.onSubscribe != nil {
		options.onSubscribe(options.subscriber)
	}
	return options.scheduler, options.subscriber
}

//jig:template Observable<Foo> Subscribe
//jig:needs SubscribeOption

// Subscribe operates upon the emissions and notifications from an Observable.
// This method returns a Subscription.
func (o ObservableFoo) Subscribe(observe FooObserveFunc, options ...SubscribeOption) Subscription {
	scheduler, subscriber := newSchedulerAndSubscriber(options)
	observer := func(next foo, err error, done bool) {
		if !done {
			observe(next, err, done)
		} else {
			observe(zeroFoo, err, true)
			subscriber.Unsubscribe()
		}
	}
	o(observer, scheduler, subscriber)
	return subscriber
}

//jig:template Observable<Foo> SubscribeNext
//jig:needs Observable<Foo> Subscribe

// SubscribeNext operates upon the emissions from an Observable only.
// This method returns a Subscription.
func (o ObservableFoo) SubscribeNext(f func(next foo), options ...SubscribeOption) Subscription {
	return o.Subscribe(func(next foo, err error, done bool) {
		if !done {
			f(next)
		}
	}, options...)
}

//jig:template Observable<Foo> Println
//jig:needs Observable<Foo> Subscribe

// Println subscribes to the Observable and prints every item to os.Stdout while
// it waits for completion or error. Returns either the error or nil when the
// Observable completed normally.
func (o ObservableFoo) Println(options ...SubscribeOption) (e error) {
	o.Subscribe(func(next foo, err error, done bool) {
		if !done {
			fmt.Println(next)
		} else {
			e = err
		}
	}, options...).Wait()
	return e
}

//jig:template Observable ToChan
//jig:needs Observable Subscribe

// ToChan returns a channel that emits interface{} values. If the source
// observable does not emit values but emits an error or complete, then the
// returned channel will enit any error and then close without emitting any
// values.
//
// Because the channel is fed by subscribing to the observable, ToChan would
// block when subscribed on the standard Trampoline scheduler which is initially
// synchronous. That's why the subscribing is done on the Goroutine scheduler.
//
// To cancel the subscription created internally by ToChan you will need access
// to the subscription used internnally by ToChan. To get at this subscription,
// pass the result of a call to option OnSubscribe(func(Subscription)) as a
// parameter to ToChan. On suscription the callback will be called with the
// subscription that was created.
func (o Observable) ToChan(options ...SubscribeOption) <-chan interface{} {
	scheduler := NewGoroutineScheduler()
	nextch := make(chan interface{}, 1)
	o.Subscribe(func(next interface{}, err error, done bool) {
		if !done {
			nextch <- next
		} else {
			if err != nil {
				nextch <- err
			}
			close(nextch)
		}
	}, SubscribeOn(scheduler, options...))
	return nextch
}

//jig:template Observable<Foo> ToChan
//jig:needs Observable<Foo> Subscribe
//jig:required-vars Foo

// ToChan returns a channel that emits foo values. If the source observable does
// not emit values but emits an error or complete, then the returned channel
// will close without emitting any values.
//
// There is no way to determine whether the observable feeding into the
// channel terminated with an error or completed normally.
// Because the channel is fed by subscribing to the observable, ToChan would
// block when subscribed on the standard Trampoline scheduler which is initially
// synchronous. That's why the subscribing is done on the Goroutine scheduler.
// It is not possible to cancel the subscription created internally by ToChan.
func (o ObservableFoo) ToChan(options ...SubscribeOption) <-chan foo {
	scheduler := NewGoroutineScheduler()
	nextch := make(chan foo, 1)
	o.Subscribe(func(next foo, err error, done bool) {
		if !done {
			nextch <- next
		} else {
			close(nextch)
		}
	}, SubscribeOn(scheduler, options...))
	return nextch
}

//jig:template Observable<Foo> ToSingle
//jig:needs Observable<Foo> Subscribe

// ToSingle blocks until the ObservableFoo emits exactly one value or an error.
// The value and any error are returned.
//
// This function subscribes to the source observable on the Goroutine scheduler.
// The Goroutine scheduler works in more situations for complex chains of
// observables, like when merging the output of multiple observables.
func (o ObservableFoo) ToSingle(options ...SubscribeOption) (v foo, e error) {
	scheduler := NewGoroutineScheduler()
	o.Single().Subscribe(func(next foo, err error, done bool) {
		if !done {
			v = next
		} else {
			e = err
		}
	}, SubscribeOn(scheduler, options...)).Wait()
	return v, e
}

//jig:template Observable<Foo> ToSlice
//jig:needs Observable<Foo> Subscribe

// ToSlice collects all values from the ObservableFoo into an slice. The
// complete slice and any error are returned.
//
// This function subscribes to the source observable on the Goroutine scheduler.
// The Goroutine scheduler works in more situations for complex chains of
// observables, like when merging the output of multiple observables.
func (o ObservableFoo) ToSlice(options ...SubscribeOption) (a []foo, e error) {
	scheduler := NewGoroutineScheduler()
	o.Subscribe(func(next foo, err error, done bool) {
		if !done {
			a = append(a, next)
		} else {
			e = err
		}
	}, SubscribeOn(scheduler, options...)).Wait()
	return a, e
}

//jig:template Observable<Foo> Wait
//jig:needs Observable<Foo> Subscribe

// Wait subscribes to the Observable and waits for completion or error.
// Returns either the error or nil when the Observable completed normally.
func (o ObservableFoo) Wait(options ...SubscribeOption) (e error) {
	o.Subscribe(func(next foo, err error, done bool) {
		if done {
			e = err
		}
	}, options...).Wait()
	return e
}
<commit_msg>Refactor Println method to no longer use Subscribe and SubscribeOptions.<commit_after>package rx

import (
	"fmt"

	"github.com/reactivego/subscriber"
)

//jig:template Subscriber

// Subscriber is an alias for the subscriber.Subscriber interface type.
type Subscriber subscriber.Subscriber

// Subscription is an alias for the subscriber.Subscription interface type.
type Subscription subscriber.Subscription

//jig:template Observable<Foo> Print
//jig:needs Schedulers, Subscriber

// Println subscribes to the Observable and prints every item to os.Stdout
// while it waits for completion or error. Returns either the error or nil
// when the Observable completed normally.
func (o ObservableFoo) Println() (err error) {
	subscriber := subscriber.New()
	scheduler := CurrentGoroutineScheduler()
	observer := func(next foo, e error, done bool) {
		if !done {
			fmt.Println(next)
		} else {
			err = e
			subscriber.Unsubscribe()
		}
	}
	o(observer, scheduler, subscriber)
	subscriber.Wait()
	return
}

//jig:template SubscribeOption
//jig:needs Schedulers, Subscriber

// SubscribeOption is an option that can be passed to the Subscribe method.
type SubscribeOption func(options *subscribeOptions)

type subscribeOptions struct {
	scheduler Scheduler
	subscriber Subscriber
	onSubscribe func(subscription Subscription)
	onUnsubscribe func()
}

// SubscribeOn returns an option that can be passed to the Subscribe method.
// It takes the scheduler to subscribe the observable on. The tasks that
// actually perform the observable functionality are scheduled on this
// scheduler. The other options that can be passed here are applied after the
// scheduler was set so any schedulers passed in via other will override
// the scheduler passed here.
func SubscribeOn(scheduler Scheduler, other ...SubscribeOption) SubscribeOption {
	return func(options *subscribeOptions) {
		options.scheduler = scheduler
		for _, setter := range other {
			setter(options)
		}
	}
}

// WithSubscriber returns an option that can be passed to the Subscribe method.
// The Subscribe method will use the subscriber passed here instead of creating
// a new one.
func WithSubscriber(subscriber Subscriber) SubscribeOption {
	return func(options *subscribeOptions) {
		options.subscriber = subscriber
	}
}

// OnSubscribe returns an option that can be passed to the Subscribe method.
// It takes a callback that is called from the Subscribe method just before
// subscribing continues further.
func OnSubscribe(callback func(Subscription)) SubscribeOption {
	return func(options *subscribeOptions) { options.onSubscribe = callback }
}

// OnUnsubscribe returns an option that can be passed to the Subscribe method.
// It takes a callback that is called by the Subscribe method to notify the
// client that the subscription has been canceled.
func OnUnsubscribe(callback func()) SubscribeOption {
	return func(options *subscribeOptions) { options.onUnsubscribe = callback }
}

// newSchedulerAndSubscriber will return either return the scheduler and subscriber
// passed in through the SubscribeOn() and WithSubscriber() options or it will
// return newly created scheduler and subscriber. Before returning the callback
// passed in through OnSubscribe() will already have been called.
func newSchedulerAndSubscriber(setters []SubscribeOption) (Scheduler, Subscriber) {
	options := &subscribeOptions{scheduler: CurrentGoroutineScheduler()}
	for _, setter := range setters {
		setter(options)
	}
	if options.subscriber == nil {
		options.subscriber = subscriber.New()
	}
	options.subscriber.OnUnsubscribe(options.onUnsubscribe)
	if options.onSubscribe != nil {
		options.onSubscribe(options.subscriber)
	}
	return options.scheduler, options.subscriber
}

//jig:template Observable<Foo> Subscribe
//jig:needs SubscribeOption

// Subscribe operates upon the emissions and notifications from an Observable.
// This method returns a Subscription.
func (o ObservableFoo) Subscribe(observe FooObserveFunc, options ...SubscribeOption) Subscription {
	scheduler, subscriber := newSchedulerAndSubscriber(options)
	observer := func(next foo, err error, done bool) {
		if !done {
			observe(next, err, done)
		} else {
			observe(zeroFoo, err, true)
			subscriber.Unsubscribe()
		}
	}
	o(observer, scheduler, subscriber)
	return subscriber
}

//jig:template Observable<Foo> SubscribeNext
//jig:needs Observable<Foo> Subscribe

// SubscribeNext operates upon the emissions from an Observable only.
// This method returns a Subscription.
func (o ObservableFoo) SubscribeNext(f func(next foo), options ...SubscribeOption) Subscription {
	return o.Subscribe(func(next foo, err error, done bool) {
		if !done {
			f(next)
		}
	}, options...)
}

//jig:template Observable ToChan
//jig:needs Observable Subscribe

// ToChan returns a channel that emits interface{} values. If the source
// observable does not emit values but emits an error or complete, then the
// returned channel will enit any error and then close without emitting any
// values.
//
// Because the channel is fed by subscribing to the observable, ToChan would
// block when subscribed on the standard Trampoline scheduler which is initially
// synchronous. That's why the subscribing is done on the Goroutine scheduler.
//
// To cancel the subscription created internally by ToChan you will need access
// to the subscription used internnally by ToChan. To get at this subscription,
// pass the result of a call to option OnSubscribe(func(Subscription)) as a
// parameter to ToChan. On suscription the callback will be called with the
// subscription that was created.
func (o Observable) ToChan(options ...SubscribeOption) <-chan interface{} {
	scheduler := NewGoroutineScheduler()
	nextch := make(chan interface{}, 1)
	o.Subscribe(func(next interface{}, err error, done bool) {
		if !done {
			nextch <- next
		} else {
			if err != nil {
				nextch <- err
			}
			close(nextch)
		}
	}, SubscribeOn(scheduler, options...))
	return nextch
}

//jig:template Observable<Foo> ToChan
//jig:needs Observable<Foo> Subscribe
//jig:required-vars Foo

// ToChan returns a channel that emits foo values. If the source observable does
// not emit values but emits an error or complete, then the returned channel
// will close without emitting any values.
//
// There is no way to determine whether the observable feeding into the
// channel terminated with an error or completed normally.
// Because the channel is fed by subscribing to the observable, ToChan would
// block when subscribed on the standard Trampoline scheduler which is initially
// synchronous. That's why the subscribing is done on the Goroutine scheduler.
// It is not possible to cancel the subscription created internally by ToChan.
func (o ObservableFoo) ToChan(options ...SubscribeOption) <-chan foo {
	scheduler := NewGoroutineScheduler()
	nextch := make(chan foo, 1)
	o.Subscribe(func(next foo, err error, done bool) {
		if !done {
			nextch <- next
		} else {
			close(nextch)
		}
	}, SubscribeOn(scheduler, options...))
	return nextch
}

//jig:template Observable<Foo> ToSingle
//jig:needs Observable<Foo> Subscribe

// ToSingle blocks until the ObservableFoo emits exactly one value or an error.
// The value and any error are returned.
//
// This function subscribes to the source observable on the Goroutine scheduler.
// The Goroutine scheduler works in more situations for complex chains of
// observables, like when merging the output of multiple observables.
func (o ObservableFoo) ToSingle(options ...SubscribeOption) (v foo, e error) {
	scheduler := NewGoroutineScheduler()
	o.Single().Subscribe(func(next foo, err error, done bool) {
		if !done {
			v = next
		} else {
			e = err
		}
	}, SubscribeOn(scheduler, options...)).Wait()
	return v, e
}

//jig:template Observable<Foo> ToSlice
//jig:needs Observable<Foo> Subscribe

// ToSlice collects all values from the ObservableFoo into an slice. The
// complete slice and any error are returned.
//
// This function subscribes to the source observable on the Goroutine scheduler.
// The Goroutine scheduler works in more situations for complex chains of
// observables, like when merging the output of multiple observables.
func (o ObservableFoo) ToSlice(options ...SubscribeOption) (a []foo, e error) {
	scheduler := NewGoroutineScheduler()
	o.Subscribe(func(next foo, err error, done bool) {
		if !done {
			a = append(a, next)
		} else {
			e = err
		}
	}, SubscribeOn(scheduler, options...)).Wait()
	return a, e
}

//jig:template Observable<Foo> Wait
//jig:needs Observable<Foo> Subscribe

// Wait subscribes to the Observable and waits for completion or error.
// Returns either the error or nil when the Observable completed normally.
func (o ObservableFoo) Wait(options ...SubscribeOption) (e error) {
	o.Subscribe(func(next foo, err error, done bool) {
		if done {
			e = err
		}
	}, options...).Wait()
	return e
}
<|endoftext|>


<commit_before>/**
 * (C) Copyright 2014, Deft Labs
 *
 * 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 dlshared

import "time"

type metricType int8

const (
	Counter metricType = 0
	Gauge metricType = 1
)

type Metric struct {
	Name string
	Type metricType
	Value float64
}

type Metrics struct {
	sourceName string
	quitChannel chan bool
	relayFunc func(string, map[string]Metric)
	relayPeriodInSecs int
	metricChannel chan *Metric
	ticker *time.Ticker
}

func NewMetrics(	sourceName string,
					relayFunc func(string, map[string]Metric),
					relayPeriodInSecs int,
					metricQueueLength int) *Metrics {

	return &Metrics{
		sourceName : sourceName,
		relayFunc : relayFunc,
		relayPeriodInSecs: relayPeriodInSecs,
		quitChannel : make(chan bool),
		metricChannel : make(chan *Metric, metricQueueLength),
	}
}

// Update the gauge value
func (self *Metrics) Gauge(metricName string, value float64) {
	self.metricChannel <- &Metric{
		Name: metricName,
		Type: Gauge,
		Value: value,
	}
}

// Increases the counter by one.
func (self *Metrics) Count(metricName string) {
	self.metricChannel <- &Metric{
		Name: metricName,
		Type: Counter,
		Value: 1,
	}
}

// Increase the counter
func (self *Metrics) CountWithValue(metricName string, value float64) {
	self.metricChannel <- &Metric{
		Name: metricName,
		Type: Counter,
		Value: value,
	}
}

func (self *Metrics) listenForEvents() {

	metrics := make(map[string]*Metric)

    for {
        select {
			case metric := <- self.metricChannel:
				current, found := metrics[metric.Name]
				if !found {
					metrics[metric.Name] = metric
					continue
				}

				if metric.Type == Counter {
					current.Value = current.Value + metric.Value
				} else {
					// This is a gague
					current.Value = metric.Value
				}

			case <- self.ticker.C:
				metricsToRelay := make(map[string]Metric)
				for k, v := range metrics {
					metricsToRelay[k] = Metric{ Name: v.Name, Type: v.Type, Value: v.Value }
				}

				go self.relayFunc(self.sourceName, metricsToRelay)

			case <- self.quitChannel:
				self.ticker.Stop()
				return
        }
    }
}

func (self *Metrics) Start() error {

	self.ticker = time.NewTicker(time.Duration(self.relayPeriodInSecs) * time.Second)

	go self.listenForEvents()

	return nil
}

func (self *Metrics) Stop() error {
	self.quitChannel <- true
	return nil
}

<commit_msg>changed relay to use slice versus map<commit_after>/**
 * (C) Copyright 2014, Deft Labs
 *
 * 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 dlshared

import "time"

type metricType int8

const (
	Counter metricType = 0
	Gauge metricType = 1
)

type Metric struct {
	Name string
	Type metricType
	Value float64
}

type Metrics struct {
	sourceName string
	quitChannel chan bool
	relayFunc func(string, []Metric)
	relayPeriodInSecs int
	metricChannel chan *Metric
	ticker *time.Ticker
}

func NewMetrics(	sourceName string,
					relayFunc func(string, []Metric),
					relayPeriodInSecs int,
					metricQueueLength int) *Metrics {

	return &Metrics{
		sourceName : sourceName,
		relayFunc : relayFunc,
		relayPeriodInSecs: relayPeriodInSecs,
		quitChannel : make(chan bool),
		metricChannel : make(chan *Metric, metricQueueLength),
	}
}

// Update the gauge value
func (self *Metrics) Gauge(metricName string, value float64) {
	self.metricChannel <- &Metric{
		Name: metricName,
		Type: Gauge,
		Value: value,
	}
}

// Increases the counter by one.
func (self *Metrics) Count(metricName string) {
	self.metricChannel <- &Metric{
		Name: metricName,
		Type: Counter,
		Value: 1,
	}
}

// Increase the counter
func (self *Metrics) CountWithValue(metricName string, value float64) {
	self.metricChannel <- &Metric{
		Name: metricName,
		Type: Counter,
		Value: value,
	}
}

func (self *Metrics) listenForEvents() {

	metrics := make(map[string]*Metric)

    for {
        select {
			case metric := <- self.metricChannel:
				current, found := metrics[metric.Name]
				if !found {
					metrics[metric.Name] = metric
					continue
				}

				if metric.Type == Counter {
					current.Value = current.Value + metric.Value
				} else {
					// This is a gague
					current.Value = metric.Value
				}

			case <- self.ticker.C:
				toRelay := make([]Metric, len(metrics))
				for _, v := range metrics {
					toRelay = append(toRelay, Metric{ Name: v.Name, Type: v.Type, Value: v.Value })
				}

				go self.relayFunc(self.sourceName, toRelay)

			case <- self.quitChannel:
				self.ticker.Stop()
				return
        }
    }
}

func (self *Metrics) Start() error {

	self.ticker = time.NewTicker(time.Duration(self.relayPeriodInSecs) * time.Second)

	go self.listenForEvents()

	return nil
}

func (self *Metrics) Stop() error {
	self.quitChannel <- true
	return nil
}

<|endoftext|>


<commit_before>package cache // import "gopkg.in/go-redis/cache.v4"

import (
	"errors"
	"log"
	"sync/atomic"
	"time"

	"gopkg.in/go-redis/cache.v4/internal/singleflight"
	"gopkg.in/go-redis/cache.v4/lrucache"
	"gopkg.in/redis.v4"
)

const defaultExpiration = 3 * 24 * time.Hour

var ErrCacheMiss = errors.New("cache: keys is missing")

type rediser interface {
	Set(key string, value interface{}, expiration time.Duration) *redis.StatusCmd
	Get(key string) *redis.StringCmd
	Del(keys ...string) *redis.IntCmd
}

type Codec struct {
	Redis rediser

	// Local LRU cache for super hot items.
	LocalCache *lrucache.Cache

	Marshal   func(interface{}) ([]byte, error)
	Unmarshal func([]byte, interface{}) error

	group        singleflight.Group
	hits, misses int64
}

type Item struct {
	Key    string
	Object interface{}

	// Func returns object to cache.
	Func func() (interface{}, error)

	// Expiration is the cache expiration time.
	// Zero means the Item has no expiration time.
	Expiration time.Duration
}

func (item *Item) object() (interface{}, error) {
	if item.Object != nil {
		return item.Object, nil
	}
	if item.Func != nil {
		return item.Func()
	}
	return nil, nil
}

// Set caches the item.
func (cd *Codec) Set(item *Item) error {
	if item.Expiration != 0 && item.Expiration < time.Second {
		panic("Expiration can't be less than 1 second")
	}

	object, err := item.object()
	if err != nil {
		return err
	}

	b, err := cd.Marshal(object)
	if err != nil {
		log.Printf("cache: Marshal failed: %s", err)
		return err
	}

	if cd.LocalCache != nil {
		cd.LocalCache.Set(item.Key, b)
	}

	err = cd.Redis.Set(item.Key, b, item.Expiration).Err()
	if err != nil {
		log.Printf("cache: Set key=%s failed: %s", item.Key, err)
	}
	return err
}

// Get gets the object for the given key.
func (cd *Codec) Get(key string, object interface{}) error {
	b, err := cd.getBytes(key)
	if err == redis.Nil {
		atomic.AddInt64(&cd.misses, 1)
		return ErrCacheMiss
	} else if err != nil {
		log.Printf("cache: Get key=%s failed: %s", key, err)
		atomic.AddInt64(&cd.misses, 1)
		return err
	}

	if object != nil {
		if err := cd.Unmarshal(b, object); err != nil {
			log.Printf("cache: Unmarshal failed: %s", err)
			atomic.AddInt64(&cd.misses, 1)
			return err
		}
	}

	atomic.AddInt64(&cd.hits, 1)
	return nil
}

// Do gets the item.Object for the given item.Key from the cache or
// executes, caches, and returns the results of the given item.Func,
// making sure that only one execution is in-flight for a given item.Key
// at a time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
func (cd *Codec) Do(item *Item) (interface{}, error) {
	if err := cd.Get(item.Key, item.Object); err == nil {
		return item.Object, nil
	}

	return cd.group.Do(item.Key, func() (interface{}, error) {
		obj, err := item.Func()
		if err != nil {
			return nil, err
		}

		item.Object = obj
		cd.Set(item)

		return obj, nil
	})
}

func (cd *Codec) getBytes(key string) ([]byte, error) {
	if cd.LocalCache != nil {
		v, ok := cd.LocalCache.Get(key)
		if ok {
			b, ok := v.([]byte)
			if ok {
				return b, nil
			}
		}
	}

	b, err := cd.Redis.Get(key).Bytes()
	if err == nil && cd.LocalCache != nil {
		cd.LocalCache.Set(key, b)
	}
	return b, err
}

func (cd *Codec) Delete(key string) error {
	if cd.LocalCache != nil {
		cd.LocalCache.Delete(key)
	}

	deleted, err := cd.Redis.Del(key).Result()
	if err != nil {
		log.Printf("cache: Del key=%s failed: %s", key, err)
		return err
	}
	if deleted == 0 {
		return ErrCacheMiss
	}
	return nil
}

func (cd *Codec) Hits() int {
	return int(atomic.LoadInt64(&cd.hits))
}

func (cd *Codec) Misses() int {
	return int(atomic.LoadInt64(&cd.misses))
}
<commit_msg>Better log message.<commit_after>package cache // import "gopkg.in/go-redis/cache.v4"

import (
	"errors"
	"log"
	"sync/atomic"
	"time"

	"gopkg.in/go-redis/cache.v4/internal/singleflight"
	"gopkg.in/go-redis/cache.v4/lrucache"
	"gopkg.in/redis.v4"
)

const defaultExpiration = 3 * 24 * time.Hour

var ErrCacheMiss = errors.New("cache: keys is missing")

type rediser interface {
	Set(key string, value interface{}, expiration time.Duration) *redis.StatusCmd
	Get(key string) *redis.StringCmd
	Del(keys ...string) *redis.IntCmd
}

type Codec struct {
	Redis rediser

	// Local LRU cache for super hot items.
	LocalCache *lrucache.Cache

	Marshal   func(interface{}) ([]byte, error)
	Unmarshal func([]byte, interface{}) error

	group        singleflight.Group
	hits, misses int64
}

type Item struct {
	Key    string
	Object interface{}

	// Func returns object to cache.
	Func func() (interface{}, error)

	// Expiration is the cache expiration time.
	// Zero means the Item has no expiration time.
	Expiration time.Duration
}

func (item *Item) object() (interface{}, error) {
	if item.Object != nil {
		return item.Object, nil
	}
	if item.Func != nil {
		return item.Func()
	}
	return nil, nil
}

// Set caches the item.
func (cd *Codec) Set(item *Item) error {
	if item.Expiration != 0 && item.Expiration < time.Second {
		panic("Expiration can't be less than 1 second")
	}

	object, err := item.object()
	if err != nil {
		return err
	}

	b, err := cd.Marshal(object)
	if err != nil {
		log.Printf("cache: Marshal failed: %s", err)
		return err
	}

	if cd.LocalCache != nil {
		cd.LocalCache.Set(item.Key, b)
	}

	err = cd.Redis.Set(item.Key, b, item.Expiration).Err()
	if err != nil {
		log.Printf("cache: Set key=%q failed: %s", item.Key, err)
	}
	return err
}

// Get gets the object for the given key.
func (cd *Codec) Get(key string, object interface{}) error {
	b, err := cd.getBytes(key)
	if err == redis.Nil {
		atomic.AddInt64(&cd.misses, 1)
		return ErrCacheMiss
	} else if err != nil {
		log.Printf("cache: Get key=%q failed: %s", key, err)
		atomic.AddInt64(&cd.misses, 1)
		return err
	}

	if object != nil {
		if err := cd.Unmarshal(b, object); err != nil {
			log.Printf("cache: Unmarshal failed: %s", err)
			atomic.AddInt64(&cd.misses, 1)
			return err
		}
	}

	atomic.AddInt64(&cd.hits, 1)
	return nil
}

// Do gets the item.Object for the given item.Key from the cache or
// executes, caches, and returns the results of the given item.Func,
// making sure that only one execution is in-flight for a given item.Key
// at a time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
func (cd *Codec) Do(item *Item) (interface{}, error) {
	if err := cd.Get(item.Key, item.Object); err == nil {
		return item.Object, nil
	}

	return cd.group.Do(item.Key, func() (interface{}, error) {
		obj, err := item.Func()
		if err != nil {
			return nil, err
		}

		item.Object = obj
		cd.Set(item)

		return obj, nil
	})
}

func (cd *Codec) getBytes(key string) ([]byte, error) {
	if cd.LocalCache != nil {
		v, ok := cd.LocalCache.Get(key)
		if ok {
			b, ok := v.([]byte)
			if ok {
				return b, nil
			}
		}
	}

	b, err := cd.Redis.Get(key).Bytes()
	if err == nil && cd.LocalCache != nil {
		cd.LocalCache.Set(key, b)
	}
	return b, err
}

func (cd *Codec) Delete(key string) error {
	if cd.LocalCache != nil {
		cd.LocalCache.Delete(key)
	}

	deleted, err := cd.Redis.Del(key).Result()
	if err != nil {
		log.Printf("cache: Del key=%q failed: %s", key, err)
		return err
	}
	if deleted == 0 {
		return ErrCacheMiss
	}
	return nil
}

func (cd *Codec) Hits() int {
	return int(atomic.LoadInt64(&cd.hits))
}

func (cd *Codec) Misses() int {
	return int(atomic.LoadInt64(&cd.misses))
}
<|endoftext|>


<commit_before>package completion

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"unicode"

	"github.com/zetamatta/go-box"

	"github.com/zetamatta/nyagos/readline"
	"github.com/zetamatta/nyagos/texts"
)

type Element struct {
	InsertStr string
	ListupStr string
}

type List struct {
	AllLine string
	List    []Element
	RawWord string // have quotation
	Word    string
	Pos     int
	Field   []string
	Left    string
}

var UseSlash = false

func isTop(s string, indexes [][]int) bool {
	if len(indexes) < 1 {
		return true
	}
	if len(indexes) == 1 {
		return indexes[0][1] == len(s)
	}
	prev := s[indexes[len(indexes)-2][0]:indexes[len(indexes)-2][1]]
	return prev == ";" || prev == "|" || prev == "&"
}

func listUpComplete(this *readline.Buffer) (*List, rune, error) {
	var err error
	rv := &List{
		AllLine: this.String(),
		Left:    this.SubString(0, this.Cursor),
	}

	// environment completion.

	indexes := texts.SplitLikeShell(rv.Left)
	for _, p := range indexes {
		rv.Field = append(rv.Field, rv.Left[p[0]:p[1]])
	}
	rv.List, rv.Pos, err = listUpEnv(rv.AllLine)
	default_delimiter := rune(readline.Delimiters[0])
	if len(rv.List) > 0 && rv.Pos >= 0 && err == nil {
		rv.RawWord = rv.AllLine[rv.Pos:]
		rv.Word = rv.RawWord
		return rv, default_delimiter, nil
	}

	// filename or commandname completion
	rv.RawWord, rv.Pos = this.CurrentWord()
	found_delimter := false
	rv.Word = strings.Map(func(c rune) rune {
		if strings.ContainsRune(readline.Delimiters, c) {
			if !found_delimter {
				default_delimiter = c
			}
			found_delimter = true
			return -1
		} else {
			return c
		}
	}, rv.RawWord)

	start := strings.LastIndexAny(rv.Word, ";=") + 1

	if isTop(rv.Left, indexes) {
		rv.List, err = listUpCommands(this.Context, rv.Word[start:])
	} else {
		rv.List, err = listUpFiles(this.Context, rv.Word[start:])
	}

	for i := 0; i < len(rv.List); i++ {
		rv.List[i].InsertStr = rv.Word[:start] + rv.List[i].InsertStr
	}
	for _, f := range HookToList {
		rv, err = f(this, rv)
		if err != nil {
			break
		}
	}
	return rv, default_delimiter, err
}

func toComplete(source []Element) []string {
	result := make([]string, len(source))
	for key, val := range source {
		result[key] = val.InsertStr
	}
	return result
}

func toDisplay(source []Element) []string {
	result := make([]string, len(source))
	for key, val := range source {
		result[key] = val.ListupStr
	}
	return result
}

func KeyFuncCompletionList(this *readline.Buffer) readline.Result {
	comp, _, err := listUpComplete(this)
	if comp == nil {
		return readline.CONTINUE
	}
	fmt.Fprint(readline.Console, "\n")
	if err != nil {
		fmt.Fprintf(readline.Console, "(warning) %s\n", err.Error())
	}
	box.Print(this.Context, toDisplay(comp.List), readline.Console)
	this.RepaintAll()
	return readline.CONTINUE
}

func CommonPrefix(list []string) string {
	if len(list) < 1 {
		return ""
	}
	common := list[0]
	for _, f := range list[1:] {
		cr := strings.NewReader(common)
		fr := strings.NewReader(f)
		i := 0
		var buffer bytes.Buffer
		for {
			ch, _, cerr := cr.ReadRune()
			fh, _, ferr := fr.ReadRune()
			if cerr != nil || ferr != nil || unicode.ToUpper(ch) != unicode.ToUpper(fh) {
				break
			}
			buffer.WriteRune(ch)
			i++
		}
		common = buffer.String()
	}
	return common
}

func endWithRoot(path string) bool {
	return len(path) >= 1 && os.IsPathSeparator(path[len(path)-1])
}

func KeyFuncCompletion(this *readline.Buffer) readline.Result {
	comp, default_delimiter, err := listUpComplete(this)
	if comp.List == nil || len(comp.List) <= 0 {
		return readline.CONTINUE
	}

	slashToBackSlash := true
	firstFoundSlashPos := strings.IndexRune(comp.Word, '/')
	firstFoundBackSlashPos := strings.IndexRune(comp.Word, os.PathSeparator)
	if UseSlash {
		slashToBackSlash = false
		if firstFoundBackSlashPos >= 0 && (firstFoundSlashPos == -1 || firstFoundBackSlashPos < firstFoundSlashPos) {
			slashToBackSlash = true
		}
	} else {
		if firstFoundSlashPos >= 0 && (firstFoundBackSlashPos == -1 || firstFoundSlashPos < firstFoundBackSlashPos) {
			slashToBackSlash = false
		}
	}

	complete_list := toComplete(comp.List)
	commonStr := CommonPrefix(complete_list)
	quotechar := byte(0)
	if i := strings.IndexAny(comp.Word, readline.Delimiters); i >= 0 {
		quotechar = comp.Word[i]
	} else {
		for _, node := range complete_list {
			if strings.ContainsAny(node, " &!") {
				quotechar = byte(default_delimiter)
				break
			}
		}
	}
	if quotechar != 0 {
		buffer := make([]byte, 0, len(commonStr)+3)
		if len(commonStr) >= 2 && commonStr[0] == '~' && os.IsPathSeparator(commonStr[1]) {
			buffer = append(buffer, commonStr[:1]...)
			buffer = append(buffer, quotechar)
			buffer = append(buffer, commonStr[1:]...)
		} else {
			buffer = append(buffer, quotechar)
			buffer = append(buffer, commonStr...)
		}
		if len(comp.List) == 1 && !endWithRoot(comp.List[0].InsertStr) {
			buffer = append(buffer, quotechar)
		}
		commonStr = string(buffer)
	}
	if len(comp.List) == 1 && !endWithRoot(commonStr) {
		commonStr += " "
	}
	if slashToBackSlash {
		commonStr = filepath.FromSlash(commonStr)
	}
	if comp.RawWord == commonStr {
		fmt.Fprint(readline.Console, "\n")
		if err != nil {
			fmt.Fprintf(readline.Console, "(warning) %s\n", err.Error())
		}
		box.Print(nil, toDisplay(comp.List), readline.Console)
		this.RepaintAll()
		return readline.CONTINUE
	}
	this.ReplaceAndRepaint(comp.Pos, commonStr)
	return readline.CONTINUE
}
<commit_msg>Don't append SPACE after PERCENT mark on completion<commit_after>package completion

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"unicode"

	"github.com/zetamatta/go-box"

	"github.com/zetamatta/nyagos/readline"
	"github.com/zetamatta/nyagos/texts"
)

type Element struct {
	InsertStr string
	ListupStr string
}

type List struct {
	AllLine string
	List    []Element
	RawWord string // have quotation
	Word    string
	Pos     int
	Field   []string
	Left    string
}

var UseSlash = false

func isTop(s string, indexes [][]int) bool {
	if len(indexes) < 1 {
		return true
	}
	if len(indexes) == 1 {
		return indexes[0][1] == len(s)
	}
	prev := s[indexes[len(indexes)-2][0]:indexes[len(indexes)-2][1]]
	return prev == ";" || prev == "|" || prev == "&"
}

func listUpComplete(this *readline.Buffer) (*List, rune, error) {
	var err error
	rv := &List{
		AllLine: this.String(),
		Left:    this.SubString(0, this.Cursor),
	}

	// environment completion.

	indexes := texts.SplitLikeShell(rv.Left)
	for _, p := range indexes {
		rv.Field = append(rv.Field, rv.Left[p[0]:p[1]])
	}
	rv.List, rv.Pos, err = listUpEnv(rv.AllLine)
	default_delimiter := rune(readline.Delimiters[0])
	if len(rv.List) > 0 && rv.Pos >= 0 && err == nil {
		rv.RawWord = rv.AllLine[rv.Pos:]
		rv.Word = rv.RawWord
		return rv, default_delimiter, nil
	}

	// filename or commandname completion
	rv.RawWord, rv.Pos = this.CurrentWord()
	found_delimter := false
	rv.Word = strings.Map(func(c rune) rune {
		if strings.ContainsRune(readline.Delimiters, c) {
			if !found_delimter {
				default_delimiter = c
			}
			found_delimter = true
			return -1
		} else {
			return c
		}
	}, rv.RawWord)

	start := strings.LastIndexAny(rv.Word, ";=") + 1

	if isTop(rv.Left, indexes) {
		rv.List, err = listUpCommands(this.Context, rv.Word[start:])
	} else {
		rv.List, err = listUpFiles(this.Context, rv.Word[start:])
	}

	for i := 0; i < len(rv.List); i++ {
		rv.List[i].InsertStr = rv.Word[:start] + rv.List[i].InsertStr
	}
	for _, f := range HookToList {
		rv, err = f(this, rv)
		if err != nil {
			break
		}
	}
	return rv, default_delimiter, err
}

func toComplete(source []Element) []string {
	result := make([]string, len(source))
	for key, val := range source {
		result[key] = val.InsertStr
	}
	return result
}

func toDisplay(source []Element) []string {
	result := make([]string, len(source))
	for key, val := range source {
		result[key] = val.ListupStr
	}
	return result
}

func KeyFuncCompletionList(this *readline.Buffer) readline.Result {
	comp, _, err := listUpComplete(this)
	if comp == nil {
		return readline.CONTINUE
	}
	fmt.Fprint(readline.Console, "\n")
	if err != nil {
		fmt.Fprintf(readline.Console, "(warning) %s\n", err.Error())
	}
	box.Print(this.Context, toDisplay(comp.List), readline.Console)
	this.RepaintAll()
	return readline.CONTINUE
}

func CommonPrefix(list []string) string {
	if len(list) < 1 {
		return ""
	}
	common := list[0]
	for _, f := range list[1:] {
		cr := strings.NewReader(common)
		fr := strings.NewReader(f)
		i := 0
		var buffer bytes.Buffer
		for {
			ch, _, cerr := cr.ReadRune()
			fh, _, ferr := fr.ReadRune()
			if cerr != nil || ferr != nil || unicode.ToUpper(ch) != unicode.ToUpper(fh) {
				break
			}
			buffer.WriteRune(ch)
			i++
		}
		common = buffer.String()
	}
	return common
}

func endWithRoot(path string) bool {
	return len(path) >= 1 && os.IsPathSeparator(path[len(path)-1])
}

func KeyFuncCompletion(this *readline.Buffer) readline.Result {
	comp, default_delimiter, err := listUpComplete(this)
	if comp.List == nil || len(comp.List) <= 0 {
		return readline.CONTINUE
	}

	slashToBackSlash := true
	firstFoundSlashPos := strings.IndexRune(comp.Word, '/')
	firstFoundBackSlashPos := strings.IndexRune(comp.Word, os.PathSeparator)
	if UseSlash {
		slashToBackSlash = false
		if firstFoundBackSlashPos >= 0 && (firstFoundSlashPos == -1 || firstFoundBackSlashPos < firstFoundSlashPos) {
			slashToBackSlash = true
		}
	} else {
		if firstFoundSlashPos >= 0 && (firstFoundBackSlashPos == -1 || firstFoundSlashPos < firstFoundBackSlashPos) {
			slashToBackSlash = false
		}
	}

	complete_list := toComplete(comp.List)
	commonStr := CommonPrefix(complete_list)
	quotechar := byte(0)
	if i := strings.IndexAny(comp.Word, readline.Delimiters); i >= 0 {
		quotechar = comp.Word[i]
	} else {
		for _, node := range complete_list {
			if strings.ContainsAny(node, " &!") {
				quotechar = byte(default_delimiter)
				break
			}
		}
	}
	if quotechar != 0 {
		buffer := make([]byte, 0, len(commonStr)+3)
		if len(commonStr) >= 2 && commonStr[0] == '~' && os.IsPathSeparator(commonStr[1]) {
			buffer = append(buffer, commonStr[:1]...)
			buffer = append(buffer, quotechar)
			buffer = append(buffer, commonStr[1:]...)
		} else {
			buffer = append(buffer, quotechar)
			buffer = append(buffer, commonStr...)
		}
		if len(comp.List) == 1 && !endWithRoot(comp.List[0].InsertStr) {
			buffer = append(buffer, quotechar)
		}
		commonStr = string(buffer)
	}
	if len(comp.List) == 1 && !endWithRoot(commonStr) && !strings.HasSuffix(commonStr, `%`) {
		commonStr += " "
	}
	if slashToBackSlash {
		commonStr = filepath.FromSlash(commonStr)
	}
	if comp.RawWord == commonStr {
		fmt.Fprint(readline.Console, "\n")
		if err != nil {
			fmt.Fprintf(readline.Console, "(warning) %s\n", err.Error())
		}
		box.Print(nil, toDisplay(comp.List), readline.Console)
		this.RepaintAll()
		return readline.CONTINUE
	}
	this.ReplaceAndRepaint(comp.Pos, commonStr)
	return readline.CONTINUE
}
<|endoftext|>


<commit_before>package bundler

import (
	"archive/zip"
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"

	"github.com/TykTechnologies/goverify"
	"github.com/TykTechnologies/tyk/apidef"
	logger "github.com/TykTechnologies/tyk/log"

	kingpin "gopkg.in/alecthomas/kingpin.v2"
)

const (
	cmdName = "bundle"
	cmdDesc = "Manage plugin bundles"

	defaultManifestPath = "manifest.json"
	defaultBundlePath   = "bundle.zip"
	defaultBundlePerm   = 0755
)

var (
	bundler *Bundler

	errNoHooks      = errors.New("No hooks defined")
	errNoDriver     = errors.New("No driver specified")
	errManifestLoad = errors.New("Couldn't load manifest file")
	errBundleData   = errors.New("Couldn't read/write bundle data")
	errBundleSign   = errors.New("Couldn't sign bundle")

	log = logger.Get().WithField("prefix", "tyk")
)

// Bundler wraps the bundler data structure.
type Bundler struct {
	keyPath      *string
	bundlePath   *string
	skipSigning  *bool
	manifestPath *string
}

func init() {
	bundler = &Bundler{}
}

// Bundle is the entrypoint function for this subcommand.
func (b *Bundler) Bundle(ctx *kingpin.ParseContext) error {
	return nil
}

// Build builds a bundle.
func (b *Bundler) Build(ctx *kingpin.ParseContext) error {
	manifestPath := *b.manifestPath
	bundlePath := *b.bundlePath
	skipSigning := *b.skipSigning
	key := *b.keyPath

	log.Infof("Building bundle using '%s'", manifestPath)
	manifest, err := b.loadManifest(manifestPath)
	if err != nil {
		return err
	}
	if bundlePath == defaultBundlePath {
		log.Warningf("Using default bundle path '%s'", defaultBundlePath)
	}

	// Write the file:
	bundleBuf := new(bytes.Buffer)
	for _, file := range manifest.FileList {
		var data []byte
		data, err = ioutil.ReadFile(file)
		if err != nil {
			break
		}
		bundleBuf.Write(data)
	}
	if err != nil {
		return err
	}

	// Compute the checksum and append it to the manifest data structure:
	manifest.Checksum = fmt.Sprintf("%x", md5.Sum(bundleBuf.Bytes()))

	if key == "" {
		if skipSigning {
			log.Warning("The bundle will be unsigned")
		} else {
			log.Warning("The bundle will be unsigned, type \"y\" or \"yes\" to confirm:")
			reader := bufio.NewReader(os.Stdin)
			text, _ := reader.ReadString('\n')
			ch := text[0:1]
			if ch != "y" {
				log.Fatal("Aborting")
				os.Exit(1)
			}
		}
	} else {
		err = b.sign(key, manifest, bundleBuf)
		if err != nil {
			return err
		}
	}

	manifestData, err := json.Marshal(&manifest)
	if err != nil {
		return err
	}

	// Write the ZIP contents into a buffer:
	buf := new(bytes.Buffer)
	zipWriter := zip.NewWriter(buf)
	for _, file := range manifest.FileList {
		var outputFile io.Writer
		outputFile, err = zipWriter.Create(file)
		if err != nil {
			break
		}
		var data []byte
		data, err = ioutil.ReadFile(file)
		if err != nil {
			break
		}
		if _, err = outputFile.Write(data); err != nil {
			break
		}
	}
	if err != nil {
		return err
	}

	// Append the updated manifest file to the ZIP file:
	newManifest, err := zipWriter.Create(defaultManifestPath)
	_, err = newManifest.Write(manifestData)
	zipWriter.Close()
	err = ioutil.WriteFile(bundlePath, buf.Bytes(), defaultBundlePerm)
	if err != nil {
		return err
	}
	log.Infof("Wrote '%s' (%d bytes)", bundlePath, buf.Len())
	return nil
}

func (b *Bundler) sign(key string, manifest *apidef.BundleManifest, bundle *bytes.Buffer) (err error) {
	signer, err := goverify.LoadPrivateKeyFromFile(key)
	if err != nil {
		return err
	}
	signed, err := signer.Sign(bundle.Bytes())
	if err != nil {
		return err
	}
	manifest.Signature = base64.StdEncoding.EncodeToString(signed)
	log.Infof("Signing bundle with key '%s'", key)
	return nil
}

func (b *Bundler) validateManifest(manifest *apidef.BundleManifest) (err error) {
	for _, f := range manifest.FileList {
		if _, err := os.Stat(f); err != nil {
			err = errors.New("Referencing a nonexistent file: " + f)
			return err
		}
	}

	// The custom middleware block must specify at least one hook:
	definedHooks := len(manifest.CustomMiddleware.Pre) + len(manifest.CustomMiddleware.Post) + len(manifest.CustomMiddleware.PostKeyAuth)

	// We should count the auth check middleware (single), if it's present:
	if manifest.CustomMiddleware.AuthCheck.Name != "" {
		definedHooks++
	}

	if definedHooks == 0 {
		return errNoHooks
	}

	// The custom middleware block must specify a driver:
	if manifest.CustomMiddleware.Driver == "" {
		return errNoDriver
	}

	return nil
}

func (b *Bundler) loadManifest(path string) (manifest *apidef.BundleManifest, err error) {
	rawManifest, err := ioutil.ReadFile(path)
	if err != nil {
		return manifest, errManifestLoad
	}
	err = json.Unmarshal(rawManifest, &manifest)
	if err != nil {
		return manifest, err
	}
	err = b.validateManifest(manifest)
	if err != nil {
		return manifest, err
	}
	return manifest, err
}

// AddTo initializes an importer object.
func AddTo(app *kingpin.Application) {
	cmd := app.Command(cmdName, cmdDesc)

	buildCmd := cmd.Command("build", "Build a new plugin bundle using a manifest file and its specified files")
	bundler.keyPath = buildCmd.Flag("key", "Key for bundle signature").Short('k').String()
	bundler.bundlePath = buildCmd.Flag("output", "Output file").Short('o').Default(defaultBundlePath).String()
	bundler.skipSigning = buildCmd.Flag("skip-signing", "Skip bundle signing").Short('y').Bool()
	bundler.manifestPath = buildCmd.Flag("manifest", "Path to manifest file").Default(defaultManifestPath).Short('m').String()
	buildCmd.Action(bundler.Build)
}
<commit_msg>cli: check response hooks when validating bundle manifest files (#3096)<commit_after>package bundler

import (
	"archive/zip"
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"

	"github.com/TykTechnologies/goverify"
	"github.com/TykTechnologies/tyk/apidef"
	logger "github.com/TykTechnologies/tyk/log"

	kingpin "gopkg.in/alecthomas/kingpin.v2"
)

const (
	cmdName = "bundle"
	cmdDesc = "Manage plugin bundles"

	defaultManifestPath = "manifest.json"
	defaultBundlePath   = "bundle.zip"
	defaultBundlePerm   = 0755
)

var (
	bundler *Bundler

	errNoHooks      = errors.New("No hooks defined")
	errNoDriver     = errors.New("No driver specified")
	errManifestLoad = errors.New("Couldn't load manifest file")
	errBundleData   = errors.New("Couldn't read/write bundle data")
	errBundleSign   = errors.New("Couldn't sign bundle")

	log = logger.Get().WithField("prefix", "tyk")
)

// Bundler wraps the bundler data structure.
type Bundler struct {
	keyPath      *string
	bundlePath   *string
	skipSigning  *bool
	manifestPath *string
}

func init() {
	bundler = &Bundler{}
}

// Bundle is the entrypoint function for this subcommand.
func (b *Bundler) Bundle(ctx *kingpin.ParseContext) error {
	return nil
}

// Build builds a bundle.
func (b *Bundler) Build(ctx *kingpin.ParseContext) error {
	manifestPath := *b.manifestPath
	bundlePath := *b.bundlePath
	skipSigning := *b.skipSigning
	key := *b.keyPath

	log.Infof("Building bundle using '%s'", manifestPath)
	manifest, err := b.loadManifest(manifestPath)
	if err != nil {
		return err
	}
	if bundlePath == defaultBundlePath {
		log.Warningf("Using default bundle path '%s'", defaultBundlePath)
	}

	// Write the file:
	bundleBuf := new(bytes.Buffer)
	for _, file := range manifest.FileList {
		var data []byte
		data, err = ioutil.ReadFile(file)
		if err != nil {
			break
		}
		bundleBuf.Write(data)
	}
	if err != nil {
		return err
	}

	// Compute the checksum and append it to the manifest data structure:
	manifest.Checksum = fmt.Sprintf("%x", md5.Sum(bundleBuf.Bytes()))

	if key == "" {
		if skipSigning {
			log.Warning("The bundle will be unsigned")
		} else {
			log.Warning("The bundle will be unsigned, type \"y\" or \"yes\" to confirm:")
			reader := bufio.NewReader(os.Stdin)
			text, _ := reader.ReadString('\n')
			ch := text[0:1]
			if ch != "y" {
				log.Fatal("Aborting")
				os.Exit(1)
			}
		}
	} else {
		err = b.sign(key, manifest, bundleBuf)
		if err != nil {
			return err
		}
	}

	manifestData, err := json.Marshal(&manifest)
	if err != nil {
		return err
	}

	// Write the ZIP contents into a buffer:
	buf := new(bytes.Buffer)
	zipWriter := zip.NewWriter(buf)
	for _, file := range manifest.FileList {
		var outputFile io.Writer
		outputFile, err = zipWriter.Create(file)
		if err != nil {
			break
		}
		var data []byte
		data, err = ioutil.ReadFile(file)
		if err != nil {
			break
		}
		if _, err = outputFile.Write(data); err != nil {
			break
		}
	}
	if err != nil {
		return err
	}

	// Append the updated manifest file to the ZIP file:
	newManifest, err := zipWriter.Create(defaultManifestPath)
	_, err = newManifest.Write(manifestData)
	zipWriter.Close()
	err = ioutil.WriteFile(bundlePath, buf.Bytes(), defaultBundlePerm)
	if err != nil {
		return err
	}
	log.Infof("Wrote '%s' (%d bytes)", bundlePath, buf.Len())
	return nil
}

func (b *Bundler) sign(key string, manifest *apidef.BundleManifest, bundle *bytes.Buffer) (err error) {
	signer, err := goverify.LoadPrivateKeyFromFile(key)
	if err != nil {
		return err
	}
	signed, err := signer.Sign(bundle.Bytes())
	if err != nil {
		return err
	}
	manifest.Signature = base64.StdEncoding.EncodeToString(signed)
	log.Infof("Signing bundle with key '%s'", key)
	return nil
}

func (b *Bundler) validateManifest(manifest *apidef.BundleManifest) (err error) {
	for _, f := range manifest.FileList {
		if _, err := os.Stat(f); err != nil {
			err = errors.New("Referencing a nonexistent file: " + f)
			return err
		}
	}

	// The custom middleware block must specify at least one hook:
	definedHooks := len(manifest.CustomMiddleware.Pre) + len(manifest.CustomMiddleware.Post) + len(manifest.CustomMiddleware.PostKeyAuth) + len(manifest.CustomMiddleware.Response)

	// We should count the auth check middleware (single), if it's present:
	if manifest.CustomMiddleware.AuthCheck.Name != "" {
		definedHooks++
	}

	if definedHooks == 0 {
		return errNoHooks
	}

	// The custom middleware block must specify a driver:
	if manifest.CustomMiddleware.Driver == "" {
		return errNoDriver
	}

	return nil
}

func (b *Bundler) loadManifest(path string) (manifest *apidef.BundleManifest, err error) {
	rawManifest, err := ioutil.ReadFile(path)
	if err != nil {
		return manifest, errManifestLoad
	}
	err = json.Unmarshal(rawManifest, &manifest)
	if err != nil {
		return manifest, err
	}
	err = b.validateManifest(manifest)
	if err != nil {
		return manifest, err
	}
	return manifest, err
}

// AddTo initializes an importer object.
func AddTo(app *kingpin.Application) {
	cmd := app.Command(cmdName, cmdDesc)

	buildCmd := cmd.Command("build", "Build a new plugin bundle using a manifest file and its specified files")
	bundler.keyPath = buildCmd.Flag("key", "Key for bundle signature").Short('k').String()
	bundler.bundlePath = buildCmd.Flag("output", "Output file").Short('o').Default(defaultBundlePath).String()
	bundler.skipSigning = buildCmd.Flag("skip-signing", "Skip bundle signing").Short('y').Bool()
	bundler.manifestPath = buildCmd.Flag("manifest", "Path to manifest file").Default(defaultManifestPath).Short('m').String()
	buildCmd.Action(bundler.Build)
}
<|endoftext|>