<commit_before>package cli

import (
	"fmt"
	"io/ioutil"
	"sort"
	"strings"
)

// Command is a subcommand for a cli.App.
type Command struct {
	// The name of the command
	Name string
	// short name of the command. Typically one character (deprecated, use `Aliases`)
	ShortName string
	// A list of aliases for the command
	Aliases []string
	// A short description of the usage of this command
	Usage string
	// Custom text to show on USAGE section of help
	UsageText string
	// A longer explanation of how the command works
	Description string
	// A short description of the arguments of this command
	ArgsUsage string
	// The category the command is part of
	Category string
	// The function to call when checking for bash command completions
	BashComplete BashCompleteFunc
	// An action to execute before any sub-subcommands are run, but after the context is ready
	// If a non-nil error is returned, no sub-subcommands are run
	Before BeforeFunc
	// An action to execute after any subcommands are run, but after the subcommand has finished
	// It is run even if Action() panics
	After AfterFunc
	// The function to call when this command is invoked
	Action interface{}
	// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
	// of deprecation period has passed, maybe?

	// Execute this function if a usage error occurs.
	OnUsageError OnUsageErrorFunc
	// List of child commands
	Subcommands Commands
	// List of flags to parse
	Flags []Flag
	// Treat all flags as normal arguments if true
	SkipFlagParsing bool
	// Skip argument reordering which attempts to move flags before arguments,
	// but only works if all flags appear after all arguments. This behavior was
	// removed n version 2 since it only works under specific conditions so we
	// backport here by exposing it as an option for compatibility.
	SkipArgReorder bool
	// Boolean to hide built-in help command
	HideHelp bool
	// Boolean to hide this command from help or completion
	Hidden bool
	// Boolean to enable short-option handling so user can combine several
	// single-character bool arguements into one
	// i.e. foobar -o -v -> foobar -ov
	UseShortOptionHandling bool

	// Full name of command for help, defaults to full command name, including parent commands.
	HelpName        string
	commandNamePath []string

	// CustomHelpTemplate the text template for the command help topic.
	// cli.go uses text/template to render templates. You can
	// render custom help text by setting this variable.
	CustomHelpTemplate string
}

type CommandsByName []Command

func (c CommandsByName) Len() int {
	return len(c)
}

func (c CommandsByName) Less(i, j int) bool {
	return lexicographicLess(c[i].Name, c[j].Name)
}

func (c CommandsByName) Swap(i, j int) {
	c[i], c[j] = c[j], c[i]
}

// FullName returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string {
	if c.commandNamePath == nil {
		return c.Name
	}
	return strings.Join(c.commandNamePath, " ")
}

// Commands is a slice of Command
type Commands []Command

// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) (err error) {
	if len(c.Subcommands) > 0 {
		return c.startApp(ctx)
	}

	if !c.HideHelp && (HelpFlag != BoolFlag{}) {
		// append help to flags
		c.Flags = append(
			c.Flags,
			HelpFlag,
		)
	}

	set, err := flagSet(c.Name, c.Flags)
	if err != nil {
		return err
	}
	set.SetOutput(ioutil.Discard)
	firstFlagIndex, terminatorIndex := getIndexes(ctx)
	flagArgs, regularArgs := getAllArgs(ctx.Args(), firstFlagIndex, terminatorIndex)
	if c.UseShortOptionHandling {
		flagArgs = translateShortOptions(flagArgs)
	}
	if c.SkipFlagParsing {
		err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
	} else if !c.SkipArgReorder {
		if firstFlagIndex > -1 {
			err = set.Parse(append(flagArgs, regularArgs...))
		} else {
			err = set.Parse(ctx.Args().Tail())
		}
	} else if c.UseShortOptionHandling {
		if terminatorIndex == -1 && firstFlagIndex > -1 {
			// Handle shortname AND no options
			err = set.Parse(append(regularArgs, flagArgs...))
		} else {
			// Handle shortname and options
			err = set.Parse(flagArgs)
		}
	} else {
		err = set.Parse(append(regularArgs, flagArgs...))
	}

	nerr := normalizeFlags(c.Flags, set)
	if nerr != nil {
		fmt.Fprintln(ctx.App.Writer, nerr)
		fmt.Fprintln(ctx.App.Writer)
		ShowCommandHelp(ctx, c.Name)
		return nerr
	}

	context := NewContext(ctx.App, set, ctx)
	context.Command = c
	if checkCommandCompletions(context, c.Name) {
		return nil
	}

	if err != nil {
		if c.OnUsageError != nil {
			err := c.OnUsageError(context, err, false)
			context.App.handleExitCoder(context, err)
			return err
		}
		fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
		fmt.Fprintln(context.App.Writer)
		ShowCommandHelp(context, c.Name)
		return err
	}

	if checkCommandHelp(context, c.Name) {
		return nil
	}

	if c.After != nil {
		defer func() {
			afterErr := c.After(context)
			if afterErr != nil {
				context.App.handleExitCoder(context, err)
				if err != nil {
					err = NewMultiError(err, afterErr)
				} else {
					err = afterErr
				}
			}
		}()
	}

	if c.Before != nil {
		err = c.Before(context)
		if err != nil {
			ShowCommandHelp(context, c.Name)
			context.App.handleExitCoder(context, err)
			return err
		}
	}

	if c.Action == nil {
		c.Action = helpSubcommand.Action
	}

	err = HandleAction(c.Action, context)

	if err != nil {
		context.App.handleExitCoder(context, err)
	}
	return err
}

func getIndexes(ctx *Context) (int, int) {
	firstFlagIndex := -1
	terminatorIndex := -1
	for index, arg := range ctx.Args() {
		if arg == "--" {
			terminatorIndex = index
			break
		} else if arg == "-" {
			// Do nothing. A dash alone is not really a flag.
			continue
		} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
			firstFlagIndex = index
		}
	}
	if len(ctx.Args()) > 0 && !strings.HasPrefix(ctx.Args()[0], "-") && firstFlagIndex == -1 {
		return -1, -1
	}

	return firstFlagIndex, terminatorIndex

}

// copyStringslice takes a string slice and copies it
func copyStringSlice(slice []string, start, end int) []string {
	newSlice := make([]string, end-start)
	copy(newSlice, slice[start:end])
	return newSlice
}

// getAllArgs extracts and returns two string slices representing
// regularArgs and flagArgs
func getAllArgs(args []string, firstFlagIndex, terminatorIndex int) ([]string, []string) {
	var regularArgs []string
	// if there are no options, the we set the index to 1 manually
	if firstFlagIndex == -1 {
		firstFlagIndex = 1
		regularArgs = copyStringSlice(args, 0, len(args))
	} else {
		regularArgs = copyStringSlice(args, 1, firstFlagIndex)
	}
	var flagArgs []string
	// a flag terminatorIndex was found in the input. we need to collect
	// flagArgs based on it.
	if terminatorIndex > -1 {
		flagArgs = copyStringSlice(args, firstFlagIndex, terminatorIndex)
		additionalRegularArgs := copyStringSlice(args, terminatorIndex, len(args))
		regularArgs = append(regularArgs, additionalRegularArgs...)
		for _, i := range additionalRegularArgs {
			regularArgs = append(regularArgs, i)
		}
	} else {
		flagArgs = args[firstFlagIndex:]
	}
	return flagArgs, regularArgs
}

func translateShortOptions(flagArgs Args) []string {
	// separate combined flags
	var flagArgsSeparated []string
	for _, flagArg := range flagArgs {
		if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 {
			for _, flagChar := range flagArg[1:] {
				flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar))
			}
		} else {
			flagArgsSeparated = append(flagArgsSeparated, flagArg)
		}
	}
	return flagArgsSeparated
}

// Names returns the names including short names and aliases.
func (c Command) Names() []string {
	names := []string{c.Name}

	if c.ShortName != "" {
		names = append(names, c.ShortName)
	}

	return append(names, c.Aliases...)
}

// HasName returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool {
	for _, n := range c.Names() {
		if n == name {
			return true
		}
	}
	return false
}

func (c Command) startApp(ctx *Context) error {
	app := NewApp()
	app.Metadata = ctx.App.Metadata
	// set the name and usage
	app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
	if c.HelpName == "" {
		app.HelpName = c.HelpName
	} else {
		app.HelpName = app.Name
	}

	app.Usage = c.Usage
	app.Description = c.Description
	app.ArgsUsage = c.ArgsUsage

	// set CommandNotFound
	app.CommandNotFound = ctx.App.CommandNotFound
	app.CustomAppHelpTemplate = c.CustomHelpTemplate

	// set the flags and commands
	app.Commands = c.Subcommands
	app.Flags = c.Flags
	app.HideHelp = c.HideHelp

	app.Version = ctx.App.Version
	app.HideVersion = ctx.App.HideVersion
	app.Compiled = ctx.App.Compiled
	app.Author = ctx.App.Author
	app.Email = ctx.App.Email
	app.Writer = ctx.App.Writer
	app.ErrWriter = ctx.App.ErrWriter

	app.categories = CommandCategories{}
	for _, command := range c.Subcommands {
		app.categories = app.categories.AddCommand(command.Category, command)
	}

	sort.Sort(app.categories)

	// bash completion
	app.EnableBashCompletion = ctx.App.EnableBashCompletion
	if c.BashComplete != nil {
		app.BashComplete = c.BashComplete
	}

	// set the actions
	app.Before = c.Before
	app.After = c.After
	if c.Action != nil {
		app.Action = c.Action
	} else {
		app.Action = helpSubcommand.Action
	}
	app.OnUsageError = c.OnUsageError

	for index, cc := range app.Commands {
		app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
	}

	return app.RunAsSubcommand(ctx)
}

// VisibleFlags returns a slice of the Flags with Hidden=false
func (c Command) VisibleFlags() []Flag {
	return visibleFlags(c.Flags)
}
<commit_msg>Refactor flag handling logic<commit_after>package cli

import (
	"flag"
	"fmt"
	"io/ioutil"
	"sort"
	"strings"
)

// Command is a subcommand for a cli.App.
type Command struct {
	// The name of the command
	Name string
	// short name of the command. Typically one character (deprecated, use `Aliases`)
	ShortName string
	// A list of aliases for the command
	Aliases []string
	// A short description of the usage of this command
	Usage string
	// Custom text to show on USAGE section of help
	UsageText string
	// A longer explanation of how the command works
	Description string
	// A short description of the arguments of this command
	ArgsUsage string
	// The category the command is part of
	Category string
	// The function to call when checking for bash command completions
	BashComplete BashCompleteFunc
	// An action to execute before any sub-subcommands are run, but after the context is ready
	// If a non-nil error is returned, no sub-subcommands are run
	Before BeforeFunc
	// An action to execute after any subcommands are run, but after the subcommand has finished
	// It is run even if Action() panics
	After AfterFunc
	// The function to call when this command is invoked
	Action interface{}
	// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
	// of deprecation period has passed, maybe?

	// Execute this function if a usage error occurs.
	OnUsageError OnUsageErrorFunc
	// List of child commands
	Subcommands Commands
	// List of flags to parse
	Flags []Flag
	// Treat all flags as normal arguments if true
	SkipFlagParsing bool
	// Skip argument reordering which attempts to move flags before arguments,
	// but only works if all flags appear after all arguments. This behavior was
	// removed n version 2 since it only works under specific conditions so we
	// backport here by exposing it as an option for compatibility.
	SkipArgReorder bool
	// Boolean to hide built-in help command
	HideHelp bool
	// Boolean to hide this command from help or completion
	Hidden bool
	// Boolean to enable short-option handling so user can combine several
	// single-character bool arguements into one
	// i.e. foobar -o -v -> foobar -ov
	UseShortOptionHandling bool

	// Full name of command for help, defaults to full command name, including parent commands.
	HelpName        string
	commandNamePath []string

	// CustomHelpTemplate the text template for the command help topic.
	// cli.go uses text/template to render templates. You can
	// render custom help text by setting this variable.
	CustomHelpTemplate string
}

type CommandsByName []Command

func (c CommandsByName) Len() int {
	return len(c)
}

func (c CommandsByName) Less(i, j int) bool {
	return lexicographicLess(c[i].Name, c[j].Name)
}

func (c CommandsByName) Swap(i, j int) {
	c[i], c[j] = c[j], c[i]
}

// FullName returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string {
	if c.commandNamePath == nil {
		return c.Name
	}
	return strings.Join(c.commandNamePath, " ")
}

// Commands is a slice of Command
type Commands []Command

// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) (err error) {
	if len(c.Subcommands) > 0 {
		return c.startApp(ctx)
	}

	if !c.HideHelp && (HelpFlag != BoolFlag{}) {
		// append help to flags
		c.Flags = append(
			c.Flags,
			HelpFlag,
		)
	}

	set, err := c.parseFlags(ctx.Args().Tail())

	context := NewContext(ctx.App, set, ctx)
	context.Command = c
	if checkCommandCompletions(context, c.Name) {
		return nil
	}

	if err != nil {
		if c.OnUsageError != nil {
			err := c.OnUsageError(context, err, false)
			context.App.handleExitCoder(context, err)
			return err
		}
		fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
		fmt.Fprintln(context.App.Writer)
		ShowCommandHelp(context, c.Name)
		return err
	}

	if checkCommandHelp(context, c.Name) {
		return nil
	}

	if c.After != nil {
		defer func() {
			afterErr := c.After(context)
			if afterErr != nil {
				context.App.handleExitCoder(context, err)
				if err != nil {
					err = NewMultiError(err, afterErr)
				} else {
					err = afterErr
				}
			}
		}()
	}

	if c.Before != nil {
		err = c.Before(context)
		if err != nil {
			ShowCommandHelp(context, c.Name)
			context.App.handleExitCoder(context, err)
			return err
		}
	}

	if c.Action == nil {
		c.Action = helpSubcommand.Action
	}

	err = HandleAction(c.Action, context)

	if err != nil {
		context.App.handleExitCoder(context, err)
	}
	return err
}

func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
	set, err := flagSet(c.Name, c.Flags)
	if err != nil {
		return nil, err
	}
	set.SetOutput(ioutil.Discard)

	if c.SkipFlagParsing {
		return set, set.Parse(append([]string{c.Name, "--"}, args...))
	}

	if c.UseShortOptionHandling {
		args = translateShortOptions(args)
	}

	if !c.SkipArgReorder {
		args = reorderArgs(args)
	}

	err = set.Parse(args)
	if err != nil {
		return nil, err
	}

	err = normalizeFlags(c.Flags, set)
	if err != nil {
		return nil, err
	}

	return set, nil
}

// reorderArgs moves all flags before arguments as this is what flag expects
func reorderArgs(args []string) []string {
	var nonflags, flags []string

	readFlagValue := false
	for i, arg := range args {
		if arg == "--" {
			nonflags = append(nonflags, args[i:]...)
			break
		}

		if readFlagValue {
			readFlagValue = false
			flags = append(flags, arg)
			continue
		}

		if arg != "-" && strings.HasPrefix(arg, "-") {
			flags = append(flags, arg)

			readFlagValue = !strings.Contains(arg, "=")
		} else {
			nonflags = append(nonflags, arg)
		}
	}

	return append(flags, nonflags...)
}

func translateShortOptions(flagArgs Args) []string {
	// separate combined flags
	var flagArgsSeparated []string
	for _, flagArg := range flagArgs {
		if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 {
			for _, flagChar := range flagArg[1:] {
				flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar))
			}
		} else {
			flagArgsSeparated = append(flagArgsSeparated, flagArg)
		}
	}
	return flagArgsSeparated
}

// Names returns the names including short names and aliases.
func (c Command) Names() []string {
	names := []string{c.Name}

	if c.ShortName != "" {
		names = append(names, c.ShortName)
	}

	return append(names, c.Aliases...)
}

// HasName returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool {
	for _, n := range c.Names() {
		if n == name {
			return true
		}
	}
	return false
}

func (c Command) startApp(ctx *Context) error {
	app := NewApp()
	app.Metadata = ctx.App.Metadata
	// set the name and usage
	app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
	if c.HelpName == "" {
		app.HelpName = c.HelpName
	} else {
		app.HelpName = app.Name
	}

	app.Usage = c.Usage
	app.Description = c.Description
	app.ArgsUsage = c.ArgsUsage

	// set CommandNotFound
	app.CommandNotFound = ctx.App.CommandNotFound
	app.CustomAppHelpTemplate = c.CustomHelpTemplate

	// set the flags and commands
	app.Commands = c.Subcommands
	app.Flags = c.Flags
	app.HideHelp = c.HideHelp

	app.Version = ctx.App.Version
	app.HideVersion = ctx.App.HideVersion
	app.Compiled = ctx.App.Compiled
	app.Author = ctx.App.Author
	app.Email = ctx.App.Email
	app.Writer = ctx.App.Writer
	app.ErrWriter = ctx.App.ErrWriter

	app.categories = CommandCategories{}
	for _, command := range c.Subcommands {
		app.categories = app.categories.AddCommand(command.Category, command)
	}

	sort.Sort(app.categories)

	// bash completion
	app.EnableBashCompletion = ctx.App.EnableBashCompletion
	if c.BashComplete != nil {
		app.BashComplete = c.BashComplete
	}

	// set the actions
	app.Before = c.Before
	app.After = c.After
	if c.Action != nil {
		app.Action = c.Action
	} else {
		app.Action = helpSubcommand.Action
	}
	app.OnUsageError = c.OnUsageError

	for index, cc := range app.Commands {
		app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
	}

	return app.RunAsSubcommand(ctx)
}

// VisibleFlags returns a slice of the Flags with Hidden=false
func (c Command) VisibleFlags() []Flag {
	return visibleFlags(c.Flags)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"net"
	"os"
	"net/http"
  "os/signal"
  "syscall"
	"flag"
)

var msg = "Dr. Who say's alonzi\n"
var anothermsg = "Winter is in Season 10\n"

type server struct {
	httpServer *http.Server
	listener   net.Listener
}

func (s *server) listenAndServe() error {

	listener, err := net.Listen("tcp", s.httpServer.Addr)
	if err != nil {
		return err
	}
	s.listener = listener
	go s.httpServer.Serve(s.listener)
	fmt.Println("Server now listening")
	return nil

}

func (s *server) shutdown() error {

	if s.listener != nil {
		err := s.listener.Close()
		s.listener = nil
		if err != nil {
			return err
		}
	}
	fmt.Println("Shutting down server")
	return nil

}

func newServer(port string) *server {

	handle := func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, msg)
	}
	anotherhandle := func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, anothermsg)
	}
	mux := http.NewServeMux()
	mux.HandleFunc("/", handle)
	mux.HandleFunc("/another", anotherhandle)

	httpServer := &http.Server{Addr: ":" + port, Handler: mux}
	return &server{httpServer: httpServer}

}

func testRig(f func()) {
	server := newServer("3333")
	server.listenAndServe()
	defer server.shutdown()
	f()
}

func main() {
	// Let's get port as a commandline input
	var port string
  flag.StringVar(&port, "port", "3333", "./simpleserver_3 -port 3333")
	flag.Parse()

	// a channel to receive unix signals
  sigs := make(chan os.Signal, 1)
	// a channel to receive a finito confirmation on interrupt
  done := make(chan bool, 1)

	// signal.Notify is a method to create a channel which receives
	// SIGINT, SIGTERM unix signals.
  signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

	moveAlong := func() {
		fmt.Println("Not the droid you lookin for...")
	}

	server := newServer(port)
	server.listenAndServe()
	defer moveAlong()

	// Another goroutine! This one blocks itself waiting for signal to be received
	// As soon as it receives a signal, it sends a 'true' to 'done' which unblocks too
	// and the program exits.
	go func() {
        sig := <-sigs
        fmt.Println()
        fmt.Println(sig)
				// this is graceful
				server.shutdown()
        done <- true
    }()

	// Ctrl-C sends a SIGINT signal to the program
	fmt.Println("Ctrl-C to interrupt...")
	<-done
	fmt.Println("Exiting...")

	// cd simpleserver_3
	// go build .
	// ./simpleserver_3 -port 4444
}
<commit_msg>Ran gofmt -w . in simpleserver_3<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
)

var msg = "Dr. Who say's alonzi\n"
var anothermsg = "Winter is in Season 10\n"

type server struct {
	httpServer *http.Server
	listener   net.Listener
}

func (s *server) listenAndServe() error {

	listener, err := net.Listen("tcp", s.httpServer.Addr)
	if err != nil {
		return err
	}
	s.listener = listener
	go s.httpServer.Serve(s.listener)
	fmt.Println("Server now listening")
	return nil

}

func (s *server) shutdown() error {

	if s.listener != nil {
		err := s.listener.Close()
		s.listener = nil
		if err != nil {
			return err
		}
	}
	fmt.Println("Shutting down server")
	return nil

}

func newServer(port string) *server {

	handle := func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, msg)
	}
	anotherhandle := func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, anothermsg)
	}
	mux := http.NewServeMux()
	mux.HandleFunc("/", handle)
	mux.HandleFunc("/another", anotherhandle)

	httpServer := &http.Server{Addr: ":" + port, Handler: mux}
	return &server{httpServer: httpServer}

}

func testRig(f func()) {
	server := newServer("3333")
	server.listenAndServe()
	defer server.shutdown()
	f()
}

func main() {
	// Let's get port as a commandline input
	var port string
	flag.StringVar(&port, "port", "3333", "./simpleserver_3 -port 3333")
	flag.Parse()

	// a channel to receive unix signals
	sigs := make(chan os.Signal, 1)
	// a channel to receive a finito confirmation on interrupt
	done := make(chan bool, 1)

	// signal.Notify is a method to create a channel which receives
	// SIGINT, SIGTERM unix signals.
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

	moveAlong := func() {
		fmt.Println("Not the droid you lookin for...")
	}

	server := newServer(port)
	server.listenAndServe()
	defer moveAlong()

	// Another goroutine! This one blocks itself waiting for signal to be received
	// As soon as it receives a signal, it sends a 'true' to 'done' which unblocks too
	// and the program exits.
	go func() {
		sig := <-sigs
		fmt.Println()
		fmt.Println(sig)
		// this is graceful
		server.shutdown()
		done <- true
	}()

	// Ctrl-C sends a SIGINT signal to the program
	fmt.Println("Ctrl-C to interrupt...")
	<-done
	fmt.Println("Exiting...")

	// cd simpleserver_3
	// go build .
	// ./simpleserver_3 -port 4444
}
<|endoftext|>


<commit_before>// Copyright 2013 Matthew Baird
// 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 elastigo

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

// Search performs a very basic search on an index via the request URI API.
//
// params:
//   @index:  the elasticsearch index
//   @_type:  optional ("" if not used) search specific type in this index
//   @args:   a map of URL parameters. Allows all the URI-request parameters allowed by ElasticSearch.
//   @query:  this can be one of 3 types:
//              1)  string value that is valid elasticsearch
//              2)  io.Reader that can be set in body (also valid elasticsearch string syntax..)
//              3)  other type marshalable to json (also valid elasticsearch json)
//
//   out, err := Search(true, "github", map[string]interface{} {"from" : 10}, qryType)
//
// http://www.elasticsearch.org/guide/reference/api/search/uri-request.html
func (c *Conn) Search(index string, _type string, args map[string]interface{}, query interface{}) (SearchResult, error) {
	var uriVal string
	var retval SearchResult
	if len(_type) > 0 && _type != "*" {
		uriVal = fmt.Sprintf("/%s/%s/_search", index, _type)
	} else {
		uriVal = fmt.Sprintf("/%s/_search", index)
	}
	body, err := c.DoCommand("POST", uriVal, args, query)
	if err != nil {
		return retval, err
	}
	if err == nil {
		// marshall into json
		jsonErr := json.Unmarshal([]byte(body), &retval)
		if jsonErr != nil {
			return retval, jsonErr
		}
	}
	retval.RawJSON = body
	return retval, err
}

func (c *Conn) Suggest(index string, args map[string]interface{}, query interface{}) (SuggestResults, error) {
	uriVal := fmt.Sprintf("/%s/_suggest", index)
	body, err := c.DoCommand("POST", uriVal, args, query)
	var retval SuggestResults
	if err != nil {
		return retval, err
	}
	jsonErr := json.Unmarshal([]byte(body), &retval.body)
	if jsonErr != nil {
		return retval, jsonErr
	}
	shards := retval.body["_shards"]
	if shards == nil {
		return retval, fmt.Errorf("Expect response to contain _shards field, got: %s", body)
	}
	jsonErr = json.Unmarshal(shards, &retval.ShardStatus)
	if jsonErr != nil {
		return retval, jsonErr
	}
	if len(retval.ShardStatus.Failures) > 0 {
		return retval, fmt.Errorf("Got the following errors:\n%s", failures(retval.ShardStatus.Failures))
	}
	return retval, nil
}

type SuggestResults struct {
	body        map[string]json.RawMessage
	ShardStatus Status
}

func (s SuggestResults) Result(suggestName string) ([]Suggestion, error) {
	var suggestions []Suggestion
	query := s.body[suggestName]
	if query == nil {
		return nil, fmt.Errorf("No such suggest name found")
	}
	err := json.Unmarshal(query, &suggestions)
	if err != nil {
		return nil, err
	}
	return suggestions, nil
}

// SearchUri performs the simplest possible query in url string
// params:
//   @index:  the elasticsearch index
//   @_type:  optional ("" if not used) search specific type in this index
//   @args: a map of URL parameters. Most important one is q
//
//   out, err := SearchUri("github","", map[string]interface{} { "q" : `user:kimchy`})
//
// produces a request like this:    host:9200/github/_search?q=user:kimchy"
//
// http://www.elasticsearch.org/guide/reference/api/search/uri-request.html
func (c *Conn) SearchUri(index, _type string, args map[string]interface{}) (SearchResult, error) {
	var uriVal string
	var retval SearchResult
	if len(_type) > 0 && _type != "*" {
		uriVal = fmt.Sprintf("/%s/%s/_search", index, _type)
	} else {
		uriVal = fmt.Sprintf("/%s/_search", index)
	}
	//log.Println(uriVal)
	body, err := c.DoCommand("GET", uriVal, args, nil)
	if err != nil {
		return retval, err
	}
	if err == nil {
		// marshall into json
		jsonErr := json.Unmarshal([]byte(body), &retval)
		if jsonErr != nil {
			return retval, jsonErr
		}
	}
	retval.RawJSON = body
	return retval, err
}

func (c *Conn) Scroll(args map[string]interface{}, scroll_id string) (SearchResult, error) {
	var url string
	var retval SearchResult

	if _, ok := args["scroll"]; !ok {
		return retval, fmt.Errorf("Cannot call scroll without 'scroll' in arguments")
	}

	url = "/_search/scroll"

	body, err := c.DoCommand("POST", url, args, scroll_id)
	if err != nil {
		return retval, err
	}
	if err == nil {
		// marshall into json
		jsonErr := json.Unmarshal([]byte(body), &retval)
		if jsonErr != nil {
			return retval, jsonErr
		}
	}
	return retval, err
}

type SuggestionOption struct {
	Payload json.RawMessage `json:"payload"`
	Score   Float32Nullable `json:"score,omitempty"`
	Text    string          `json:"text"`
}

type Suggestion struct {
	Length  int                `json:"length"`
	Offset  int                `json:"offset"`
	Options []SuggestionOption `json:"options"`
	Text    string             `json:"text"`
}

type Suggestions map[string][]Suggestion

type SearchResult struct {
	RawJSON      []byte
	Took         int             `json:"took"`
	TimedOut     bool            `json:"timed_out"`
	ShardStatus  Status          `json:"_shards"`
	Hits         Hits            `json:"hits"`
	Facets       json.RawMessage `json:"facets,omitempty"` // structure varies on query
	ScrollId     string          `json:"_scroll_id,omitempty"`
	Aggregations json.RawMessage `json:"aggregations,omitempty"` // structure varies on query
	Suggestions  Suggestions     `json:"suggest,omitempty"`
}

func (s *SearchResult) String() string {
	return fmt.Sprintf("<Results took=%v Timeout=%v hitct=%v />", s.Took, s.TimedOut, s.Hits.Total)
}

type Hits struct {
	Total int `json:"total"`
	//	MaxScore float32 `json:"max_score"`
	Hits []Hit `json:"hits"`
}

func (h *Hits) Len() int {
	return len(h.Hits)
}

type Highlight map[string][]string

type Hit struct {
	Index       string           `json:"_index"`
	Type        string           `json:"_type,omitempty"`
	Id          string           `json:"_id"`
	Score       Float32Nullable  `json:"_score,omitempty"` // Filters (no query) dont have score, so is null
	Source      *json.RawMessage `json:"_source"`          // marshalling left to consumer
	Fields      *json.RawMessage `json:"fields"`           // when a field arg is passed to ES, instead of _source it returns fields
	Explanation *Explanation     `json:"_explanation,omitempty"`
	Highlight   *Highlight       `json:"highlight,omitempty"`
}

func (e *Explanation) String(indent string) string {
	if len(e.Details) == 0 {
		return fmt.Sprintf("%s>>>  %v = %s", indent, e.Value, strings.Replace(e.Description, "\n", "", -1))
	} else {
		detailStrs := make([]string, 0)
		for _, detail := range e.Details {
			detailStrs = append(detailStrs, fmt.Sprintf("%s", detail.String(indent+"| ")))
		}
		return fmt.Sprintf("%s%v = %s(\n%s\n%s)", indent, e.Value, strings.Replace(e.Description, "\n", "", -1), strings.Join(detailStrs, "\n"), indent)
	}
}

// Elasticsearch returns some invalid (according to go) json, with floats having...
//
// json: cannot unmarshal null into Go value of type float32 (see last field.)
//
// "hits":{"total":6808,"max_score":null,
//    "hits":[{"_index":"10user","_type":"user","_id":"751820","_score":null,
type Float32Nullable float32

func (i *Float32Nullable) UnmarshalJSON(data []byte) error {
	if len(data) == 0 || string(data) == "null" {
		return nil
	}

	if in, err := strconv.ParseFloat(string(data), 32); err != nil {
		return err
	} else {
		*i = Float32Nullable(in)
	}
	return nil
}
<commit_msg>Update scroll usage<commit_after>// Copyright 2013 Matthew Baird
// 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 elastigo

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

// Search performs a very basic search on an index via the request URI API.
//
// params:
//   @index:  the elasticsearch index
//   @_type:  optional ("" if not used) search specific type in this index
//   @args:   a map of URL parameters. Allows all the URI-request parameters allowed by ElasticSearch.
//   @query:  this can be one of 3 types:
//              1)  string value that is valid elasticsearch
//              2)  io.Reader that can be set in body (also valid elasticsearch string syntax..)
//              3)  other type marshalable to json (also valid elasticsearch json)
//
//   out, err := Search(true, "github", map[string]interface{} {"from" : 10}, qryType)
//
// http://www.elasticsearch.org/guide/reference/api/search/uri-request.html
func (c *Conn) Search(index string, _type string, args map[string]interface{}, query interface{}) (SearchResult, error) {
	var uriVal string
	var retval SearchResult
	if len(_type) > 0 && _type != "*" {
		uriVal = fmt.Sprintf("/%s/%s/_search", index, _type)
	} else {
		uriVal = fmt.Sprintf("/%s/_search", index)
	}
	body, err := c.DoCommand("POST", uriVal, args, query)
	if err != nil {
		return retval, err
	}
	if err == nil {
		// marshall into json
		jsonErr := json.Unmarshal([]byte(body), &retval)
		if jsonErr != nil {
			return retval, jsonErr
		}
	}
	retval.RawJSON = body
	return retval, err
}

func (c *Conn) Suggest(index string, args map[string]interface{}, query interface{}) (SuggestResults, error) {
	uriVal := fmt.Sprintf("/%s/_suggest", index)
	body, err := c.DoCommand("POST", uriVal, args, query)
	var retval SuggestResults
	if err != nil {
		return retval, err
	}
	jsonErr := json.Unmarshal([]byte(body), &retval.body)
	if jsonErr != nil {
		return retval, jsonErr
	}
	shards := retval.body["_shards"]
	if shards == nil {
		return retval, fmt.Errorf("Expect response to contain _shards field, got: %s", body)
	}
	jsonErr = json.Unmarshal(shards, &retval.ShardStatus)
	if jsonErr != nil {
		return retval, jsonErr
	}
	if len(retval.ShardStatus.Failures) > 0 {
		return retval, fmt.Errorf("Got the following errors:\n%s", failures(retval.ShardStatus.Failures))
	}
	return retval, nil
}

type SuggestResults struct {
	body        map[string]json.RawMessage
	ShardStatus Status
}

func (s SuggestResults) Result(suggestName string) ([]Suggestion, error) {
	var suggestions []Suggestion
	query := s.body[suggestName]
	if query == nil {
		return nil, fmt.Errorf("No such suggest name found")
	}
	err := json.Unmarshal(query, &suggestions)
	if err != nil {
		return nil, err
	}
	return suggestions, nil
}

// SearchUri performs the simplest possible query in url string
// params:
//   @index:  the elasticsearch index
//   @_type:  optional ("" if not used) search specific type in this index
//   @args: a map of URL parameters. Most important one is q
//
//   out, err := SearchUri("github","", map[string]interface{} { "q" : `user:kimchy`})
//
// produces a request like this:    host:9200/github/_search?q=user:kimchy"
//
// http://www.elasticsearch.org/guide/reference/api/search/uri-request.html
func (c *Conn) SearchUri(index, _type string, args map[string]interface{}) (SearchResult, error) {
	var uriVal string
	var retval SearchResult
	if len(_type) > 0 && _type != "*" {
		uriVal = fmt.Sprintf("/%s/%s/_search", index, _type)
	} else {
		uriVal = fmt.Sprintf("/%s/_search", index)
	}
	//log.Println(uriVal)
	body, err := c.DoCommand("GET", uriVal, args, nil)
	if err != nil {
		return retval, err
	}
	if err == nil {
		// marshall into json
		jsonErr := json.Unmarshal([]byte(body), &retval)
		if jsonErr != nil {
			return retval, jsonErr
		}
	}
	retval.RawJSON = body
	return retval, err
}

func (c *Conn) Scroll(args map[string]interface{}, scrollTimeout, scrollId string) (SearchResult, error) {
	var url string
	var retval SearchResult

	url = "/_search/scroll"

	body, err := c.DoCommand("POST", url, args, map[string]string{"scroll_id": scrollId, "scroll": scrollTimeout})
	if err != nil {
		return retval, err
	}
	if err == nil {
		// marshall into json
		jsonErr := json.Unmarshal([]byte(body), &retval)
		if jsonErr != nil {
			return retval, jsonErr
		}
	}
	return retval, err
}

type SuggestionOption struct {
	Payload json.RawMessage `json:"payload"`
	Score   Float32Nullable `json:"score,omitempty"`
	Text    string          `json:"text"`
}

type Suggestion struct {
	Length  int                `json:"length"`
	Offset  int                `json:"offset"`
	Options []SuggestionOption `json:"options"`
	Text    string             `json:"text"`
}

type Suggestions map[string][]Suggestion

type SearchResult struct {
	RawJSON      []byte
	Took         int             `json:"took"`
	TimedOut     bool            `json:"timed_out"`
	ShardStatus  Status          `json:"_shards"`
	Hits         Hits            `json:"hits"`
	Facets       json.RawMessage `json:"facets,omitempty"` // structure varies on query
	ScrollId     string          `json:"_scroll_id,omitempty"`
	Aggregations json.RawMessage `json:"aggregations,omitempty"` // structure varies on query
	Suggestions  Suggestions     `json:"suggest,omitempty"`
}

func (s *SearchResult) String() string {
	return fmt.Sprintf("<Results took=%v Timeout=%v hitct=%v />", s.Took, s.TimedOut, s.Hits.Total)
}

type Hits struct {
	Total int `json:"total"`
	//	MaxScore float32 `json:"max_score"`
	Hits []Hit `json:"hits"`
}

func (h *Hits) Len() int {
	return len(h.Hits)
}

type Highlight map[string][]string

type Hit struct {
	Index       string           `json:"_index"`
	Type        string           `json:"_type,omitempty"`
	Id          string           `json:"_id"`
	Score       Float32Nullable  `json:"_score,omitempty"` // Filters (no query) dont have score, so is null
	Source      *json.RawMessage `json:"_source"`          // marshalling left to consumer
	Fields      *json.RawMessage `json:"fields"`           // when a field arg is passed to ES, instead of _source it returns fields
	Explanation *Explanation     `json:"_explanation,omitempty"`
	Highlight   *Highlight       `json:"highlight,omitempty"`
}

func (e *Explanation) String(indent string) string {
	if len(e.Details) == 0 {
		return fmt.Sprintf("%s>>>  %v = %s", indent, e.Value, strings.Replace(e.Description, "\n", "", -1))
	} else {
		detailStrs := make([]string, 0)
		for _, detail := range e.Details {
			detailStrs = append(detailStrs, fmt.Sprintf("%s", detail.String(indent+"| ")))
		}
		return fmt.Sprintf("%s%v = %s(\n%s\n%s)", indent, e.Value, strings.Replace(e.Description, "\n", "", -1), strings.Join(detailStrs, "\n"), indent)
	}
}

// Elasticsearch returns some invalid (according to go) json, with floats having...
//
// json: cannot unmarshal null into Go value of type float32 (see last field.)
//
// "hits":{"total":6808,"max_score":null,
//    "hits":[{"_index":"10user","_type":"user","_id":"751820","_score":null,
type Float32Nullable float32

func (i *Float32Nullable) UnmarshalJSON(data []byte) error {
	if len(data) == 0 || string(data) == "null" {
		return nil
	}

	if in, err := strconv.ParseFloat(string(data), 32); err != nil {
		return err
	} else {
		*i = Float32Nullable(in)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright The OpenTelemetry 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 jaegerexporter

import (
	"context"
	"net"
	"path"
	"sync"
	"testing"

	"github.com/jaegertracing/jaeger/model"
	"github.com/jaegertracing/jaeger/proto-gen/api_v2"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"

	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/config/configgrpc"
	"go.opentelemetry.io/collector/config/configtls"
	"go.opentelemetry.io/collector/consumer/pdata"
	tracev1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1"
	"go.opentelemetry.io/collector/internal/data/testdata"
)

func TestNew(t *testing.T) {
	type args struct {
		config Config
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			name: "createExporter",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     nil,
						Endpoint:    "foo.bar",
						Compression: "",
						TLSSetting: configtls.TLSClientSetting{
							Insecure: true,
						},
						Keepalive: nil,
					},
				},
			},
		},
		{
			name: "createExporterWithHeaders",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     map[string]string{"extra-header": "header-value"},
						Endpoint:    "foo.bar",
						Compression: "",
						Keepalive:   nil,
					},
				},
			},
		},
		{
			name: "createBasicSecureExporter",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     nil,
						Endpoint:    "foo.bar",
						Compression: "",
						Keepalive:   nil,
					},
				},
			},
		},
		{
			name: "createSecureExporterWithClientTLS",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     nil,
						Endpoint:    "foo.bar",
						Compression: "",
						TLSSetting: configtls.TLSClientSetting{
							TLSSetting: configtls.TLSSetting{
								CAFile: "testdata/test_cert.pem",
							},
							Insecure: false,
						},
						Keepalive: nil,
					},
				},
			},
		},
		{
			name: "createSecureExporterWithKeepAlive",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     nil,
						Endpoint:    "foo.bar",
						Compression: "",
						TLSSetting: configtls.TLSClientSetting{
							TLSSetting: configtls.TLSSetting{
								CAFile: "testdata/test_cert.pem",
							},
							Insecure:   false,
							ServerName: "",
						},
						Keepalive: &configgrpc.KeepaliveClientConfig{
							Time:                0,
							Timeout:             0,
							PermitWithoutStream: false,
						},
					},
				},
			},
		},
		{
			name: "createSecureExporterWithMissingFile",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     nil,
						Endpoint:    "foo.bar",
						Compression: "",
						TLSSetting: configtls.TLSClientSetting{
							TLSSetting: configtls.TLSSetting{
								CAFile: "testdata/test_cert_missing.pem",
							},
							Insecure: false,
						},
						Keepalive: nil,
					},
				},
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := newTraceExporter(&tt.args.config)
			if (err != nil) != tt.wantErr {
				t.Errorf("newTraceExporter() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got == nil {
				return
			}

			// This is expected to fail.
			err = got.ConsumeTraces(context.Background(), testdata.GenerateTraceDataNoLibraries())
			assert.Error(t, err)
		})
	}
}

// CA key and cert
// openssl req -new -nodes -x509 -days 9650 -keyout ca.key -out ca.crt -subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost"
// Server key and cert
// openssl genrsa -des3 -out server.key 1024
// openssl req -new -key server.key -out server.csr -subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost"
// openssl x509 -req -days 9650 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
// Client key and cert
// openssl genrsa -des3 -out client.key 1024
// openssl req -new -key client.key -out client.csr -subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost"
// openssl x509 -req -days 9650 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
// Remove passphrase
// openssl rsa -in server.key -out temp.key && rm server.key && mv temp.key server.key
// openssl rsa -in client.key -out temp.key && rm client.key && mv temp.key client.key
func TestMutualTLS(t *testing.T) {
	caPath := path.Join(".", "testdata", "ca.crt")
	serverCertPath := path.Join(".", "testdata", "server.crt")
	serverKeyPath := path.Join(".", "testdata", "server.key")
	clientCertPath := path.Join(".", "testdata", "client.crt")
	clientKeyPath := path.Join(".", "testdata", "client.key")

	// start gRPC Jaeger server
	tlsCfgOpts := configtls.TLSServerSetting{
		TLSSetting: configtls.TLSSetting{
			CAFile:   caPath,
			CertFile: serverCertPath,
			KeyFile:  serverKeyPath,
		},
	}
	tlsCfg, err := tlsCfgOpts.LoadTLSConfig()
	require.NoError(t, err)
	spanHandler := &mockSpanHandler{}
	server, serverAddr := initializeGRPCTestServer(t, func(server *grpc.Server) {
		api_v2.RegisterCollectorServiceServer(server, spanHandler)
	}, grpc.Creds(credentials.NewTLS(tlsCfg)))
	defer server.GracefulStop()

	// Create gRPC trace exporter
	factory := NewFactory()
	cfg := factory.CreateDefaultConfig().(*Config)
	cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{
		Endpoint: serverAddr.String(),
		TLSSetting: configtls.TLSClientSetting{
			TLSSetting: configtls.TLSSetting{
				CAFile:   caPath,
				CertFile: clientCertPath,
				KeyFile:  clientKeyPath,
			},
			Insecure:   false,
			ServerName: "localhost",
		},
	}
	exporter, err := factory.CreateTraceExporter(context.Background(), component.ExporterCreateParams{}, cfg)
	require.NoError(t, err)
	err = exporter.Start(context.Background(), nil)
	require.NoError(t, err)
	defer exporter.Shutdown(context.Background())

	traceID := []byte("0123456789abcdef")
	spanID := []byte("01234567")
	traces := pdata.TracesFromOtlp([]*tracev1.ResourceSpans{
		{InstrumentationLibrarySpans: []*tracev1.InstrumentationLibrarySpans{{Spans: []*tracev1.Span{{TraceId: traceID, SpanId: spanID}}}}},
	})
	err = exporter.ConsumeTraces(context.Background(), traces)
	require.NoError(t, err)
	requestes := spanHandler.getRequests()
	assert.Equal(t, 1, len(requestes))
	jTraceID, err := model.TraceIDFromBytes(traceID)
	require.NoError(t, err)
	assert.Equal(t, jTraceID, requestes[0].GetBatch().Spans[0].TraceID)
}

func initializeGRPCTestServer(t *testing.T, beforeServe func(server *grpc.Server), opts ...grpc.ServerOption) (*grpc.Server, net.Addr) {
	server := grpc.NewServer(opts...)
	lis, err := net.Listen("tcp", "localhost:0")
	require.NoError(t, err)
	beforeServe(server)
	go func() {
		require.NoError(t, server.Serve(lis))
	}()
	return server, lis.Addr()
}

type mockSpanHandler struct {
	mux      sync.Mutex
	requests []*api_v2.PostSpansRequest
}

func (h *mockSpanHandler) getRequests() []*api_v2.PostSpansRequest {
	h.mux.Lock()
	defer h.mux.Unlock()
	return h.requests
}

func (h *mockSpanHandler) PostSpans(_ context.Context, r *api_v2.PostSpansRequest) (*api_v2.PostSpansResponse, error) {
	h.mux.Lock()
	defer h.mux.Unlock()
	h.requests = append(h.requests, r)
	return &api_v2.PostSpansResponse{}, nil
}
<commit_msg>Configure mTLS in jaeger exporter test (#1564)<commit_after>// Copyright The OpenTelemetry 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 jaegerexporter

import (
	"context"
	"net"
	"path"
	"sync"
	"testing"

	"github.com/jaegertracing/jaeger/model"
	"github.com/jaegertracing/jaeger/proto-gen/api_v2"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"

	"go.opentelemetry.io/collector/component"
	"go.opentelemetry.io/collector/config/configgrpc"
	"go.opentelemetry.io/collector/config/configtls"
	"go.opentelemetry.io/collector/consumer/pdata"
	tracev1 "go.opentelemetry.io/collector/internal/data/opentelemetry-proto-gen/trace/v1"
	"go.opentelemetry.io/collector/internal/data/testdata"
)

func TestNew(t *testing.T) {
	type args struct {
		config Config
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{
		{
			name: "createExporter",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     nil,
						Endpoint:    "foo.bar",
						Compression: "",
						TLSSetting: configtls.TLSClientSetting{
							Insecure: true,
						},
						Keepalive: nil,
					},
				},
			},
		},
		{
			name: "createExporterWithHeaders",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     map[string]string{"extra-header": "header-value"},
						Endpoint:    "foo.bar",
						Compression: "",
						Keepalive:   nil,
					},
				},
			},
		},
		{
			name: "createBasicSecureExporter",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     nil,
						Endpoint:    "foo.bar",
						Compression: "",
						Keepalive:   nil,
					},
				},
			},
		},
		{
			name: "createSecureExporterWithClientTLS",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     nil,
						Endpoint:    "foo.bar",
						Compression: "",
						TLSSetting: configtls.TLSClientSetting{
							TLSSetting: configtls.TLSSetting{
								CAFile: "testdata/test_cert.pem",
							},
							Insecure: false,
						},
						Keepalive: nil,
					},
				},
			},
		},
		{
			name: "createSecureExporterWithKeepAlive",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     nil,
						Endpoint:    "foo.bar",
						Compression: "",
						TLSSetting: configtls.TLSClientSetting{
							TLSSetting: configtls.TLSSetting{
								CAFile: "testdata/test_cert.pem",
							},
							Insecure:   false,
							ServerName: "",
						},
						Keepalive: &configgrpc.KeepaliveClientConfig{
							Time:                0,
							Timeout:             0,
							PermitWithoutStream: false,
						},
					},
				},
			},
		},
		{
			name: "createSecureExporterWithMissingFile",
			args: args{
				config: Config{
					GRPCClientSettings: configgrpc.GRPCClientSettings{
						Headers:     nil,
						Endpoint:    "foo.bar",
						Compression: "",
						TLSSetting: configtls.TLSClientSetting{
							TLSSetting: configtls.TLSSetting{
								CAFile: "testdata/test_cert_missing.pem",
							},
							Insecure: false,
						},
						Keepalive: nil,
					},
				},
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := newTraceExporter(&tt.args.config)
			if (err != nil) != tt.wantErr {
				t.Errorf("newTraceExporter() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got == nil {
				return
			}

			// This is expected to fail.
			err = got.ConsumeTraces(context.Background(), testdata.GenerateTraceDataNoLibraries())
			assert.Error(t, err)
		})
	}
}

// CA key and cert
// openssl req -new -nodes -x509 -days 9650 -keyout ca.key -out ca.crt -subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost"
// Server key and cert
// openssl genrsa -des3 -out server.key 1024
// openssl req -new -key server.key -out server.csr -subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost"
// openssl x509 -req -days 9650 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
// Client key and cert
// openssl genrsa -des3 -out client.key 1024
// openssl req -new -key client.key -out client.csr -subj "/C=US/ST=California/L=Mountain View/O=Your Organization/OU=Your Unit/CN=localhost"
// openssl x509 -req -days 9650 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
// Remove passphrase
// openssl rsa -in server.key -out temp.key && rm server.key && mv temp.key server.key
// openssl rsa -in client.key -out temp.key && rm client.key && mv temp.key client.key
func TestMutualTLS(t *testing.T) {
	caPath := path.Join(".", "testdata", "ca.crt")
	serverCertPath := path.Join(".", "testdata", "server.crt")
	serverKeyPath := path.Join(".", "testdata", "server.key")
	clientCertPath := path.Join(".", "testdata", "client.crt")
	clientKeyPath := path.Join(".", "testdata", "client.key")

	// start gRPC Jaeger server
	tlsCfgOpts := configtls.TLSServerSetting{
		TLSSetting: configtls.TLSSetting{
			CertFile: serverCertPath,
			KeyFile:  serverKeyPath,
		},
		ClientCAFile: caPath,
	}
	tlsCfg, err := tlsCfgOpts.LoadTLSConfig()
	require.NoError(t, err)
	spanHandler := &mockSpanHandler{}
	server, serverAddr := initializeGRPCTestServer(t, func(server *grpc.Server) {
		api_v2.RegisterCollectorServiceServer(server, spanHandler)
	}, grpc.Creds(credentials.NewTLS(tlsCfg)))
	defer server.GracefulStop()

	// Create gRPC trace exporter
	factory := NewFactory()
	cfg := factory.CreateDefaultConfig().(*Config)
	cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{
		Endpoint: serverAddr.String(),
		TLSSetting: configtls.TLSClientSetting{
			TLSSetting: configtls.TLSSetting{
				CAFile:   caPath,
				CertFile: clientCertPath,
				KeyFile:  clientKeyPath,
			},
			Insecure:   false,
			ServerName: "localhost",
		},
	}
	exporter, err := factory.CreateTraceExporter(context.Background(), component.ExporterCreateParams{}, cfg)
	require.NoError(t, err)
	err = exporter.Start(context.Background(), nil)
	require.NoError(t, err)
	defer exporter.Shutdown(context.Background())

	traceID := []byte("0123456789abcdef")
	spanID := []byte("01234567")
	traces := pdata.TracesFromOtlp([]*tracev1.ResourceSpans{
		{InstrumentationLibrarySpans: []*tracev1.InstrumentationLibrarySpans{{Spans: []*tracev1.Span{{TraceId: traceID, SpanId: spanID}}}}},
	})
	err = exporter.ConsumeTraces(context.Background(), traces)
	require.NoError(t, err)
	requestes := spanHandler.getRequests()
	assert.Equal(t, 1, len(requestes))
	jTraceID, err := model.TraceIDFromBytes(traceID)
	require.NoError(t, err)
	assert.Equal(t, jTraceID, requestes[0].GetBatch().Spans[0].TraceID)
}

func initializeGRPCTestServer(t *testing.T, beforeServe func(server *grpc.Server), opts ...grpc.ServerOption) (*grpc.Server, net.Addr) {
	server := grpc.NewServer(opts...)
	lis, err := net.Listen("tcp", "localhost:0")
	require.NoError(t, err)
	beforeServe(server)
	go func() {
		require.NoError(t, server.Serve(lis))
	}()
	return server, lis.Addr()
}

type mockSpanHandler struct {
	mux      sync.Mutex
	requests []*api_v2.PostSpansRequest
}

func (h *mockSpanHandler) getRequests() []*api_v2.PostSpansRequest {
	h.mux.Lock()
	defer h.mux.Unlock()
	return h.requests
}

func (h *mockSpanHandler) PostSpans(_ context.Context, r *api_v2.PostSpansRequest) (*api_v2.PostSpansResponse, error) {
	h.mux.Lock()
	defer h.mux.Unlock()
	h.requests = append(h.requests, r)
	return &api_v2.PostSpansResponse{}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"compress/gzip"
	"encoding/xml"
	"io/ioutil"
	"net/http"
	"time"
	// "github.com/grokify/html-strip-tags-go"
	"github.com/lunny/html2md"
)

type disqusThread struct {
	XMLName xml.Name `xml:"thread"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
	URL     string   `xml:"link"`
	Name    string   `xml:"name"`
}

type disqusAuthor struct {
	XMLName     xml.Name `xml:"author"`
	IsAnonymous bool     `xml:"isAnonymous"`
	Name        string   `xml:"name"`
	Email       string   `xml:"email"`
}

type disqusThreadId struct {
	XMLName xml.Name `xml:"thread"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusParentId struct {
	XMLName xml.Name `xml:"parent"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusPostId struct {
	XMLName xml.Name `xml:"post"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusPost struct {
	XMLName      xml.Name       `xml:"post"`
	Id           string         `xml:"http://disqus.com/disqus-internals id,attr"`
	ThreadId     disqusThreadId `xml:"thread"`
	ParentId     disqusParentId `xml:"parent"`
	PostId       disqusPostId   `xml:"post"`
	Message      string         `xml:"message"`
	CreationDate time.Time      `xml:"createdAt"`
	IsDeleted    bool           `xml:"isDeleted"`
	IsSpam       bool           `xml:"isSpam"`
	Author       disqusAuthor   `xml:"author"`
}

type disqusXML struct {
	XMLName xml.Name       `xml:"disqus"`
	Threads []disqusThread `xml:"thread"`
	Posts   []disqusPost   `xml:"post"`
}

func domainImportDisqus(domain string, url string) (int, error) {
	if domain == "" || url == "" {
		return 0, errorMissingField
	}

	// TODO: make sure this is from disqus.com
	resp, err := http.Get(url)
	if err != nil {
		logger.Errorf("cannot get url: %v", err)
		return 0, errorCannotDownloadDisqus
	}

	defer resp.Body.Close()

	zr, err := gzip.NewReader(resp.Body)
	if err != nil {
		logger.Errorf("cannot create gzip reader: %v", err)
		return 0, errorInternal
	}

	contents, err := ioutil.ReadAll(zr)
	if err != nil {
		logger.Errorf("cannot read gzip contents uncompressed: %v", err)
		return 0, errorInternal
	}

	x := disqusXML{}
	err = xml.Unmarshal(contents, &x)
	if err != nil {
		logger.Errorf("cannot unmarshal XML: %v", err)
		return 0, errorInternal
	}

	// Map Disqus thread IDs to threads.
	threads := make(map[string]disqusThread)
	for _, thread := range x.Threads {
		threads[thread.Id] = thread
	}

	// Map Disqus emails to commenterHex (if not available, create a new one
	// with a random password that can be reset later).
	commenterHex := make(map[string]string)
	for _, post := range x.Posts {
		if post.IsDeleted || post.IsSpam {
			continue
		}

		if _, ok := commenterHex[post.Author.Email]; ok {
			continue
		}

		c, err := commenterGetByEmail("commento", post.Author.Email)
		if err != nil && err != errorNoSuchCommenter {
			logger.Errorf("cannot get commenter by email: %v", err)
			return 0, errorInternal
		}

		if err == nil {
			commenterHex[post.Author.Email] = c.CommenterHex
			continue
		}

		randomPassword, err := randomHex(32)
		if err != nil {
			logger.Errorf("cannot generate random password for new commenter: %v", err)
			return 0, errorInternal
		}

		commenterHex[post.Author.Email], err = commenterNew(post.Author.Email, post.Author.Name, "undefined", "undefined", "commento", randomPassword)
		if err != nil {
			return 0, err
		}
	}

	// For each Disqus post, create a Commento comment. Attempt to convert the
	// HTML to markdown.
	numImported := 0
	disqusIdMap := make(map[string]string)
	for _, post := range x.Posts {
		if post.IsDeleted || post.IsSpam {
			continue
		}

		parentHex := "root"
		if val, ok := disqusIdMap[post.ParentId.Id]; ok {
			parentHex = val
		}

		// TODO: restrict the list of tags to just the basics: , , ,
		// Especially remove  (convert it to ).
		commentHex, err := commentNew(
			commenterHex[post.Author.Email],
			domain,
			stripPath(threads[post.ThreadId.Id].URL),
			parentHex,
			html2md.Convert(post.Message),
			"approved",
			post.CreationDate)
		if err != nil {
			return numImported, err
		}

		disqusIdMap[post.PostId.Id] = commentHex
		numImported += 1
	}

	return numImported, nil
}

func domainImportDisqusHandler(w http.ResponseWriter, r *http.Request) {
	type request struct {
		Session *string `json:"session"`
		Domain  *string `json:"domain"`
		URL     *string `json:"url"`
	}

	var x request
	if err := unmarshalBody(r, &x); err != nil {
		writeBody(w, response{"success": false, "message": err.Error()})
		return
	}

	o, err := ownerGetBySession(*x.Session)
	if err != nil {
		writeBody(w, response{"success": false, "message": err.Error()})
		return
	}

	domain := stripDomain(*x.Domain)
	isOwner, err := domainOwnershipVerify(o.OwnerHex, domain)
	if err != nil {
		writeBody(w, response{"success": false, "message": err.Error()})
		return
	}

	if !isOwner {
		writeBody(w, response{"success": false, "message": errorNotAuthorised.Error()})
		return
	}

	numImported, err := domainImportDisqus(domain, *x.URL)
	if err != nil {
		writeBody(w, response{"success": false, "message": err.Error()})
		return
	}

	writeBody(w, response{"success": true, "numImported": numImported})
}
<commit_msg>domain_import_disqus.go: remove unused import<commit_after>package main

import (
	"compress/gzip"
	"encoding/xml"
	"github.com/lunny/html2md"
	"io/ioutil"
	"net/http"
	"time"
)

type disqusThread struct {
	XMLName xml.Name `xml:"thread"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
	URL     string   `xml:"link"`
	Name    string   `xml:"name"`
}

type disqusAuthor struct {
	XMLName     xml.Name `xml:"author"`
	IsAnonymous bool     `xml:"isAnonymous"`
	Name        string   `xml:"name"`
	Email       string   `xml:"email"`
}

type disqusThreadId struct {
	XMLName xml.Name `xml:"thread"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusParentId struct {
	XMLName xml.Name `xml:"parent"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusPostId struct {
	XMLName xml.Name `xml:"post"`
	Id      string   `xml:"http://disqus.com/disqus-internals id,attr"`
}

type disqusPost struct {
	XMLName      xml.Name       `xml:"post"`
	Id           string         `xml:"http://disqus.com/disqus-internals id,attr"`
	ThreadId     disqusThreadId `xml:"thread"`
	ParentId     disqusParentId `xml:"parent"`
	PostId       disqusPostId   `xml:"post"`
	Message      string         `xml:"message"`
	CreationDate time.Time      `xml:"createdAt"`
	IsDeleted    bool           `xml:"isDeleted"`
	IsSpam       bool           `xml:"isSpam"`
	Author       disqusAuthor   `xml:"author"`
}

type disqusXML struct {
	XMLName xml.Name       `xml:"disqus"`
	Threads []disqusThread `xml:"thread"`
	Posts   []disqusPost   `xml:"post"`
}

func domainImportDisqus(domain string, url string) (int, error) {
	if domain == "" || url == "" {
		return 0, errorMissingField
	}

	// TODO: make sure this is from disqus.com
	resp, err := http.Get(url)
	if err != nil {
		logger.Errorf("cannot get url: %v", err)
		return 0, errorCannotDownloadDisqus
	}

	defer resp.Body.Close()

	zr, err := gzip.NewReader(resp.Body)
	if err != nil {
		logger.Errorf("cannot create gzip reader: %v", err)
		return 0, errorInternal
	}

	contents, err := ioutil.ReadAll(zr)
	if err != nil {
		logger.Errorf("cannot read gzip contents uncompressed: %v", err)
		return 0, errorInternal
	}

	x := disqusXML{}
	err = xml.Unmarshal(contents, &x)
	if err != nil {
		logger.Errorf("cannot unmarshal XML: %v", err)
		return 0, errorInternal
	}

	// Map Disqus thread IDs to threads.
	threads := make(map[string]disqusThread)
	for _, thread := range x.Threads {
		threads[thread.Id] = thread
	}

	// Map Disqus emails to commenterHex (if not available, create a new one
	// with a random password that can be reset later).
	commenterHex := make(map[string]string)
	for _, post := range x.Posts {
		if post.IsDeleted || post.IsSpam {
			continue
		}

		if _, ok := commenterHex[post.Author.Email]; ok {
			continue
		}

		c, err := commenterGetByEmail("commento", post.Author.Email)
		if err != nil && err != errorNoSuchCommenter {
			logger.Errorf("cannot get commenter by email: %v", err)
			return 0, errorInternal
		}

		if err == nil {
			commenterHex[post.Author.Email] = c.CommenterHex
			continue
		}

		randomPassword, err := randomHex(32)
		if err != nil {
			logger.Errorf("cannot generate random password for new commenter: %v", err)
			return 0, errorInternal
		}

		commenterHex[post.Author.Email], err = commenterNew(post.Author.Email, post.Author.Name, "undefined", "undefined", "commento", randomPassword)
		if err != nil {
			return 0, err
		}
	}

	// For each Disqus post, create a Commento comment. Attempt to convert the
	// HTML to markdown.
	numImported := 0
	disqusIdMap := make(map[string]string)
	for _, post := range x.Posts {
		if post.IsDeleted || post.IsSpam {
			continue
		}

		parentHex := "root"
		if val, ok := disqusIdMap[post.ParentId.Id]; ok {
			parentHex = val
		}

		// TODO: restrict the list of tags to just the basics: , , ,
		// Especially remove  (convert it to ).
		commentHex, err := commentNew(
			commenterHex[post.Author.Email],
			domain,
			stripPath(threads[post.ThreadId.Id].URL),
			parentHex,
			html2md.Convert(post.Message),
			"approved",
			post.CreationDate)
		if err != nil {
			return numImported, err
		}

		disqusIdMap[post.PostId.Id] = commentHex
		numImported += 1
	}

	return numImported, nil
}

func domainImportDisqusHandler(w http.ResponseWriter, r *http.Request) {
	type request struct {
		Session *string `json:"session"`
		Domain  *string `json:"domain"`
		URL     *string `json:"url"`
	}

	var x request
	if err := unmarshalBody(r, &x); err != nil {
		writeBody(w, response{"success": false, "message": err.Error()})
		return
	}

	o, err := ownerGetBySession(*x.Session)
	if err != nil {
		writeBody(w, response{"success": false, "message": err.Error()})
		return
	}

	domain := stripDomain(*x.Domain)
	isOwner, err := domainOwnershipVerify(o.OwnerHex, domain)
	if err != nil {
		writeBody(w, response{"success": false, "message": err.Error()})
		return
	}

	if !isOwner {
		writeBody(w, response{"success": false, "message": errorNotAuthorised.Error()})
		return
	}

	numImported, err := domainImportDisqus(domain, *x.URL)
	if err != nil {
		writeBody(w, response{"success": false, "message": err.Error()})
		return
	}

	writeBody(w, response{"success": true, "numImported": numImported})
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"github.com/fredhsu/go-eapi"
	"io/ioutil"
	"os"
)

type EosNode struct {
	Hostname      string
	MgmtIp        string
	Username      string
	Password      string
	Ssl           bool
	Reachable     bool
	ConfigCorrect bool
	Uptime        float64
	Version       string
}

type ChanResponse struct {
	response eapi.JsonRpcResponse
	node     EosNode
}

func versionFetcher(url string, cmds []string, format string, n EosNode,
	c chan ChanResponse) {
	response := eapi.Call(url, cmds, format)
	c <- ChanResponse{response, n}
}

func writeConfig(path string, n EosNode, config string) {
	filename := path + n.Hostname + ".eos"
	err := ioutil.WriteFile(filename, []byte(config), 0644)
	if err != nil {
		panic(err)
	}
	fmt.Println("wrote to ", filename)
}

func main() {
	file, _ := os.Open("switches.json")
	decoder := json.NewDecoder(file)
	switches := []EosNode{}
	err := decoder.Decode(&switches)

	if err != nil {
		fmt.Println("error:", err)
	}
	cmds2 := []string{"enable", "show running-config"}
	c := make(chan ChanResponse)

	for _, node := range switches {
		prefix := "http"

		if node.Ssl == true {
			prefix = prefix + "s"
		}
		url := prefix + "://" + node.Username + ":" + node.Password + "@" + node.Hostname + "/command-api"
		fmt.Println(url)
		go versionFetcher(url, cmds2, "text", node, c)
	}

	for i := 0; i < len(switches); i++ {
		msg1 := <-c
		writeConfig("/Users/fredlhsu/baseconfigs/", msg1.node, (msg1.response.Result[1]["output"]).(string))
	}
}
<commit_msg>Moved file writing to getVersion<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"github.com/fredhsu/go-eapi"
	"io/ioutil"
	"os"
)

type EosNode struct {
	Hostname      string
	MgmtIp        string
	Username      string
	Password      string
	Ssl           bool
	Reachable     bool
	ConfigCorrect bool
	Uptime        float64
	Version       string
}

type ChanResponse struct {
	response eapi.JsonRpcResponse
	node     EosNode
}

func versionFetcher(url string, cmds []string, format string, n EosNode,
	c chan ChanResponse) {
	response := eapi.Call(url, cmds, format)
	writeConfig("/Users/fredlhsu/baseconfigs/", n, response.Result[1]["output"].(string))
	c <- ChanResponse{response, n}
}

func writeConfig(path string, n EosNode, config string) {
	filename := path + n.Hostname + ".eos"
	err := ioutil.WriteFile(filename, []byte(config), 0644)
	if err != nil {
		panic(err)
	}
	fmt.Println("wrote to ", filename)
}

func main() {
	file, _ := os.Open("switches.json")
	decoder := json.NewDecoder(file)
	switches := []EosNode{}
	err := decoder.Decode(&switches)

	if err != nil {
		fmt.Println("error:", err)
	}
	cmds2 := []string{"enable", "show running-config"}
	c := make(chan ChanResponse)

	for _, node := range switches {
		prefix := "http"

		if node.Ssl == true {
			prefix = prefix + "s"
		}
		url := prefix + "://" + node.Username + ":" + node.Password + "@" + node.Hostname + "/command-api"
		fmt.Println(url)
		go versionFetcher(url, cmds2, "text", node, c)
	}

	for i := 0; i < len(switches); i++ {
		<-c
	}
}
<|endoftext|>


<commit_before>package gittest

import (
	"io/ioutil"
	"os/exec"
	"path/filepath"
	"testing"

	"github.com/weaveworks/flux/cluster/kubernetes/testfiles"
	"github.com/weaveworks/flux/git"
)

// Repo creates a new clone-able git repo, pre-populated with some kubernetes
// files and a few commits. Also returns a cleanup func to clean up after.
func Repo(t *testing.T) (git.Repo, func()) {
	newDir, cleanup := testfiles.TempDir(t)

	filesDir := filepath.Join(newDir, "files")
	gitDir := filepath.Join(newDir, "git")
	if err := execCommand("mkdir", filesDir); err != nil {
		t.Fatal(err)
	}

	var err error
	if err = execCommand("git", "-C", filesDir, "init"); err != nil {
		cleanup()
		t.Fatal(err)
	}
	if err = testfiles.WriteTestFiles(filesDir); err != nil {
		cleanup()
		t.Fatal(err)
	}
	if err = execCommand("git", "-C", filesDir, "add", "--all"); err != nil {
		cleanup()
		t.Fatal(err)
	}
	if err = execCommand("git", "-C", filesDir, "commit", "-m", "'Initial revision'"); err != nil {
		cleanup()
		t.Fatal(err)
	}

	if err = execCommand("git", "clone", "--bare", filesDir, gitDir); err != nil {
		t.Fatal(err)
	}

	return git.Repo{
		URL:    gitDir,
		Branch: "master",
	}, cleanup
}

func Checkout(t *testing.T) (*git.Checkout, func()) {
	repo, cleanup := Repo(t)
	config := git.Config{
		UserName:  "example",
		UserEmail: "example@example.com",
		SyncTag:   "flux-test",
		NotesRef:  "fluxtest",
	}
	co, err := repo.Clone(config)
	if err != nil {
		cleanup()
		t.Fatal(err)
	}
	return co, func() {
		co.Clean()
		cleanup()
	}
}

func execCommand(cmd string, args ...string) error {
	c := exec.Command(cmd, args...)
	c.Stderr = ioutil.Discard
	c.Stdout = ioutil.Discard
	return c.Run()
}
<commit_msg>Add flux uptodate tag to test git repo<commit_after>package gittest

import (
	"io/ioutil"
	"os/exec"
	"path/filepath"
	"testing"

	"github.com/weaveworks/flux/cluster/kubernetes/testfiles"
	"github.com/weaveworks/flux/git"
)

// Repo creates a new clone-able git repo, pre-populated with some kubernetes
// files and a few commits. Also returns a cleanup func to clean up after.
func Repo(t *testing.T) (git.Repo, func()) {
	newDir, cleanup := testfiles.TempDir(t)

	filesDir := filepath.Join(newDir, "files")
	gitDir := filepath.Join(newDir, "git")
	if err := execCommand("mkdir", filesDir); err != nil {
		t.Fatal(err)
	}

	var err error
	if err = execCommand("git", "-C", filesDir, "init"); err != nil {
		cleanup()
		t.Fatal(err)
	}
	if err = testfiles.WriteTestFiles(filesDir); err != nil {
		cleanup()
		t.Fatal(err)
	}
	if err = execCommand("git", "-C", filesDir, "add", "--all"); err != nil {
		cleanup()
		t.Fatal(err)
	}
	if err = execCommand("git", "-C", filesDir, "commit", "-m", "'Initial revision'"); err != nil {
		cleanup()
		t.Fatal(err)
	}
	if err = execCommand("git", "-C", filesDir, "tag", "flux-test"); err != nil {
		cleanup()
		t.Fatal(err)
	}

	if err = execCommand("git", "clone", "--bare", filesDir, gitDir); err != nil {
		t.Fatal(err)
	}

	return git.Repo{
		URL:    gitDir,
		Branch: "master",
	}, cleanup
}

func Checkout(t *testing.T) (*git.Checkout, func()) {
	repo, cleanup := Repo(t)
	config := git.Config{
		UserName:  "example",
		UserEmail: "example@example.com",
		SyncTag:   "flux-test",
		NotesRef:  "fluxtest",
	}
	co, err := repo.Clone(config)
	if err != nil {
		cleanup()
		t.Fatal(err)
	}
	return co, func() {
		co.Clean()
		cleanup()
	}
}

func execCommand(cmd string, args ...string) error {
	c := exec.Command(cmd, args...)
	c.Stderr = ioutil.Discard
	c.Stdout = ioutil.Discard
	return c.Run()
}
<|endoftext|>


<commit_before>package main

import (
	"strings"
	"time"

	"github.com/ziutek/glib"
	"github.com/ziutek/gst"
)

func (m *Manager) getRadio(config *Config) *Radio {
	id, ok := config.Servers[m.Server().Id()]
	if ok {
		return config.Backends.Radios[id]
	}
	return nil
}

func (m *Manager) setDevice(src *gst.Element, uri string) {
	if strings.Index(uri, ":") > 0 {
		parts := strings.SplitN(uri, ":", 2)
		src.SetProperty("device", parts[1])
	}
}

func (m *Manager) buildSrc(uri string) *gst.Element {
	var src *gst.Element
	if uri == "test" {
		src = makeElem("audiotestsrc")
	} else if strings.HasPrefix(uri, "alsa") {
		src = makeElem("alsasrc")
		m.setDevice(src, uri)
	} else if strings.HasPrefix(uri, "pulse") {
		src = makeElem("pulsesrc")
		m.setDevice(src, uri)
		// TODO add filter for stereo
	} else {
		src = makeElem("uridecodebin")
		src.SetProperty("uri", uri)
		src.SetProperty("buffer-duration", 1000)
	}
	return src
}

func (m *Manager) buildPipeline(uri string) {
	src := m.buildSrc(uri)
	pipe1 := makeElem("audioconvert")
	pipe2 := makeElem("audioresample")
	pipe3 := makeElem("opusenc")
	pipe3.SetProperty("bitrate", 96000)
	pipe3.SetProperty("dtx", true)
	pipe3.SetProperty("inband-fec", false)
	pipe3.SetProperty("packet-loss-percentage", 0)
	pipe4 := makeElem("oggmux")
	sink := makeElem("tcpserversink")
	s := m.Server()
	sink.SetProperty("host", s.Host)
	sink.SetProperty("port", s.Port)

	m.Pipeline = gst.NewPipeline("pipeline")
	bus := m.Pipeline.GetBus()
	bus.AddSignalWatch()
	bus.Connect("message", m.onMessage, nil)
	src.ConnectNoi("pad-added", onPadAdded, pipe1.GetStaticPad("sink"))

	addElem(m.Pipeline, src)
	addElem(m.Pipeline, pipe1)
	addElem(m.Pipeline, pipe2)
	addElem(m.Pipeline, pipe3)
	addElem(m.Pipeline, pipe4)
	addElem(m.Pipeline, sink)
	linkElems(src, pipe1)
	linkElems(pipe1, pipe2)
	linkElems(pipe2, pipe3)
	linkElems(pipe3, pipe4)
	linkElems(pipe4, sink)
}

func (m *Manager) playPipeline(uri string) {
	m.Pipeline = nil
	m.buildPipeline(uri)
	m.StartPipeline()
}

func (m *Manager) loop(l *glib.MainLoop) {
	for m.running {
		log.Debug("starting new pipeline with static stream: %s", m.StaticUri)
		m.playPipeline(m.StaticUri)
		// we don't listen for new config, but errors will reset the pipeline
		m.WaitForNewConfig()
		m.StopPipeline()
	}

	if l != nil {
		l.Quit()
	}
}

func (m *Manager) scheduleBackendTimeout(c <-chan time.Time) {
	for m.running {
		log.Debug("ping config server")
		uri := m.ConfigUri + "/api/ping/server"
		pingConfig(uri, m.Backend)
		<-c
	}
}

func (m *Manager) startSender() {
	log.Debug("starting sender")
	m.running = true
	l := glib.NewMainLoop(nil)
	go m.loop(l)
	if !m.Server().Internal {
		go m.scheduleBackendTimeout(time.Tick(backendTimeout / 2))
	}
	log.Debug("start gst loop")
	l.Run()
	log.Debug("sender stopped")
}

func (m *Manager) stopSender() {
	log.Info("stopping sender: %s", m.Server().Id())
	m.running = false
	m.NewConfig(nil)
}
<commit_msg>working on audio lag; refs #11<commit_after>package main

import (
	"strings"
	"time"

	"github.com/ziutek/glib"
	"github.com/ziutek/gst"
)

func (m *Manager) getRadio(config *Config) *Radio {
	id, ok := config.Servers[m.Server().Id()]
	if ok {
		return config.Backends.Radios[id]
	}
	return nil
}

func (m *Manager) setDevice(src *gst.Element, uri string) {
	if strings.Index(uri, ":") > 0 {
		parts := strings.SplitN(uri, ":", 2)
		src.SetProperty("device", parts[1])
	}
}

func (m *Manager) buildSrc(uri string) *gst.Element {
	var src *gst.Element
	if uri == "test" {
		src = makeElem("audiotestsrc")
	} else if strings.HasPrefix(uri, "alsa") {
		src = makeElem("alsasrc")
		m.setDevice(src, uri)
	} else if strings.HasPrefix(uri, "pulse") {
		src = makeElem("pulsesrc")
		m.setDevice(src, uri)
		// TODO add filter for stereo
	} else {
		src = makeElem("uridecodebin")
		src.SetProperty("uri", uri)
		src.SetProperty("download", true)
		src.SetProperty("use-buffering", true)
		src.SetProperty("buffer-duration", 2000)
	}
	return src
}

func (m *Manager) buildPipeline(uri string) {
	src := m.buildSrc(uri)
	pipe1 := makeElem("audioconvert")
	pipe2 := makeElem("audioresample")
	pipe3 := makeElem("opusenc")
	pipe3.SetProperty("bitrate", 96000)
	pipe3.SetProperty("bandwith", "auto")
	pipe3.SetProperty("audio", true)
	pipe3.SetProperty("dtx", true)
	pipe3.SetProperty("packet-loss-percentage", 0)
	pipe4 := makeElem("oggmux")
	pipe5 := makeElem("queue2")
	sink := makeElem("tcpserversink")
	s := m.Server()
	sink.SetProperty("host", s.Host)
	sink.SetProperty("port", s.Port)

	m.Pipeline = gst.NewPipeline("pipeline")
	bus := m.Pipeline.GetBus()
	bus.AddSignalWatch()
	bus.Connect("message", m.onMessage, nil)
	src.ConnectNoi("pad-added", onPadAdded, pipe1.GetStaticPad("sink"))

	addElem(m.Pipeline, src)
	addElem(m.Pipeline, pipe1)
	addElem(m.Pipeline, pipe2)
	addElem(m.Pipeline, pipe3)
	addElem(m.Pipeline, pipe4)
	addElem(m.Pipeline, pipe5)
	addElem(m.Pipeline, sink)
	linkElems(src, pipe1)
	linkElems(pipe1, pipe2)
	linkElems(pipe2, pipe3)
	linkElems(pipe3, pipe4)
	linkElems(pipe4, pipe5)
	linkElems(pipe5, sink)
}

func (m *Manager) playPipeline(uri string) {
	m.Pipeline = nil
	m.buildPipeline(uri)
	m.StartPipeline()
}

func (m *Manager) loop(l *glib.MainLoop) {
	for m.running {
		log.Debug("starting new pipeline with static stream: %s", m.StaticUri)
		m.playPipeline(m.StaticUri)
		// we don't listen for new config, but errors will reset the pipeline
		m.WaitForNewConfig()
		m.StopPipeline()
	}

	if l != nil {
		l.Quit()
	}
}

func (m *Manager) scheduleBackendTimeout(c <-chan time.Time) {
	for m.running {
		log.Debug("ping config server")
		uri := m.ConfigUri + "/api/ping/server"
		pingConfig(uri, m.Backend)
		<-c
	}
}

func (m *Manager) startSender() {
	log.Debug("starting sender")
	m.running = true
	l := glib.NewMainLoop(nil)
	go m.loop(l)
	if !m.Server().Internal {
		go m.scheduleBackendTimeout(time.Tick(backendTimeout / 2))
	}
	log.Debug("start gst loop")
	l.Run()
	log.Debug("sender stopped")
}

func (m *Manager) stopSender() {
	log.Info("stopping sender: %s", m.Server().Id())
	m.running = false
	m.NewConfig(nil)
}
<|endoftext|>


<commit_before>// Common tool initialization.
// import only from package main.
package common

import (
	"flag"
	"fmt"
	"net"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/cyberdelia/go-metrics-graphite"
	"github.com/davecgh/go-spew/spew"
	metrics "github.com/rcrowley/go-metrics"
	"github.com/skia-dev/glog"
)

const SAMPLE_PERIOD = time.Minute

// Runs commonly-used initialization metrics.
func Init() {
	flag.Parse()
	defer glog.Flush()
	flag.VisitAll(func(f *flag.Flag) {
		glog.Infof("Flags: --%s=%v", f.Name, f.Value)
	})

	// Use all cores.
	runtime.GOMAXPROCS(runtime.NumCPU())
}

// Runs normal Init functions as well as tracking runtime metrics.
// Sets up Graphite push for go-metrics' DefaultRegistry. Users of
// both InitWithMetrics and metrics.DefaultRegistry will not need to
// run graphite.Graphite(metrics.DefaultRegistry, ...) separately.
func InitWithMetrics(appName string, graphiteServer *string) {
	Init()

	startMetrics(appName, *graphiteServer)
}

// Get the graphite server from a callback function; useful when the graphite
// server isn't known ahead of time (e.g., when reading from a config file)
func InitWithMetricsCB(appName string, getGraphiteServer func() string) {
	Init()

	// Note(stephana): getGraphiteServer relies on Init() being called first.
	startMetrics(appName, getGraphiteServer())
}

// TODO(stephana): Refactor startMetrics to return an error instead of
// terminating the app.

func startMetrics(appName, graphiteServer string) {
	if graphiteServer == "" {
		glog.Warningf("No metrics server specified.")
		return
	}

	addr, err := net.ResolveTCPAddr("tcp", graphiteServer)
	if err != nil {
		glog.Fatalf("Unable to resolve metrics server address: %s", err)
	}

	// Get the hostname and create the app-prefix.
	hostName, err := os.Hostname()
	if err != nil {
		glog.Fatalf("Unable to retrieve hostname: %s", err)
	}
	appPrefix := fmt.Sprintf("%s.%s", appName, strings.Replace(hostName, ".", "-", -1))

	// Runtime metrics.
	metrics.RegisterRuntimeMemStats(metrics.DefaultRegistry)
	go metrics.CaptureRuntimeMemStats(metrics.DefaultRegistry, SAMPLE_PERIOD)
	go graphite.Graphite(metrics.DefaultRegistry, SAMPLE_PERIOD, appPrefix, addr)

	// Uptime.
	uptimeGuage := metrics.GetOrRegisterGaugeFloat64("uptime", metrics.DefaultRegistry)
	go func() {
		startTime := time.Now()
		uptimeGuage.Update(0)
		for _ = range time.Tick(SAMPLE_PERIOD) {
			uptimeGuage.Update(time.Since(startTime).Seconds())
		}
	}()
}

// Defer from main() to log any panics and flush the log. Defer this function before any other
// defers.
func LogPanic() {
	if r := recover(); r != nil {
		glog.Fatal(r)
	}
	glog.Flush()
}

func DecodeTomlFile(filename string, configuration interface{}) {
	if _, err := toml.DecodeFile(filename, configuration); err != nil {
		glog.Fatalf("Failed to decode config file %s: %s", filename, err)
	}

	conf_str := spew.Sdump(configuration)
	glog.Infof("Read TOML configuration from %s: %s", filename, conf_str)
}
<commit_msg>Initialize the different log levels for an app in common.InitWithMetrics<commit_after>// Common tool initialization.
// import only from package main.
package common

import (
	"flag"
	"fmt"
	"net"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/cyberdelia/go-metrics-graphite"
	"github.com/davecgh/go-spew/spew"
	metrics "github.com/rcrowley/go-metrics"
	"github.com/skia-dev/glog"
)

const SAMPLE_PERIOD = time.Minute

// Runs commonly-used initialization metrics.
func Init() {
	flag.Parse()
	defer glog.Flush()
	flag.VisitAll(func(f *flag.Flag) {
		glog.Infof("Flags: --%s=%v", f.Name, f.Value)
	})

	// See skbug.com/4386 for details on why the below section exists.
	glog.Info("Initializing logserver for log level INFO.")
	glog.Warning("Initializing logserver for log level WARNING.")
	glog.Error("Initializing logserver for log level ERROR.")

	// Use all cores.
	runtime.GOMAXPROCS(runtime.NumCPU())
}

// Runs normal Init functions as well as tracking runtime metrics.
// Sets up Graphite push for go-metrics' DefaultRegistry. Users of
// both InitWithMetrics and metrics.DefaultRegistry will not need to
// run graphite.Graphite(metrics.DefaultRegistry, ...) separately.
func InitWithMetrics(appName string, graphiteServer *string) {
	Init()

	startMetrics(appName, *graphiteServer)
}

// Get the graphite server from a callback function; useful when the graphite
// server isn't known ahead of time (e.g., when reading from a config file)
func InitWithMetricsCB(appName string, getGraphiteServer func() string) {
	Init()

	// Note(stephana): getGraphiteServer relies on Init() being called first.
	startMetrics(appName, getGraphiteServer())
}

// TODO(stephana): Refactor startMetrics to return an error instead of
// terminating the app.

func startMetrics(appName, graphiteServer string) {
	if graphiteServer == "" {
		glog.Warningf("No metrics server specified.")
		return
	}

	addr, err := net.ResolveTCPAddr("tcp", graphiteServer)
	if err != nil {
		glog.Fatalf("Unable to resolve metrics server address: %s", err)
	}

	// Get the hostname and create the app-prefix.
	hostName, err := os.Hostname()
	if err != nil {
		glog.Fatalf("Unable to retrieve hostname: %s", err)
	}
	appPrefix := fmt.Sprintf("%s.%s", appName, strings.Replace(hostName, ".", "-", -1))

	// Runtime metrics.
	metrics.RegisterRuntimeMemStats(metrics.DefaultRegistry)
	go metrics.CaptureRuntimeMemStats(metrics.DefaultRegistry, SAMPLE_PERIOD)
	go graphite.Graphite(metrics.DefaultRegistry, SAMPLE_PERIOD, appPrefix, addr)

	// Uptime.
	uptimeGuage := metrics.GetOrRegisterGaugeFloat64("uptime", metrics.DefaultRegistry)
	go func() {
		startTime := time.Now()
		uptimeGuage.Update(0)
		for _ = range time.Tick(SAMPLE_PERIOD) {
			uptimeGuage.Update(time.Since(startTime).Seconds())
		}
	}()
}

// Defer from main() to log any panics and flush the log. Defer this function before any other
// defers.
func LogPanic() {
	if r := recover(); r != nil {
		glog.Fatal(r)
	}
	glog.Flush()
}

func DecodeTomlFile(filename string, configuration interface{}) {
	if _, err := toml.DecodeFile(filename, configuration); err != nil {
		glog.Fatalf("Failed to decode config file %s: %s", filename, err)
	}

	conf_str := spew.Sdump(configuration)
	glog.Infof("Read TOML configuration from %s: %s", filename, conf_str)
}
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// 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 googet provides googet interoperability.
package googet

import (
	"errors"
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/google/logger"
	"github.com/google/glazier/go/helpers"
)

var (
	// Test Helpers
	funcExec = helpers.Exec
)

// Config provides the ability to customize GooGet behavior.
type Config struct {
	GooGetExe  string
	ExecConfig helpers.ExecConfig
}

// NewConfig generates a new Config object.
func NewConfig() *Config {
	timeout := 10 * time.Minute
	return &Config{
		GooGetExe: os.Getenv("GooGetRoot") + `\googet.exe`,
		ExecConfig: helpers.ExecConfig{
			Timeout:  &timeout,
			Verifier: helpers.NewExecVerifier(),
		},
	}
}

func call(args []string, conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}

	res, err := funcExec(conf.GooGetExe, args, &conf.ExecConfig)
	if err != nil {
		logger.Errorf("googet stdout: %v", string(res.Stdout))
		logger.Errorf("googet stderr: %v", string(res.Stderr))
		if errors.Is(err, helpers.ErrTimeout) {
			return fmt.Errorf("execution timed out after %v", conf.ExecConfig.Timeout)
		}
	}
	return err
}

// AddRepo adds a googet repository to the local system.
func AddRepo(name, url string, conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}

	if name == "" || url == "" {
		return errors.New("must specify name and url")
	}

	return call([]string{"addrepo", name, url}, conf)
}

// Install installs a Googet package.
func Install(pkg, sources string, reinstall bool, conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}

	cmd := []string{"-noconfirm", "install"}
	if reinstall {
		cmd = append(cmd, "--reinstall")
	}
	if sources != "" {
		cmd = append(cmd, "--sources", sources)
	}
	cmd = append(cmd, pkg)

	return call(cmd, conf)
}

// Package represents a GooGet package.
type Package struct {
	Name    string
	Version string
}

// Installed lists installed package(s).
func Installed(initial string, conf *Config) ([]Package, error) {
	if conf == nil {
		conf = NewConfig()
	}

	p := []Package{}
	args := []string{"installed", initial}
	res, err := funcExec(conf.GooGetExe, args, &conf.ExecConfig)
	if err != nil {
		return p, err
	}
	lines := strings.Split(string(res.Stdout), "\n")
	for i, r := range lines {
		r = strings.TrimSpace(r)
		if i == 0 || r == "" {
			continue
		}
		dat := strings.Fields(r)
		if len(dat) < 2 {
			logger.Errorf("unable to parse googet output %v", r)
			continue
		}
		p = append(p, Package{
			Name:    dat[0],
			Version: dat[1],
		})
	}

	return p, nil
}

// Repo represents a googet repository configuration.
type Repo struct {
	Name string
	URL  string
}

// ListRepos lists the googet repos configured on the local system.
func ListRepos(conf *Config) ([]Repo, error) {
	if conf == nil {
		conf = NewConfig()
	}

	repos := []Repo{}
	args := []string{"listrepos"}
	res, err := funcExec(conf.GooGetExe, args, &conf.ExecConfig)
	if err != nil {
		return repos, err
	}

	lines := strings.Split(string(res.Stdout), "\n")
	for i, r := range lines {
		r = strings.TrimSpace(r)
		if i == 0 || r == "" {
			continue
		}
		if !strings.Contains(r, "http") { // skip repo file lines
			continue
		}
		dat := strings.SplitN(r, ":", 2)
		if len(dat) < 2 {
			logger.Errorf("unable to parse googet output %v", r)
			continue
		}
		repos = append(repos, Repo{
			Name: strings.TrimSpace(dat[0]),
			URL:  strings.TrimSpace(dat[1]),
		})
	}
	return repos, nil
}

// PackageVersion attempts to retrieve the current version
// of the named package from the local system.
func PackageVersion(pkg string) (string, error) {
	conf := NewConfig()

	out, err := funcExec(conf.GooGetExe, []string{"installed", pkg}, &conf.ExecConfig)
	if err != nil {
		if errors.Is(err, helpers.ErrTimeout) {
			return "unknown", fmt.Errorf("execution timed out after %v", conf.ExecConfig.Timeout)
		}
		return "unknown", err
	}
	ver := regexp.MustCompile(`[\d\.\-]+@[\d]+`).Find(out.Stdout)
	return string(ver), nil
}

// Remove removes a Googet package.
func Remove(pkg string, dbOnly bool, conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}

	args := []string{"-noconfirm", "remove"}
	if dbOnly {
		args = append(args, "-db_only")
	}
	args = append(args, pkg)

	return call(args, conf)
}

// RemoveRepo removes a Googet repository.
func RemoveRepo(repo string, conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}

	return call([]string{"rmrepo", repo}, conf)
}

// Update updates all googet packages.
func Update(conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}
	args := []string{"-noconfirm", "update"}

	return call(args, conf)
}
<commit_msg>chore: Log the googet command that is being executed<commit_after>// Copyright 2021 Google LLC
//
// 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 googet provides googet interoperability.
package googet

import (
	"errors"
	"fmt"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/google/logger"
	"github.com/google/glazier/go/helpers"
)

var (
	// Test Helpers
	funcExec = helpers.Exec
)

// Config provides the ability to customize GooGet behavior.
type Config struct {
	GooGetExe  string
	ExecConfig helpers.ExecConfig
}

// NewConfig generates a new Config object.
func NewConfig() *Config {
	timeout := 10 * time.Minute
	return &Config{
		GooGetExe: os.Getenv("GooGetRoot") + `\googet.exe`,
		ExecConfig: helpers.ExecConfig{
			Timeout:  &timeout,
			Verifier: helpers.NewExecVerifier(),
		},
	}
}

func call(args []string, conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}

	res, err := funcExec(conf.GooGetExe, args, &conf.ExecConfig)
	logger.Infof("Executing: %s %v", conf.GooGetExe, args)
	if err != nil {
		logger.Errorf("googet stdout: %v", string(res.Stdout))
		logger.Errorf("googet stderr: %v", string(res.Stderr))
		if errors.Is(err, helpers.ErrTimeout) {
			return fmt.Errorf("execution timed out after %v", conf.ExecConfig.Timeout)
		}
	}
	return err
}

// AddRepo adds a googet repository to the local system.
func AddRepo(name, url string, conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}

	if name == "" || url == "" {
		return errors.New("must specify name and url")
	}

	return call([]string{"addrepo", name, url}, conf)
}

// Install installs a Googet package.
func Install(pkg, sources string, reinstall bool, conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}

	cmd := []string{"-noconfirm", "install"}
	if reinstall {
		cmd = append(cmd, "--reinstall")
	}
	if sources != "" {
		cmd = append(cmd, "--sources", sources)
	}
	cmd = append(cmd, pkg)

	return call(cmd, conf)
}

// Package represents a GooGet package.
type Package struct {
	Name    string
	Version string
}

// Installed lists installed package(s).
func Installed(initial string, conf *Config) ([]Package, error) {
	if conf == nil {
		conf = NewConfig()
	}

	p := []Package{}
	args := []string{"installed", initial}
	res, err := funcExec(conf.GooGetExe, args, &conf.ExecConfig)
	if err != nil {
		return p, err
	}
	lines := strings.Split(string(res.Stdout), "\n")
	for i, r := range lines {
		r = strings.TrimSpace(r)
		if i == 0 || r == "" {
			continue
		}
		dat := strings.Fields(r)
		if len(dat) < 2 {
			logger.Errorf("unable to parse googet output %v", r)
			continue
		}
		p = append(p, Package{
			Name:    dat[0],
			Version: dat[1],
		})
	}

	return p, nil
}

// Repo represents a googet repository configuration.
type Repo struct {
	Name string
	URL  string
}

// ListRepos lists the googet repos configured on the local system.
func ListRepos(conf *Config) ([]Repo, error) {
	if conf == nil {
		conf = NewConfig()
	}

	repos := []Repo{}
	args := []string{"listrepos"}
	res, err := funcExec(conf.GooGetExe, args, &conf.ExecConfig)
	if err != nil {
		return repos, err
	}

	lines := strings.Split(string(res.Stdout), "\n")
	for i, r := range lines {
		r = strings.TrimSpace(r)
		if i == 0 || r == "" {
			continue
		}
		if !strings.Contains(r, "http") { // skip repo file lines
			continue
		}
		dat := strings.SplitN(r, ":", 2)
		if len(dat) < 2 {
			logger.Errorf("unable to parse googet output %v", r)
			continue
		}
		repos = append(repos, Repo{
			Name: strings.TrimSpace(dat[0]),
			URL:  strings.TrimSpace(dat[1]),
		})
	}
	return repos, nil
}

// PackageVersion attempts to retrieve the current version
// of the named package from the local system.
func PackageVersion(pkg string) (string, error) {
	conf := NewConfig()

	out, err := funcExec(conf.GooGetExe, []string{"installed", pkg}, &conf.ExecConfig)
	if err != nil {
		if errors.Is(err, helpers.ErrTimeout) {
			return "unknown", fmt.Errorf("execution timed out after %v", conf.ExecConfig.Timeout)
		}
		return "unknown", err
	}
	ver := regexp.MustCompile(`[\d\.\-]+@[\d]+`).Find(out.Stdout)
	return string(ver), nil
}

// Remove removes a Googet package.
func Remove(pkg string, dbOnly bool, conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}

	args := []string{"-noconfirm", "remove"}
	if dbOnly {
		args = append(args, "-db_only")
	}
	args = append(args, pkg)

	return call(args, conf)
}

// RemoveRepo removes a Googet repository.
func RemoveRepo(repo string, conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}

	return call([]string{"rmrepo", repo}, conf)
}

// Update updates all googet packages.
func Update(conf *Config) error {
	if conf == nil {
		conf = NewConfig()
	}
	args := []string{"-noconfirm", "update"}

	return call(args, conf)
}
<|endoftext|>