<commit_before>package engi

import (
	"log"
	"os"
	"time"
	"unsafe"

	"azul3d.org/audio.v1"
	"azul3d.org/native/al.v1"

	_ "azul3d.org/audio/flac.dev"
	_ "azul3d.org/audio/wav.v1"
)

var (
	audioDevice  *al.Device
	audioSources []uint32
	muted        bool
)

const (
	audioSourcesCount = 31
	buffersPerStream  = 3
)

type Stream struct {
	source  uint32
	buffers []uint32
	looping bool
	done    chan struct{}

	decoder audio.Decoder
	file    *os.File
}

func (s *Stream) Play() {
	s.looping = false
	s.play()
}

func (s *Stream) Loop() {
	s.looping = true
	s.play()
}

func (s *Stream) play() {
	s.done = make(chan struct{})
	s.source = nextAvailableSource()
	s.reset()
	audioDevice.Sourcei(s.source, al.LOOPING, al.FALSE)
	// fill all buffers first
	for _, buf := range s.buffers {
		s.fill(buf)
	}

	audioDevice.SourceQueueBuffers(s.source, s.buffers)
	go s.run()

	audioDevice.SourcePlay(s.source)
}

func (s *Stream) reset() {
	// flac pkg can't seek yet so ghetto seek to 0
	s.file.Seek(0, os.SEEK_SET)
	var err error
	s.decoder, _, err = audio.NewDecoder(s.file)
	if err != nil {
		panic(err)
	}
}

func (s *Stream) run() {
	defer func() {
		s.done = nil
	}()
	for {
		select {
		case <-s.done:
			return
		default:
			processed := s.unqueue()
			if len(processed) == 0 {
				continue
			}

			for _, buf := range processed {
				err := s.fill(buf)
				switch {
				case err == audio.EOS:
					if s.looping {
						// time.Sleep(500 * time.Millisecond)
						// start over
						s.reset()
					}
				case err != nil:
					panic(err)
				}
				audioDevice.SourceQueueBuffers(s.source, []uint32{buf})
				if err == audio.EOS && !s.looping {
					return
				}
			}
		}
	}
}

func (s *Stream) unqueue() []uint32 {
	var processed int32

	audioDevice.GetSourcei(s.source, al.BUFFERS_PROCESSED, &processed)
	if processed == 0 {
		return nil
	}
	available := make([]uint32, processed)
	audioDevice.SourceUnqueueBuffers(s.source, available)
	return available
}

func (s *Stream) fill(buffer uint32) error {
	config := s.decoder.Config()
	bufSize := config.SampleRate
	samples := make(audio.PCM16Samples, bufSize)

	read, err := s.decoder.Read(samples)
	if err != nil && err != audio.EOS {
		return err
	}

	if read > 0 {
		data := []audio.PCM16(samples[:read])
		if config.Channels == 1 {
			audioDevice.BufferData(buffer, al.FORMAT_MONO16, unsafe.Pointer(&data[0]), int32(int(unsafe.Sizeof(data[0]))*read), int32(config.SampleRate))
		} else {
			audioDevice.BufferData(buffer, al.FORMAT_STEREO16, unsafe.Pointer(&data[0]), int32(int(unsafe.Sizeof(data[0]))*read), int32(config.SampleRate))
		}
	}

	return err
}

func (s *Stream) Delete() {
	if s.done != nil {
		close(s.done)
	}
	s.file.Close()

	audioDevice.DeleteBuffers(buffersPerStream, &s.buffers[0])
}

func (s *Stream) Playing() bool {
	if s.source == 0 {
		return false
	}
	var state int32
	audioDevice.GetSourcei(s.source, al.SOURCE_STATE, &state)
	return state == al.PLAYING
}

func (s *Stream) Stop() {
	if s.done != nil {
		close(s.done)
	}
	audioDevice.SourceStop(s.source)
	s.unqueue()
	s.source = 0
}

func loadStream(r Resource) (*Stream, error) {
	// func readSoundFile(filename string) (samples []audio.PCM16, config audio.Config, duration time.Duration, err error) {
	file, err := os.Open(r.url)
	if err != nil {
		return nil, err
	}

	s := Stream{
		buffers: make([]uint32, buffersPerStream),
		file:    file,
	}

	audioDevice.GenBuffers(buffersPerStream, &s.buffers[0])

	return &s, nil
}

type Sound struct {
	source     uint32
	buffer     uint32
	duration   time.Duration // seconds
	sampleRate int
	looping    bool
}

func (s *Sound) bind() {
	s.source = nextAvailableSource()
	audioDevice.Sourcei(s.source, al.BUFFER, int32(s.buffer))
	if s.looping {
		audioDevice.Sourcei(s.source, al.LOOPING, al.TRUE)
	} else {
		audioDevice.Sourcei(s.source, al.LOOPING, al.FALSE)
	}
}

func (s *Sound) Play() {
	s.looping = false
	s.bind()
	audioDevice.SourcePlay(s.source)
}

func (s *Sound) Loop() {
	s.looping = true
	s.bind()
	audioDevice.SourcePlay(s.source)
}

func (s *Sound) Stop() {
	audioDevice.SourceStop(s.source)
	s.unqueue()
}

func (s *Sound) unqueue() []uint32 {
	var processed int32
	audioDevice.GetSourcei(s.source, al.BUFFERS_PROCESSED, &processed)
	if processed == 0 {
		return nil
	}
	available := make([]uint32, processed)
	audioDevice.SourceUnqueueBuffers(s.source, available)
	return available
}

func (s *Sound) Delete() {
	audioDevice.DeleteBuffers(1, &s.buffer)
}

func (s *Sound) Playing() bool {
	var state int32
	audioDevice.GetSourcei(s.source, al.SOURCE_STATE, &state)
	return state == al.PLAYING
}

func (s *Sound) Duration() time.Duration {
	return s.duration
}

func ToggleMute() {
	muted = !muted
	gain := float32(1)
	if muted {
		gain = 0
	}

	for _, src := range audioSources {
		audioDevice.Sourcef(src, al.GAIN, gain)
	}
}

func Muted() bool {
	return muted
}

func setupAudio() {
	var err error
	audioDevice, err = al.OpenDevice("", nil)
	fatalErr(err)

	audioSources = make([]uint32, audioSourcesCount)
	audioDevice.GenSources(audioSourcesCount, &audioSources[0])
}

func nextAvailableSource() uint32 {
	// find unused source
	for _, source := range audioSources {
		var state int32
		audioDevice.GetSourcei(source, al.SOURCE_STATE, &state)
		if state != al.PLAYING {
			audioDevice.Sourcei(source, al.BUFFER, 0)
			return source
		}
	}

	// no free sounds. find non-looping one and cut it short
	for _, source := range audioSources {
		var looping int32
		audioDevice.GetSourcei(source, al.LOOPING, &looping)
		if looping != al.TRUE {
			audioDevice.SourceStop(source)
			return source
		}
	}

	// give up, take the last one
	source := audioSources[len(audioSources)-1]
	audioDevice.SourceStop(source)
	return source
}

func loadSound(r Resource) (*Sound, error) {
	samples, config, duration, err := readSoundFile(r.url)
	if err != nil {
		return nil, err
	}

	s := Sound{
		duration:   duration,
		sampleRate: config.SampleRate,
	}
	audioDevice.GenBuffers(1, &s.buffer)
	if config.Channels == 1 {
		audioDevice.BufferData(s.buffer, al.FORMAT_MONO16, unsafe.Pointer(&samples[0]), int32(int(unsafe.Sizeof(samples[0]))*len(samples)), int32(config.SampleRate))
	} else {
		audioDevice.BufferData(s.buffer, al.FORMAT_STEREO16, unsafe.Pointer(&samples[0]), int32(int(unsafe.Sizeof(samples[0]))*len(samples)), int32(config.SampleRate))
	}
	return &s, err
}

func readSoundFile(filename string) (samples []audio.PCM16, config audio.Config, duration time.Duration, err error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, audio.Config{}, 0, err
	}
	defer file.Close()
	fi, err := file.Stat()
	if err != nil {
		return nil, audio.Config{}, 0, err
	}

	decoder, _, err := audio.NewDecoder(file)
	if err != nil {
		return nil, audio.Config{}, 0, err
	}

	config = decoder.Config()

	// Convert everything to 16-bit samples
	bufSize := int(fi.Size())
	samples = make(audio.PCM16Samples, 0, bufSize)

	// TODO: surely there is a better way to do this
	var read int
	buf := make(audio.PCM16Samples, 1024*1024)
	err = nil
	for err != audio.EOS {
		var r int
		r, err = decoder.Read(buf)
		if err != nil && err != audio.EOS {
			return nil, audio.Config{}, 0, err
		}
		read += r
		samples = append(samples, buf[:r]...)
	}

	secs := 1 / float64(config.SampleRate) * float64(read)
	duration = time.Duration(float64(time.Second) * secs)
	return []audio.PCM16(samples)[:read], config, duration, nil
}

func cleanupAudio() {
	audioDevice.DeleteSources(int32(len(audioSources)), &audioSources[0])
	// TODO: openal: invalid name parameter
	// error somewhere around here
	for _, s := range Files.sounds {
		s.Delete()
	}
	for _, s := range Files.streams {
		s.Delete()
	}
	if audioDevice != nil {
		audioDevice.Close()
	}
}

func init() {
	al.SetErrorHandler(func(err error) {
		log.Println("[audio]", err)
	})
}
<commit_msg>fix openAL complaints<commit_after>package engi

import (
	"log"
	"os"
	"time"
	"unsafe"

	"azul3d.org/audio.v1"
	"azul3d.org/native/al.v1"

	_ "azul3d.org/audio/flac.dev"
	_ "azul3d.org/audio/wav.v1"
)

var (
	audioDevice  *al.Device
	audioSources []uint32
	muted        bool
)

const (
	audioSourcesCount = 31
	buffersPerStream  = 3
)

type Stream struct {
	source  uint32
	buffers []uint32
	looping bool
	done    chan struct{}

	decoder audio.Decoder
	file    *os.File
}

func (s *Stream) Play() {
	s.looping = false
	s.play()
}

func (s *Stream) Loop() {
	s.looping = true
	s.play()
}

func (s *Stream) play() {
	s.done = make(chan struct{})
	s.source = nextAvailableSource()
	s.reset()
	audioDevice.Sourcei(s.source, al.LOOPING, al.FALSE)
	// fill all buffers first
	for _, buf := range s.buffers {
		s.fill(buf)
	}

	audioDevice.SourceQueueBuffers(s.source, s.buffers)
	go s.run()

	audioDevice.SourcePlay(s.source)
}

func (s *Stream) reset() {
	// flac pkg can't seek yet so ghetto seek to 0
	s.file.Seek(0, os.SEEK_SET)
	var err error
	s.decoder, _, err = audio.NewDecoder(s.file)
	if err != nil {
		panic(err)
	}
}

func (s *Stream) run() {
	defer func() {
		s.done = nil
	}()
	for {
		select {
		case <-s.done:
			return
		default:
			processed := s.unqueue()
			if len(processed) == 0 {
				continue
			}

			for _, buf := range processed {
				err := s.fill(buf)
				switch {
				case err == audio.EOS:
					if s.looping {
						// time.Sleep(500 * time.Millisecond)
						// start over
						s.reset()
					}
				case err != nil:
					panic(err)
				}
				audioDevice.SourceQueueBuffers(s.source, []uint32{buf})
				if err == audio.EOS && !s.looping {
					return
				}
			}
		}
	}
}

func (s *Stream) unqueue() []uint32 {
	var processed int32

	audioDevice.GetSourcei(s.source, al.BUFFERS_PROCESSED, &processed)
	if processed == 0 {
		return nil
	}
	available := make([]uint32, processed)
	audioDevice.SourceUnqueueBuffers(s.source, available)
	return available
}

func (s *Stream) fill(buffer uint32) error {
	config := s.decoder.Config()
	bufSize := config.SampleRate
	samples := make(audio.PCM16Samples, bufSize)

	read, err := s.decoder.Read(samples)
	if err != nil && err != audio.EOS {
		return err
	}

	if read > 0 {
		data := []audio.PCM16(samples[:read])
		if config.Channels == 1 {
			audioDevice.BufferData(buffer, al.FORMAT_MONO16, unsafe.Pointer(&data[0]), int32(int(unsafe.Sizeof(data[0]))*read), int32(config.SampleRate))
		} else {
			audioDevice.BufferData(buffer, al.FORMAT_STEREO16, unsafe.Pointer(&data[0]), int32(int(unsafe.Sizeof(data[0]))*read), int32(config.SampleRate))
		}
	}

	return err
}

func (s *Stream) Delete() {
	if s.done != nil {
		close(s.done)
	}
	s.file.Close()

	audioDevice.DeleteBuffers(buffersPerStream, &s.buffers[0])
}

func (s *Stream) Playing() bool {
	if s.source == 0 {
		return false
	}
	var state int32
	audioDevice.GetSourcei(s.source, al.SOURCE_STATE, &state)
	return state == al.PLAYING
}

func (s *Stream) Stop() {
	if s.done != nil {
		close(s.done)
	}
	audioDevice.SourceStop(s.source)
	s.unqueue()
	s.source = 0
}

func loadStream(r Resource) (*Stream, error) {
	// func readSoundFile(filename string) (samples []audio.PCM16, config audio.Config, duration time.Duration, err error) {
	file, err := os.Open(r.url)
	if err != nil {
		return nil, err
	}

	s := Stream{
		buffers: make([]uint32, buffersPerStream),
		file:    file,
	}

	audioDevice.GenBuffers(buffersPerStream, &s.buffers[0])

	return &s, nil
}

type Sound struct {
	source     uint32
	buffer     uint32
	duration   time.Duration // seconds
	sampleRate int
	looping    bool
}

func (s *Sound) bind() {
	s.source = nextAvailableSource()
	audioDevice.Sourcei(s.source, al.BUFFER, int32(s.buffer))
	if s.looping {
		audioDevice.Sourcei(s.source, al.LOOPING, al.TRUE)
	} else {
		audioDevice.Sourcei(s.source, al.LOOPING, al.FALSE)
	}
}

func (s *Sound) Play() {
	s.looping = false
	s.bind()
	audioDevice.SourcePlay(s.source)
}

func (s *Sound) Loop() {
	s.looping = true
	s.bind()
	audioDevice.SourcePlay(s.source)
}

func (s *Sound) Stop() {
	audioDevice.SourceStop(s.source)
	s.unqueue()
}

func (s *Sound) unqueue() []uint32 {
	var processed int32
	audioDevice.GetSourcei(s.source, al.BUFFERS_PROCESSED, &processed)
	if processed == 0 {
		return nil
	}
	available := make([]uint32, processed)
	audioDevice.SourceUnqueueBuffers(s.source, available)
	return available
}

func (s *Sound) Delete() {
	audioDevice.DeleteBuffers(1, &s.buffer)
}

func (s *Sound) Playing() bool {
	var state int32
	audioDevice.GetSourcei(s.source, al.SOURCE_STATE, &state)
	return state == al.PLAYING
}

func (s *Sound) Duration() time.Duration {
	return s.duration
}

func ToggleMute() {
	muted = !muted
	gain := float32(1)
	if muted {
		gain = 0
	}

	for _, src := range audioSources {
		audioDevice.Sourcef(src, al.GAIN, gain)
	}
}

func Muted() bool {
	return muted
}

func setupAudio() {
	var err error
	audioDevice, err = al.OpenDevice("", nil)
	fatalErr(err)

	audioSources = make([]uint32, audioSourcesCount)
	audioDevice.GenSources(audioSourcesCount, &audioSources[0])
}

func nextAvailableSource() uint32 {
	// find unused source
	for _, source := range audioSources {
		var state int32
		audioDevice.GetSourcei(source, al.SOURCE_STATE, &state)
		if state != al.PLAYING {
			audioDevice.Sourcei(source, al.BUFFER, 0)
			return source
		}
	}

	// no free sounds. find non-looping one and cut it short
	for _, source := range audioSources {
		var looping int32
		audioDevice.GetSourcei(source, al.LOOPING, &looping)
		if looping != al.TRUE {
			audioDevice.SourceStop(source)
			return source
		}
	}

	// give up, take the last one
	source := audioSources[len(audioSources)-1]
	audioDevice.SourceStop(source)
	return source
}

func loadSound(r Resource) (*Sound, error) {
	samples, config, duration, err := readSoundFile(r.url)
	if err != nil {
		return nil, err
	}

	s := Sound{
		duration:   duration,
		sampleRate: config.SampleRate,
	}
	audioDevice.GenBuffers(1, &s.buffer)
	if config.Channels == 1 {
		audioDevice.BufferData(s.buffer, al.FORMAT_MONO16, unsafe.Pointer(&samples[0]), int32(int(unsafe.Sizeof(samples[0]))*len(samples)), int32(config.SampleRate))
	} else {
		audioDevice.BufferData(s.buffer, al.FORMAT_STEREO16, unsafe.Pointer(&samples[0]), int32(int(unsafe.Sizeof(samples[0]))*len(samples)), int32(config.SampleRate))
	}
	return &s, err
}

func readSoundFile(filename string) (samples []audio.PCM16, config audio.Config, duration time.Duration, err error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, audio.Config{}, 0, err
	}
	defer file.Close()
	fi, err := file.Stat()
	if err != nil {
		return nil, audio.Config{}, 0, err
	}

	decoder, _, err := audio.NewDecoder(file)
	if err != nil {
		return nil, audio.Config{}, 0, err
	}

	config = decoder.Config()

	// Convert everything to 16-bit samples
	bufSize := int(fi.Size())
	samples = make(audio.PCM16Samples, 0, bufSize)

	// TODO: surely there is a better way to do this
	var read int
	buf := make(audio.PCM16Samples, 1024*1024)
	err = nil
	for err != audio.EOS {
		var r int
		r, err = decoder.Read(buf)
		if err != nil && err != audio.EOS {
			return nil, audio.Config{}, 0, err
		}
		read += r
		samples = append(samples, buf[:r]...)
	}

	secs := 1 / float64(config.SampleRate) * float64(read)
	duration = time.Duration(float64(time.Second) * secs)
	return []audio.PCM16(samples)[:read], config, duration, nil
}

func cleanupAudio() {
	audioDevice.DeleteSources(int32(len(audioSources)), &audioSources[0])
	for _, s := range Files.sounds {
		if s.Playing() {
			s.Stop()
		}
		s.Delete()
	}
	for _, s := range Files.streams {
		if s.Playing() {
			s.Stop()
		}
		s.Delete()
	}
	if audioDevice != nil {
		audioDevice.Close()
	}
}

func init() {
	al.SetErrorHandler(func(err error) {
		log.Println("[audio]", err)
	})
}
<|endoftext|>


<commit_before>package main

import "fmt"
import "os"

func main() {
    fmt.Printf("Hello welcome to the Distributed email\n")
        for {
            fmt.Printf("Menu\n 1. New email\n 2. Check your inbox\n 3. Exit\n Enter Number:\n")
            menu()
        }
}

func menu(){
  var input int
  n, err := fmt.Scanln(&input)
  if n < 1 || n > 2 || err != nil {
    fmt.Println("Invalid input\n")
    return
  }
  switch input {
  case 1:
      fmt.Println("Chamar funcao criar mail")
  case 2:
        fmt.Println("Chamar funcao check inbox")
  case 3:
      fmt.Printf("Hello welcome to the Distributed email\n")
      os.Exit(0)
  default:
      fmt.Println("Invalid Input\n")
  }

}
<commit_msg>menu + criar mail<commit_after>package main

import "fmt"
import "os"

func main() {
    fmt.Printf("Hello welcome to the Distributed email\n")
        for {
          fmt.Printf("Menu\n 1.New email 2.Check your inbox 3. Exit\n Enter Number:\n")
            menu()
        }
}

func menu(){
  var input int
  n, err := fmt.Scanln(&input)
  if n < 1 || n > 2 || err != nil {
    fmt.Println("Invalid input\n")
    return
  }
  switch input {
  case 1:
      mail_information()
  case 2:
        fmt.Println("Chamar funcao check inbox\n")
  case 3:
      fmt.Println("Goodbye thank you for choosing us\n")
      os.Exit(0)
  default:
      fmt.Println("Invalid Input\n")
  }

}

func mail_information(){
  fmt.Println("Email information\n")
  var i = 1
  for i > 0{
    fmt.Println("From: \n")
    var input string
    n, err := fmt.Scanln(&input)
    if n < 1 || n > 2 || err != nil {
      fmt.Println("Invalid input\n")
      return
    } else {
      i = 0
      fmt.Println(input)
    }
  }
}
<|endoftext|>


<commit_before>/*
Copyright 2015 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

package state

import (
	"context"
	"io"
	"io/ioutil"
	"time"

	"github.com/gravitational/teleport/lib/events"
	"github.com/gravitational/teleport/lib/session"

	log "github.com/Sirupsen/logrus"
	"github.com/cenkalti/backoff"
	"github.com/gravitational/trace"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	// DefaultQueueLen determines how many logging events to queue in-memory
	// before start dropping them (probably because logging server is down)
	DefaultQueueLen = 300
	// DefaultFlushTimeout is a period to flush after no other events have been received
	DefaultFlushTimeout = time.Second
	// DefaultFlushChunks is a max chunks accumulated over period to flush
	DefaultFlushChunks = 250
	// DefaultFlushBytes is a max bytes of the chunks before the flush will be triggered
	DefaultFlushBytes = 100000
	// DefaultThrottleTimeout is a latency after we will
	DefaultThrottleTimeout = 500 * time.Millisecond
	// DefaultThrottleDuration is a period that we will throttle the slow network for
	// before trying to send again
	DefaultThrottleDuration = 10 * time.Second
	// DefaultBackoffInitialInterval is initial interval for backoff
	DefaultBackoffInitialInterval = 100 * time.Millisecond
	// DefaultBackoffMaxInterval is maximum interval for backoff
	DefaultBackoffMaxInterval = DefaultThrottleDuration
)

var (
	errNotSupported = trace.BadParameter("method not supported")
)

var (
	auditLatencies = prometheus.NewHistogram(
		prometheus.HistogramOpts{
			Name: "audit_latency_microseconds",
			Help: "Latency for audit log submission",
			// Buckets in microsecnd latencies
			Buckets: prometheus.ExponentialBuckets(5000, 1.5, 15),
		},
	)
	auditChunks = prometheus.NewHistogram(
		prometheus.HistogramOpts{
			Name:    "audit_chunks_total",
			Help:    "Chunks per slice submitted",
			Buckets: prometheus.LinearBuckets(10, 20, 10),
		},
	)
	auditBytesPerChunk = prometheus.NewHistogram(
		prometheus.HistogramOpts{
			Name:    "audit_chunk_bytes",
			Help:    "Bytes per submitted per chunk",
			Buckets: prometheus.ExponentialBuckets(10, 3.0, 10),
		},
	)
	auditBytesPerSlice = prometheus.NewHistogram(
		prometheus.HistogramOpts{
			Name:    "audit_slice_bytes",
			Help:    "Bytes per submitted slice of chunks",
			Buckets: prometheus.ExponentialBuckets(100, 3.0, 10),
		},
	)
	auditRequests = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "audit_requests_total",
			Help: "Number of audit requests",
		},
		[]string{"result"},
	)
)

func init() {
	// Metrics have to be registered to be exposed:
	prometheus.MustRegister(auditLatencies)
	prometheus.MustRegister(auditChunks)
	prometheus.MustRegister(auditBytesPerChunk)
	prometheus.MustRegister(auditBytesPerSlice)
	prometheus.MustRegister(auditRequests)
}

// CachingAuditLogConifig sets configuration for caching audit log
type CachingAuditLogConfig struct {
	// Namespace is session namespace
	Namespace string
	// SessionID is session ID this log forwards for
	SessionID string
	// Server is the server receiving audit events
	Server events.IAuditLog
	// QueueLen is length of the caching queue
	QueueLen int
	// FlushChunks controls how many chunks to aggregate before submit
	FlushChunks int
	// Context is an optional context
	Context context.Context
	// ThrottleTimeout is a timeout that triggers throttling
	ThrottleTimeout time.Duration
	// ThrottleDuration is a duration for throttling
	ThrottleDuration time.Duration
	// FlushTimeout is a period to flush buffered chunks if the queue
	// has not filled up yet
	FlushTimeout time.Duration
	// FlushBytes sets amount of bytes per slice that triggers
	// the flush to the server
	FlushBytes int64
	// BackoffInitialInterval is initial interval for backoff
	BackoffInitialInterval time.Duration
	// BackoffMaxInterval is maximum interval for backoff
	BackoffMaxInterval time.Duration
}

// CheckAndSetDefaults checks and sets defaults
func (c *CachingAuditLogConfig) CheckAndSetDefaults() error {
	if c.Namespace == "" {
		return trace.BadParameter("missing parameter Namespace")
	}
	if c.SessionID == "" {
		return trace.BadParameter("missing parameter SessionID")
	}
	if c.Server == nil {
		return trace.BadParameter("missing parameter Server")
	}
	if c.QueueLen == 0 {
		c.QueueLen = DefaultQueueLen
	}
	if c.FlushChunks == 0 {
		c.FlushChunks = DefaultFlushChunks
	}
	if c.Context == nil {
		c.Context = context.TODO()
	}
	if c.ThrottleTimeout == 0 {
		c.ThrottleTimeout = DefaultThrottleTimeout
	}
	if c.ThrottleTimeout == 0 {
		c.ThrottleTimeout = DefaultThrottleTimeout
	}
	if c.ThrottleDuration == 0 {
		c.ThrottleDuration = DefaultThrottleDuration
	}
	if c.FlushTimeout == 0 {
		c.FlushTimeout = DefaultFlushTimeout
	}
	if c.FlushBytes == 0 {
		c.FlushBytes = DefaultFlushBytes
	}
	if c.BackoffInitialInterval == 0 {
		c.BackoffInitialInterval = DefaultBackoffInitialInterval
	}
	if c.BackoffMaxInterval == 0 {
		c.BackoffMaxInterval = DefaultBackoffMaxInterval
	}
	return nil
}

// CachingAuditLog implements events.IAuditLog on the recording machine (SSH server)
// It captures the local recording and forwards it to the AuditLog network server
// Some important properties of this implementation:
//
// * Without back pressure on posting session chunks, audit log was loosing events
//   because produce was much faster than consume and buffer was oveflowing
//
// * Throttle is important to continue the session in case if audit log
//   slowness, as the session output will block and timeout on every request
//
// * It is important to pack chunnks, because ls -laR / would otherwise
//   generate about 10K requests per second. With this packing approach
//   we reduced this number to about 40-50 requests per second,
//   we can now tweak this parameter now by setting queue size and flush buffers.
//
// * Current implementation attaches audit log forwarder per session
type CachingAuditLog struct {
	CachingAuditLogConfig
	queue         chan []*events.SessionChunk
	cancel        context.CancelFunc
	ctx           context.Context
	chunks        []*events.SessionChunk
	bytes         int64
	throttleStart time.Time
}

func (ll *CachingAuditLog) add(chunks []*events.SessionChunk) {
	ll.chunks = append(ll.chunks, chunks...)
	for i := range chunks {
		ll.bytes += int64(len(chunks[i].Data))
	}
}

func (ll *CachingAuditLog) reset() []*events.SessionChunk {
	out := ll.chunks
	ll.chunks = make([]*events.SessionChunk, 0, ll.FlushChunks)
	ll.bytes = 0
	return out
}

// NewCachingAuditLog creaets a new & fully initialized instance of the alog
func NewCachingAuditLog(cfg CachingAuditLogConfig) (*CachingAuditLog, error) {
	if err := cfg.CheckAndSetDefaults(); err != nil {
		return nil, trace.Wrap(err)
	}
	ctx, cancel := context.WithCancel(context.TODO())
	ll := &CachingAuditLog{
		CachingAuditLogConfig: cfg,
		cancel:                cancel,
		ctx:                   ctx,
	}
	ll.queue = make(chan []*events.SessionChunk, ll.QueueLen)
	go ll.run()
	return ll, nil
}

// run thread is picking up logging events and tries to forward them
// to the logging server
func (ll *CachingAuditLog) run() {
	ticker := time.NewTicker(ll.FlushTimeout)
	defer ticker.Stop()
	var tickerC <-chan time.Time
	for {
		select {
		case <-ll.ctx.Done():
			ll.flush(flushOpts{force: true, noRetry: true})
			return
		case <-tickerC:
			// tick received to force flush after time passed
			tickerC = nil
			ll.flush(flushOpts{force: true})
		case chunks := <-ll.queue:
			ll.add(chunks)
			// we have received, set the timer
			// if no other chunks will not arrive to flush it
			tickerC = ticker.C
			ll.flush(flushOpts{force: false})
		}
	}
}

func (ll *CachingAuditLog) newExponentialBackoff() *backoff.ExponentialBackOff {
	b := &backoff.ExponentialBackOff{
		InitialInterval:     ll.BackoffInitialInterval,
		RandomizationFactor: backoff.DefaultRandomizationFactor,
		Multiplier:          backoff.DefaultMultiplier,
		MaxInterval:         ll.BackoffMaxInterval,
		Clock:               backoff.SystemClock,
	}
	b.Reset()
	return b
}

type flushOpts struct {
	force   bool
	noRetry bool
}

func (ll *CachingAuditLog) flush(opts flushOpts) {
	if len(ll.chunks) == 0 {
		return
	}
	if !opts.force {
		if len(ll.chunks) < ll.FlushChunks && ll.bytes < ll.FlushBytes {
			return
		}
	}
	chunks := ll.reset()
	slice := events.SessionSlice{
		Namespace: ll.Namespace,
		SessionID: ll.SessionID,
		Chunks:    chunks,
	}
	err := ll.postSlice(slice)
	if err == nil {
		return
	}
	log.Warningf("lost connection: %v", err)
	if opts.noRetry {
		return
	}
	ticker := backoff.NewTicker(ll.newExponentialBackoff())
	defer ticker.Stop()
	for {
		select {
		case <-ll.ctx.Done():
			log.Warningf("flush has returned")
			return
		case <-ticker.C:
			err := ll.postSlice(slice)
			if err == nil {
				return
			} else {
				log.Warningf("lost connection, retried with error: %v", err)
			}
		}
	}
}

func (ll *CachingAuditLog) postSlice(slice events.SessionSlice) error {
	start := time.Now()
	err := ll.Server.PostSessionSlice(slice)
	auditRequests.WithLabelValues("total").Inc()
	if err != nil {
		auditRequests.WithLabelValues("fail").Inc()
	} else {
		auditRequests.WithLabelValues("ok").Inc()
	}
	auditLatencies.Observe(float64(time.Now().Sub(start) / time.Microsecond))
	auditChunks.Observe(float64(len(slice.Chunks)))

	var bytes int64
	for _, c := range slice.Chunks {
		bytes += int64(len(c.Data))
		auditBytesPerChunk.Observe(float64(len(c.Data)))
	}
	auditBytesPerSlice.Observe(float64(bytes))
	return err
}

func (ll *CachingAuditLog) post(chunks []*events.SessionChunk) error {
	if time.Now().Before(ll.throttleStart) {
		return nil
	}

	select {
	case <-ll.ctx.Done():
		return nil
	case ll.queue <- chunks:
		return nil
	default:
		// the queue is blocked, now we will create a timer
		// to detect the timeout
	}
	timer := time.NewTimer(ll.ThrottleTimeout)
	defer timer.Stop()
	select {
	case ll.queue <- chunks:
	case <-ll.ctx.Done():
		return nil
	case <-timer.C:
		ll.throttleStart = time.Now().Add(ll.ThrottleDuration)
		log.Warningf("latency spiked over %v, will throttle audit log forward until %v", ll.ThrottleTimeout, ll.throttleStart)
	}
	return nil
}

func (ll *CachingAuditLog) Close() error {
	ll.cancel()
	return nil
}

func (ll *CachingAuditLog) EmitAuditEvent(eventType string, fields events.EventFields) error {
	return ll.Server.EmitAuditEvent(eventType, fields)
}

func (ll *CachingAuditLog) PostSessionChunk(namespace string, sid session.ID, reader io.Reader) error {
	data, err := ioutil.ReadAll(reader)
	if err != nil {
		return trace.Wrap(err)
	}
	chunks := []*events.SessionChunk{
		{
			Data: data,
			Time: time.Now().UTC().UnixNano(),
		},
	}
	return ll.post(chunks)
}

func (ll *CachingAuditLog) PostSessionSlice(slice events.SessionSlice) error {
	return ll.post(slice.Chunks)
}

func (ll *CachingAuditLog) GetSessionChunk(string, session.ID, int, int) ([]byte, error) {
	return nil, errNotSupported
}
func (ll *CachingAuditLog) GetSessionEvents(string, session.ID, int) ([]events.EventFields, error) {
	return nil, errNotSupported
}
func (ll *CachingAuditLog) SearchEvents(time.Time, time.Time, string) ([]events.EventFields, error) {
	return nil, errNotSupported
}
<commit_msg>collect remaining chunks after close<commit_after>/*
Copyright 2015 Gravitational, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

package state

import (
	"context"
	"io"
	"io/ioutil"
	"time"

	"github.com/gravitational/teleport/lib/events"
	"github.com/gravitational/teleport/lib/session"

	log "github.com/Sirupsen/logrus"
	"github.com/cenkalti/backoff"
	"github.com/gravitational/trace"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	// DefaultQueueLen determines how many logging events to queue in-memory
	// before start dropping them (probably because logging server is down)
	DefaultQueueLen = 300
	// DefaultFlushTimeout is a period to flush after no other events have been received
	DefaultFlushTimeout = time.Second
	// DefaultFlushChunks is a max chunks accumulated over period to flush
	DefaultFlushChunks = 250
	// DefaultFlushBytes is a max bytes of the chunks before the flush will be triggered
	DefaultFlushBytes = 100000
	// DefaultThrottleTimeout is a latency after we will
	DefaultThrottleTimeout = 500 * time.Millisecond
	// DefaultThrottleDuration is a period that we will throttle the slow network for
	// before trying to send again
	DefaultThrottleDuration = 10 * time.Second
	// DefaultBackoffInitialInterval is initial interval for backoff
	DefaultBackoffInitialInterval = 100 * time.Millisecond
	// DefaultBackoffMaxInterval is maximum interval for backoff
	DefaultBackoffMaxInterval = DefaultThrottleDuration
)

var (
	errNotSupported = trace.BadParameter("method not supported")
)

var (
	auditLatencies = prometheus.NewHistogram(
		prometheus.HistogramOpts{
			Name: "audit_latency_microseconds",
			Help: "Latency for audit log submission",
			// Buckets in microsecnd latencies
			Buckets: prometheus.ExponentialBuckets(5000, 1.5, 15),
		},
	)
	auditChunks = prometheus.NewHistogram(
		prometheus.HistogramOpts{
			Name:    "audit_chunks_total",
			Help:    "Chunks per slice submitted",
			Buckets: prometheus.LinearBuckets(10, 20, 10),
		},
	)
	auditBytesPerChunk = prometheus.NewHistogram(
		prometheus.HistogramOpts{
			Name:    "audit_chunk_bytes",
			Help:    "Bytes per submitted per chunk",
			Buckets: prometheus.ExponentialBuckets(10, 3.0, 10),
		},
	)
	auditBytesPerSlice = prometheus.NewHistogram(
		prometheus.HistogramOpts{
			Name:    "audit_slice_bytes",
			Help:    "Bytes per submitted slice of chunks",
			Buckets: prometheus.ExponentialBuckets(100, 3.0, 10),
		},
	)
	auditRequests = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "audit_requests_total",
			Help: "Number of audit requests",
		},
		[]string{"result"},
	)
)

func init() {
	// Metrics have to be registered to be exposed:
	prometheus.MustRegister(auditLatencies)
	prometheus.MustRegister(auditChunks)
	prometheus.MustRegister(auditBytesPerChunk)
	prometheus.MustRegister(auditBytesPerSlice)
	prometheus.MustRegister(auditRequests)
}

// CachingAuditLogConifig sets configuration for caching audit log
type CachingAuditLogConfig struct {
	// Namespace is session namespace
	Namespace string
	// SessionID is session ID this log forwards for
	SessionID string
	// Server is the server receiving audit events
	Server events.IAuditLog
	// QueueLen is length of the caching queue
	QueueLen int
	// FlushChunks controls how many chunks to aggregate before submit
	FlushChunks int
	// Context is an optional context
	Context context.Context
	// ThrottleTimeout is a timeout that triggers throttling
	ThrottleTimeout time.Duration
	// ThrottleDuration is a duration for throttling
	ThrottleDuration time.Duration
	// FlushTimeout is a period to flush buffered chunks if the queue
	// has not filled up yet
	FlushTimeout time.Duration
	// FlushBytes sets amount of bytes per slice that triggers
	// the flush to the server
	FlushBytes int64
	// BackoffInitialInterval is initial interval for backoff
	BackoffInitialInterval time.Duration
	// BackoffMaxInterval is maximum interval for backoff
	BackoffMaxInterval time.Duration
}

// CheckAndSetDefaults checks and sets defaults
func (c *CachingAuditLogConfig) CheckAndSetDefaults() error {
	if c.Namespace == "" {
		return trace.BadParameter("missing parameter Namespace")
	}
	if c.SessionID == "" {
		return trace.BadParameter("missing parameter SessionID")
	}
	if c.Server == nil {
		return trace.BadParameter("missing parameter Server")
	}
	if c.QueueLen == 0 {
		c.QueueLen = DefaultQueueLen
	}
	if c.FlushChunks == 0 {
		c.FlushChunks = DefaultFlushChunks
	}
	if c.Context == nil {
		c.Context = context.TODO()
	}
	if c.ThrottleTimeout == 0 {
		c.ThrottleTimeout = DefaultThrottleTimeout
	}
	if c.ThrottleTimeout == 0 {
		c.ThrottleTimeout = DefaultThrottleTimeout
	}
	if c.ThrottleDuration == 0 {
		c.ThrottleDuration = DefaultThrottleDuration
	}
	if c.FlushTimeout == 0 {
		c.FlushTimeout = DefaultFlushTimeout
	}
	if c.FlushBytes == 0 {
		c.FlushBytes = DefaultFlushBytes
	}
	if c.BackoffInitialInterval == 0 {
		c.BackoffInitialInterval = DefaultBackoffInitialInterval
	}
	if c.BackoffMaxInterval == 0 {
		c.BackoffMaxInterval = DefaultBackoffMaxInterval
	}
	return nil
}

// CachingAuditLog implements events.IAuditLog on the recording machine (SSH server)
// It captures the local recording and forwards it to the AuditLog network server
// Some important properties of this implementation:
//
// * Without back pressure on posting session chunks, audit log was loosing events
//   because produce was much faster than consume and buffer was oveflowing
//
// * Throttle is important to continue the session in case if audit log
//   slowness, as the session output will block and timeout on every request
//
// * It is important to pack chunnks, because ls -laR / would otherwise
//   generate about 10K requests per second. With this packing approach
//   we reduced this number to about 40-50 requests per second,
//   we can now tweak this parameter now by setting queue size and flush buffers.
//
// * Current implementation attaches audit log forwarder per session
type CachingAuditLog struct {
	CachingAuditLogConfig
	queue         chan []*events.SessionChunk
	cancel        context.CancelFunc
	ctx           context.Context
	chunks        []*events.SessionChunk
	bytes         int64
	throttleStart time.Time
}

func (ll *CachingAuditLog) add(chunks []*events.SessionChunk) {
	ll.chunks = append(ll.chunks, chunks...)
	for i := range chunks {
		ll.bytes += int64(len(chunks[i].Data))
	}
}

func (ll *CachingAuditLog) reset() []*events.SessionChunk {
	out := ll.chunks
	ll.chunks = make([]*events.SessionChunk, 0, ll.FlushChunks)
	ll.bytes = 0
	return out
}

// NewCachingAuditLog creaets a new & fully initialized instance of the alog
func NewCachingAuditLog(cfg CachingAuditLogConfig) (*CachingAuditLog, error) {
	if err := cfg.CheckAndSetDefaults(); err != nil {
		return nil, trace.Wrap(err)
	}
	ctx, cancel := context.WithCancel(context.TODO())
	ll := &CachingAuditLog{
		CachingAuditLogConfig: cfg,
		cancel:                cancel,
		ctx:                   ctx,
	}
	ll.queue = make(chan []*events.SessionChunk, ll.QueueLen)
	go ll.run()
	return ll, nil
}

func (ll *CachingAuditLog) collectRemainingChunks() {
	for {
		select {
		case chunks := <-ll.queue:
			ll.add(chunks)
		default:
			return
		}
	}
}

// run thread is picking up logging events and tries to forward them
// to the logging server
func (ll *CachingAuditLog) run() {
	ticker := time.NewTicker(ll.FlushTimeout)
	defer ticker.Stop()
	var tickerC <-chan time.Time
	for {
		select {
		case <-ll.ctx.Done():
			ll.collectRemainingChunks()
			ll.flush(flushOpts{force: true, noRetry: true})
			return
		case <-tickerC:
			// tick received to force flush after time passed
			tickerC = nil
			ll.flush(flushOpts{force: true})
		case chunks := <-ll.queue:
			ll.add(chunks)
			// we have received, set the timer
			// if no other chunks will not arrive to flush it
			tickerC = ticker.C
			ll.flush(flushOpts{force: false})
		}
	}
}

func (ll *CachingAuditLog) newExponentialBackoff() *backoff.ExponentialBackOff {
	b := &backoff.ExponentialBackOff{
		InitialInterval:     ll.BackoffInitialInterval,
		RandomizationFactor: backoff.DefaultRandomizationFactor,
		Multiplier:          backoff.DefaultMultiplier,
		MaxInterval:         ll.BackoffMaxInterval,
		Clock:               backoff.SystemClock,
	}
	b.Reset()
	return b
}

type flushOpts struct {
	force   bool
	noRetry bool
}

func (ll *CachingAuditLog) flush(opts flushOpts) {
	log.Debugf("flush %#v, chunks %v", opts, ll.chunks)
	if len(ll.chunks) == 0 {
		return
	}
	if !opts.force {
		if len(ll.chunks) < ll.FlushChunks && ll.bytes < ll.FlushBytes {
			return
		}
	}
	chunks := ll.reset()
	slice := events.SessionSlice{
		Namespace: ll.Namespace,
		SessionID: ll.SessionID,
		Chunks:    chunks,
	}
	err := ll.postSlice(slice)
	if err == nil {
		return
	}
	log.Warningf("lost connection: %v", err)
	if opts.noRetry {
		return
	}
	ticker := backoff.NewTicker(ll.newExponentialBackoff())
	defer ticker.Stop()
	for {
		select {
		case <-ll.ctx.Done():
			return
		case <-ticker.C:
			err := ll.postSlice(slice)
			if err == nil {
				return
			} else {
				log.Warningf("lost connection, retried with error: %v", err)
			}
		}
	}
}

func (ll *CachingAuditLog) postSlice(slice events.SessionSlice) error {
	start := time.Now()
	err := ll.Server.PostSessionSlice(slice)
	auditRequests.WithLabelValues("total").Inc()
	if err != nil {
		auditRequests.WithLabelValues("fail").Inc()
	} else {
		auditRequests.WithLabelValues("ok").Inc()
	}
	auditLatencies.Observe(float64(time.Now().Sub(start) / time.Microsecond))
	auditChunks.Observe(float64(len(slice.Chunks)))

	var bytes int64
	for _, c := range slice.Chunks {
		bytes += int64(len(c.Data))
		auditBytesPerChunk.Observe(float64(len(c.Data)))
	}
	auditBytesPerSlice.Observe(float64(bytes))
	return err
}

func (ll *CachingAuditLog) post(chunks []*events.SessionChunk) error {
	if time.Now().Before(ll.throttleStart) {
		return nil
	}

	select {
	case <-ll.ctx.Done():
		return nil
	case ll.queue <- chunks:
		return nil
	default:
		// the queue is blocked, now we will create a timer
		// to detect the timeout
	}
	timer := time.NewTimer(ll.ThrottleTimeout)
	defer timer.Stop()
	select {
	case ll.queue <- chunks:
	case <-ll.ctx.Done():
		return nil
	case <-timer.C:
		ll.throttleStart = time.Now().Add(ll.ThrottleDuration)
		log.Warningf("latency spiked over %v, will throttle audit log forward until %v", ll.ThrottleTimeout, ll.throttleStart)
	}
	return nil
}

func (ll *CachingAuditLog) Close() error {
	ll.cancel()
	return nil
}

func (ll *CachingAuditLog) EmitAuditEvent(eventType string, fields events.EventFields) error {
	return ll.Server.EmitAuditEvent(eventType, fields)
}

func (ll *CachingAuditLog) PostSessionChunk(namespace string, sid session.ID, reader io.Reader) error {
	data, err := ioutil.ReadAll(reader)
	if err != nil {
		return trace.Wrap(err)
	}
	chunks := []*events.SessionChunk{
		{
			Data: data,
			Time: time.Now().UTC().UnixNano(),
		},
	}
	return ll.post(chunks)
}

func (ll *CachingAuditLog) PostSessionSlice(slice events.SessionSlice) error {
	return ll.post(slice.Chunks)
}

func (ll *CachingAuditLog) GetSessionChunk(string, session.ID, int, int) ([]byte, error) {
	return nil, errNotSupported
}
func (ll *CachingAuditLog) GetSessionEvents(string, session.ID, int) ([]events.EventFields, error) {
	return nil, errNotSupported
}
func (ll *CachingAuditLog) SearchEvents(time.Time, time.Time, string) ([]events.EventFields, error) {
	return nil, errNotSupported
}
<|endoftext|>


<commit_before>package main

import (
  "os"
  "fmt"
  "time"
  "regexp"
  "strings"
  "strconv"
  "gopkg.in/urfave/cli.v1"
  "github.com/parnurzeal/gorequest"
)

var globalEnableDebug bool
var globalBuildVersion string

func main() {
  year, _, _ := time.Now().Date()
  app := cli.NewApp()
  app.Name = "nelson-cli"
  app.Version = CurrentVersion()
  app.Copyright = "© "+strconv.Itoa(year)+" Verizon Labs"
  app.Usage = "remote control for the Nelson deployment system"
  app.EnableBashCompletion = true

  http := gorequest.New()
  pi   := ProgressIndicator()

  // switches for the cli
  var userGithubToken string
  var disableTLS bool
  var selectedDatacenter string
  var selectedNamespace string
  var selectedStatus string
  var selectedUnitPrefix string
  var selectedVersion string

  var selectedPort int64
  var selectedServiceType string
  var stackHash string
  var description string

  app.Flags = []cli.Flag {
    cli.BoolFlag{
      Name: "debug",
      Usage: "Enable debug mode on the network requests",
      Destination: &globalEnableDebug,
    },
  }

  app.Commands = []cli.Command {
    ////////////////////////////// LOGIN //////////////////////////////////
    {
      Name:    "login",
      Usage:   "login to nelson",
      Flags: []cli.Flag {
        cli.StringFlag{
          Name:   "token, t",
          Value:  "",
          Usage:  "your github personal access token",
          EnvVar: "GITHUB_TOKEN",
          Destination: &userGithubToken,
        },
        cli.BoolFlag {
          Name:  "disable-tls",
          Destination: &disableTLS,
        },
      },
      Action:  func(c *cli.Context) error {
        host := strings.TrimSpace(c.Args().First())
        if len(host) <= 0 {
          host = os.Getenv("NELSON_ADDR")
          if len(host) <= 0 {
            return cli.NewExitError("Either supply a host explicitly, or set $NELSON_ADDR", 1)
          }
        }

        if len(userGithubToken) <= 0 {
          return cli.NewExitError("You must specifiy a `--token` or a `-t` to login.", 1)
        }

        // fmt.Println("token: ", userGithubToken)
        // fmt.Println("host: ", host)
        pi.Start()
        Login(http, userGithubToken, host, disableTLS)
        pi.Stop()
        fmt.Println("Sucsessfully logged in to " + host)
        return nil
      },
    },
    ////////////////////////////// DATACENTER //////////////////////////////////
    {
      Name:        "datacenters",
      Aliases:     []string{"dcs"},
      Usage:       "Set of commands for working with Nelson datacenters",
      Subcommands: []cli.Command{
        {
          Name:  "list",
          Usage: "List all the available datacenters",
          Action: func(c *cli.Context) error {
            pi.Start()
            r, e := ListDatacenters(http, LoadDefaultConfig())
            pi.Stop()
            if e != nil {
              return cli.NewExitError("Unable to list datacenters.", 1)
            } else {
              PrintListDatacenters(r)
            }
            return nil
          },
        },
      },
    },
    ////////////////////////////// UNITS //////////////////////////////////
    {
      Name:        "units",
      Usage:       "Set of commands to obtain details about logical deployment units",
      Subcommands: []cli.Command{
        {
          Name:  "list",
          Usage: "list the available units",
          Flags: []cli.Flag {
            cli.StringFlag{
              Name:   "datacenters, d",
              Value:  "",
              Usage:  "Restrict list of units to a particular datacenter",
              Destination: &selectedDatacenter,
            },
            cli.StringFlag{
              Name:   "namespaces, ns",
              Value:  "",
              Usage:  "Restrict list of units to a particular namespace",
              Destination: &selectedNamespace,
            },
            cli.StringFlag{
              Name:   "statuses, s",
              Value:  "",
              Usage:  "Restrict list of units to a particular status. Defaults to 'active,manual'",
              Destination: &selectedStatus,
            },
          },
          Action: func(c *cli.Context) error {
            if(len(selectedDatacenter) > 0){
              if (!isValidCommaDelimitedList(selectedDatacenter)){
                return cli.NewExitError("You supplied an argument for 'datacenters' but it was not a valid comma-delimited list.", 1)
              }
            }
            if(len(selectedNamespace) > 0){
              if (!isValidCommaDelimitedList(selectedNamespace)){
                return cli.NewExitError("You supplied an argument for 'namespaces' but it was not a valid comma-delimited list.", 1)
              }
            } else {
              return cli.NewExitError("You must supply --namespaces or -ns argument to specify the namesapce(s) as a comma delimted form. i.e. devel,qa,prod or just devel", 1)
            }
            if(len(selectedStatus) > 0){
              if (!isValidCommaDelimitedList(selectedStatus)){
                return cli.NewExitError("You supplied an argument for 'statuses' but it was not a valid comma-delimited list.", 1)
              }
            }

            pi.Start()
            us, errs := ListUnits(selectedDatacenter, selectedNamespace, selectedStatus, http, LoadDefaultConfig())
            pi.Stop()
            if(errs != nil){
              return cli.NewExitError("Unable to list units", 1)
            } else {
              PrintListUnits(us)
            }
            return nil
          },
        },
        {
          Name:  "inspect",
          Usage: "Display details about a logical unit",
          Action: func(c *cli.Context) error {
            fmt.Println("inspecting unit: ", c.Args().First())
            return nil
          },
        },
      },
    },
    ////////////////////////////// STACK //////////////////////////////////
    {
      Name:        "stacks",
      Usage:       "Set of commands to obtain details about deployed stacks",
      Subcommands: []cli.Command{
        {
          Name:  "list",
          Usage: "list the available stacks",
          Flags: []cli.Flag {
            cli.StringFlag{
              Name:   "unit, u",
              Value:  "",
              Usage:  "Only list stacks that match a specified unit prefix",
              Destination: &selectedUnitPrefix,
            },
            cli.StringFlag{
              Name:   "datacenters, d",
              Value:  "",
              Usage:  "Restrict list of units to a particular datacenter",
              Destination: &selectedDatacenter,
            },
            cli.StringFlag{
              Name:   "namespaces, ns",
              Value:  "",
              Usage:  "Restrict list of units to a particular namespace",
              Destination: &selectedNamespace,
            },
            cli.StringFlag{
              Name:   "statuses, s",
              Value:  "",
              Usage:  "Restrict list of units to a particular status. Defaults to 'active,manual'",
              Destination: &selectedStatus,
            },
          },
          Action: func(c *cli.Context) error {
            if(len(selectedDatacenter) > 0){
              if (!isValidCommaDelimitedList(selectedDatacenter)){
                return cli.NewExitError("You supplied an argument for 'datacenters' but it was not a valid comma-delimited list.", 1)
              }
            }
            if(len(selectedNamespace) > 0){
              if (!isValidCommaDelimitedList(selectedNamespace)){
                return cli.NewExitError("You supplied an argument for 'namespaces' but it was not a valid comma-delimited list.", 1)
              }
            } else {
              return cli.NewExitError("You must supply --namespaces or -ns argument to specify the namesapce(s) as a comma delimted form. i.e. devel,qa,prod or just devel", 1)
            }
            if(len(selectedStatus) > 0){
              if (!isValidCommaDelimitedList(selectedStatus)){
                return cli.NewExitError("You supplied an argument for 'statuses' but it was not a valid comma-delimited list.", 1)
              }
            }

            pi.Start()
            r, e := ListStacks(selectedDatacenter, selectedNamespace, selectedStatus, http, LoadDefaultConfig())
            pi.Stop()
            if e != nil {
              PrintTerminalErrors(e)
              return cli.NewExitError("Unable to list stacks.", 1)
            } else {
              PrintListStacks(r)
            }
            return nil
          },
        },
        {
          Name:  "inspect",
          Usage: "Display the current status and details about a specific stack",
          Action: func(c *cli.Context) error {
            guid := c.Args().First()
            if len(guid) > 0 && IsValidGUID(guid) {
              pi.Start()
              r, e := InspectStack(guid, http, LoadDefaultConfig())
              pi.Stop()
              if e != nil {
                PrintTerminalErrors(e)
                return cli.NewExitError("Unable to inspect stacks '"+guid+"'.", 1)
              } else {
                PrintInspectStack(r)
              }
            } else {
              return cli.NewExitError("You must supply a valid GUID for the stack you want to inspect.", 1)
            }
            return nil
          },
        },
        {
          Name:  "redeploy",
          Usage: "Trigger a redeployment for a specific stack",
          Action: func(c *cli.Context) error {
            guid := c.Args().First()
            if IsValidGUID(guid) {
              r,e := Redeploy(guid, http, LoadDefaultConfig())

              if e != nil {
                return cli.NewExitError("Unable to request a redeploy. Response was:\n"+r, 1)
              } else {
                fmt.Println("===>> "+r)
              }
            } else {
              return cli.NewExitError("You must specify a valid GUID reference in order to redeploy a stack.", 1)
            }
            return nil
          },
        },
        {
          Name:  "manual",
          Usage: "Register a manual deployment",
          Flags: []cli.Flag {
            cli.StringFlag{
              Name:   "datacenter, dc",
              Value:  "",
              Usage:  "The datacenter for the service",
              Destination: &selectedDatacenter,
            },
            cli.StringFlag{
              Name:   "namespace, ns",
              Value:  "",
              Usage:  "The namespace for the service",
              Destination: &selectedNamespace,
            },
            cli.StringFlag{
              Name:   "service-type, st",
              Value:  "",
              Usage:  "The service type for the service",
              Destination: &selectedServiceType,
            },
            cli.StringFlag{
              Name:   "version, v",
              Value:  "",
              Usage:  "The version for the service",
              Destination: &selectedVersion,
            },
            cli.StringFlag{
              Name:   "hash",
              Value:  "",
              Usage:  "The hash for the stack",
              Destination: &stackHash,
            },
            cli.StringFlag{
              Name:   "description, d",
              Value:  "",
              Usage:  "Description for the service",
              Destination: &description,
            },
            cli.Int64Flag{
              Name:   "port",
              Value:  0,
              Usage:  "The exposed port for the service",
              Destination: &selectedPort,
            },
          },
          Action: func(c *cli.Context) error {
            if len(selectedDatacenter) > 0 && len(selectedNamespace) > 0 &&
               len(selectedServiceType) > 0 &&
               len(selectedVersion) > 0 &&
               len(stackHash) > 0 &&
               len(description) > 0 &&
               selectedPort > 0 {
              req := ManualDeploymentRequest{
                Datacenter: selectedDatacenter,
                Namespace: selectedNamespace,
                ServiceType: selectedServiceType,
                Version: selectedVersion,
                Hash: stackHash,
                Description: description,
                Port: selectedPort,
              }
              pi.Start()
              res, e := RegisterManualDeployment(req, http, LoadDefaultConfig())
              pi.Stop()
              if e != nil {
                return cli.NewExitError("Unable to register manual deployment.", 1)
              } else {
                fmt.Println(res)
              }
            } else {
              return cli.NewExitError("You must specify the following switches: \n\t--datacenter <string> \n\t--namespace <string> \n\t--service-type <string> \n\t--version <string> \n\t--hash <string> \n\t--port <int>", 1)
            }
            return nil
          },
        },
        {
          Name:  "deprecate",
          Usage: "Deprecate a unit/version combination (and all patch series)",
          Flags: []cli.Flag {
            cli.StringFlag{
              Name:   "unit, u",
              Value:  "",
              Usage:  "The unit you want to deprecate",
              Destination: &selectedUnitPrefix,
            },
            cli.StringFlag{
              Name:   "version, v",
              Value:  "",
              Usage:  "The feature version series you want to deprecate",
              Destination: &selectedVersion,
            },
          },
          Action: func(c *cli.Context) error {
            if len(selectedUnitPrefix) > 0 && len(selectedVersion) > 0 {
              match, _ := regexp.MatchString("(\\d+)\\.(\\d+)", selectedVersion)
              if (match == true) {
                splitVersion := strings.Split(selectedVersion, ".")
                mjr, _ := strconv.Atoi(splitVersion[0])
                min, _ := strconv.Atoi(splitVersion[1])
                ver := FeatureVersion {
                  Major: mjr,
                  Minor: min,
                }
                req := DeprecationRequest {
                  ServiceType: selectedUnitPrefix,
                  Version: ver,
                }
                r,e := Deprecate(req, http, LoadDefaultConfig())
                if e != nil {
                  return cli.NewExitError("Unable to deprecate unit+version series. Response was:\n"+r, 1)
                } else {
                  fmt.Println("===>> Deprecated "+selectedUnitPrefix+" "+selectedVersion)
                }
              } else {
                return cli.NewExitError("You must supply a feature version of the format XXX.XXX, e.g. 2.3, 4.56, 1.7", 1)
              }
            } else {
              return cli.NewExitError("Required --unit and/or --version inputs were not valid", 1)
            }
            return nil
          },
        },
        {
          Name:  "fs",
          Aliases: []string{"logs"},
          Usage: "Fetch the deployment log for a given stack",
          Action: func(c *cli.Context) error {
            guid := c.Args().First()
            if len(guid) > 0 && IsValidGUID(guid) {
              GetDeploymentLog(guid, http, LoadDefaultConfig())
            } else {
              return cli.NewExitError("You must specify a valid GUID for the stack you wish to view logs for.", 1)
            }
            return nil
          },
        },
      },
    },
    ////////////////////////////// SYSTEM //////////////////////////////////
    {
      Name:    "system",
      Usage:   "A set of operations to query Nelson to see what options are available",
      Subcommands: []cli.Command{
        {
          Name:  "cleanup-policies",
          Usage: "list the available cleanup policies",
          Action: func(c *cli.Context) error {
            pi.Start()
            policies, e := ListCleanupPolicies(http, LoadDefaultConfig())
            pi.Stop()
            if e != nil {
              PrintTerminalErrors(e)
              return cli.NewExitError("Unable to list the cleanup policies at this time.", 1)
            } else {
              PrintCleanupPolicies(policies)
            }
            return nil
          },
        },
      },
    },
    ////////////////////////////// WHOAMI //////////////////////////////////
    {
      Name:    "whoami",
      Usage:   "Ask nelson who you are currently logged in as",
      Action:  func(c *cli.Context) error {
        pi.Start()
        cfg := LoadDefaultConfig()
        sr, e := WhoAmI(http, cfg)
        pi.Stop()
        if e != nil {
          PrintTerminalErrors(e)
          return cli.NewExitError("Unable to determine who is currently logged into Nelson.", 1)
        } else {
          fmt.Println("===>> Currently logged in as "+sr.User.Name+" @ "+cfg.Endpoint)
        }
        return nil
      },
    },
  }

  app.Run(os.Args)
}
<commit_msg>fix typo<commit_after>package main

import (
  "os"
  "fmt"
  "time"
  "regexp"
  "strings"
  "strconv"
  "gopkg.in/urfave/cli.v1"
  "github.com/parnurzeal/gorequest"
)

var globalEnableDebug bool
var globalBuildVersion string

func main() {
  year, _, _ := time.Now().Date()
  app := cli.NewApp()
  app.Name = "nelson-cli"
  app.Version = CurrentVersion()
  app.Copyright = "© "+strconv.Itoa(year)+" Verizon Labs"
  app.Usage = "remote control for the Nelson deployment system"
  app.EnableBashCompletion = true

  http := gorequest.New()
  pi   := ProgressIndicator()

  // switches for the cli
  var userGithubToken string
  var disableTLS bool
  var selectedDatacenter string
  var selectedNamespace string
  var selectedStatus string
  var selectedUnitPrefix string
  var selectedVersion string

  var selectedPort int64
  var selectedServiceType string
  var stackHash string
  var description string

  app.Flags = []cli.Flag {
    cli.BoolFlag{
      Name: "debug",
      Usage: "Enable debug mode on the network requests",
      Destination: &globalEnableDebug,
    },
  }

  app.Commands = []cli.Command {
    ////////////////////////////// LOGIN //////////////////////////////////
    {
      Name:    "login",
      Usage:   "login to nelson",
      Flags: []cli.Flag {
        cli.StringFlag{
          Name:   "token, t",
          Value:  "",
          Usage:  "your github personal access token",
          EnvVar: "GITHUB_TOKEN",
          Destination: &userGithubToken,
        },
        cli.BoolFlag {
          Name:  "disable-tls",
          Destination: &disableTLS,
        },
      },
      Action:  func(c *cli.Context) error {
        host := strings.TrimSpace(c.Args().First())
        if len(host) <= 0 {
          host = os.Getenv("NELSON_ADDR")
          if len(host) <= 0 {
            return cli.NewExitError("Either supply a host explicitly, or set $NELSON_ADDR", 1)
          }
        }

        if len(userGithubToken) <= 0 {
          return cli.NewExitError("You must specifiy a `--token` or a `-t` to login.", 1)
        }

        // fmt.Println("token: ", userGithubToken)
        // fmt.Println("host: ", host)
        pi.Start()
        Login(http, userGithubToken, host, disableTLS)
        pi.Stop()
        fmt.Println("Successfully logged in to " + host)
        return nil
      },
    },
    ////////////////////////////// DATACENTER //////////////////////////////////
    {
      Name:        "datacenters",
      Aliases:     []string{"dcs"},
      Usage:       "Set of commands for working with Nelson datacenters",
      Subcommands: []cli.Command{
        {
          Name:  "list",
          Usage: "List all the available datacenters",
          Action: func(c *cli.Context) error {
            pi.Start()
            r, e := ListDatacenters(http, LoadDefaultConfig())
            pi.Stop()
            if e != nil {
              return cli.NewExitError("Unable to list datacenters.", 1)
            } else {
              PrintListDatacenters(r)
            }
            return nil
          },
        },
      },
    },
    ////////////////////////////// UNITS //////////////////////////////////
    {
      Name:        "units",
      Usage:       "Set of commands to obtain details about logical deployment units",
      Subcommands: []cli.Command{
        {
          Name:  "list",
          Usage: "list the available units",
          Flags: []cli.Flag {
            cli.StringFlag{
              Name:   "datacenters, d",
              Value:  "",
              Usage:  "Restrict list of units to a particular datacenter",
              Destination: &selectedDatacenter,
            },
            cli.StringFlag{
              Name:   "namespaces, ns",
              Value:  "",
              Usage:  "Restrict list of units to a particular namespace",
              Destination: &selectedNamespace,
            },
            cli.StringFlag{
              Name:   "statuses, s",
              Value:  "",
              Usage:  "Restrict list of units to a particular status. Defaults to 'active,manual'",
              Destination: &selectedStatus,
            },
          },
          Action: func(c *cli.Context) error {
            if(len(selectedDatacenter) > 0){
              if (!isValidCommaDelimitedList(selectedDatacenter)){
                return cli.NewExitError("You supplied an argument for 'datacenters' but it was not a valid comma-delimited list.", 1)
              }
            }
            if(len(selectedNamespace) > 0){
              if (!isValidCommaDelimitedList(selectedNamespace)){
                return cli.NewExitError("You supplied an argument for 'namespaces' but it was not a valid comma-delimited list.", 1)
              }
            } else {
              return cli.NewExitError("You must supply --namespaces or -ns argument to specify the namesapce(s) as a comma delimted form. i.e. devel,qa,prod or just devel", 1)
            }
            if(len(selectedStatus) > 0){
              if (!isValidCommaDelimitedList(selectedStatus)){
                return cli.NewExitError("You supplied an argument for 'statuses' but it was not a valid comma-delimited list.", 1)
              }
            }

            pi.Start()
            us, errs := ListUnits(selectedDatacenter, selectedNamespace, selectedStatus, http, LoadDefaultConfig())
            pi.Stop()
            if(errs != nil){
              return cli.NewExitError("Unable to list units", 1)
            } else {
              PrintListUnits(us)
            }
            return nil
          },
        },
        {
          Name:  "inspect",
          Usage: "Display details about a logical unit",
          Action: func(c *cli.Context) error {
            fmt.Println("inspecting unit: ", c.Args().First())
            return nil
          },
        },
      },
    },
    ////////////////////////////// STACK //////////////////////////////////
    {
      Name:        "stacks",
      Usage:       "Set of commands to obtain details about deployed stacks",
      Subcommands: []cli.Command{
        {
          Name:  "list",
          Usage: "list the available stacks",
          Flags: []cli.Flag {
            cli.StringFlag{
              Name:   "unit, u",
              Value:  "",
              Usage:  "Only list stacks that match a specified unit prefix",
              Destination: &selectedUnitPrefix,
            },
            cli.StringFlag{
              Name:   "datacenters, d",
              Value:  "",
              Usage:  "Restrict list of units to a particular datacenter",
              Destination: &selectedDatacenter,
            },
            cli.StringFlag{
              Name:   "namespaces, ns",
              Value:  "",
              Usage:  "Restrict list of units to a particular namespace",
              Destination: &selectedNamespace,
            },
            cli.StringFlag{
              Name:   "statuses, s",
              Value:  "",
              Usage:  "Restrict list of units to a particular status. Defaults to 'active,manual'",
              Destination: &selectedStatus,
            },
          },
          Action: func(c *cli.Context) error {
            if(len(selectedDatacenter) > 0){
              if (!isValidCommaDelimitedList(selectedDatacenter)){
                return cli.NewExitError("You supplied an argument for 'datacenters' but it was not a valid comma-delimited list.", 1)
              }
            }
            if(len(selectedNamespace) > 0){
              if (!isValidCommaDelimitedList(selectedNamespace)){
                return cli.NewExitError("You supplied an argument for 'namespaces' but it was not a valid comma-delimited list.", 1)
              }
            } else {
              return cli.NewExitError("You must supply --namespaces or -ns argument to specify the namesapce(s) as a comma delimted form. i.e. devel,qa,prod or just devel", 1)
            }
            if(len(selectedStatus) > 0){
              if (!isValidCommaDelimitedList(selectedStatus)){
                return cli.NewExitError("You supplied an argument for 'statuses' but it was not a valid comma-delimited list.", 1)
              }
            }

            pi.Start()
            r, e := ListStacks(selectedDatacenter, selectedNamespace, selectedStatus, http, LoadDefaultConfig())
            pi.Stop()
            if e != nil {
              PrintTerminalErrors(e)
              return cli.NewExitError("Unable to list stacks.", 1)
            } else {
              PrintListStacks(r)
            }
            return nil
          },
        },
        {
          Name:  "inspect",
          Usage: "Display the current status and details about a specific stack",
          Action: func(c *cli.Context) error {
            guid := c.Args().First()
            if len(guid) > 0 && IsValidGUID(guid) {
              pi.Start()
              r, e := InspectStack(guid, http, LoadDefaultConfig())
              pi.Stop()
              if e != nil {
                PrintTerminalErrors(e)
                return cli.NewExitError("Unable to inspect stacks '"+guid+"'.", 1)
              } else {
                PrintInspectStack(r)
              }
            } else {
              return cli.NewExitError("You must supply a valid GUID for the stack you want to inspect.", 1)
            }
            return nil
          },
        },
        {
          Name:  "redeploy",
          Usage: "Trigger a redeployment for a specific stack",
          Action: func(c *cli.Context) error {
            guid := c.Args().First()
            if IsValidGUID(guid) {
              r,e := Redeploy(guid, http, LoadDefaultConfig())

              if e != nil {
                return cli.NewExitError("Unable to request a redeploy. Response was:\n"+r, 1)
              } else {
                fmt.Println("===>> "+r)
              }
            } else {
              return cli.NewExitError("You must specify a valid GUID reference in order to redeploy a stack.", 1)
            }
            return nil
          },
        },
        {
          Name:  "manual",
          Usage: "Register a manual deployment",
          Flags: []cli.Flag {
            cli.StringFlag{
              Name:   "datacenter, dc",
              Value:  "",
              Usage:  "The datacenter for the service",
              Destination: &selectedDatacenter,
            },
            cli.StringFlag{
              Name:   "namespace, ns",
              Value:  "",
              Usage:  "The namespace for the service",
              Destination: &selectedNamespace,
            },
            cli.StringFlag{
              Name:   "service-type, st",
              Value:  "",
              Usage:  "The service type for the service",
              Destination: &selectedServiceType,
            },
            cli.StringFlag{
              Name:   "version, v",
              Value:  "",
              Usage:  "The version for the service",
              Destination: &selectedVersion,
            },
            cli.StringFlag{
              Name:   "hash",
              Value:  "",
              Usage:  "The hash for the stack",
              Destination: &stackHash,
            },
            cli.StringFlag{
              Name:   "description, d",
              Value:  "",
              Usage:  "Description for the service",
              Destination: &description,
            },
            cli.Int64Flag{
              Name:   "port",
              Value:  0,
              Usage:  "The exposed port for the service",
              Destination: &selectedPort,
            },
          },
          Action: func(c *cli.Context) error {
            if len(selectedDatacenter) > 0 && len(selectedNamespace) > 0 &&
               len(selectedServiceType) > 0 &&
               len(selectedVersion) > 0 &&
               len(stackHash) > 0 &&
               len(description) > 0 &&
               selectedPort > 0 {
              req := ManualDeploymentRequest{
                Datacenter: selectedDatacenter,
                Namespace: selectedNamespace,
                ServiceType: selectedServiceType,
                Version: selectedVersion,
                Hash: stackHash,
                Description: description,
                Port: selectedPort,
              }
              pi.Start()
              res, e := RegisterManualDeployment(req, http, LoadDefaultConfig())
              pi.Stop()
              if e != nil {
                return cli.NewExitError("Unable to register manual deployment.", 1)
              } else {
                fmt.Println(res)
              }
            } else {
              return cli.NewExitError("You must specify the following switches: \n\t--datacenter <string> \n\t--namespace <string> \n\t--service-type <string> \n\t--version <string> \n\t--hash <string> \n\t--port <int>", 1)
            }
            return nil
          },
        },
        {
          Name:  "deprecate",
          Usage: "Deprecate a unit/version combination (and all patch series)",
          Flags: []cli.Flag {
            cli.StringFlag{
              Name:   "unit, u",
              Value:  "",
              Usage:  "The unit you want to deprecate",
              Destination: &selectedUnitPrefix,
            },
            cli.StringFlag{
              Name:   "version, v",
              Value:  "",
              Usage:  "The feature version series you want to deprecate",
              Destination: &selectedVersion,
            },
          },
          Action: func(c *cli.Context) error {
            if len(selectedUnitPrefix) > 0 && len(selectedVersion) > 0 {
              match, _ := regexp.MatchString("(\\d+)\\.(\\d+)", selectedVersion)
              if (match == true) {
                splitVersion := strings.Split(selectedVersion, ".")
                mjr, _ := strconv.Atoi(splitVersion[0])
                min, _ := strconv.Atoi(splitVersion[1])
                ver := FeatureVersion {
                  Major: mjr,
                  Minor: min,
                }
                req := DeprecationRequest {
                  ServiceType: selectedUnitPrefix,
                  Version: ver,
                }
                r,e := Deprecate(req, http, LoadDefaultConfig())
                if e != nil {
                  return cli.NewExitError("Unable to deprecate unit+version series. Response was:\n"+r, 1)
                } else {
                  fmt.Println("===>> Deprecated "+selectedUnitPrefix+" "+selectedVersion)
                }
              } else {
                return cli.NewExitError("You must supply a feature version of the format XXX.XXX, e.g. 2.3, 4.56, 1.7", 1)
              }
            } else {
              return cli.NewExitError("Required --unit and/or --version inputs were not valid", 1)
            }
            return nil
          },
        },
        {
          Name:  "fs",
          Aliases: []string{"logs"},
          Usage: "Fetch the deployment log for a given stack",
          Action: func(c *cli.Context) error {
            guid := c.Args().First()
            if len(guid) > 0 && IsValidGUID(guid) {
              GetDeploymentLog(guid, http, LoadDefaultConfig())
            } else {
              return cli.NewExitError("You must specify a valid GUID for the stack you wish to view logs for.", 1)
            }
            return nil
          },
        },
      },
    },
    ////////////////////////////// SYSTEM //////////////////////////////////
    {
      Name:    "system",
      Usage:   "A set of operations to query Nelson to see what options are available",
      Subcommands: []cli.Command{
        {
          Name:  "cleanup-policies",
          Usage: "list the available cleanup policies",
          Action: func(c *cli.Context) error {
            pi.Start()
            policies, e := ListCleanupPolicies(http, LoadDefaultConfig())
            pi.Stop()
            if e != nil {
              PrintTerminalErrors(e)
              return cli.NewExitError("Unable to list the cleanup policies at this time.", 1)
            } else {
              PrintCleanupPolicies(policies)
            }
            return nil
          },
        },
      },
    },
    ////////////////////////////// WHOAMI //////////////////////////////////
    {
      Name:    "whoami",
      Usage:   "Ask nelson who you are currently logged in as",
      Action:  func(c *cli.Context) error {
        pi.Start()
        cfg := LoadDefaultConfig()
        sr, e := WhoAmI(http, cfg)
        pi.Stop()
        if e != nil {
          PrintTerminalErrors(e)
          return cli.NewExitError("Unable to determine who is currently logged into Nelson.", 1)
        } else {
          fmt.Println("===>> Currently logged in as "+sr.User.Name+" @ "+cfg.Endpoint)
        }
        return nil
      },
    },
  }

  app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

/// had to have a `util` file.
/// Because pragmatism.
/// Because irony.

import (
	"fmt"
	"github.com/briandowns/spinner"
	humanize "github.com/dustin/go-humanize"
	"github.com/olekukonko/tablewriter"
	"github.com/parnurzeal/gorequest"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"runtime"
	"time"
)

func AugmentRequest(c *gorequest.SuperAgent, cfg *Config) *gorequest.SuperAgent {
	return c.
		AddCookie(cfg.GetAuthCookie()).
		Set("Content-type", "application/json").
		Set("User-Agent", UserAgentString(globalBuildVersion)).
		Timeout(5*time.Second).
		Retry(3, 1*time.Second, http.StatusBadGateway, http.StatusInternalServerError).
		SetCurlCommand(globalEnableCurl).
		SetDebug(globalEnableDebug)
}

func RenderTableToStdout(headers []string, data [][]string) {
	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader(headers)
	table.SetBorders(tablewriter.Border{Left: false, Top: false, Right: false, Bottom: false})
	table.SetHeaderLine(false)
	table.SetRowLine(false)
	table.SetColWidth(100)
	table.SetColumnSeparator("")
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.AppendBulk(data) // Add Bulk Data
	table.Render()
}

func hostFromUri(str string) (error, string) {
	u, e := url.Parse(str)
	if e != nil {
		return e, ""
	}
	return e, u.Host
}

/*
 * return a java-style System.currentTimeMillis to match
 * the values being returned from the server.
 */
func currentTimeMillis() int64 {
	return time.Now().UnixNano() / int64(time.Millisecond)
}

func javaEpochToTime(long int64) time.Time {
	return time.Unix(0, long*int64(time.Millisecond))
}

func javaEpochToHumanizedTime(long int64) string {
	return humanize.Time(javaEpochToTime(long))
}

func JavaEpochToDateStr(long int64) string {
	t := javaEpochToTime(long)
	return t.Format(time.RFC3339)
}

func ProgressIndicator() *spinner.Spinner {
	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
	s.Color("green")
	return s
}

func PrintTerminalErrors(errs []error) {
	for i, j := 0, len(errs)-1; i < j; i, j = i+1, j-1 {
		errs[i], errs[j] = errs[j], errs[i]
	}

	for _, e := range errs {
		fmt.Println(e)
	}
}

func isValidGUID(in string) bool {
	match, _ := regexp.MatchString(`^[a-z0-9]{12,12}$`, in)
	return match
}

func isValidCommaDelimitedList(str string) bool {
	match, _ := regexp.MatchString(`^([a-z0-9/\\-]+,?)+$`, str)
	return match
}

func CurrentVersion() string {
	if len(globalBuildVersion) == 0 {
		return "dev"
	} else {
		return "v" + globalBuildVersion
	}
}

func UserAgentString(globalBuildVersion string) string {
	var name = "NelsonCLI"
	var version = getVersionForMode(globalBuildVersion)
	return name + "/" + version + " (" + runtime.GOOS + ")"
}

func getVersionForMode(globalBuildVersion string) string {
	if len(globalBuildVersion) == 0 {
		return "dev"
	} else {
		return "0.5." + globalBuildVersion
	}
}

func truncateString(str string, num int) string {
	bnoden := str
	if len(str) > num {
		if num > 3 {
			num -= 3
		}
		bnoden = str[0:num] + "..."
	}
	return bnoden
}
<commit_msg>increase the timeout to accommodate slow network <commit_after>package main

/// had to have a `util` file.
/// Because pragmatism.
/// Because irony.

import (
	"fmt"
	"github.com/briandowns/spinner"
	humanize "github.com/dustin/go-humanize"
	"github.com/olekukonko/tablewriter"
	"github.com/parnurzeal/gorequest"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"runtime"
	"time"
)

func AugmentRequest(c *gorequest.SuperAgent, cfg *Config) *gorequest.SuperAgent {
	return c.
		AddCookie(cfg.GetAuthCookie()).
		Set("Content-type", "application/json").
		Set("User-Agent", UserAgentString(globalBuildVersion)).
		Timeout(10*time.Second).
		Retry(3, 1*time.Second, http.StatusBadGateway, http.StatusInternalServerError).
		SetCurlCommand(globalEnableCurl).
		SetDebug(globalEnableDebug)
}

func RenderTableToStdout(headers []string, data [][]string) {
	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader(headers)
	table.SetBorders(tablewriter.Border{Left: false, Top: false, Right: false, Bottom: false})
	table.SetHeaderLine(false)
	table.SetRowLine(false)
	table.SetColWidth(100)
	table.SetColumnSeparator("")
	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.AppendBulk(data) // Add Bulk Data
	table.Render()
}

func hostFromUri(str string) (error, string) {
	u, e := url.Parse(str)
	if e != nil {
		return e, ""
	}
	return e, u.Host
}

/*
 * return a java-style System.currentTimeMillis to match
 * the values being returned from the server.
 */
func currentTimeMillis() int64 {
	return time.Now().UnixNano() / int64(time.Millisecond)
}

func javaEpochToTime(long int64) time.Time {
	return time.Unix(0, long*int64(time.Millisecond))
}

func javaEpochToHumanizedTime(long int64) string {
	return humanize.Time(javaEpochToTime(long))
}

func JavaEpochToDateStr(long int64) string {
	t := javaEpochToTime(long)
	return t.Format(time.RFC3339)
}

func ProgressIndicator() *spinner.Spinner {
	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
	s.Color("green")
	return s
}

func PrintTerminalErrors(errs []error) {
	for i, j := 0, len(errs)-1; i < j; i, j = i+1, j-1 {
		errs[i], errs[j] = errs[j], errs[i]
	}

	for _, e := range errs {
		fmt.Println(e)
	}
}

func isValidGUID(in string) bool {
	match, _ := regexp.MatchString(`^[a-z0-9]{12,12}$`, in)
	return match
}

func isValidCommaDelimitedList(str string) bool {
	match, _ := regexp.MatchString(`^([a-z0-9/\\-]+,?)+$`, str)
	return match
}

func CurrentVersion() string {
	if len(globalBuildVersion) == 0 {
		return "dev"
	} else {
		return "v" + globalBuildVersion
	}
}

func UserAgentString(globalBuildVersion string) string {
	var name = "NelsonCLI"
	var version = getVersionForMode(globalBuildVersion)
	return name + "/" + version + " (" + runtime.GOOS + ")"
}

func getVersionForMode(globalBuildVersion string) string {
	if len(globalBuildVersion) == 0 {
		return "dev"
	} else {
		return "0.5." + globalBuildVersion
	}
}

func truncateString(str string, num int) string {
	bnoden := str
	if len(str) > num {
		if num > 3 {
			num -= 3
		}
		bnoden = str[0:num] + "..."
	}
	return bnoden
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 Soichiro Kashima
// Licensed under MIT license.

package main

import (
	"flag"
	"github.com/ksoichiro/fint"
	"os"
)

func main() {
	var (
		srcRoot    = flag.String("s", "", "Source directory")
		configPath = flag.String("c", "conf/config.json", "Config file path")
		locale     = flag.String("l", "default", "Message locale")
		id         = flag.String("i", "", "ID of the rule set")
	)
	flag.Parse()

	err := fint.ExecuteAsCommand(&fint.Opt{SrcRoot: *srcRoot, ConfigPath: *configPath, Locale: *locale, Id: *id})
	if err != nil {
		os.Exit(1)
	}
}
<commit_msg>Made exit code a constant.<commit_after>// Copyright (c) 2014 Soichiro Kashima
// Licensed under MIT license.

package main

import (
	"flag"
	"github.com/ksoichiro/fint"
	"os"
)

const (
	ExitCodeError = 1
)

func main() {
	var (
		srcRoot    = flag.String("s", "", "Source directory")
		configPath = flag.String("c", "conf/config.json", "Config file path")
		locale     = flag.String("l", "default", "Message locale")
		id         = flag.String("i", "", "ID of the rule set")
	)
	flag.Parse()

	err := fint.ExecuteAsCommand(&fint.Opt{SrcRoot: *srcRoot, ConfigPath: *configPath, Locale: *locale, Id: *id})
	if err != nil {
		os.Exit(ExitCodeError)
	}
}
<|endoftext|>