<commit_before>// 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 spinner is a simple package to add a spinner / progress indicator to any terminal application.
package spinner

import (
	"errors"
	"fmt"
	"io"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
	"unicode/utf8"

	"github.com/fatih/color"
)

// errInvalidColor is returned when attempting to set an invalid color
var errInvalidColor = errors.New("invalid color")

// validColors holds an array of the only colors allowed
var validColors = map[string]bool{
	// default colors for backwards compatibility
	"black":   true,
	"red":     true,
	"green":   true,
	"yellow":  true,
	"blue":    true,
	"magenta": true,
	"cyan":    true,
	"white":   true,

	// attributes
	"reset":        true,
	"bold":         true,
	"faint":        true,
	"italic":       true,
	"underline":    true,
	"blinkslow":    true,
	"blinkrapid":   true,
	"reversevideo": true,
	"concealed":    true,
	"crossedout":   true,

	// foreground text
	"fgBlack":   true,
	"fgRed":     true,
	"fgGreen":   true,
	"fgYellow":  true,
	"fgBlue":    true,
	"fgMagenta": true,
	"fgCyan":    true,
	"fgWhite":   true,

	// foreground Hi-Intensity text
	"fgHiBlack":   true,
	"fgHiRed":     true,
	"fgHiGreen":   true,
	"fgHiYellow":  true,
	"fgHiBlue":    true,
	"fgHiMagenta": true,
	"fgHiCyan":    true,
	"fgHiWhite":   true,

	// background text
	"bgBlack":   true,
	"bgRed":     true,
	"bgGreen":   true,
	"bgYellow":  true,
	"bgBlue":    true,
	"bgMagenta": true,
	"bgCyan":    true,
	"bgWhite":   true,

	// background Hi-Intensity text
	"bgHiBlack":   true,
	"bgHiRed":     true,
	"bgHiGreen":   true,
	"bgHiYellow":  true,
	"bgHiBlue":    true,
	"bgHiMagenta": true,
	"bgHiCyan":    true,
	"bgHiWhite":   true,
}

// returns a valid color's foreground text color attribute
var colorAttributeMap = map[string]color.Attribute{
	// default colors for backwards compatibility
	"black":   color.FgBlack,
	"red":     color.FgRed,
	"green":   color.FgGreen,
	"yellow":  color.FgYellow,
	"blue":    color.FgBlue,
	"magenta": color.FgMagenta,
	"cyan":    color.FgCyan,
	"white":   color.FgWhite,

	// attributes
	"reset":        color.Reset,
	"bold":         color.Bold,
	"faint":        color.Faint,
	"italic":       color.Italic,
	"underline":    color.Underline,
	"blinkslow":    color.BlinkSlow,
	"blinkrapid":   color.BlinkRapid,
	"reversevideo": color.ReverseVideo,
	"concealed":    color.Concealed,
	"crossedout":   color.CrossedOut,

	// foreground text colors
	"fgBlack":   color.FgBlack,
	"fgRed":     color.FgRed,
	"fgGreen":   color.FgGreen,
	"fgYellow":  color.FgYellow,
	"fgBlue":    color.FgBlue,
	"fgMagenta": color.FgMagenta,
	"fgCyan":    color.FgCyan,
	"fgWhite":   color.FgWhite,

	// foreground Hi-Intensity text colors
	"fgHiBlack":   color.FgHiBlack,
	"fgHiRed":     color.FgHiRed,
	"fgHiGreen":   color.FgHiGreen,
	"fgHiYellow":  color.FgHiYellow,
	"fgHiBlue":    color.FgHiBlue,
	"fgHiMagenta": color.FgHiMagenta,
	"fgHiCyan":    color.FgHiCyan,
	"fgHiWhite":   color.FgHiWhite,

	// background text colors
	"bgBlack":   color.BgBlack,
	"bgRed":     color.BgRed,
	"bgGreen":   color.BgGreen,
	"bgYellow":  color.BgYellow,
	"bgBlue":    color.BgBlue,
	"bgMagenta": color.BgMagenta,
	"bgCyan":    color.BgCyan,
	"bgWhite":   color.BgWhite,

	// background Hi-Intensity text colors
	"bgHiBlack":   color.BgHiBlack,
	"bgHiRed":     color.BgHiRed,
	"bgHiGreen":   color.BgHiGreen,
	"bgHiYellow":  color.BgHiYellow,
	"bgHiBlue":    color.BgHiBlue,
	"bgHiMagenta": color.BgHiMagenta,
	"bgHiCyan":    color.BgHiCyan,
	"bgHiWhite":   color.BgHiWhite,
}

// validColor will make sure the given color is actually allowed.
func validColor(c string) bool {
	return validColors[c]
}

// Spinner struct to hold the provided options.
type Spinner struct {
	mu         *sync.RWMutex                 //
	Delay      time.Duration                 // Delay is the speed of the indicator
	chars      []string                      // chars holds the chosen character set
	Prefix     string                        // Prefix is the text preppended to the indicator
	Suffix     string                        // Suffix is the text appended to the indicator
	FinalMSG   string                        // string displayed after Stop() is called
	lastOutput string                        // last character(set) written
	color      func(a ...interface{}) string // default color is white
	Writer     io.Writer                     // to make testing better, exported so users have access. Use `WithWriter` to update after initialization.
	active     bool                          // active holds the state of the spinner
	stopChan   chan struct{}                 // stopChan is a channel used to stop the indicator
	HideCursor bool                          // hideCursor determines if the cursor is visible
	PreUpdate  func(s *Spinner)              // will be triggered before every spinner update
	PostUpdate func(s *Spinner)              // will be triggered after every spinner update
}

// New provides a pointer to an instance of Spinner with the supplied options.
func New(cs []string, d time.Duration, options ...Option) *Spinner {
	s := &Spinner{
		Delay:    d,
		chars:    cs,
		color:    color.New(color.FgWhite).SprintFunc(),
		mu:       &sync.RWMutex{},
		Writer:   color.Output,
		active:   false,
		stopChan: make(chan struct{}, 1),
	}

	for _, option := range options {
		option(s)
	}
	return s
}

// Option is a function that takes a spinner and applies
// a given configuration.
type Option func(*Spinner)

// Options contains fields to configure the spinner.
type Options struct {
	Color      string
	Suffix     string
	FinalMSG   string
	HideCursor bool
}

// WithColor adds the given color to the spinner.
func WithColor(color string) Option {
	return func(s *Spinner) {
		s.Color(color)
	}
}

// WithSuffix adds the given string to the spinner
// as the suffix.
func WithSuffix(suffix string) Option {
	return func(s *Spinner) {
		s.Suffix = suffix
	}
}

// WithFinalMSG adds the given string ot the spinner
// as the final message to be written.
func WithFinalMSG(finalMsg string) Option {
	return func(s *Spinner) {
		s.FinalMSG = finalMsg
	}
}

// WithHiddenCursor hides the cursor
// if hideCursor = true given.
func WithHiddenCursor(hideCursor bool) Option {
	return func(s *Spinner) {
		s.HideCursor = hideCursor
	}
}

// WithWriter adds the given writer to the spinner. This
// function should be favored over directly assigning to
// the struct value.
func WithWriter(w io.Writer) Option {
	return func(s *Spinner) {
		s.mu.Lock()
		s.Writer = w
		s.mu.Unlock()
	}
}

// Active will return whether or not the spinner is currently active.
func (s *Spinner) Active() bool {
	return s.active
}

// Start will start the indicator.
func (s *Spinner) Start() {
	s.mu.Lock()
	if s.active {
		s.mu.Unlock()
		return
	}
	if s.HideCursor && runtime.GOOS != "windows" {
		// hides the cursor
		fmt.Fprint(s.Writer, "\033[?25l")
	}
	s.active = true
	s.mu.Unlock()

	go func() {
		for {
			for i := 0; i < len(s.chars); i++ {
				select {
				case <-s.stopChan:
					return
				default:
					s.mu.Lock()
					if !s.active {
						s.mu.Unlock()
						return
					}
					s.erase()

					if s.PreUpdate != nil {
						s.PreUpdate(s)
					}

					var outColor string
					if runtime.GOOS == "windows" {
						if s.Writer == os.Stderr {
							outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
						} else {
							outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
						}
					} else {
						outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
					}
					outPlain := fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
					fmt.Fprint(s.Writer, outColor)
					s.lastOutput = outPlain
					delay := s.Delay

					if s.PostUpdate != nil {
						s.PostUpdate(s)
					}

					s.mu.Unlock()
					time.Sleep(delay)
				}
			}
		}
	}()
}

// Stop stops the indicator.
func (s *Spinner) Stop() {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.active {
		s.active = false
		if s.HideCursor && runtime.GOOS != "windows" {
			// makes the cursor visible
			fmt.Fprint(s.Writer, "\033[?25h")
		}
		s.erase()
		if s.FinalMSG != "" {
			fmt.Fprint(s.Writer, s.FinalMSG)
		}
		s.stopChan <- struct{}{}
	}
}

// Restart will stop and start the indicator.
func (s *Spinner) Restart() {
	s.Stop()
	s.Start()
}

// Reverse will reverse the order of the slice assigned to the indicator.
func (s *Spinner) Reverse() {
	s.mu.Lock()
	for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
		s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
	}
	s.mu.Unlock()
}

// Color will set the struct field for the given color to be used. The spinner
// will need to be explicitly restarted.
func (s *Spinner) Color(colors ...string) error {
	colorAttributes := make([]color.Attribute, len(colors))

	// Verify colours are valid and place the appropriate attribute in the array
	for index, c := range colors {
		if !validColor(c) {
			return errInvalidColor
		}
		colorAttributes[index] = colorAttributeMap[c]
	}

	s.mu.Lock()
	s.color = color.New(colorAttributes...).SprintFunc()
	s.mu.Unlock()
	return nil
}

// UpdateSpeed will set the indicator delay to the given value.
func (s *Spinner) UpdateSpeed(d time.Duration) {
	s.mu.Lock()
	s.Delay = d
	s.mu.Unlock()
}

// UpdateCharSet will change the current character set to the given one.
func (s *Spinner) UpdateCharSet(cs []string) {
	s.mu.Lock()
	s.chars = cs
	s.mu.Unlock()
}

// erase deletes written characters.
// Caller must already hold s.lock.
func (s *Spinner) erase() {
	n := utf8.RuneCountInString(s.lastOutput)
	if runtime.GOOS == "windows" {
		clearString := "\r" + strings.Repeat(" ", n) + "\r"
		fmt.Fprint(s.Writer, clearString)
		s.lastOutput = ""
		return
	}
	fmt.Fprintf(s.Writer, "\r\033[K") // erases to end of line
	s.lastOutput = ""
}

// Lock allows for manual control to lock the spinner.
func (s *Spinner) Lock() {
	s.mu.Lock()
}

// Unlock allows for manual control to unlock the spinner.
func (s *Spinner) Unlock() {
	s.mu.Unlock()
}

// GenerateNumberSequence will generate a slice of integers at the
// provided length and convert them each to a string.
func GenerateNumberSequence(length int) []string {
	numSeq := make([]string, length)
	for i := 0; i < length; i++ {
		numSeq[i] = strconv.Itoa(i)
	}
	return numSeq
}
<commit_msg>fix flashing during erase() on windows.<commit_after>// 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 spinner is a simple package to add a spinner / progress indicator to any terminal application.
package spinner

import (
	"errors"
	"fmt"
	"io"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
	"unicode/utf8"

	"github.com/fatih/color"
)

// errInvalidColor is returned when attempting to set an invalid color
var errInvalidColor = errors.New("invalid color")

// validColors holds an array of the only colors allowed
var validColors = map[string]bool{
	// default colors for backwards compatibility
	"black":   true,
	"red":     true,
	"green":   true,
	"yellow":  true,
	"blue":    true,
	"magenta": true,
	"cyan":    true,
	"white":   true,

	// attributes
	"reset":        true,
	"bold":         true,
	"faint":        true,
	"italic":       true,
	"underline":    true,
	"blinkslow":    true,
	"blinkrapid":   true,
	"reversevideo": true,
	"concealed":    true,
	"crossedout":   true,

	// foreground text
	"fgBlack":   true,
	"fgRed":     true,
	"fgGreen":   true,
	"fgYellow":  true,
	"fgBlue":    true,
	"fgMagenta": true,
	"fgCyan":    true,
	"fgWhite":   true,

	// foreground Hi-Intensity text
	"fgHiBlack":   true,
	"fgHiRed":     true,
	"fgHiGreen":   true,
	"fgHiYellow":  true,
	"fgHiBlue":    true,
	"fgHiMagenta": true,
	"fgHiCyan":    true,
	"fgHiWhite":   true,

	// background text
	"bgBlack":   true,
	"bgRed":     true,
	"bgGreen":   true,
	"bgYellow":  true,
	"bgBlue":    true,
	"bgMagenta": true,
	"bgCyan":    true,
	"bgWhite":   true,

	// background Hi-Intensity text
	"bgHiBlack":   true,
	"bgHiRed":     true,
	"bgHiGreen":   true,
	"bgHiYellow":  true,
	"bgHiBlue":    true,
	"bgHiMagenta": true,
	"bgHiCyan":    true,
	"bgHiWhite":   true,
}

// returns a valid color's foreground text color attribute
var colorAttributeMap = map[string]color.Attribute{
	// default colors for backwards compatibility
	"black":   color.FgBlack,
	"red":     color.FgRed,
	"green":   color.FgGreen,
	"yellow":  color.FgYellow,
	"blue":    color.FgBlue,
	"magenta": color.FgMagenta,
	"cyan":    color.FgCyan,
	"white":   color.FgWhite,

	// attributes
	"reset":        color.Reset,
	"bold":         color.Bold,
	"faint":        color.Faint,
	"italic":       color.Italic,
	"underline":    color.Underline,
	"blinkslow":    color.BlinkSlow,
	"blinkrapid":   color.BlinkRapid,
	"reversevideo": color.ReverseVideo,
	"concealed":    color.Concealed,
	"crossedout":   color.CrossedOut,

	// foreground text colors
	"fgBlack":   color.FgBlack,
	"fgRed":     color.FgRed,
	"fgGreen":   color.FgGreen,
	"fgYellow":  color.FgYellow,
	"fgBlue":    color.FgBlue,
	"fgMagenta": color.FgMagenta,
	"fgCyan":    color.FgCyan,
	"fgWhite":   color.FgWhite,

	// foreground Hi-Intensity text colors
	"fgHiBlack":   color.FgHiBlack,
	"fgHiRed":     color.FgHiRed,
	"fgHiGreen":   color.FgHiGreen,
	"fgHiYellow":  color.FgHiYellow,
	"fgHiBlue":    color.FgHiBlue,
	"fgHiMagenta": color.FgHiMagenta,
	"fgHiCyan":    color.FgHiCyan,
	"fgHiWhite":   color.FgHiWhite,

	// background text colors
	"bgBlack":   color.BgBlack,
	"bgRed":     color.BgRed,
	"bgGreen":   color.BgGreen,
	"bgYellow":  color.BgYellow,
	"bgBlue":    color.BgBlue,
	"bgMagenta": color.BgMagenta,
	"bgCyan":    color.BgCyan,
	"bgWhite":   color.BgWhite,

	// background Hi-Intensity text colors
	"bgHiBlack":   color.BgHiBlack,
	"bgHiRed":     color.BgHiRed,
	"bgHiGreen":   color.BgHiGreen,
	"bgHiYellow":  color.BgHiYellow,
	"bgHiBlue":    color.BgHiBlue,
	"bgHiMagenta": color.BgHiMagenta,
	"bgHiCyan":    color.BgHiCyan,
	"bgHiWhite":   color.BgHiWhite,
}

// validColor will make sure the given color is actually allowed.
func validColor(c string) bool {
	return validColors[c]
}

// Spinner struct to hold the provided options.
type Spinner struct {
	mu         *sync.RWMutex                 //
	Delay      time.Duration                 // Delay is the speed of the indicator
	chars      []string                      // chars holds the chosen character set
	Prefix     string                        // Prefix is the text preppended to the indicator
	Suffix     string                        // Suffix is the text appended to the indicator
	FinalMSG   string                        // string displayed after Stop() is called
	lastOutput string                        // last character(set) written
	color      func(a ...interface{}) string // default color is white
	Writer     io.Writer                     // to make testing better, exported so users have access. Use `WithWriter` to update after initialization.
	active     bool                          // active holds the state of the spinner
	stopChan   chan struct{}                 // stopChan is a channel used to stop the indicator
	HideCursor bool                          // hideCursor determines if the cursor is visible
	PreUpdate  func(s *Spinner)              // will be triggered before every spinner update
	PostUpdate func(s *Spinner)              // will be triggered after every spinner update
}

// New provides a pointer to an instance of Spinner with the supplied options.
func New(cs []string, d time.Duration, options ...Option) *Spinner {
	s := &Spinner{
		Delay:    d,
		chars:    cs,
		color:    color.New(color.FgWhite).SprintFunc(),
		mu:       &sync.RWMutex{},
		Writer:   color.Output,
		active:   false,
		stopChan: make(chan struct{}, 1),
	}

	for _, option := range options {
		option(s)
	}
	return s
}

// Option is a function that takes a spinner and applies
// a given configuration.
type Option func(*Spinner)

// Options contains fields to configure the spinner.
type Options struct {
	Color      string
	Suffix     string
	FinalMSG   string
	HideCursor bool
}

// WithColor adds the given color to the spinner.
func WithColor(color string) Option {
	return func(s *Spinner) {
		s.Color(color)
	}
}

// WithSuffix adds the given string to the spinner
// as the suffix.
func WithSuffix(suffix string) Option {
	return func(s *Spinner) {
		s.Suffix = suffix
	}
}

// WithFinalMSG adds the given string ot the spinner
// as the final message to be written.
func WithFinalMSG(finalMsg string) Option {
	return func(s *Spinner) {
		s.FinalMSG = finalMsg
	}
}

// WithHiddenCursor hides the cursor
// if hideCursor = true given.
func WithHiddenCursor(hideCursor bool) Option {
	return func(s *Spinner) {
		s.HideCursor = hideCursor
	}
}

// WithWriter adds the given writer to the spinner. This
// function should be favored over directly assigning to
// the struct value.
func WithWriter(w io.Writer) Option {
	return func(s *Spinner) {
		s.mu.Lock()
		s.Writer = w
		s.mu.Unlock()
	}
}

// Active will return whether or not the spinner is currently active.
func (s *Spinner) Active() bool {
	return s.active
}

// Start will start the indicator.
func (s *Spinner) Start() {
	s.mu.Lock()
	if s.active {
		s.mu.Unlock()
		return
	}
	if s.HideCursor && len(os.Getenv("WT_SESSION")) == 0 {
		// hides the cursor
		fmt.Fprint(s.Writer, "\033[?25l")
	}
	s.active = true
	s.mu.Unlock()

	go func() {
		for {
			for i := 0; i < len(s.chars); i++ {
				select {
				case <-s.stopChan:
					return
				default:
					s.mu.Lock()
					if !s.active {
						s.mu.Unlock()
						return
					}
					if len(os.Getenv("WT_SESSION")) == 0 {
						s.erase()
					}

					if s.PreUpdate != nil {
						s.PreUpdate(s)
					}

					var outColor string
					if runtime.GOOS == "windows" {
						if s.Writer == os.Stderr {
							outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
						} else {
							outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
						}
					} else {
						outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
					}
					outPlain := fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
					fmt.Fprint(s.Writer, outColor)
					s.lastOutput = outPlain
					delay := s.Delay

					if s.PostUpdate != nil {
						s.PostUpdate(s)
					}

					s.mu.Unlock()
					time.Sleep(delay)
				}
			}
		}
	}()
}

// Stop stops the indicator.
func (s *Spinner) Stop() {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.active {
		s.active = false
		if s.HideCursor && len(os.Getenv("WT_SESSION")) == 0 {
			// makes the cursor visible
			fmt.Fprint(s.Writer, "\033[?25h")
		}
		s.erase()
		if s.FinalMSG != "" {
			if runtime.GOOS == "windows"  && len(os.Getenv("WT_SESSION")) > 0{
				fmt.Fprint(s.Writer, "\r", s.FinalMSG)
			}else{
				fmt.Fprint(s.Writer, s.FinalMSG)
			}
		}
		s.stopChan <- struct{}{}
	}
}

// Restart will stop and start the indicator.
func (s *Spinner) Restart() {
	s.Stop()
	s.Start()
}

// Reverse will reverse the order of the slice assigned to the indicator.
func (s *Spinner) Reverse() {
	s.mu.Lock()
	for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
		s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
	}
	s.mu.Unlock()
}

// Color will set the struct field for the given color to be used. The spinner
// will need to be explicitly restarted.
func (s *Spinner) Color(colors ...string) error {
	colorAttributes := make([]color.Attribute, len(colors))

	// Verify colours are valid and place the appropriate attribute in the array
	for index, c := range colors {
		if !validColor(c) {
			return errInvalidColor
		}
		colorAttributes[index] = colorAttributeMap[c]
	}

	s.mu.Lock()
	s.color = color.New(colorAttributes...).SprintFunc()
	s.mu.Unlock()
	return nil
}

// UpdateSpeed will set the indicator delay to the given value.
func (s *Spinner) UpdateSpeed(d time.Duration) {
	s.mu.Lock()
	s.Delay = d
	s.mu.Unlock()
}

// UpdateCharSet will change the current character set to the given one.
func (s *Spinner) UpdateCharSet(cs []string) {
	s.mu.Lock()
	s.chars = cs
	s.mu.Unlock()
}

// erase deletes written characters.
// Caller must already hold s.lock.
func (s *Spinner) erase() {
	n := utf8.RuneCountInString(s.lastOutput)
	if runtime.GOOS == "windows" && len(os.Getenv("WT_SESSION")) == 0 {
		clearString := "\r" + strings.Repeat(" ", n) + "\r"
		fmt.Fprint(s.Writer, clearString)
		s.lastOutput = ""
		return
	}
	fmt.Fprintf(s.Writer, "\r\033[K") // erases to end of line
	s.lastOutput = ""
}

// Lock allows for manual control to lock the spinner.
func (s *Spinner) Lock() {
	s.mu.Lock()
}

// Unlock allows for manual control to unlock the spinner.
func (s *Spinner) Unlock() {
	s.mu.Unlock()
}

// GenerateNumberSequence will generate a slice of integers at the
// provided length and convert them each to a string.
func GenerateNumberSequence(length int) []string {
	numSeq := make([]string, length)
	for i := 0; i < length; i++ {
		numSeq[i] = strconv.Itoa(i)
	}
	return numSeq
}
<|endoftext|>


<commit_before>// Package ieeeoui implements a library to know manufacturer of a MAC (IEEE public OUI).
package ieeeoui

import (
	"bufio"
	"errors"
	"io"
	"log"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"sync"
)

const (
	ouiURL = "http://standards.ieee.org/develop/regauth/oui/oui.txt"
)

var (
	ouiRE = regexp.MustCompile(`^([\da-fA-F-]+)\s+\(hex\)\s+(.*)$`)
	rp    = strings.NewReplacer("-", "", ":", "")
)

func New() *Resolver {
	r := Resolver{manufacturer: make(map[int64]string)}
	r.Add(1)
	go r.init()
	return &r
}

type Resolver struct {
	sync.Mutex     // to protect WaitGroup for Add
	sync.WaitGroup // to have Find wait for init to complete
	manufacturer   map[int64]string
	err            error
}

func (r *Resolver) init() {
	defer r.Done()
	resp, err := http.Get(ouiURL)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	rd := bufio.NewReader(resp.Body)
	for {
		line, err := rd.ReadString('\n')
		if err == io.EOF {
			break
		} else if err != nil {
			log.Println("ieeeoui: error reading OUI data: ", err)
			r.err = errors.New("could not read OUI data")
			return
		}
		m := ouiRE.FindStringSubmatch(strings.TrimSpace(line))
		if m == nil {
			continue
		}
		oui, err := strconv.ParseInt(rp.Replace(m[1]), 16, 0)
		if err != nil {
			continue
		}
		if v, ok := r.manufacturer[oui]; ok {
			r.manufacturer[oui] = v + " / " + m[2]
		} else {
			r.manufacturer[oui] = m[2]
		}
	}
	if len(r.manufacturer) == 0 {
		log.Println("ieeeoui: OUI data empty, parsing error?")
		r.err = errors.New("could not parse OUI data")
		return
	}
	r.err = nil
}

func (r *Resolver) Find(address string) (string, error) {
	oui, err := strconv.ParseInt(rp.Replace(address)[:6], 16, 0)
	if err != nil {
		return "", errors.New("invalid address")
	}
	r.Lock()
	defer r.Unlock()
	r.Wait()
	if r.err != nil {
		r.Add(1)
		go r.init() // retry
		return "", r.err
	}
	manufacturer, found := r.manufacturer[oui]
	if !found {
		return "", errors.New("not found")
	}
	return manufacturer, nil
}
<commit_msg>lib: ieeeoui: minor fmt fix<commit_after>// Package ieeeoui implements a library to know manufacturer of a MAC (IEEE public OUI).
package ieeeoui

import (
	"bufio"
	"errors"
	"io"
	"log"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"sync"
)

const ouiURL = "http://standards.ieee.org/develop/regauth/oui/oui.txt"

var (
	ouiRE = regexp.MustCompile(`^([\da-fA-F-]+)\s+\(hex\)\s+(.*)$`)
	rp    = strings.NewReplacer("-", "", ":", "")
)

func New() *Resolver {
	r := Resolver{manufacturer: make(map[int64]string)}
	r.Add(1)
	go r.init()
	return &r
}

type Resolver struct {
	sync.Mutex     // to protect WaitGroup for Add
	sync.WaitGroup // to have Find wait for init to complete
	manufacturer   map[int64]string
	err            error
}

func (r *Resolver) init() {
	defer r.Done()
	resp, err := http.Get(ouiURL)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	rd := bufio.NewReader(resp.Body)
	for {
		line, err := rd.ReadString('\n')
		if err == io.EOF {
			break
		} else if err != nil {
			log.Println("ieeeoui: error reading OUI data: ", err)
			r.err = errors.New("could not read OUI data")
			return
		}
		m := ouiRE.FindStringSubmatch(strings.TrimSpace(line))
		if m == nil {
			continue
		}
		oui, err := strconv.ParseInt(rp.Replace(m[1]), 16, 0)
		if err != nil {
			continue
		}
		if v, ok := r.manufacturer[oui]; ok {
			r.manufacturer[oui] = v + " / " + m[2]
		} else {
			r.manufacturer[oui] = m[2]
		}
	}
	if len(r.manufacturer) == 0 {
		log.Println("ieeeoui: OUI data empty, parsing error?")
		r.err = errors.New("could not parse OUI data")
		return
	}
	r.err = nil
}

func (r *Resolver) Find(address string) (string, error) {
	oui, err := strconv.ParseInt(rp.Replace(address)[:6], 16, 0)
	if err != nil {
		return "", errors.New("invalid address")
	}
	r.Lock()
	defer r.Unlock()
	r.Wait()
	if r.err != nil {
		r.Add(1)
		go r.init() // retry
		return "", r.err
	}
	manufacturer, found := r.manufacturer[oui]
	if !found {
		return "", errors.New("not found")
	}
	return manufacturer, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 spanner

import (
	"bytes"
	"context"
	"encoding/gob"
	"log"
	"time"

	"github.com/golang/protobuf/proto"
	sppb "google.golang.org/genproto/googleapis/spanner/v1"
)

// BatchReadOnlyTransaction is a ReadOnlyTransaction that allows for exporting
// arbitrarily large amounts of data from Cloud Spanner databases.
// BatchReadOnlyTransaction partitions a read/query request. Read/query request
// can then be executed independently over each partition while observing the
// same snapshot of the database. BatchReadOnlyTransaction can also be shared
// across multiple clients by passing around the BatchReadOnlyTransactionID and
// then recreating the transaction using Client.BatchReadOnlyTransactionFromID.
//
// Note: if a client is used only to run partitions, you can
// create it using a ClientConfig with both MinOpened and MaxIdle set to
// zero to avoid creating unnecessary sessions. You can also avoid excess
// gRPC channels by setting ClientConfig.NumChannels to the number of
// concurrently active BatchReadOnlyTransactions you expect to have.
type BatchReadOnlyTransaction struct {
	ReadOnlyTransaction
	ID BatchReadOnlyTransactionID
}

// BatchReadOnlyTransactionID is a unique identifier for a
// BatchReadOnlyTransaction. It can be used to re-create a
// BatchReadOnlyTransaction on a different machine or process by calling
// Client.BatchReadOnlyTransactionFromID.
type BatchReadOnlyTransactionID struct {
	// unique ID for the transaction.
	tid transactionID
	// sid is the id of the Cloud Spanner session used for this transaction.
	sid string
	// rts is the read timestamp of this transaction.
	rts time.Time
}

// Partition defines a segment of data to be read in a batch read or query. A
// partition can be serialized and processed across several different machines
// or processes.
type Partition struct {
	pt   []byte
	qreq *sppb.ExecuteSqlRequest
	rreq *sppb.ReadRequest
}

// PartitionOptions specifies options for a PartitionQueryRequest and
// PartitionReadRequest. See
// https://godoc.org/google.golang.org/genproto/googleapis/spanner/v1#PartitionOptions
// for more details.
type PartitionOptions struct {
	// The desired data size for each partition generated.
	PartitionBytes int64
	// The desired maximum number of partitions to return.
	MaxPartitions int64
}

// toProto converts a spanner.PartitionOptions into a sppb.PartitionOptions
func (opt PartitionOptions) toProto() *sppb.PartitionOptions {
	return &sppb.PartitionOptions{
		PartitionSizeBytes: opt.PartitionBytes,
		MaxPartitions:      opt.MaxPartitions,
	}
}

// PartitionRead returns a list of Partitions that can be used to read rows from
// the database. These partitions can be executed across multiple processes,
// even across different machines. The partition size and count hints can be
// configured using PartitionOptions.
func (t *BatchReadOnlyTransaction) PartitionRead(ctx context.Context, table string, keys KeySet, columns []string, opt PartitionOptions) ([]*Partition, error) {
	return t.PartitionReadUsingIndex(ctx, table, "", keys, columns, opt)
}

// PartitionReadUsingIndex returns a list of Partitions that can be used to read
// rows from the database using an index.
func (t *BatchReadOnlyTransaction) PartitionReadUsingIndex(ctx context.Context, table, index string, keys KeySet, columns []string, opt PartitionOptions) ([]*Partition, error) {
	sh, ts, err := t.acquire(ctx)
	if err != nil {
		return nil, err
	}
	sid, client := sh.getID(), sh.getClient()
	var (
		kset       *sppb.KeySet
		resp       *sppb.PartitionResponse
		partitions []*Partition
	)
	kset, err = keys.keySetProto()
	// Request partitions.
	if err != nil {
		return nil, err
	}
	resp, err = client.PartitionRead(ctx, &sppb.PartitionReadRequest{
		Session:          sid,
		Transaction:      ts,
		Table:            table,
		Index:            index,
		Columns:          columns,
		KeySet:           kset,
		PartitionOptions: opt.toProto(),
	})
	// Prepare ReadRequest.
	req := &sppb.ReadRequest{
		Session:     sid,
		Transaction: ts,
		Table:       table,
		Index:       index,
		Columns:     columns,
		KeySet:      kset,
	}
	// Generate partitions.
	for _, p := range resp.GetPartitions() {
		partitions = append(partitions, &Partition{
			pt:   p.PartitionToken,
			rreq: req,
		})
	}
	return partitions, err
}

// PartitionQuery returns a list of Partitions that can be used to execute a
// query against the database.
func (t *BatchReadOnlyTransaction) PartitionQuery(ctx context.Context, statement Statement, opt PartitionOptions) ([]*Partition, error) {
	return t.partitionQuery(ctx, statement, opt, t.ReadOnlyTransaction.txReadOnly.qo)
}

// PartitionQueryWithOptions returns a list of Partitions that can be used to
// execute a query against the database. The sql query execution will be
// optimized based on the given query options.
func (t *BatchReadOnlyTransaction) PartitionQueryWithOptions(ctx context.Context, statement Statement, opt PartitionOptions, qOpts QueryOptions) ([]*Partition, error) {
	return t.partitionQuery(ctx, statement, opt, t.ReadOnlyTransaction.txReadOnly.qo.merge(qOpts))
}

func (t *BatchReadOnlyTransaction) partitionQuery(ctx context.Context, statement Statement, opt PartitionOptions, qOpts QueryOptions) ([]*Partition, error) {
	sh, ts, err := t.acquire(ctx)
	if err != nil {
		return nil, err
	}
	sid, client := sh.getID(), sh.getClient()
	params, paramTypes, err := statement.convertParams()
	if err != nil {
		return nil, err
	}

	// request Partitions
	req := &sppb.PartitionQueryRequest{
		Session:          sid,
		Transaction:      ts,
		Sql:              statement.SQL,
		PartitionOptions: opt.toProto(),
		Params:           params,
		ParamTypes:       paramTypes,
	}
	resp, err := client.PartitionQuery(ctx, req)

	// prepare ExecuteSqlRequest
	r := &sppb.ExecuteSqlRequest{
		Session:      sid,
		Transaction:  ts,
		Sql:          statement.SQL,
		Params:       params,
		ParamTypes:   paramTypes,
		QueryOptions: qOpts.Options,
	}

	// generate Partitions
	var partitions []*Partition
	for _, p := range resp.GetPartitions() {
		partitions = append(partitions, &Partition{
			pt:   p.PartitionToken,
			qreq: r,
		})
	}
	return partitions, err
}

// release implements txReadEnv.release, noop.
func (t *BatchReadOnlyTransaction) release(err error) {
}

// setTimestamp implements txReadEnv.setTimestamp, noop.
//
// read timestamp is ready on txn initialization, avoid contending writing to it
// with future partitions.
func (t *BatchReadOnlyTransaction) setTimestamp(ts time.Time) {
}

// Close marks the txn as closed.
func (t *BatchReadOnlyTransaction) Close() {
	t.mu.Lock()
	defer t.mu.Unlock()
	t.state = txClosed
}

// Cleanup cleans up all the resources used by this transaction and makes
// it unusable. Once this method is invoked, the transaction is no longer
// usable anywhere, including other clients/processes with which this
// transaction was shared.
//
// Calling Cleanup is optional, but recommended. If Cleanup is not called, the
// transaction's resources will be freed when the session expires on the backend
// and is deleted. For more information about recycled sessions, see
// https://cloud.google.com/spanner/docs/sessions.
func (t *BatchReadOnlyTransaction) Cleanup(ctx context.Context) {
	t.Close()
	t.mu.Lock()
	defer t.mu.Unlock()
	sh := t.sh
	if sh == nil {
		return
	}
	t.sh = nil
	sid, client := sh.getID(), sh.getClient()
	err := client.DeleteSession(contextWithOutgoingMetadata(ctx, sh.getMetadata()), &sppb.DeleteSessionRequest{Name: sid})
	if err != nil {
		var logger *log.Logger
		if sh.session != nil {
			logger = sh.session.logger
		}
		logf(logger, "Failed to delete session %v. Error: %v", sid, err)
	}
}

// Execute runs a single Partition obtained from PartitionRead or
// PartitionQuery.
func (t *BatchReadOnlyTransaction) Execute(ctx context.Context, p *Partition) *RowIterator {
	var (
		sh  *sessionHandle
		err error
		rpc func(ct context.Context, resumeToken []byte) (streamingReceiver, error)
	)
	if sh, _, err = t.acquire(ctx); err != nil {
		return &RowIterator{err: err}
	}
	client := sh.getClient()
	if client == nil {
		// Might happen if transaction is closed in the middle of a API call.
		return &RowIterator{err: errSessionClosed(sh)}
	}
	// Read or query partition.
	if p.rreq != nil {
		req := *p.rreq
		req.PartitionToken = p.pt
		rpc = func(ctx context.Context, resumeToken []byte) (streamingReceiver, error) {
			req.ResumeToken = resumeToken
			return client.StreamingRead(ctx, &req)
		}
	} else {
		req := *p.qreq
		req.PartitionToken = p.pt
		rpc = func(ctx context.Context, resumeToken []byte) (streamingReceiver, error) {
			req.ResumeToken = resumeToken
			return client.ExecuteStreamingSql(ctx, &req)
		}
	}
	return stream(
		contextWithOutgoingMetadata(ctx, sh.getMetadata()),
		sh.session.logger,
		rpc,
		t.setTimestamp,
		t.release)
}

// MarshalBinary implements BinaryMarshaler.
func (tid BatchReadOnlyTransactionID) MarshalBinary() (data []byte, err error) {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(tid.tid); err != nil {
		return nil, err
	}
	if err := enc.Encode(tid.sid); err != nil {
		return nil, err
	}
	if err := enc.Encode(tid.rts); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

// UnmarshalBinary implements BinaryUnmarshaler.
func (tid *BatchReadOnlyTransactionID) UnmarshalBinary(data []byte) error {
	dec := gob.NewDecoder(bytes.NewReader(data))
	if err := dec.Decode(&tid.tid); err != nil {
		return err
	}
	if err := dec.Decode(&tid.sid); err != nil {
		return err
	}
	return dec.Decode(&tid.rts)
}

// MarshalBinary implements BinaryMarshaler.
func (p Partition) MarshalBinary() (data []byte, err error) {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(p.pt); err != nil {
		return nil, err
	}
	var isReadPartition bool
	var req proto.Message
	if p.rreq != nil {
		isReadPartition = true
		req = p.rreq
	} else {
		isReadPartition = false
		req = p.qreq
	}
	if err := enc.Encode(isReadPartition); err != nil {
		return nil, err
	}
	if data, err = proto.Marshal(req); err != nil {
		return nil, err
	}
	if err := enc.Encode(data); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

// UnmarshalBinary implements BinaryUnmarshaler.
func (p *Partition) UnmarshalBinary(data []byte) error {
	var (
		isReadPartition bool
		d               []byte
		err             error
	)
	dec := gob.NewDecoder(bytes.NewReader(data))
	if err := dec.Decode(&p.pt); err != nil {
		return err
	}
	if err := dec.Decode(&isReadPartition); err != nil {
		return err
	}
	if err := dec.Decode(&d); err != nil {
		return err
	}
	if isReadPartition {
		p.rreq = &sppb.ReadRequest{}
		err = proto.Unmarshal(d, p.rreq)
	} else {
		p.qreq = &sppb.ExecuteSqlRequest{}
		err = proto.Unmarshal(d, p.qreq)
	}
	return err
}
<commit_msg>spanner: do not copy protobuf messages by value. (#2581)<commit_after>/*
Copyright 2018 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 spanner

import (
	"bytes"
	"context"
	"encoding/gob"
	"log"
	"time"

	"github.com/golang/protobuf/proto"
	sppb "google.golang.org/genproto/googleapis/spanner/v1"
)

// BatchReadOnlyTransaction is a ReadOnlyTransaction that allows for exporting
// arbitrarily large amounts of data from Cloud Spanner databases.
// BatchReadOnlyTransaction partitions a read/query request. Read/query request
// can then be executed independently over each partition while observing the
// same snapshot of the database. BatchReadOnlyTransaction can also be shared
// across multiple clients by passing around the BatchReadOnlyTransactionID and
// then recreating the transaction using Client.BatchReadOnlyTransactionFromID.
//
// Note: if a client is used only to run partitions, you can
// create it using a ClientConfig with both MinOpened and MaxIdle set to
// zero to avoid creating unnecessary sessions. You can also avoid excess
// gRPC channels by setting ClientConfig.NumChannels to the number of
// concurrently active BatchReadOnlyTransactions you expect to have.
type BatchReadOnlyTransaction struct {
	ReadOnlyTransaction
	ID BatchReadOnlyTransactionID
}

// BatchReadOnlyTransactionID is a unique identifier for a
// BatchReadOnlyTransaction. It can be used to re-create a
// BatchReadOnlyTransaction on a different machine or process by calling
// Client.BatchReadOnlyTransactionFromID.
type BatchReadOnlyTransactionID struct {
	// unique ID for the transaction.
	tid transactionID
	// sid is the id of the Cloud Spanner session used for this transaction.
	sid string
	// rts is the read timestamp of this transaction.
	rts time.Time
}

// Partition defines a segment of data to be read in a batch read or query. A
// partition can be serialized and processed across several different machines
// or processes.
type Partition struct {
	pt   []byte
	qreq *sppb.ExecuteSqlRequest
	rreq *sppb.ReadRequest
}

// PartitionOptions specifies options for a PartitionQueryRequest and
// PartitionReadRequest. See
// https://godoc.org/google.golang.org/genproto/googleapis/spanner/v1#PartitionOptions
// for more details.
type PartitionOptions struct {
	// The desired data size for each partition generated.
	PartitionBytes int64
	// The desired maximum number of partitions to return.
	MaxPartitions int64
}

// toProto converts a spanner.PartitionOptions into a sppb.PartitionOptions
func (opt PartitionOptions) toProto() *sppb.PartitionOptions {
	return &sppb.PartitionOptions{
		PartitionSizeBytes: opt.PartitionBytes,
		MaxPartitions:      opt.MaxPartitions,
	}
}

// PartitionRead returns a list of Partitions that can be used to read rows from
// the database. These partitions can be executed across multiple processes,
// even across different machines. The partition size and count hints can be
// configured using PartitionOptions.
func (t *BatchReadOnlyTransaction) PartitionRead(ctx context.Context, table string, keys KeySet, columns []string, opt PartitionOptions) ([]*Partition, error) {
	return t.PartitionReadUsingIndex(ctx, table, "", keys, columns, opt)
}

// PartitionReadUsingIndex returns a list of Partitions that can be used to read
// rows from the database using an index.
func (t *BatchReadOnlyTransaction) PartitionReadUsingIndex(ctx context.Context, table, index string, keys KeySet, columns []string, opt PartitionOptions) ([]*Partition, error) {
	sh, ts, err := t.acquire(ctx)
	if err != nil {
		return nil, err
	}
	sid, client := sh.getID(), sh.getClient()
	var (
		kset       *sppb.KeySet
		resp       *sppb.PartitionResponse
		partitions []*Partition
	)
	kset, err = keys.keySetProto()
	// Request partitions.
	if err != nil {
		return nil, err
	}
	resp, err = client.PartitionRead(ctx, &sppb.PartitionReadRequest{
		Session:          sid,
		Transaction:      ts,
		Table:            table,
		Index:            index,
		Columns:          columns,
		KeySet:           kset,
		PartitionOptions: opt.toProto(),
	})
	// Prepare ReadRequest.
	req := &sppb.ReadRequest{
		Session:     sid,
		Transaction: ts,
		Table:       table,
		Index:       index,
		Columns:     columns,
		KeySet:      kset,
	}
	// Generate partitions.
	for _, p := range resp.GetPartitions() {
		partitions = append(partitions, &Partition{
			pt:   p.PartitionToken,
			rreq: req,
		})
	}
	return partitions, err
}

// PartitionQuery returns a list of Partitions that can be used to execute a
// query against the database.
func (t *BatchReadOnlyTransaction) PartitionQuery(ctx context.Context, statement Statement, opt PartitionOptions) ([]*Partition, error) {
	return t.partitionQuery(ctx, statement, opt, t.ReadOnlyTransaction.txReadOnly.qo)
}

// PartitionQueryWithOptions returns a list of Partitions that can be used to
// execute a query against the database. The sql query execution will be
// optimized based on the given query options.
func (t *BatchReadOnlyTransaction) PartitionQueryWithOptions(ctx context.Context, statement Statement, opt PartitionOptions, qOpts QueryOptions) ([]*Partition, error) {
	return t.partitionQuery(ctx, statement, opt, t.ReadOnlyTransaction.txReadOnly.qo.merge(qOpts))
}

func (t *BatchReadOnlyTransaction) partitionQuery(ctx context.Context, statement Statement, opt PartitionOptions, qOpts QueryOptions) ([]*Partition, error) {
	sh, ts, err := t.acquire(ctx)
	if err != nil {
		return nil, err
	}
	sid, client := sh.getID(), sh.getClient()
	params, paramTypes, err := statement.convertParams()
	if err != nil {
		return nil, err
	}

	// request Partitions
	req := &sppb.PartitionQueryRequest{
		Session:          sid,
		Transaction:      ts,
		Sql:              statement.SQL,
		PartitionOptions: opt.toProto(),
		Params:           params,
		ParamTypes:       paramTypes,
	}
	resp, err := client.PartitionQuery(ctx, req)

	// prepare ExecuteSqlRequest
	r := &sppb.ExecuteSqlRequest{
		Session:      sid,
		Transaction:  ts,
		Sql:          statement.SQL,
		Params:       params,
		ParamTypes:   paramTypes,
		QueryOptions: qOpts.Options,
	}

	// generate Partitions
	var partitions []*Partition
	for _, p := range resp.GetPartitions() {
		partitions = append(partitions, &Partition{
			pt:   p.PartitionToken,
			qreq: r,
		})
	}
	return partitions, err
}

// release implements txReadEnv.release, noop.
func (t *BatchReadOnlyTransaction) release(err error) {
}

// setTimestamp implements txReadEnv.setTimestamp, noop.
//
// read timestamp is ready on txn initialization, avoid contending writing to it
// with future partitions.
func (t *BatchReadOnlyTransaction) setTimestamp(ts time.Time) {
}

// Close marks the txn as closed.
func (t *BatchReadOnlyTransaction) Close() {
	t.mu.Lock()
	defer t.mu.Unlock()
	t.state = txClosed
}

// Cleanup cleans up all the resources used by this transaction and makes
// it unusable. Once this method is invoked, the transaction is no longer
// usable anywhere, including other clients/processes with which this
// transaction was shared.
//
// Calling Cleanup is optional, but recommended. If Cleanup is not called, the
// transaction's resources will be freed when the session expires on the backend
// and is deleted. For more information about recycled sessions, see
// https://cloud.google.com/spanner/docs/sessions.
func (t *BatchReadOnlyTransaction) Cleanup(ctx context.Context) {
	t.Close()
	t.mu.Lock()
	defer t.mu.Unlock()
	sh := t.sh
	if sh == nil {
		return
	}
	t.sh = nil
	sid, client := sh.getID(), sh.getClient()
	err := client.DeleteSession(contextWithOutgoingMetadata(ctx, sh.getMetadata()), &sppb.DeleteSessionRequest{Name: sid})
	if err != nil {
		var logger *log.Logger
		if sh.session != nil {
			logger = sh.session.logger
		}
		logf(logger, "Failed to delete session %v. Error: %v", sid, err)
	}
}

// Execute runs a single Partition obtained from PartitionRead or
// PartitionQuery.
func (t *BatchReadOnlyTransaction) Execute(ctx context.Context, p *Partition) *RowIterator {
	var (
		sh  *sessionHandle
		err error
		rpc func(ct context.Context, resumeToken []byte) (streamingReceiver, error)
	)
	if sh, _, err = t.acquire(ctx); err != nil {
		return &RowIterator{err: err}
	}
	client := sh.getClient()
	if client == nil {
		// Might happen if transaction is closed in the middle of a API call.
		return &RowIterator{err: errSessionClosed(sh)}
	}
	// Read or query partition.
	if p.rreq != nil {
		rpc = func(ctx context.Context, resumeToken []byte) (streamingReceiver, error) {
			return client.StreamingRead(ctx, &sppb.ReadRequest{
				Session:        p.rreq.Session,
				Transaction:    p.rreq.Transaction,
				Table:          p.rreq.Table,
				Index:          p.rreq.Index,
				Columns:        p.rreq.Columns,
				KeySet:         p.rreq.KeySet,
				PartitionToken: p.pt,
				ResumeToken:    resumeToken,
			})
		}
	} else {
		rpc = func(ctx context.Context, resumeToken []byte) (streamingReceiver, error) {
			return client.ExecuteStreamingSql(ctx, &sppb.ExecuteSqlRequest{
				Session:        p.qreq.Session,
				Transaction:    p.qreq.Transaction,
				Sql:            p.qreq.Sql,
				Params:         p.qreq.Params,
				ParamTypes:     p.qreq.ParamTypes,
				QueryOptions:   p.qreq.QueryOptions,
				PartitionToken: p.pt,
				ResumeToken:    resumeToken,
			})
		}
	}
	return stream(
		contextWithOutgoingMetadata(ctx, sh.getMetadata()),
		sh.session.logger,
		rpc,
		t.setTimestamp,
		t.release)
}

// MarshalBinary implements BinaryMarshaler.
func (tid BatchReadOnlyTransactionID) MarshalBinary() (data []byte, err error) {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(tid.tid); err != nil {
		return nil, err
	}
	if err := enc.Encode(tid.sid); err != nil {
		return nil, err
	}
	if err := enc.Encode(tid.rts); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

// UnmarshalBinary implements BinaryUnmarshaler.
func (tid *BatchReadOnlyTransactionID) UnmarshalBinary(data []byte) error {
	dec := gob.NewDecoder(bytes.NewReader(data))
	if err := dec.Decode(&tid.tid); err != nil {
		return err
	}
	if err := dec.Decode(&tid.sid); err != nil {
		return err
	}
	return dec.Decode(&tid.rts)
}

// MarshalBinary implements BinaryMarshaler.
func (p Partition) MarshalBinary() (data []byte, err error) {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(p.pt); err != nil {
		return nil, err
	}
	var isReadPartition bool
	var req proto.Message
	if p.rreq != nil {
		isReadPartition = true
		req = p.rreq
	} else {
		isReadPartition = false
		req = p.qreq
	}
	if err := enc.Encode(isReadPartition); err != nil {
		return nil, err
	}
	if data, err = proto.Marshal(req); err != nil {
		return nil, err
	}
	if err := enc.Encode(data); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

// UnmarshalBinary implements BinaryUnmarshaler.
func (p *Partition) UnmarshalBinary(data []byte) error {
	var (
		isReadPartition bool
		d               []byte
		err             error
	)
	dec := gob.NewDecoder(bytes.NewReader(data))
	if err := dec.Decode(&p.pt); err != nil {
		return err
	}
	if err := dec.Decode(&isReadPartition); err != nil {
		return err
	}
	if err := dec.Decode(&d); err != nil {
		return err
	}
	if isReadPartition {
		p.rreq = &sppb.ReadRequest{}
		err = proto.Unmarshal(d, p.rreq)
	} else {
		p.qreq = &sppb.ExecuteSqlRequest{}
		err = proto.Unmarshal(d, p.qreq)
	}
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
)

type doForService func(string, yamlConfig) (int, error)

type Dcm struct {
	Config *Config
	Args   []string
}

func NewDcm(c *Config, args []string) *Dcm {
	return &Dcm{c, args}
}

func (d *Dcm) Command() (int, error) {
	if len(d.Args) < 1 {
		d.Usage()
		return 1, nil
	}

	moreArgs := d.Args[1:]

	switch d.Args[0] {
	case "help", "h":
		d.Usage()
		return 0, nil
	case "setup":
		return d.Setup()
	case "run", "r":
		return d.Run(moreArgs...)
	case "build", "b":
		return d.Run("build")
	case "dir", "d":
		return d.Dir(moreArgs...)
	case "shell", "sh":
		return d.Shell(moreArgs...)
	case "branch", "br":
		return d.Branch(moreArgs...)
	case "update", "u":
		return d.Update(moreArgs...)
	case "purge", "rm":
		return d.Purge(moreArgs...)
	default:
		d.Usage()
		return 127, nil
	}
}

func (d *Dcm) Setup() (int, error) {
	if _, err := os.Stat(d.Config.Srv); os.IsNotExist(err) {
		os.MkdirAll(d.Config.Srv, 0777)
	}

	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		repo, ok := getMapVal(configs, "labels", "dcm.repository").(string)
		if !ok {
			return 1, errors.New(
				"Error reading git repository config for service: " + service)
		}
		dir := d.Config.Srv + "/" + service
		c := cmd("git", "clone", repo, dir)
		c.Dir = d.Config.Dir
		if err := c.Run(); err != nil {
			return 1, errors.New("Error cloning git repository for service: " + service)
		}
		branch, ok := getMapVal(configs, "labels", "dcm.branch").(string)
		if ok {
			c = cmd("git", "checkout", branch)
			c.Dir = d.Config.Srv + "/" + service + "/"
			if err := c.Run(); err != nil {
				return 1, err
			}
		}
		return 0, nil
	})
}

func (d *Dcm) doForEachService(fn doForService) (int, error) {
	for service, configs := range d.Config.Config {
		service, _ := service.(string)
		configs, ok := configs.(yamlConfig)
		if !ok {
			panic("Error reading configs for service: " + service)
		}

		code, err := fn(service, configs)
		if err != nil {
			return code, err
		}
	}

	return 0, nil
}

func (d *Dcm) Run(args ...string) (int, error) {
	if len(args) == 0 {
		args = append(args, "default")
	}

	switch args[0] {
	case "execute":
		return d.runExecute(args[1:]...)
	case "init":
		fmt.Println("Initializing project [" + d.Config.Project + "]...")
		return d.runInit()
	case "build":
		fmt.Println("Building project [" + d.Config.Project + "]...")
		return d.Run("execute", "build")
	case "start":
		fmt.Println("Starting project [" + d.Config.Project + "]...")
		return d.Run("execute", "start")
	case "stop":
		fmt.Println("Stopping project [" + d.Config.Project + "]...")
		return d.Run("execute", "stop")
	case "restart":
		fmt.Println("Restarting project [" + d.Config.Project + "]...")
		return d.Run("execute", "restart")
	case "up":
		fmt.Println("Bringing up project [" + d.Config.Project + "]...")
		return d.runUp()
	default:
		return d.Run("up")
	}
}

func (d *Dcm) runExecute(args ...string) (int, error) {
	cmd := cmd("docker-compose", args...)
	cmd.Dir = d.Config.Dir
	cmd.Env = append(
		os.Environ(),
		"COMPOSE_PROJECT_NAME="+d.Config.Project,
		"COMPOSE_FILE="+d.Config.File,
	)
	if err := cmd.Run(); err != nil {
		return 1, fmt.Errorf(
			"Error executing docker-compose with args [%s], and envs [%s]",
			strings.Join(args, ", "), strings.Join(cmd.Env, ", "),
		)
	}
	return 0, nil
}

func (d *Dcm) runInit() (int, error) {
	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		init, ok := getMapVal(configs, "labels", "dcm.initscript").(string)
		if !ok {
			fmt.Println("Skipping init script for service:", service, "...")
			return 0, nil
		}
		c := cmd("/bin/bash", init)
		c.Dir = d.Config.Srv + "/" + service + "/"
		if err := c.Run(); err != nil {
			return 1, fmt.Errorf(
				"Error executing init script [%s] for service [%s]: %s",
				init, service, err,
			)
		}
		return 0, nil
	})
}

func (d *Dcm) runUp() (int, error) {
	code, err := d.Run("execute", "up", "-d", "--force-recreate")
	if err != nil {
		return code, err
	}
	return d.Run("init")
}

func (d *Dcm) Dir(args ...string) (int, error) {
	var dir string
	if len(args) < 1 {
		dir = d.Config.Dir
	} else {
		dir = d.Config.Srv + "/" + args[0]
		if _, err := os.Stat(dir); os.IsNotExist(err) {
			dir = d.Config.Dir
		}
	}
	fmt.Fprint(os.Stdout, dir)
	return 0, nil
}

func (d *Dcm) Shell(args ...string) (int, error) {
	if len(args) < 1 {
		return 1, errors.New("Error: no service name specified.")
	}

	cid, err := d.getContainerId(args[0])
	if err != nil {
		return 1, err
	}

	err = cmd("docker", "exec", "-it", cid, "bash").Run()
	if err != nil {
		return 1, err
	}

	return 0, nil
}

func (d *Dcm) getContainerId(service string) (string, error) {
	filter := fmt.Sprintf("name=%s_%s_", d.Config.Project, service)
	out, err := exec.Command("docker", "ps", "-q", "-f", filter).Output()
	if err != nil {
		return "", err
	}

	cid := strings.TrimSpace(string(out))
	if cid == "" {
		return "", fmt.Errorf(
			"Error: no running container name starts with %s_%s_",
			d.Config.Project, service,
		)
	}

	return cid, nil
}

func (d *Dcm) getImageRepository(service string) (string, error) {
	first := exec.Command("docker", "images")
	second := exec.Command("awk", fmt.Sprintf(
		"$1 ~ /%s_%s/ { print $1 }", d.Config.Project, service))

	reader, writer := io.Pipe()
	first.Stdout = writer
	second.Stdin = reader
	var buff bytes.Buffer
	second.Stdout = &buff

	if err := first.Start(); err != nil {
		return "", err
	}
	if err := second.Start(); err != nil {
		return "", err
	}
	if err := first.Wait(); err != nil {
		return "", err
	}
	if err := writer.Close(); err != nil {
		return "", err
	}
	if err := second.Wait(); err != nil {
		return "", err
	}

	return strings.TrimSpace(buff.String()), nil
}

func (d *Dcm) Branch(args ...string) (int, error) {
	if len(args) < 1 {
		return d.branchForAll()
	} else {
		return d.branchForOne(args[0])
	}
}

func (d *Dcm) branchForAll() (int, error) {
	code, err := d.branchForOne("dcm")
	if err != nil {
		return code, err
	}
	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		return d.branchForOne(service)
	})
}

func (d *Dcm) branchForOne(service string) (int, error) {
	var dir string
	if service == "dcm" {
		dir = d.Config.Dir
	} else {
		dir = d.Config.Srv + "/" + service
	}

	if err := os.Chdir(dir); err != nil {
		return 1, err
	}

	fmt.Print(service + ": ")
	if err := cmd("git", "rev-parse", "--abbrev-ref", "HEAD").Run(); err != nil {
		return 1, err
	}

	return 0, nil
}

func (d *Dcm) Update(args ...string) (int, error) {
	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		fmt.Print(service + ": ")
		if err := os.Chdir(d.Config.Srv + "/" + service); err != nil {
			return 1, err
		}
		branch, ok := getMapVal(configs, "labels", "dcm.branch").(string)
		if !ok {
			// When service > labels > dcm.branch is not defined in
			// the yaml config file, use "master" as default branch
			branch = "master"
		}
		if err := cmd("git", "checkout", branch).Run(); err != nil {
			return 1, err
		}
		if err := cmd("git", "pull").Run(); err != nil {
			return 1, err
		}
		return 0, nil
	})
}

func (d *Dcm) Purge(args ...string) (int, error) {
	if len(args) == 0 {
		args = append(args, "default")
	}

	switch args[0] {
	case "img", "images":
		return d.purgeImages()
	case "con", "containers":
		return d.purgeContainers()
	case "all":
		return d.purgeAll()
	default:
		return d.Purge("containers")
	}
}

func (d *Dcm) purgeImages() (int, error) {
	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		repo, err := d.getImageRepository(service)
		if err != nil {
			return 1, err
		}
		if err := cmd("docker", "rmi", repo).Run(); err != nil {
			return 1, err
		}
		return 0, nil
	})
}

func (d *Dcm) purgeContainers() (int, error) {
	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		cid, err := d.getContainerId(service)
		if err != nil {
			return 1, err
		}
		if err := cmd("docker", "kill", cid).Run(); err != nil {
			return 1, err
		}
		if err := cmd("docker", "rm", "-v", cid).Run(); err != nil {
			return 1, err
		}
		return 0, nil
	})
}

func (d *Dcm) purgeAll() (int, error) {
	code, err := d.Purge("containers")
	if err != nil {
		return code, err
	}
	return d.Purge("images")
}

func (d *Dcm) Usage() {
	fmt.Println("")
	fmt.Println("Docker Compose Manager")
	fmt.Println("")
	fmt.Println("Usage:")
	fmt.Println("  dcm help                Show this message")
	fmt.Println("                          shorthand ver.: `dcm h`")
	fmt.Println("  dcm setup               Check out all the repositories for API, UI and services")
	fmt.Println("  dcm run [<args>]        Run docker-compose commands. If <args> is not given, by")
	fmt.Println("                          default DCM will run `up` command.")
	fmt.Println("                          <args>: up, build, start, stop, restart")
	fmt.Println("                          shorthand ver.: `dcm r [<args>]`")
	fmt.Println("  dcm run build           Run build command that (re)create all the service images")
	fmt.Println("                          shorthand ver.: `dcm build` or `dcm b`")
	fmt.Println("  dcm shell <service>     Log into a given service container")
	fmt.Println("                          <service>: api, ui, postgres, mongo, redis, nginx, php")
	fmt.Println("                          shorthand ver.: `dcm sh <service>`")
	fmt.Println("  dcm purge [<type>]      Remove either all the containers or all the images or")
	fmt.Println("                          everything. If <type> is not given, by default DCM will")
	fmt.Println("                          purge everything")
	fmt.Println("                          <type>: images|img, containers|con, all")
	fmt.Println("                          shorthand ver.: `dcm rm [<type>]`")
	fmt.Println("  dcm branch <service>    Display the current branch for the given service name")
	fmt.Println("                          <service>: api, ui, postgres, mongo, redis, nginx, php")
	fmt.Println("                          shorthand ver.: `dcm br <service>`")
	fmt.Println("  dcm goto [<service>]    Go to the service's folder. If <service> is not given,")
	fmt.Println("                          by default DCM will go to $DCM_DIR")
	fmt.Println("                          <service>: api, ui, postgres, mongo, redis, nginx, php")
	fmt.Println("                          shorthand ver.: `dcm gt [<service>]`")
	fmt.Println("  dcm update [<service>]  Update DCM and dependent services (PostgrSQL, MongoDB,")
	fmt.Println("                          Redis, Nginx and Base PHP). If <service> is not given,")
	fmt.Println("                          by default DCM will update everything except api and ui.")
	fmt.Println("                          <service>: postgres, mongo, redis, nginx, php")
	fmt.Println("                          shorthand ver.: `dcm u`")
	fmt.Println("")
	fmt.Println("Example:")
	fmt.Println("  Initial setup")
	fmt.Println("    dcm setup")
	fmt.Println("    dcm run")
	fmt.Println("")
	fmt.Println("  Rebuild API or UI after switching branch")
	fmt.Println("    dcm build")
	fmt.Println("    dcm run")
	fmt.Println("")
	fmt.Println("  Log into different service containers")
	fmt.Println("    dcm shell api")
	fmt.Println("    dcm shell ui")
	fmt.Println("    ...")
	fmt.Println("")
}
<commit_msg>Improved error message for "dcm shell"<commit_after>package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
)

type doForService func(string, yamlConfig) (int, error)

type Dcm struct {
	Config *Config
	Args   []string
}

func NewDcm(c *Config, args []string) *Dcm {
	return &Dcm{c, args}
}

func (d *Dcm) Command() (int, error) {
	if len(d.Args) < 1 {
		d.Usage()
		return 1, nil
	}

	moreArgs := d.Args[1:]

	switch d.Args[0] {
	case "help", "h":
		d.Usage()
		return 0, nil
	case "setup":
		return d.Setup()
	case "run", "r":
		return d.Run(moreArgs...)
	case "build", "b":
		return d.Run("build")
	case "dir", "d":
		return d.Dir(moreArgs...)
	case "shell", "sh":
		return d.Shell(moreArgs...)
	case "branch", "br":
		return d.Branch(moreArgs...)
	case "update", "u":
		return d.Update(moreArgs...)
	case "purge", "rm":
		return d.Purge(moreArgs...)
	default:
		d.Usage()
		return 127, nil
	}
}

func (d *Dcm) Setup() (int, error) {
	if _, err := os.Stat(d.Config.Srv); os.IsNotExist(err) {
		os.MkdirAll(d.Config.Srv, 0777)
	}

	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		repo, ok := getMapVal(configs, "labels", "dcm.repository").(string)
		if !ok {
			return 1, errors.New(
				"Error reading git repository config for service: " + service)
		}
		dir := d.Config.Srv + "/" + service
		c := cmd("git", "clone", repo, dir)
		c.Dir = d.Config.Dir
		if err := c.Run(); err != nil {
			return 1, errors.New("Error cloning git repository for service: " + service)
		}
		branch, ok := getMapVal(configs, "labels", "dcm.branch").(string)
		if ok {
			c = cmd("git", "checkout", branch)
			c.Dir = d.Config.Srv + "/" + service + "/"
			if err := c.Run(); err != nil {
				return 1, err
			}
		}
		return 0, nil
	})
}

func (d *Dcm) doForEachService(fn doForService) (int, error) {
	for service, configs := range d.Config.Config {
		service, _ := service.(string)
		configs, ok := configs.(yamlConfig)
		if !ok {
			panic("Error reading configs for service: " + service)
		}

		code, err := fn(service, configs)
		if err != nil {
			return code, err
		}
	}

	return 0, nil
}

func (d *Dcm) Run(args ...string) (int, error) {
	if len(args) == 0 {
		args = append(args, "default")
	}

	switch args[0] {
	case "execute":
		return d.runExecute(args[1:]...)
	case "init":
		fmt.Println("Initializing project [" + d.Config.Project + "]...")
		return d.runInit()
	case "build":
		fmt.Println("Building project [" + d.Config.Project + "]...")
		return d.Run("execute", "build")
	case "start":
		fmt.Println("Starting project [" + d.Config.Project + "]...")
		return d.Run("execute", "start")
	case "stop":
		fmt.Println("Stopping project [" + d.Config.Project + "]...")
		return d.Run("execute", "stop")
	case "restart":
		fmt.Println("Restarting project [" + d.Config.Project + "]...")
		return d.Run("execute", "restart")
	case "up":
		fmt.Println("Bringing up project [" + d.Config.Project + "]...")
		return d.runUp()
	default:
		return d.Run("up")
	}
}

func (d *Dcm) runExecute(args ...string) (int, error) {
	cmd := cmd("docker-compose", args...)
	cmd.Dir = d.Config.Dir
	cmd.Env = append(
		os.Environ(),
		"COMPOSE_PROJECT_NAME="+d.Config.Project,
		"COMPOSE_FILE="+d.Config.File,
	)
	if err := cmd.Run(); err != nil {
		return 1, fmt.Errorf(
			"Error executing docker-compose with args [%s], and envs [%s]",
			strings.Join(args, ", "), strings.Join(cmd.Env, ", "),
		)
	}
	return 0, nil
}

func (d *Dcm) runInit() (int, error) {
	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		init, ok := getMapVal(configs, "labels", "dcm.initscript").(string)
		if !ok {
			fmt.Println("Skipping init script for service:", service, "...")
			return 0, nil
		}
		c := cmd("/bin/bash", init)
		c.Dir = d.Config.Srv + "/" + service + "/"
		if err := c.Run(); err != nil {
			return 1, fmt.Errorf(
				"Error executing init script [%s] for service [%s]: %s",
				init, service, err,
			)
		}
		return 0, nil
	})
}

func (d *Dcm) runUp() (int, error) {
	code, err := d.Run("execute", "up", "-d", "--force-recreate")
	if err != nil {
		return code, err
	}
	return d.Run("init")
}

func (d *Dcm) Dir(args ...string) (int, error) {
	var dir string
	if len(args) < 1 {
		dir = d.Config.Dir
	} else {
		dir = d.Config.Srv + "/" + args[0]
		if _, err := os.Stat(dir); os.IsNotExist(err) {
			dir = d.Config.Dir
		}
	}
	fmt.Fprint(os.Stdout, dir)
	return 0, nil
}

func (d *Dcm) Shell(args ...string) (int, error) {
	if len(args) < 1 {
		return 1, errors.New("Error: no service name specified.")
	}

	cid, err := d.getContainerId(args[0])
	if err != nil {
		return 1, err
	}

	err = cmd("docker", "exec", "-it", cid, "bash").Run()
	if err != nil {
		return 1, err
	}

	return 0, nil
}

func (d *Dcm) getContainerId(service string) (string, error) {
	filter := fmt.Sprintf("name=%s_%s_", d.Config.Project, service)
	out, err := cmd("docker", "ps", "-q", "-f", filter).CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("%s: %s", err.Error(), string(out))
	}

	cid := strings.TrimSpace(string(out))
	if cid == "" {
		return "", fmt.Errorf(
			"Error: no running container name starts with %s_%s_",
			d.Config.Project, service,
		)
	}

	return cid, nil
}

func (d *Dcm) getImageRepository(service string) (string, error) {
	first := exec.Command("docker", "images")
	second := exec.Command("awk", fmt.Sprintf(
		"$1 ~ /%s_%s/ { print $1 }", d.Config.Project, service))

	reader, writer := io.Pipe()
	first.Stdout = writer
	second.Stdin = reader
	var buff bytes.Buffer
	second.Stdout = &buff

	if err := first.Start(); err != nil {
		return "", err
	}
	if err := second.Start(); err != nil {
		return "", err
	}
	if err := first.Wait(); err != nil {
		return "", err
	}
	if err := writer.Close(); err != nil {
		return "", err
	}
	if err := second.Wait(); err != nil {
		return "", err
	}

	return strings.TrimSpace(buff.String()), nil
}

func (d *Dcm) Branch(args ...string) (int, error) {
	if len(args) < 1 {
		return d.branchForAll()
	} else {
		return d.branchForOne(args[0])
	}
}

func (d *Dcm) branchForAll() (int, error) {
	code, err := d.branchForOne("dcm")
	if err != nil {
		return code, err
	}
	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		return d.branchForOne(service)
	})
}

func (d *Dcm) branchForOne(service string) (int, error) {
	var dir string
	if service == "dcm" {
		dir = d.Config.Dir
	} else {
		dir = d.Config.Srv + "/" + service
	}

	if err := os.Chdir(dir); err != nil {
		return 1, err
	}

	fmt.Print(service + ": ")
	if err := cmd("git", "rev-parse", "--abbrev-ref", "HEAD").Run(); err != nil {
		return 1, err
	}

	return 0, nil
}

func (d *Dcm) Update(args ...string) (int, error) {
	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		fmt.Print(service + ": ")
		if err := os.Chdir(d.Config.Srv + "/" + service); err != nil {
			return 1, err
		}
		branch, ok := getMapVal(configs, "labels", "dcm.branch").(string)
		if !ok {
			// When service > labels > dcm.branch is not defined in
			// the yaml config file, use "master" as default branch
			branch = "master"
		}
		if err := cmd("git", "checkout", branch).Run(); err != nil {
			return 1, err
		}
		if err := cmd("git", "pull").Run(); err != nil {
			return 1, err
		}
		return 0, nil
	})
}

func (d *Dcm) Purge(args ...string) (int, error) {
	if len(args) == 0 {
		args = append(args, "default")
	}

	switch args[0] {
	case "img", "images":
		return d.purgeImages()
	case "con", "containers":
		return d.purgeContainers()
	case "all":
		return d.purgeAll()
	default:
		return d.Purge("containers")
	}
}

func (d *Dcm) purgeImages() (int, error) {
	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		repo, err := d.getImageRepository(service)
		if err != nil {
			return 1, err
		}
		if err := cmd("docker", "rmi", repo).Run(); err != nil {
			return 1, err
		}
		return 0, nil
	})
}

func (d *Dcm) purgeContainers() (int, error) {
	return d.doForEachService(func(service string, configs yamlConfig) (int, error) {
		cid, err := d.getContainerId(service)
		if err != nil {
			return 1, err
		}
		if err := cmd("docker", "kill", cid).Run(); err != nil {
			return 1, err
		}
		if err := cmd("docker", "rm", "-v", cid).Run(); err != nil {
			return 1, err
		}
		return 0, nil
	})
}

func (d *Dcm) purgeAll() (int, error) {
	code, err := d.Purge("containers")
	if err != nil {
		return code, err
	}
	return d.Purge("images")
}

func (d *Dcm) Usage() {
	fmt.Println("")
	fmt.Println("Docker Compose Manager")
	fmt.Println("")
	fmt.Println("Usage:")
	fmt.Println("  dcm help                Show this message")
	fmt.Println("                          shorthand ver.: `dcm h`")
	fmt.Println("  dcm setup               Check out all the repositories for API, UI and services")
	fmt.Println("  dcm run [<args>]        Run docker-compose commands. If <args> is not given, by")
	fmt.Println("                          default DCM will run `up` command.")
	fmt.Println("                          <args>: up, build, start, stop, restart")
	fmt.Println("                          shorthand ver.: `dcm r [<args>]`")
	fmt.Println("  dcm run build           Run build command that (re)create all the service images")
	fmt.Println("                          shorthand ver.: `dcm build` or `dcm b`")
	fmt.Println("  dcm shell <service>     Log into a given service container")
	fmt.Println("                          <service>: api, ui, postgres, mongo, redis, nginx, php")
	fmt.Println("                          shorthand ver.: `dcm sh <service>`")
	fmt.Println("  dcm purge [<type>]      Remove either all the containers or all the images or")
	fmt.Println("                          everything. If <type> is not given, by default DCM will")
	fmt.Println("                          purge everything")
	fmt.Println("                          <type>: images|img, containers|con, all")
	fmt.Println("                          shorthand ver.: `dcm rm [<type>]`")
	fmt.Println("  dcm branch <service>    Display the current branch for the given service name")
	fmt.Println("                          <service>: api, ui, postgres, mongo, redis, nginx, php")
	fmt.Println("                          shorthand ver.: `dcm br <service>`")
	fmt.Println("  dcm goto [<service>]    Go to the service's folder. If <service> is not given,")
	fmt.Println("                          by default DCM will go to $DCM_DIR")
	fmt.Println("                          <service>: api, ui, postgres, mongo, redis, nginx, php")
	fmt.Println("                          shorthand ver.: `dcm gt [<service>]`")
	fmt.Println("  dcm update [<service>]  Update DCM and dependent services (PostgrSQL, MongoDB,")
	fmt.Println("                          Redis, Nginx and Base PHP). If <service> is not given,")
	fmt.Println("                          by default DCM will update everything except api and ui.")
	fmt.Println("                          <service>: postgres, mongo, redis, nginx, php")
	fmt.Println("                          shorthand ver.: `dcm u`")
	fmt.Println("")
	fmt.Println("Example:")
	fmt.Println("  Initial setup")
	fmt.Println("    dcm setup")
	fmt.Println("    dcm run")
	fmt.Println("")
	fmt.Println("  Rebuild API or UI after switching branch")
	fmt.Println("    dcm build")
	fmt.Println("    dcm run")
	fmt.Println("")
	fmt.Println("  Log into different service containers")
	fmt.Println("    dcm shell api")
	fmt.Println("    dcm shell ui")
	fmt.Println("    ...")
	fmt.Println("")
}
<|endoftext|>


<commit_before>package splunk

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"os"
	"time"
)

// Event represents the log event object that is sent to Splunk when Client.Log is called.
type Event struct {
	Time       int64       `json:"time"`                 // epoch time in seconds
	Host       string      `json:"host"`                 // hostname
	Source     string      `json:"source,omitempty"`     // optional description of the source of the event; typically the app's name
	SourceType string      `json:"sourcetype,omitempty"` // optional name of a Splunk parsing configuration; this is usually inferred by Splunk
	Index      string      `json:"index,omitempty"`      // optional name of the Splunk index to store the event in; not required if the token has a default index set in Splunk
	Event      interface{} `json:"event"`                // throw any useful key/val pairs here
}

// Client manages communication with Splunk's HTTP Event Collector.
// New client objects should be created using the NewClient function.
//
// The URL field must be defined and pointed at a Splunk servers Event Collector port (i.e. https://{your-splunk-URL}:8088/services/collector).
// The Token field must be defined with your access token to the Event Collector.
type Client struct {
	HTTPClient *http.Client // HTTP client used to communicate with the API
	URL        string
	Hostname   string
	Token      string
	Source     string //Default source
	SourceType string //Default source type
	Index      string //Default index
}

// NewClient creates a new client to Splunk.
// This should be the primary way a Splunk client object is constructed.
//
// If an httpClient object is specified it will be used instead of the
// default http.DefaultClient.
func NewClient(httpClient *http.Client, URL string, Token string, Source string, SourceType string, Index string) *Client {
	// Create a new client
	if httpClient == nil {
		tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: false}}
		httpClient = &http.Client{Timeout: time.Second * 20, Transport: tr}
	}
	hostname, _ := os.Hostname()
	c := &Client{
		HTTPClient: httpClient,
		URL:        URL,
		Hostname:   hostname,
		Token:      Token,
		Source:     Source,
		SourceType: SourceType,
		Index:      Index,
	}
	return c
}

// NewEvent creates a new log event to send to Splunk.
// This should be the primary way a Splunk log object is constructed, and is automatically called by the Log function attached to the client.
// This method takes the current timestamp for the event, meaning that the event is generated at runtime.
func (c *Client) NewEvent(event interface{}, source string, sourcetype string, index string) *Event {
	e := &Event{
		Time:       time.Now().Unix(),
		Host:       c.Hostname,
		Source:     source,
		SourceType: sourcetype,
		Index:      index,
		Event:      event,
	}
	return e
}

// NewEventWithTime creates a new log event with a specified timetamp to send to Splunk.
// This is similar to NewEvent but if you want to log in a different time rather than time.Now this becomes handy. If that's
// the case, use this function to create the Event object and the the LogEvent function.
func (c *Client) NewEventWithTime(t int64, event interface{}, source string, sourcetype string, index string) *Event {
	e := &Event{
		Time:       t,
		Host:       c.Hostname,
		Source:     source,
		SourceType: sourcetype,
		Index:      index,
		Event:      event,
	}
	return e
}

// Client.Log is used to construct a new log event and POST it to the Splunk server.
//
// All that must be provided for a log event are the desired map[string]string key/val pairs. These can be anything
// that provide context or information for the situation you are trying to log (i.e. err messages, status codes, etc).
// The function auto-generates the event timestamp and hostname for you.
func (c *Client) Log(event interface{}) error {
	// create Splunk log
	log := c.NewEvent(event, c.Source, c.SourceType, c.Index)
	return c.LogEvent(log)
}

// Client.LogWithTime is used to construct a new log event with a scpecified timestamp and POST it to the Splunk server.
//
// This is similar to Client.Log, just with the t parameter.
func (c *Client) LogWithTime(t int64, event interface{}) error {
	// create Splunk log
	log := c.NewEventWithTime(t, event, c.Source, c.SourceType, c.Index)
	return c.LogEvent(log)
}

// Client.LogEvent is used to POST a single event to the Splunk server.
func (c *Client) LogEvent(e *Event) error {
	// Convert requestBody struct to byte slice to prep for http.NewRequest
	b, err := json.Marshal(e)
	if err != nil {
		return err
	}
	return c.doRequest(bytes.NewBuffer(b))
}

// Client.LogEvents is used to POST multiple events with a single request to the Splunk server.
func (c *Client) LogEvents(events []*Event) error {
	buf := new(bytes.Buffer)
	for _, e := range events {
		b, err := json.Marshal(e)
		if err != nil {
			return err
		}
		buf.Write(b)
		// Each json object should be separated by a blank line
		buf.WriteString("\r\n\r\n")
	}
	// Convert requestBody struct to byte slice to prep for http.NewRequest
	return c.doRequest(buf)
}

//Writer is a convience method for creating an io.Writer from a Writer with default values
func (c *Client) Writer() io.Writer {
	return &Writer{
		Client: c,
	}
}

// Client.doRequest is used internally to POST the bytes of events to the Splunk server.
func (c *Client) doRequest(b *bytes.Buffer) error {
	// make new request
	url := c.URL
	req, err := http.NewRequest("POST", url, b)
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Authorization", "Splunk "+c.Token)

	// receive response
	res, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}

	// need to make sure we close the body to avoid hanging the connection
	defer res.Body.Close()

	// If statusCode is not good, return error string
	switch res.StatusCode {
	case 200:
		// need to read the reply otherwise the connection hangs
		buf := new(bytes.Buffer)
		buf.ReadFrom(res.Body)

		return nil
	default:
		// Turn response into string and return it
		buf := new(bytes.Buffer)
		buf.ReadFrom(res.Body)
		responseBody := buf.String()
		err = errors.New(responseBody)

	}
	return err
}
<commit_msg>nicer way to consume response<commit_after>package splunk

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

// Event represents the log event object that is sent to Splunk when Client.Log is called.
type Event struct {
	Time       int64       `json:"time"`                 // epoch time in seconds
	Host       string      `json:"host"`                 // hostname
	Source     string      `json:"source,omitempty"`     // optional description of the source of the event; typically the app's name
	SourceType string      `json:"sourcetype,omitempty"` // optional name of a Splunk parsing configuration; this is usually inferred by Splunk
	Index      string      `json:"index,omitempty"`      // optional name of the Splunk index to store the event in; not required if the token has a default index set in Splunk
	Event      interface{} `json:"event"`                // throw any useful key/val pairs here
}

// Client manages communication with Splunk's HTTP Event Collector.
// New client objects should be created using the NewClient function.
//
// The URL field must be defined and pointed at a Splunk servers Event Collector port (i.e. https://{your-splunk-URL}:8088/services/collector).
// The Token field must be defined with your access token to the Event Collector.
type Client struct {
	HTTPClient *http.Client // HTTP client used to communicate with the API
	URL        string
	Hostname   string
	Token      string
	Source     string //Default source
	SourceType string //Default source type
	Index      string //Default index
}

// NewClient creates a new client to Splunk.
// This should be the primary way a Splunk client object is constructed.
//
// If an httpClient object is specified it will be used instead of the
// default http.DefaultClient.
func NewClient(httpClient *http.Client, URL string, Token string, Source string, SourceType string, Index string) *Client {
	// Create a new client
	if httpClient == nil {
		tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: false}}
		httpClient = &http.Client{Timeout: time.Second * 20, Transport: tr}
	}
	hostname, _ := os.Hostname()
	c := &Client{
		HTTPClient: httpClient,
		URL:        URL,
		Hostname:   hostname,
		Token:      Token,
		Source:     Source,
		SourceType: SourceType,
		Index:      Index,
	}
	return c
}

// NewEvent creates a new log event to send to Splunk.
// This should be the primary way a Splunk log object is constructed, and is automatically called by the Log function attached to the client.
// This method takes the current timestamp for the event, meaning that the event is generated at runtime.
func (c *Client) NewEvent(event interface{}, source string, sourcetype string, index string) *Event {
	e := &Event{
		Time:       time.Now().Unix(),
		Host:       c.Hostname,
		Source:     source,
		SourceType: sourcetype,
		Index:      index,
		Event:      event,
	}
	return e
}

// NewEventWithTime creates a new log event with a specified timetamp to send to Splunk.
// This is similar to NewEvent but if you want to log in a different time rather than time.Now this becomes handy. If that's
// the case, use this function to create the Event object and the the LogEvent function.
func (c *Client) NewEventWithTime(t int64, event interface{}, source string, sourcetype string, index string) *Event {
	e := &Event{
		Time:       t,
		Host:       c.Hostname,
		Source:     source,
		SourceType: sourcetype,
		Index:      index,
		Event:      event,
	}
	return e
}

// Client.Log is used to construct a new log event and POST it to the Splunk server.
//
// All that must be provided for a log event are the desired map[string]string key/val pairs. These can be anything
// that provide context or information for the situation you are trying to log (i.e. err messages, status codes, etc).
// The function auto-generates the event timestamp and hostname for you.
func (c *Client) Log(event interface{}) error {
	// create Splunk log
	log := c.NewEvent(event, c.Source, c.SourceType, c.Index)
	return c.LogEvent(log)
}

// Client.LogWithTime is used to construct a new log event with a scpecified timestamp and POST it to the Splunk server.
//
// This is similar to Client.Log, just with the t parameter.
func (c *Client) LogWithTime(t int64, event interface{}) error {
	// create Splunk log
	log := c.NewEventWithTime(t, event, c.Source, c.SourceType, c.Index)
	return c.LogEvent(log)
}

// Client.LogEvent is used to POST a single event to the Splunk server.
func (c *Client) LogEvent(e *Event) error {
	// Convert requestBody struct to byte slice to prep for http.NewRequest
	b, err := json.Marshal(e)
	if err != nil {
		return err
	}
	return c.doRequest(bytes.NewBuffer(b))
}

// Client.LogEvents is used to POST multiple events with a single request to the Splunk server.
func (c *Client) LogEvents(events []*Event) error {
	buf := new(bytes.Buffer)
	for _, e := range events {
		b, err := json.Marshal(e)
		if err != nil {
			return err
		}
		buf.Write(b)
		// Each json object should be separated by a blank line
		buf.WriteString("\r\n\r\n")
	}
	// Convert requestBody struct to byte slice to prep for http.NewRequest
	return c.doRequest(buf)
}

//Writer is a convience method for creating an io.Writer from a Writer with default values
func (c *Client) Writer() io.Writer {
	return &Writer{
		Client: c,
	}
}

// Client.doRequest is used internally to POST the bytes of events to the Splunk server.
func (c *Client) doRequest(b *bytes.Buffer) error {
	// make new request
	url := c.URL
	req, err := http.NewRequest("POST", url, b)
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Authorization", "Splunk "+c.Token)

	// receive response
	res, err := c.HTTPClient.Do(req)
	if err != nil {
		return err
	}

	// need to make sure we close the body to avoid hanging the connection
	defer res.Body.Close()

	// If statusCode is not good, return error string
	switch res.StatusCode {
	case 200:
		// need to read the reply otherwise the connection hangs
		io.Copy(ioutil.Discard, res.Body)
		return nil
	default:
		// Turn response into string and return it
		buf := new(bytes.Buffer)
		buf.ReadFrom(res.Body)
		responseBody := buf.String()
		err = errors.New(responseBody)

	}
	return err
}
<|endoftext|>


<commit_before>/*
package aead uses aead crypto to encrypt and authenticate content composed of a plaintext metadata string and a plaintext data string.
An encryption results in a string literal of the form <b64URLmetadata>.<b64URLciphertext>.<b64URLnonce>. The user of this package must supply a
crypto.AEAD created with the same key in order to encrypt, transmit and decrypt a literal produced by Encrypt.
*/
package aead

import (
	"bytes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"strings"
)

//Encrypt generates a literal of the form <b64URLmetadata>.<b64URLciphertext>.<b64URLnonce> given an AEAD, a metadata string and a data
//string. Only the data is encrypted - the metadata must be appropriate to expose in the clear. Each call generates a random
//nonce of the length required by the aeadCipher.
func Encrypt(aeadCipher cipher.AEAD, metadata, data string) (string, error) {

	var (
		nonce         = make([]byte, aeadCipher.NonceSize())
		ciphertext    []byte
		b64metadata   []byte
		b64ciphertext []byte
		b64nonce      []byte
		buf           bytes.Buffer
		err           error
	)

	//A nonce of the length required by the AEAD is generated
	_, err = rand.Read(nonce)
	if err != nil {
		return "", err
	}

	//Seal encrypts the data using the aeadCipher's key and the nonce and appends an authentication code for the metadata
	ciphertext = aeadCipher.Seal(ciphertext, nonce, []byte(data), []byte(metadata))

	//Base64 Encode metadata, ciphertext and nonce
	b64metadata = make([]byte, base64.URLEncoding.EncodedLen(len([]byte(metadata))))
	base64.StdEncoding.Encode(b64metadata, []byte(metadata))
	b64ciphertext = make([]byte, base64.URLEncoding.EncodedLen(len(ciphertext)))
	base64.URLEncoding.Encode(b64ciphertext, ciphertext)
	b64nonce = make([]byte, base64.URLEncoding.EncodedLen(len(nonce)))
	base64.URLEncoding.Encode(b64nonce, nonce)

	//Compose a <b64URLmetadata>.<b64URLciphertext>.<b64URLnonce> literal
	buf.Write(b64metadata)
	buf.Write([]byte("."))
	buf.Write(b64ciphertext)
	buf.Write([]byte("."))
	buf.Write(b64nonce)

	//Return the AEAD literal
	return string(buf.Bytes()), nil
}

//Decrypt decrypts a literal of the form <b64URLmetadata>.<b64URLciphertext>.<b64URLnonce> given an AEAD and
//producing metadata, data strings
func Decrypt(aeadCipher cipher.AEAD, literal string) (string, string, error) {
	var (
		literalSubStrings []string
		metadata          []byte
		ciphertext        []byte
		nonce             []byte
		data              []byte
		err               error
	)

	//Split the literal into its base64 encoded metadata, ciphertext and nonce components
	literalSubStrings = strings.Split(literal, ".")
	if len(literalSubStrings) != 3 {
		return "", "", fmt.Errorf("Bad AEAD Literal: %v\n", literal)
	}

	//Decode the metadata, ciphertext and nonce
	metadata, err = base64.URLEncoding.DecodeString(literalSubStrings[0])
	if err != nil {
		return "", "", fmt.Errorf("Decode metadata failed: %v\n", literal)
	}
	ciphertext, err = base64.URLEncoding.DecodeString(literalSubStrings[1])
	if err != nil {
		return "", "", fmt.Errorf("Decode ciphertext failed: %v\n", literal)
	}
	nonce, err = base64.URLEncoding.DecodeString(literalSubStrings[2])
	if err != nil {
		return "", "", fmt.Errorf("Decode nonce failed: %v\n", literal)
	}

	//Open validates the integrity of the metadata using the authentication code in the ciphertext
	//and, if valid, decrypts the ciphertext
	data, err = aeadCipher.Open(data, nonce, ciphertext, metadata)
	if err != nil {
		return "", "", err
	}
	return string(metadata), string(data), nil
}
<commit_msg>Improved aead<commit_after>/*
package aead uses aead crypto to encrypt and authenticate content composed of a plaintext metadata string and a plaintext data string.
An encryption results in a string literal of the form <b64URLmetadata>.<b64URLciphertext>.<b64URLnonce>. The user of this package must supply a
crypto.AEAD created with the same key in order to encrypt, transmit and decrypt a literal produced by Encrypt.
*/
package aead

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"strings"
)

/*
NewAEADCipher creates a new AEAD cipher using an aes cipher created with a 32 byte random key for use with this package
*/
func NewAEADCipher() (cipher.AEAD, error) {
	var (
		key         = make([]byte, 32)
		cipherBlock cipher.Block
		aeadCipher  cipher.AEAD
		err         error
	)

	//Create a new key
	_, err = rand.Read(key)
	if err != nil {
		return nil, err
	}
	//The key is used to create an AES Cipher Block
	cipherBlock, err = aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	//The AES Cipher Block is used to create an AEAD GCM which is a 128-bit, block cipher wrapped
	//in a Galois Counter Mode with the standard nonce length.
	//This is used to encrypt/decrypt all subscriber identifiers in the hidden fields of TBD 2nd Factor Selection Forms.
	aeadCipher, err = cipher.NewGCM(cipherBlock)
	if err != nil {
		return nil, err
	}
	return aeadCipher, nil
}

/*
Encrypt generates a literal of the form <b64URLmetadata>.<b64URLciphertext>.<b64URLnonce> given an AEAD, a metadata string and a data
string. Only the data is encrypted - the metadata must be appropriate to expose in the clear. Each call generates a random
nonce of the length required by the aeadCipher.
*/
func Encrypt(aeadCipher cipher.AEAD, metadata, data string) (string, error) {

	var (
		nonce         = make([]byte, aeadCipher.NonceSize())
		ciphertext    []byte
		b64metadata   []byte
		b64ciphertext []byte
		b64nonce      []byte
		buf           bytes.Buffer
		err           error
	)

	//A nonce of the length required by the AEAD is generated
	_, err = rand.Read(nonce)
	if err != nil {
		return "", err
	}

	//Seal encrypts the data using the aeadCipher's key and the nonce and appends an authentication code for the metadata
	ciphertext = aeadCipher.Seal(ciphertext, nonce, []byte(data), []byte(metadata))

	//Base64 Encode metadata, ciphertext and nonce
	b64metadata = make([]byte, base64.URLEncoding.EncodedLen(len([]byte(metadata))))
	base64.StdEncoding.Encode(b64metadata, []byte(metadata))
	b64ciphertext = make([]byte, base64.URLEncoding.EncodedLen(len(ciphertext)))
	base64.URLEncoding.Encode(b64ciphertext, ciphertext)
	b64nonce = make([]byte, base64.URLEncoding.EncodedLen(len(nonce)))
	base64.URLEncoding.Encode(b64nonce, nonce)

	//Compose a <b64URLmetadata>.<b64URLciphertext>.<b64URLnonce> literal
	buf.Write(b64metadata)
	buf.Write([]byte("."))
	buf.Write(b64ciphertext)
	buf.Write([]byte("."))
	buf.Write(b64nonce)

	//Return the AEAD literal
	return string(buf.Bytes()), nil
}

/*
Decrypt decrypts a literal of the form <b64URLmetadata>.<b64URLciphertext>.<b64URLnonce> given an AEAD and
producing metadata, data strings
*/
func Decrypt(aeadCipher cipher.AEAD, literal string) (string, string, error) {
	var (
		literalSubStrings []string
		metadata          []byte
		ciphertext        []byte
		nonce             []byte
		data              []byte
		err               error
	)

	//Split the literal into its base64 encoded metadata, ciphertext and nonce components
	literalSubStrings = strings.Split(literal, ".")
	if len(literalSubStrings) != 3 {
		return "", "", fmt.Errorf("Bad AEAD Literal: %v\n", literal)
	}

	//Decode the metadata, ciphertext and nonce
	metadata, err = base64.URLEncoding.DecodeString(literalSubStrings[0])
	if err != nil {
		return "", "", fmt.Errorf("Decode metadata failed: %v\n", literal)
	}
	ciphertext, err = base64.URLEncoding.DecodeString(literalSubStrings[1])
	if err != nil {
		return "", "", fmt.Errorf("Decode ciphertext failed: %v\n", literal)
	}
	nonce, err = base64.URLEncoding.DecodeString(literalSubStrings[2])
	if err != nil {
		return "", "", fmt.Errorf("Decode nonce failed: %v\n", literal)
	}

	//Open validates the integrity of the metadata using the authentication code in the ciphertext
	//and, if valid, decrypts the ciphertext
	data, err = aeadCipher.Open(data, nonce, ciphertext, metadata)
	if err != nil {
		return "", "", err
	}
	return string(metadata), string(data), nil
}
<|endoftext|>