<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"os/signal"
	"runtime"
	"syscall"
	"time"
	"net/http"
	"github.com/gorilla/mux"
	"github.com/op/go-logging"
	"github.com/boivie/lovebeat-go/internal"
	"github.com/boivie/lovebeat-go/service"
	"github.com/boivie/lovebeat-go/dashboard"
	"github.com/boivie/lovebeat-go/httpapi"
	"github.com/boivie/lovebeat-go/udpapi"
	"github.com/boivie/lovebeat-go/tcpapi"
)

var log = logging.MustGetLogger("lovebeat")

const (
	VERSION                 = "0.1.0"
	MAX_UNPROCESSED_PACKETS = 1000
)

var (
	udpAddr          = flag.String("udp", ":8127", "UDP service address")
	tcpAddr          = flag.String("tcp", ":8127", "TCP service address")
	expiryInterval   = flag.Int64("expiry-interval", 1, "Expiry interval (seconds)")
	debug            = flag.Bool("debug", false, "print statistics sent to graphite")
	showVersion      = flag.Bool("version", false, "print version string")
)

var (
	ServiceCmdChan    = make(chan *internal.Cmd, MAX_UNPROCESSED_PACKETS)
	ViewCmdChan         = make(chan *internal.ViewCmd, MAX_UNPROCESSED_PACKETS)
	signalchan chan os.Signal
)

func now() int64 { return time.Now().Unix() }

func monitor() {
	period := time.Duration(*expiryInterval) * time.Second
	ticker := time.NewTicker(period)
	for {
		select {
		case sig := <-signalchan:
			fmt.Printf("!! Caught signal %d... shutting down\n", sig)
			return
		case <-ticker.C:
			var ts = now()
			for _, s := range service.GetServices() {
				if (s.State == service.STATE_PAUSED || s.State == s.StateAt(ts)) {
					continue;
				}
				var ref = *s
				s.State = s.StateAt(ts)
				s.Save(&ref, ts)
				s.UpdateViews(ViewCmdChan)
			}
		case c := <-ViewCmdChan:
			var ts = now()
			switch c.Action {
			case service.ACTION_REFRESH_VIEW:
				log.Debug("Refresh view %s", c.View)
				var view = service.GetView(c.View)
				var ref = *view
				view.Refresh(ts)
				view.Save(&ref, ts);
			}
		case c := <-ServiceCmdChan:
			var ts = now()
			var s = service.GetService(c.Service)
			var ref = *s
			switch c.Action {
			case internal.ACTION_SET_WARN:
				s.WarningTimeout = int64(c.Value)
			case internal.ACTION_SET_ERR:
				s.ErrorTimeout = int64(c.Value)
			case internal.ACTION_BEAT:
				if c.Value > 0 {
					s.LastBeat = ts
					var diff = ts - ref.LastBeat
					s.Log("%d|beat|%d", ts, diff)
					log.Debug("Beat from %s", s.Name)
				}
			}
			s.State = s.StateAt(ts)
			s.Save(&ref, ts)
			s.UpdateViews(ViewCmdChan)
		}
	}
}

func httpServer(port int16) {
	rtr := mux.NewRouter()
	httpapi.Register(rtr, ServiceCmdChan, ViewCmdChan)
	dashboard.Register(rtr)
	http.Handle("/", rtr)
	log.Info("HTTP server running on port %d\n", port)
        http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}

func main() {
	flag.Parse()
	if *showVersion {
		fmt.Printf("lovebeats v%s (built w/%s)\n", VERSION, runtime.Version())
		return
	}

	service.Startup()

	signalchan = make(chan os.Signal, 1)
	signal.Notify(signalchan, syscall.SIGTERM)

	var format = logging.MustStringFormatter("%{level} %{message}")
	logging.SetFormatter(format)
	if *debug {
		logging.SetLevel(logging.DEBUG, "lovebeat")
	} else {
		logging.SetLevel(logging.INFO, "lovebeat")
	}
	log.Debug("Debug logs enabled")

	go httpServer(8080)
	go udpapi.Listener(*udpAddr, ServiceCmdChan)
	go tcpapi.Listener(*tcpAddr, ServiceCmdChan)
	monitor()
}
<commit_msg>Minor refactoring<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"os/signal"
	"runtime"
	"syscall"
	"time"
	"net/http"
	"github.com/gorilla/mux"
	"github.com/op/go-logging"
	"github.com/boivie/lovebeat-go/internal"
	"github.com/boivie/lovebeat-go/service"
	"github.com/boivie/lovebeat-go/dashboard"
	"github.com/boivie/lovebeat-go/httpapi"
	"github.com/boivie/lovebeat-go/udpapi"
	"github.com/boivie/lovebeat-go/tcpapi"
)

var log = logging.MustGetLogger("lovebeat")

const (
	VERSION                 = "0.1.0"
	MAX_UNPROCESSED_PACKETS = 1000
)

var (
	udpAddr          = flag.String("udp", ":8127", "UDP service address")
	tcpAddr          = flag.String("tcp", ":8127", "TCP service address")
	expiryInterval   = flag.Int64("expiry-interval", 1, "Expiry interval (seconds)")
	debug            = flag.Bool("debug", false, "print statistics sent to graphite")
	showVersion      = flag.Bool("version", false, "print version string")
)

var (
	ServiceCmdChan    = make(chan *internal.Cmd, MAX_UNPROCESSED_PACKETS)
	ViewCmdChan         = make(chan *internal.ViewCmd, MAX_UNPROCESSED_PACKETS)
	signalchan = make(chan os.Signal, 1)

)

func now() int64 { return time.Now().Unix() }

func monitor() {
	period := time.Duration(*expiryInterval) * time.Second
	ticker := time.NewTicker(period)
	for {
		select {
		case sig := <-signalchan:
			fmt.Printf("!! Caught signal %d... shutting down\n", sig)
			return
		case <-ticker.C:
			var ts = now()
			for _, s := range service.GetServices() {
				if (s.State == service.STATE_PAUSED || s.State == s.StateAt(ts)) {
					continue;
				}
				var ref = *s
				s.State = s.StateAt(ts)
				s.Save(&ref, ts)
				s.UpdateViews(ViewCmdChan)
			}
		case c := <-ViewCmdChan:
			var ts = now()
			switch c.Action {
			case service.ACTION_REFRESH_VIEW:
				log.Debug("Refresh view %s", c.View)
				var view = service.GetView(c.View)
				var ref = *view
				view.Refresh(ts)
				view.Save(&ref, ts);
			}
		case c := <-ServiceCmdChan:
			var ts = now()
			var s = service.GetService(c.Service)
			var ref = *s
			switch c.Action {
			case internal.ACTION_SET_WARN:
				s.WarningTimeout = int64(c.Value)
			case internal.ACTION_SET_ERR:
				s.ErrorTimeout = int64(c.Value)
			case internal.ACTION_BEAT:
				if c.Value > 0 {
					s.LastBeat = ts
					var diff = ts - ref.LastBeat
					s.Log("%d|beat|%d", ts, diff)
					log.Debug("Beat from %s", s.Name)
				}
			}
			s.State = s.StateAt(ts)
			s.Save(&ref, ts)
			s.UpdateViews(ViewCmdChan)
		}
	}
}

func httpServer(port int16) {
	rtr := mux.NewRouter()
	httpapi.Register(rtr, ServiceCmdChan, ViewCmdChan)
	dashboard.Register(rtr)
	http.Handle("/", rtr)
	log.Info("HTTP server running on port %d\n", port)
        http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}

func main() {
	flag.Parse()
	if *showVersion {
		fmt.Printf("lovebeats v%s (built w/%s)\n", VERSION, runtime.Version())
		return
	}

	service.Startup()

	signal.Notify(signalchan, syscall.SIGTERM)

	var format = logging.MustStringFormatter("%{level} %{message}")
	logging.SetFormatter(format)
	if *debug {
		logging.SetLevel(logging.DEBUG, "lovebeat")
	} else {
		logging.SetLevel(logging.INFO, "lovebeat")
	}
	log.Debug("Debug logs enabled")

	go httpServer(8080)
	go udpapi.Listener(*udpAddr, ServiceCmdChan)
	go tcpapi.Listener(*tcpAddr, ServiceCmdChan)
	monitor()
}
<|endoftext|>


<commit_before>// checkpoint is a package for checking version information and alerts
// for a HashiCorp product.
package checkpoint

import (
	"crypto/rand"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	mrand "math/rand"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"time"

	"github.com/hashicorp/cleanhttp"
)

var magicBytes [4]byte = [4]byte{0x35, 0x77, 0x69, 0xFB}

// CheckParams are the parameters for configuring a check request.
type CheckParams struct {
	// Product and version are used to lookup the correct product and
	// alerts for the proper version. The version is also used to perform
	// a version check.
	Product string
	Version string

	// Arch and OS are used to filter alerts potentially only to things
	// affecting a specific os/arch combination. If these aren't specified,
	// they'll be automatically filled in.
	Arch string
	OS   string

	// Signature is some random signature that should be stored and used
	// as a cookie-like value. This ensures that alerts aren't repeated.
	// If the signature is changed, repeat alerts may be sent down. The
	// signature should NOT be anything identifiable to a user (such as
	// a MAC address). It should be random.
	//
	// If SignatureFile is given, then the signature will be read from this
	// file. If the file doesn't exist, then a random signature will
	// automatically be generated and stored here. SignatureFile will be
	// ignored if Signature is given.
	Signature     string
	SignatureFile string

	// CacheFile, if specified, will cache the result of a check. The
	// duration of the cache is specified by CacheDuration, and defaults
	// to 48 hours if not specified. If the CacheFile is newer than the
	// CacheDuration, than the Check will short-circuit and use those
	// results.
	//
	// If the CacheFile directory doesn't exist, it will be created with
	// permissions 0755.
	CacheFile     string
	CacheDuration time.Duration

	// Force, if true, will force the check even if CHECKPOINT_DISABLE
	// is set. Within HashiCorp products, this is ONLY USED when the user
	// specifically requests it. This is never automatically done without
	// the user's consent.
	Force bool
}

// CheckResponse is the response for a check request.
type CheckResponse struct {
	Product             string
	CurrentVersion      string `json:"current_version"`
	CurrentReleaseDate  int    `json:"current_release_date"`
	CurrentDownloadURL  string `json:"current_download_url"`
	CurrentChangelogURL string `json:"current_changelog_url"`
	ProjectWebsite      string `json:"project_website"`
	Outdated            bool   `json:"outdated"`
	Alerts              []*CheckAlert
}

// CheckAlert is a single alert message from a check request.
//
// These never have to be manually constructed, and are typically populated
// into a CheckResponse as a result of the Check request.
type CheckAlert struct {
	ID      int
	Date    int
	Message string
	URL     string
	Level   string
}

// Check checks for alerts and new version information.
func Check(p *CheckParams) (*CheckResponse, error) {
	if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" && !p.Force {
		return &CheckResponse{}, nil
	}

	// If we have a cached result, then use that
	if r, err := checkCache(p.Version, p.CacheFile, p.CacheDuration); err != nil {
		return nil, err
	} else if r != nil {
		defer r.Close()
		return checkResult(r)
	}

	var u url.URL

	if p.Arch == "" {
		p.Arch = runtime.GOARCH
	}
	if p.OS == "" {
		p.OS = runtime.GOOS
	}

	// If we're given a SignatureFile, then attempt to read that.
	signature := p.Signature
	if p.Signature == "" && p.SignatureFile != "" {
		var err error
		signature, err = checkSignature(p.SignatureFile)
		if err != nil {
			return nil, err
		}
	}

	v := u.Query()
	v.Set("version", p.Version)
	v.Set("arch", p.Arch)
	v.Set("os", p.OS)
	v.Set("signature", signature)

	u.Scheme = "https"
	u.Host = "checkpoint-api.hashicorp.com"
	u.Path = fmt.Sprintf("/v1/check/%s", p.Product)
	u.RawQuery = v.Encode()

	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Accept", "application/json")
	req.Header.Add("User-Agent", "HashiCorp/go-checkpoint")

	client := cleanhttp.DefaultClient()
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Unknown status: %d", resp.StatusCode)
	}

	var r io.Reader = resp.Body
	if p.CacheFile != "" {
		// Make sure the directory holding our cache exists.
		if err := os.MkdirAll(filepath.Dir(p.CacheFile), 0755); err != nil {
			return nil, err
		}

		// We have to cache the result, so write the response to the
		// file as we read it.
		f, err := os.Create(p.CacheFile)
		if err != nil {
			return nil, err
		}

		// Write the cache header
		if err := writeCacheHeader(f, p.Version); err != nil {
			f.Close()
			os.Remove(p.CacheFile)
			return nil, err
		}

		defer f.Close()
		r = io.TeeReader(r, f)
	}

	return checkResult(r)
}

// CheckInterval is used to check for a response on a given interval duration.
// The interval is not exact, and checks are randomized to prevent a thundering
// herd. However, it is expected that on average one check is performed per
// interval. The returned channel may be closed to stop background checks.
func CheckInterval(p *CheckParams, interval time.Duration, cb func(*CheckResponse, error)) chan struct{} {
	doneCh := make(chan struct{})

	if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" {
		return doneCh
	}

	go func() {
		for {
			select {
			case <-time.After(randomStagger(interval)):
				resp, err := Check(p)
				cb(resp, err)
			case <-doneCh:
				return
			}
		}
	}()

	return doneCh
}

// randomStagger returns an interval that is between 3/4 and 5/4 of
// the given interval. The expected value is the interval.
func randomStagger(interval time.Duration) time.Duration {
	stagger := time.Duration(mrand.Int63()) % (interval / 2)
	return 3*(interval/4) + stagger
}

func checkCache(current string, path string, d time.Duration) (io.ReadCloser, error) {
	fi, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			// File doesn't exist, not a problem
			return nil, nil
		}

		return nil, err
	}

	if d == 0 {
		d = 48 * time.Hour
	}

	if fi.ModTime().Add(d).Before(time.Now()) {
		// Cache is busted, delete the old file and re-request. We ignore
		// errors here because re-creating the file is fine too.
		os.Remove(path)
		return nil, nil
	}

	// File looks good so far, open it up so we can inspect the contents.
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}

	// Check the signature of the file
	var sig [4]byte
	if err := binary.Read(f, binary.LittleEndian, sig[:]); err != nil {
		f.Close()
		return nil, err
	}
	if !reflect.DeepEqual(sig, magicBytes) {
		// Signatures don't match. Reset.
		f.Close()
		return nil, nil
	}

	// Check the version. If it changed, then rewrite
	var length uint32
	if err := binary.Read(f, binary.LittleEndian, &length); err != nil {
		f.Close()
		return nil, err
	}
	data := make([]byte, length)
	if _, err := io.ReadFull(f, data); err != nil {
		f.Close()
		return nil, err
	}
	if string(data) != current {
		// Version changed, reset
		f.Close()
		return nil, nil
	}

	return f, nil
}

func checkResult(r io.Reader) (*CheckResponse, error) {
	var result CheckResponse
	dec := json.NewDecoder(r)
	if err := dec.Decode(&result); err != nil {
		return nil, err
	}

	return &result, nil
}

func checkSignature(path string) (string, error) {
	_, err := os.Stat(path)
	if err == nil {
		// The file exists, read it out
		sigBytes, err := ioutil.ReadFile(path)
		if err != nil {
			return "", err
		}

		// Split the file into lines
		lines := strings.SplitN(string(sigBytes), "\n", 2)
		if len(lines) > 0 {
			return strings.TrimSpace(lines[0]), nil
		}
	}

	// If this isn't a non-exist error, then return that.
	if !os.IsNotExist(err) {
		return "", err
	}

	// The file doesn't exist, so create a signature.
	var b [16]byte
	n := 0
	for n < 16 {
		n2, err := rand.Read(b[n:])
		if err != nil {
			return "", err
		}

		n += n2
	}
	signature := fmt.Sprintf(
		"%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])

	// Make sure the directory holding our signature exists.
	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
		return "", err
	}

	// Write the signature
	if err := ioutil.WriteFile(path, []byte(signature+"\n\n"+userMessage+"\n"), 0644); err != nil {
		return "", err
	}

	return signature, nil
}

func writeCacheHeader(f io.Writer, v string) error {
	// Write our signature first
	if err := binary.Write(f, binary.LittleEndian, magicBytes); err != nil {
		return err
	}

	// Write out our current version length
	var length uint32 = uint32(len(v))
	if err := binary.Write(f, binary.LittleEndian, length); err != nil {
		return err
	}

	_, err := f.Write([]byte(v))
	return err
}

// userMessage is suffixed to the signature file to provide feedback.
var userMessage = `
This signature is a randomly generated UUID used to de-duplicate
alerts and version information. This signature is random, it is
not based on any personally identifiable information. To create
a new signature, you can simply delete this file at any time.
See the documentation for the software using Checkpoint for more
information on how to disable it.
`
<commit_msg>update cleanhttp repo location<commit_after>// checkpoint is a package for checking version information and alerts
// for a HashiCorp product.
package checkpoint

import (
	"crypto/rand"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	mrand "math/rand"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"time"

	"github.com/hashicorp/go-cleanhttp"
)

var magicBytes [4]byte = [4]byte{0x35, 0x77, 0x69, 0xFB}

// CheckParams are the parameters for configuring a check request.
type CheckParams struct {
	// Product and version are used to lookup the correct product and
	// alerts for the proper version. The version is also used to perform
	// a version check.
	Product string
	Version string

	// Arch and OS are used to filter alerts potentially only to things
	// affecting a specific os/arch combination. If these aren't specified,
	// they'll be automatically filled in.
	Arch string
	OS   string

	// Signature is some random signature that should be stored and used
	// as a cookie-like value. This ensures that alerts aren't repeated.
	// If the signature is changed, repeat alerts may be sent down. The
	// signature should NOT be anything identifiable to a user (such as
	// a MAC address). It should be random.
	//
	// If SignatureFile is given, then the signature will be read from this
	// file. If the file doesn't exist, then a random signature will
	// automatically be generated and stored here. SignatureFile will be
	// ignored if Signature is given.
	Signature     string
	SignatureFile string

	// CacheFile, if specified, will cache the result of a check. The
	// duration of the cache is specified by CacheDuration, and defaults
	// to 48 hours if not specified. If the CacheFile is newer than the
	// CacheDuration, than the Check will short-circuit and use those
	// results.
	//
	// If the CacheFile directory doesn't exist, it will be created with
	// permissions 0755.
	CacheFile     string
	CacheDuration time.Duration

	// Force, if true, will force the check even if CHECKPOINT_DISABLE
	// is set. Within HashiCorp products, this is ONLY USED when the user
	// specifically requests it. This is never automatically done without
	// the user's consent.
	Force bool
}

// CheckResponse is the response for a check request.
type CheckResponse struct {
	Product             string
	CurrentVersion      string `json:"current_version"`
	CurrentReleaseDate  int    `json:"current_release_date"`
	CurrentDownloadURL  string `json:"current_download_url"`
	CurrentChangelogURL string `json:"current_changelog_url"`
	ProjectWebsite      string `json:"project_website"`
	Outdated            bool   `json:"outdated"`
	Alerts              []*CheckAlert
}

// CheckAlert is a single alert message from a check request.
//
// These never have to be manually constructed, and are typically populated
// into a CheckResponse as a result of the Check request.
type CheckAlert struct {
	ID      int
	Date    int
	Message string
	URL     string
	Level   string
}

// Check checks for alerts and new version information.
func Check(p *CheckParams) (*CheckResponse, error) {
	if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" && !p.Force {
		return &CheckResponse{}, nil
	}

	// If we have a cached result, then use that
	if r, err := checkCache(p.Version, p.CacheFile, p.CacheDuration); err != nil {
		return nil, err
	} else if r != nil {
		defer r.Close()
		return checkResult(r)
	}

	var u url.URL

	if p.Arch == "" {
		p.Arch = runtime.GOARCH
	}
	if p.OS == "" {
		p.OS = runtime.GOOS
	}

	// If we're given a SignatureFile, then attempt to read that.
	signature := p.Signature
	if p.Signature == "" && p.SignatureFile != "" {
		var err error
		signature, err = checkSignature(p.SignatureFile)
		if err != nil {
			return nil, err
		}
	}

	v := u.Query()
	v.Set("version", p.Version)
	v.Set("arch", p.Arch)
	v.Set("os", p.OS)
	v.Set("signature", signature)

	u.Scheme = "https"
	u.Host = "checkpoint-api.hashicorp.com"
	u.Path = fmt.Sprintf("/v1/check/%s", p.Product)
	u.RawQuery = v.Encode()

	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Accept", "application/json")
	req.Header.Add("User-Agent", "HashiCorp/go-checkpoint")

	client := cleanhttp.DefaultClient()
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("Unknown status: %d", resp.StatusCode)
	}

	var r io.Reader = resp.Body
	if p.CacheFile != "" {
		// Make sure the directory holding our cache exists.
		if err := os.MkdirAll(filepath.Dir(p.CacheFile), 0755); err != nil {
			return nil, err
		}

		// We have to cache the result, so write the response to the
		// file as we read it.
		f, err := os.Create(p.CacheFile)
		if err != nil {
			return nil, err
		}

		// Write the cache header
		if err := writeCacheHeader(f, p.Version); err != nil {
			f.Close()
			os.Remove(p.CacheFile)
			return nil, err
		}

		defer f.Close()
		r = io.TeeReader(r, f)
	}

	return checkResult(r)
}

// CheckInterval is used to check for a response on a given interval duration.
// The interval is not exact, and checks are randomized to prevent a thundering
// herd. However, it is expected that on average one check is performed per
// interval. The returned channel may be closed to stop background checks.
func CheckInterval(p *CheckParams, interval time.Duration, cb func(*CheckResponse, error)) chan struct{} {
	doneCh := make(chan struct{})

	if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" {
		return doneCh
	}

	go func() {
		for {
			select {
			case <-time.After(randomStagger(interval)):
				resp, err := Check(p)
				cb(resp, err)
			case <-doneCh:
				return
			}
		}
	}()

	return doneCh
}

// randomStagger returns an interval that is between 3/4 and 5/4 of
// the given interval. The expected value is the interval.
func randomStagger(interval time.Duration) time.Duration {
	stagger := time.Duration(mrand.Int63()) % (interval / 2)
	return 3*(interval/4) + stagger
}

func checkCache(current string, path string, d time.Duration) (io.ReadCloser, error) {
	fi, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			// File doesn't exist, not a problem
			return nil, nil
		}

		return nil, err
	}

	if d == 0 {
		d = 48 * time.Hour
	}

	if fi.ModTime().Add(d).Before(time.Now()) {
		// Cache is busted, delete the old file and re-request. We ignore
		// errors here because re-creating the file is fine too.
		os.Remove(path)
		return nil, nil
	}

	// File looks good so far, open it up so we can inspect the contents.
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}

	// Check the signature of the file
	var sig [4]byte
	if err := binary.Read(f, binary.LittleEndian, sig[:]); err != nil {
		f.Close()
		return nil, err
	}
	if !reflect.DeepEqual(sig, magicBytes) {
		// Signatures don't match. Reset.
		f.Close()
		return nil, nil
	}

	// Check the version. If it changed, then rewrite
	var length uint32
	if err := binary.Read(f, binary.LittleEndian, &length); err != nil {
		f.Close()
		return nil, err
	}
	data := make([]byte, length)
	if _, err := io.ReadFull(f, data); err != nil {
		f.Close()
		return nil, err
	}
	if string(data) != current {
		// Version changed, reset
		f.Close()
		return nil, nil
	}

	return f, nil
}

func checkResult(r io.Reader) (*CheckResponse, error) {
	var result CheckResponse
	dec := json.NewDecoder(r)
	if err := dec.Decode(&result); err != nil {
		return nil, err
	}

	return &result, nil
}

func checkSignature(path string) (string, error) {
	_, err := os.Stat(path)
	if err == nil {
		// The file exists, read it out
		sigBytes, err := ioutil.ReadFile(path)
		if err != nil {
			return "", err
		}

		// Split the file into lines
		lines := strings.SplitN(string(sigBytes), "\n", 2)
		if len(lines) > 0 {
			return strings.TrimSpace(lines[0]), nil
		}
	}

	// If this isn't a non-exist error, then return that.
	if !os.IsNotExist(err) {
		return "", err
	}

	// The file doesn't exist, so create a signature.
	var b [16]byte
	n := 0
	for n < 16 {
		n2, err := rand.Read(b[n:])
		if err != nil {
			return "", err
		}

		n += n2
	}
	signature := fmt.Sprintf(
		"%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])

	// Make sure the directory holding our signature exists.
	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
		return "", err
	}

	// Write the signature
	if err := ioutil.WriteFile(path, []byte(signature+"\n\n"+userMessage+"\n"), 0644); err != nil {
		return "", err
	}

	return signature, nil
}

func writeCacheHeader(f io.Writer, v string) error {
	// Write our signature first
	if err := binary.Write(f, binary.LittleEndian, magicBytes); err != nil {
		return err
	}

	// Write out our current version length
	var length uint32 = uint32(len(v))
	if err := binary.Write(f, binary.LittleEndian, length); err != nil {
		return err
	}

	_, err := f.Write([]byte(v))
	return err
}

// userMessage is suffixed to the signature file to provide feedback.
var userMessage = `
This signature is a randomly generated UUID used to de-duplicate
alerts and version information. This signature is random, it is
not based on any personally identifiable information. To create
a new signature, you can simply delete this file at any time.
See the documentation for the software using Checkpoint for more
information on how to disable it.
`
<|endoftext|>


<commit_before>package cli

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/user"
	"reflect"
	"strconv"
	"strings"
)

var defaultConfig = `{
	"ping" : {
		"timeout" : "2s",
		"interval": "1s",
		"count":	4
	},
	"hping" : {
		"timeout"  : "2s",
		"method"   : "HEAD",
		"data"	   : "mylg",
		"count"	   : 5
	},
	"web" : {
		"port"	   : 8080,
		"address"  : "127.0.0.1"
	},
	"scan" : {
		"port"     : "1-500"
	},
	"trace" : {
		"wait"  : "2s"
	}
}`

// Config represents configuration
type Config struct {
	Ping  Ping  `json:"ping"`
	Hping HPing `json:"hping"`
	Web   Web   `json:"web"`
	Scan  Scan  `json:"scan"`
	Trace Trace `json:"trace"`
}

// Ping represents ping command options
type Ping struct {
	Timeout  string `json:"timeout"`
	Interval string `json:"interval"`
	Count    int    `json:"count"`
}

// HPing represents ping command options
type HPing struct {
	Timeout string `json:"timeout"`
	Method  string `json:"method"`
	Data    string `json:"data"`
	Count   int    `json:"count"`
}

// Web represents web command options
type Web struct {
	Port    int    `json:"port"`
	Address string `json:"address"`
}

// Scan represents scan command options
type Scan struct {
	Port string `json:"port"`
}

// Trace represents trace command options
type Trace struct {
	Wait string `json:"wait"`
}

// WriteConfig write config to disk
func WriteConfig(cfg Config) error {
	f, err := cfgFile()
	if err != nil {
		return err
	}

	h, err := os.Create(f)
	if err != nil {
		return err
	}

	b, err := json.Marshal(cfg)
	if err != nil {
		return err
	}
	_, err = h.Write(bytes.ToLower(b))
	if err != nil {
		return err
	}
	h.Close()

	return nil
}

// GetOptions returns option(s)/value(s) for specific command
func GetOptions(s interface{}, key string) ([]string, []interface{}) {
	var (
		opts []string
		vals []interface{}
	)
	v := reflect.ValueOf(s)
	t := v.Type()

	for i := 0; i < v.NumField(); i++ {
		if t.Field(i).Name == key {
			f := v.Field(i)
			ft := f.Type()
			for j := 0; j < f.NumField(); j++ {
				vals = append(vals, f.Field(j))
				opts = append(opts, ft.Field(j).Name)
			}
			break
		}
	}
	return opts, vals
}

// GetCMDNames returns command line names
func GetCMDNames(s interface{}) []string {
	var fields []string

	v := reflect.ValueOf(s)
	t := v.Type()

	for i := 0; i < t.NumField(); i++ {
		fields = append(fields, t.Field(i).Name)
	}
	return fields
}

// UpgradeConfig adds / removes new command(s)/option(s)
func UpgradeConfig(cfg *Config) error {
	var (
		conf  map[string]interface{}
		cConf Config
	)

	b := make([]byte, 2048)
	f, err := cfgFile()
	if err != nil {
		return err
	}

	h, err := os.Open(f)
	if err != nil {
		return err
	}
	n, _ := h.Read(b)
	b = b[:n]
	// load saved/old config to conf
	json.Unmarshal(b, &conf)
	// load default config to cConf
	json.Unmarshal([]byte(defaultConfig), &cConf)

	for _, cmd := range GetCMDNames(cConf) {
		opts, vals := GetOptions(cConf, cmd)
		for i, opt := range opts {
			if v, ok := conf[strings.ToLower(cmd)].(interface{}); ok {
				if _, ok = v.(map[string]interface{})[strings.ToLower(opt)]; !ok {
					args := fmt.Sprintf("%s %s %v", cmd, opt, vals[i])
					SetConfig(args, cfg)
				}
			} else {
				// there is new command
				args := fmt.Sprintf("%s %s %v", cmd, opt, vals[i])
				SetConfig(args, cfg)
			}
		}
	}
	return nil
}

// LoadConfig loads configuration
func LoadConfig() Config {
	var cfg Config

	cfg = ReadConfig()
	UpgradeConfig(&cfg)
	return cfg
}

// InitConfig creates new config file
func InitConfig(f string) ([]byte, error) {
	h, err := os.Create(f)
	if err != nil {
		return []byte(""), err
	}

	h.Chmod(os.FileMode(int(0600)))
	h.WriteString(defaultConfig)
	h.Close()

	return []byte(defaultConfig), nil
}

// ReadConfig reads configuration from existing
// or default configuration
func ReadConfig() Config {
	var (
		b    = make([]byte, 2048)
		conf Config
		err  error
	)
	f, err := cfgFile()
	if err != nil {

	}

	h, err := os.Open(f)

	if err != nil {
		switch {
		case os.IsNotExist(err):
			if b, err = InitConfig(f); err != nil {
				println(err.Error())
			}
		case os.IsPermission(err):
			println("cannot read configuration file due to insufficient permissions")
			b = []byte(defaultConfig)
		default:
			println(err.Error())
			b = []byte(defaultConfig)
		}
	} else {
		n, _ := h.Read(b)
		b = b[:n]
	}

	err = json.Unmarshal(b, &conf)
	if err != nil {
		println(err.Error())
		b = []byte(defaultConfig)
		json.Unmarshal(b, &conf)
	}

	return conf
}

// ReadDefaultConfig returns default configuration
func ReadDefaultConfig() (Config, error) {
	var (
		b    = make([]byte, 2048)
		conf Config
	)
	b = []byte(defaultConfig)
	err := json.Unmarshal(b, &conf)
	return conf, err
}

// cfgFile returns config file
func cfgFile() (string, error) {
	user, err := user.Current()
	if err != nil {
		return "", err
	}
	return user.HomeDir + "/.mylg.config", nil
}

// SetConfig handles update option's value
func SetConfig(args string, s *Config) {
	var (
		v     reflect.Value
		i     int64
		float float64
		err   error
	)

	args = strings.ToLower(args)
	f := strings.Fields(args)
	if len(f) < 1 {
		helpSet()
		return
	}

	v = reflect.ValueOf(s)
	v = reflect.Indirect(v)
	v = v.FieldByName(strings.Title(f[0]))

	if v.IsValid() {
		if i, err = strconv.ParseInt(f[2], 10, 64); err == nil {
			// integer
			err = SetValue(v.Addr(), strings.Title(f[1]), i)
		} else if float, err = strconv.ParseFloat(f[2], 64); err == nil {
			// float
			err = SetValue(v.Addr(), strings.Title(f[1]), float)
		} else {
			// string
			err = SetValue(v.Addr(), strings.Title(f[1]), f[2])
		}
	} else {
		err = fmt.Errorf("invalid")
	}

	if err != nil {
		println(err.Error())
	} else {
		if err = WriteConfig(*s); err != nil {
			println(err.Error())
		}
	}

}

// SetValue set optioni's value
func SetValue(v reflect.Value, rec string, val interface{}) error {

	if v.Kind() != reflect.Ptr {
		return fmt.Errorf("not a pointer value")
	}

	v = reflect.Indirect(v)
	switch v.Kind() {
	case reflect.Int:
		if value, ok := val.(int64); ok {
			v.SetInt(value)
		} else {
			return fmt.Errorf("the value should be integer")
		}
	case reflect.Float64:
		if value, ok := val.(float64); ok {
			v.SetFloat(value)
		} else {
			return fmt.Errorf("the value should be float")
		}
	case reflect.String:
		if value, ok := val.(string); ok {
			v.SetString(value)
		} else {
			return fmt.Errorf("the value shouldn't be number")
		}
	case reflect.Struct:
		for i := 0; i < v.NumField(); i++ {
			if v.Type().Field(i).Name == rec {
				err := SetValue(v.Field(i).Addr(), rec, val)
				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// ShowConfig prints the configuration
func ShowConfig(s *Config) {
	var v reflect.Value

	v = reflect.ValueOf(s)
	v = reflect.Indirect(v)

	for i := 0; i < v.NumField(); i++ {
		cmd := v.Type().Field(i).Name
		cmd = strings.ToLower(cmd)

		vv := v.Field(i).Addr()
		vv = reflect.Indirect(vv)

		for j := 0; j < vv.NumField(); j++ {
			subCmd := vv.Type().Field(j).Name
			subCmd = strings.ToLower(subCmd)
			value := vv.Field(j)
			fmt.Printf("set %-8s %-10s %v\n", cmd, subCmd, value)
		}
	}
}

// helpSet shows set command
func helpSet() {
	println(`
          usage:
               set command option value
          example:
               set ping timeout 2s
	`)

}
<commit_msg>add snmp struct and default conf, fixed set command validation<commit_after>package cli

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/user"
	"reflect"
	"strconv"
	"strings"
)

var defaultConfig = `{
	"ping" : {
		"timeout" : "2s",
		"interval": "1s",
		"count":	4
	},
	"hping" : {
		"timeout"  : "2s",
		"method"   : "HEAD",
		"data"	   : "mylg",
		"count"	   : 5
	},
	"web" : {
		"port"	   : 8080,
		"address"  : "127.0.0.1"
	},
	"scan" : {
		"port"     : "1-500"
	},
	"trace" : {
		"wait"  : "2s"
	},
	"snmp" : {
		"community" : "public",
		"timeout" : "1s",
		"version" : "2c",
		"retries" : 1,
		"port"    : 161
	}
}`

// Config represents configuration
type Config struct {
	Ping  Ping  `json:"ping"`
	Hping HPing `json:"hping"`
	Web   Web   `json:"web"`
	Scan  Scan  `json:"scan"`
	Trace Trace `json:"trace"`
	Snmp  SNMP  `json:"snmp"`
}

// Ping represents ping command options
type Ping struct {
	Timeout  string `json:"timeout"`
	Interval string `json:"interval"`
	Count    int    `json:"count"`
}

// HPing represents ping command options
type HPing struct {
	Timeout string `json:"timeout"`
	Method  string `json:"method"`
	Data    string `json:"data"`
	Count   int    `json:"count"`
}

// Web represents web command options
type Web struct {
	Port    int    `json:"port"`
	Address string `json:"address"`
}

// Scan represents scan command options
type Scan struct {
	Port string `json:"port"`
}

// Trace represents trace command options
type Trace struct {
	Wait string `json:"wait"`
}

// SNMP represents nms command options
type SNMP struct {
	Community string `json:"community"`
	Timeout   string `json:"timeout"`
	Version   string `json:"version"`
	Retries   int    `json:"retries"`
	Port      int    `json:"port"`
}

// WriteConfig write config to disk
func WriteConfig(cfg Config) error {
	f, err := cfgFile()
	if err != nil {
		return err
	}

	h, err := os.Create(f)
	if err != nil {
		return err
	}

	b, err := json.Marshal(cfg)
	if err != nil {
		return err
	}
	_, err = h.Write(bytes.ToLower(b))
	if err != nil {
		return err
	}
	h.Close()

	return nil
}

// GetOptions returns option(s)/value(s) for specific command
func GetOptions(s interface{}, key string) ([]string, []interface{}) {
	var (
		opts []string
		vals []interface{}
	)
	v := reflect.ValueOf(s)
	t := v.Type()

	for i := 0; i < v.NumField(); i++ {
		if t.Field(i).Name == key {
			f := v.Field(i)
			ft := f.Type()
			for j := 0; j < f.NumField(); j++ {
				vals = append(vals, f.Field(j))
				opts = append(opts, ft.Field(j).Name)
			}
			break
		}
	}
	return opts, vals
}

// GetCMDNames returns command line names
func GetCMDNames(s interface{}) []string {
	var fields []string

	v := reflect.ValueOf(s)
	t := v.Type()

	for i := 0; i < t.NumField(); i++ {
		fields = append(fields, t.Field(i).Name)
	}
	return fields
}

// UpgradeConfig adds / removes new command(s)/option(s)
func UpgradeConfig(cfg *Config) error {
	var (
		conf  map[string]interface{}
		cConf Config
	)

	b := make([]byte, 2048)
	f, err := cfgFile()
	if err != nil {
		return err
	}

	h, err := os.Open(f)
	if err != nil {
		return err
	}
	n, _ := h.Read(b)
	b = b[:n]
	// load saved/old config to conf
	json.Unmarshal(b, &conf)
	// load default config to cConf
	json.Unmarshal([]byte(defaultConfig), &cConf)

	for _, cmd := range GetCMDNames(cConf) {
		opts, vals := GetOptions(cConf, cmd)
		for i, opt := range opts {
			if v, ok := conf[strings.ToLower(cmd)].(interface{}); ok {
				if _, ok = v.(map[string]interface{})[strings.ToLower(opt)]; !ok {
					args := fmt.Sprintf("%s %s %v", cmd, opt, vals[i])
					SetConfig(args, cfg)
				}
			} else {
				// there is new command
				args := fmt.Sprintf("%s %s %v", cmd, opt, vals[i])
				SetConfig(args, cfg)
			}
		}
	}
	return nil
}

// LoadConfig loads configuration
func LoadConfig() Config {
	var cfg Config

	cfg = ReadConfig()
	UpgradeConfig(&cfg)
	return cfg
}

// InitConfig creates new config file
func InitConfig(f string) ([]byte, error) {
	h, err := os.Create(f)
	if err != nil {
		return []byte(""), err
	}

	h.Chmod(os.FileMode(int(0600)))
	h.WriteString(defaultConfig)
	h.Close()

	return []byte(defaultConfig), nil
}

// ReadConfig reads configuration from existing
// or default configuration
func ReadConfig() Config {
	var (
		b    = make([]byte, 2048)
		conf Config
		err  error
	)
	f, err := cfgFile()
	if err != nil {

	}

	h, err := os.Open(f)

	if err != nil {
		switch {
		case os.IsNotExist(err):
			if b, err = InitConfig(f); err != nil {
				println(err.Error())
			}
		case os.IsPermission(err):
			println("cannot read configuration file due to insufficient permissions")
			b = []byte(defaultConfig)
		default:
			println(err.Error())
			b = []byte(defaultConfig)
		}
	} else {
		n, _ := h.Read(b)
		b = b[:n]
	}

	err = json.Unmarshal(b, &conf)
	if err != nil {
		println(err.Error())
		b = []byte(defaultConfig)
		json.Unmarshal(b, &conf)
	}

	return conf
}

// ReadDefaultConfig returns default configuration
func ReadDefaultConfig() (Config, error) {
	var (
		b    = make([]byte, 2048)
		conf Config
	)
	b = []byte(defaultConfig)
	err := json.Unmarshal(b, &conf)
	return conf, err
}

// cfgFile returns config file
func cfgFile() (string, error) {
	user, err := user.Current()
	if err != nil {
		return "", err
	}
	return user.HomeDir + "/.mylg.config", nil
}

// SetConfig handles update option's value
func SetConfig(args string, s *Config) {
	var (
		v     reflect.Value
		i     int64
		float float64
		err   error
	)

	args = strings.ToLower(args)
	f := strings.Fields(args)
	if len(f) < 3 {
		println("syntax error!")
		helpSet()
		return
	}

	v = reflect.ValueOf(s)
	v = reflect.Indirect(v)
	v = v.FieldByName(strings.Title(f[0]))

	if v.IsValid() {
		if i, err = strconv.ParseInt(f[2], 10, 64); err == nil {
			// integer
			err = SetValue(v.Addr(), strings.Title(f[1]), i)
		} else if float, err = strconv.ParseFloat(f[2], 64); err == nil {
			// float
			err = SetValue(v.Addr(), strings.Title(f[1]), float)
		} else {
			// string
			err = SetValue(v.Addr(), strings.Title(f[1]), f[2])
		}
	} else {
		err = fmt.Errorf("invalid")
	}

	if err != nil {
		println(err.Error())
	} else {
		if err = WriteConfig(*s); err != nil {
			println(err.Error())
		}
	}

}

// SetValue set optioni's value
func SetValue(v reflect.Value, rec string, val interface{}) error {

	if v.Kind() != reflect.Ptr {
		return fmt.Errorf("not a pointer value")
	}

	v = reflect.Indirect(v)
	switch v.Kind() {
	case reflect.Int:
		if value, ok := val.(int64); ok {
			v.SetInt(value)
		} else {
			return fmt.Errorf("the value should be integer")
		}
	case reflect.Float64:
		if value, ok := val.(float64); ok {
			v.SetFloat(value)
		} else {
			return fmt.Errorf("the value should be float")
		}
	case reflect.String:
		if value, ok := val.(string); ok {
			v.SetString(value)
		} else {
			return fmt.Errorf("the value shouldn't be number")
		}
	case reflect.Struct:
		for i := 0; i < v.NumField(); i++ {
			if v.Type().Field(i).Name == rec {
				err := SetValue(v.Field(i).Addr(), rec, val)
				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// ShowConfig prints the configuration
func ShowConfig(s *Config) {
	var v reflect.Value

	v = reflect.ValueOf(s)
	v = reflect.Indirect(v)

	for i := 0; i < v.NumField(); i++ {
		cmd := v.Type().Field(i).Name
		cmd = strings.ToLower(cmd)

		vv := v.Field(i).Addr()
		vv = reflect.Indirect(vv)

		for j := 0; j < vv.NumField(); j++ {
			subCmd := vv.Type().Field(j).Name
			subCmd = strings.ToLower(subCmd)
			value := vv.Field(j)
			fmt.Printf("set %-8s %-10s %v\n", cmd, subCmd, value)
		}
	}
}

// helpSet shows set command
func helpSet() {
	println(`
          usage:
               set command option value
          example:
               set ping timeout 2s
	`)

}
<|endoftext|>


<commit_before>package ranchr

import (
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	_ "os"
	"strings"
	"time"
)

func init() {
	// Psuedo-random is fine here
	rand.Seed(time.Now().UTC().UnixNano())
}

type isoer interface {
	SetChecksum() string
	SetURL() string
	SetName() string
}

// Iso information. The Baseurl and ChecksumType are set during creation
type iso struct {
	BaseURL      string
	Checksum     string
	ChecksumType string
	Name         string
	URL          string
}

// Release information. Values are set during creation
type release struct {
	iso
	Arch        string
	Distro      string
	Image       string
	Release     string
	ReleaseFull string
}

type ubuntu struct {
	release
}

// Sets the ISO information for a Packer template. If any error occurs, the
// error is saved to the setting variable. This will be reflected in the
// resulting Packer template, which will render it unusable until it is fixed.
func (u *ubuntu) SetISOInfo() error {
	u.setName()
	if err := u.setChecksum(); err != nil {
		return err
	}
	u.setURL()

	return nil
}

func (u *ubuntu) setChecksum() error {
	// Don't check for ReleaseFull existence since Release will also resolve for Ubuntu dl directories.
	// if the last character of the base url isn't a /, add it
	var page string
	var err error
	if page, err = getStringFromURL(appendSlash(u.BaseURL) + appendSlash(u.Release) + strings.ToUpper(u.ChecksumType) + "SUMS"); err != nil {
		logger.Error(err.Error())
		return err
	}

	// Now that we have a page...we need to find the checksum and set it
	if u.Checksum, err = u.findChecksum(page); err != nil {
		logger.Error(err.Error())
		return err
	}

	return nil
}

func (u *ubuntu) setURL() {
	// Its ok to use Release in the directory path because Release will resolve correctly, at the directory level, for Ubuntu.
	u.URL = appendSlash(u.BaseURL) + appendSlash(u.Release) + u.Name

	return
}

// findChecksum(s string, isoName string) finds the line in the incoming string with the isoName requested, strips out the checksum and returns it
// This is for releases.ubuntu.com checksums which are in a plain text file with each line representing n iso image and checksum pair, each line is in the format of:
//      checksumText image.isoname
// Notes: \n separate lines
//      since this is plain text processing we don't worry about runes
func (u *ubuntu) findChecksum(isoName string) (string, error) {
	if isoName == "" {
		err := errors.New("the string passed to ubuntu.findChecksum(isoName string) was empty; unable to process request")
		logger.Error(err.Error())
		return "", err
	}
	pos := strings.Index(isoName, u.Name)
	if pos <= 0 {
		// if it wasn't found, there's a chance that there's an extension on the release number
		// e.g. 12.04.4 instead of 12.04. This usually affects the LTS versions, I think.
		// For this look for a line  that contains .iso.
		// Substring the release string and explode it on '-'. Update isoName
		pos = strings.Index(isoName, ".iso")
		if pos < 0 {
			err := errors.New("Unable to find ISO information while looking for the release string on the Ubuntu checksums page.")
			logger.Error(err.Error())
			return "", err
		}
		tmpRel := isoName[:pos]
		tmpSl := strings.Split(tmpRel, "-")
		if len(tmpSl) < 3 {
			err := errors.New("Unable to parse release information on the Ubuntu checksum page.")
			logger.Error(err.Error())
			return "", err
		}

		u.ReleaseFull = tmpSl[1]
		u.setName()

		pos = strings.Index(isoName, u.Name)
		if pos < 0 {
			err := errors.New("Unable to retrieve checksum while looking for " + u.Name + " on the Ubuntu checksums page.")
			logger.Error(err.Error())
			return "", err
		}
	}

	if len(isoName) < pos-2 {
		err := errors.New("Unable to retrieve checksum information for " + u.Name)
		logger.Error(err.Error())
		return "", err
	}

	if pos-66 < 1 {
		u.Checksum = isoName[:pos-2]
	} else {
		u.Checksum = isoName[pos-66 : pos-2]
	}

	return u.Checksum, nil
}

func (u *ubuntu) setName() {
	if u.ReleaseFull == "" {
		u.ReleaseFull = u.Release
	}
	u.Name = "ubuntu-" + u.ReleaseFull + "-" + u.Image + "-" + u.Arch + ".iso"

	return
}

func (u *ubuntu) getOSType(buildType string) string {
	// Get the OSType string for the provided builder
	// OS Type varies by distro and bit and builder.
	switch buildType {
	case "vmware-iso":
		switch u.Arch {
		case "amd64":
			return "ubuntu-64"
		case "i386":
			return "ubuntu-32"
		default:
			return "linux"
		}
	case "virtualbox-iso":
		switch u.Arch {
		case "amd64":
			return "Ubuntu_64"
		case "i386":
			return "Ubuntu_32"
		default:
			return "linux"
		}
	default:
		return "linux"
	}
	return ""
}

type centOS struct {
	release
}

// For CentOS, only the whole version is needed. The latest version will be pulled from the iso name
var centOSMirrorListURL = "http://mirrorlist.centos.org/?release=%s&arch=%s&repo=os"

// Sets the ISO information for a Packer template. If any error occurs, the
// error is saved to the setting variable. This will be reflected in the
// resulting Packer template, which will render it unusable until it is fixed.
func (c *centOS) SetISOInfo() error {
	logger.Debugf("Current state: %+v", c)
	if err := c.setBaseURL(); err != nil {
		logger.Debugf("%+v", err)
		return err
	}
	if err := c.setChecksum(); err != nil {
		logger.Debugf("%+v", err)
		return err
	}
	c.setURL()
	logger.Debug("Exiting...")
	return nil
}

func (c *centOS) getOSType(buildType string) string {
	return buildType
	// Get the OSType string for the provided builder
	// OS Type varies by distro and bit and builder.
	switch buildType {
	case "vmware-iso":
		switch c.Arch {
		case "x86_64":
			return "centos-64"
		case "x386":
			return "centos-32"
		default:
			return "linux"
		}
	case "virtualbox-iso":
		switch c.Arch {
		case "x86_64":
			return "RedHat_64"
		case "x386":
			return "RedHat_32"
		default:
			return "linux"
		}
	default:
		return "linux"
	}
	return ""
}

func (c *centOS) setBaseURL() error {
	// Uses the release and arch information to get the list of mirrors.
	// Picks a mirror and uses that as the baseURL. This is only done if
	// the baseURL is empty. If there is a custom value, it is assumed that
	// it represents the mirror that Rancher should use.
	var err error
	if c.BaseURL != "" {
		logger.Debug("No changes made; centOS.BaseURL was already set to " + c.BaseURL)
		return nil
	}
	if c.Arch == "" {
		err = errors.New("Unable to set BaseURL information for CentOS because the Arch was not set.")
		logger.Critical(err.Error())
		return err
	}
	if c.Release == "" {
		err = errors.New("Unable to set BaseURL information for CentOS because the Release was not set.")
		logger.Critical(err.Error())
		return err
	}
	// We only care about the version, not the release. The version release will
	// always bring up the currently supported version.
	version := strings.Split(c.Release, ".")
	mirrorURL := fmt.Sprintf(centOSMirrorListURL, version[0], c.Arch)
	var page string
	if page, err = getStringFromURL(mirrorURL); err != nil {
		logger.Error(err.Error())
		return err
	}
	mirrors := strings.Split(page, "\n")
	mirrorCount := len(mirrors)
	if mirrorCount <= 1 {
		err = errors.New("Encountered unexpected results while processing mirror results from " + mirrorURL)
		logger.Error(err.Error())
		return err
	}
	// Use the URL provided by the list as the starting point
	c.BaseURL = mirrors[rand.Intn(mirrorCount-1)]
	// But that url is not actually usable for our needs so get the URL structure
	// and modify it so that it works for isos.
	baseURL, err := url.Parse(c.BaseURL)
	if err != nil {
		logger.Error(err)
		return err
	}
	pathParts := strings.Split(baseURL.Path, "/")
	l := len(pathParts)
	path := ""
	for i := 0; i < l-4; i++ {
		path += appendSlash(pathParts[i])
	}
	if path != "" {
		baseURL.Path = trimSuffix(path, "/")
	}
	// Set the ReleaseFull
	c.ReleaseFull = pathParts[l-4]
	c.BaseURL = baseURL.String()
	c.setName()
	logger.Debugf("BaseURL:%v, Name:%v", c.BaseURL, c.Name)
	return nil
}

func (c *centOS) setChecksum() error {
	// Don't check for ReleaseFull existence since Release will also resolve for Ubuntu dl directories.
	// if the last character of the base url isn't a /, add it
	var page string
	var err error
	if page, err = getStringFromURL(appendSlash(c.BaseURL) + c.ReleaseFull + "/isos/" + appendSlash(c.Arch) + strings.ToLower(c.ChecksumType) + "sum.txt"); err != nil {
		logger.Error(err.Error())
		return err
	}
	// Now that we have a page...we need to find the checksum and set it
	if c.Checksum, err = c.findChecksum(page); err != nil {
		logger.Error(err.Error())
		return err
	}
	logger.Debug(c.Checksum)
	return nil
}

func (c *centOS) setURL() {
	logger.Debug("entering")
	// The release needs to be set to the current release, not the major version, this only
	// applies if the number is a whole number.
	releaseParts := strings.Split(c.Release, ".")
	if len(releaseParts) == 1 {
		// This is just the version, replace it with a release
		urlParts := strings.Split(c.BaseURL, "/")
		// The version is the 3rd from last
		c.Release = urlParts[len(urlParts)-1]
	}
	c.URL = appendSlash(c.BaseURL) + c.ReleaseFull + "/isos/" + appendSlash(c.Arch) + c.Name
	logger.Debug(c.URL)
	return
}

func (c *centOS) findChecksum(page string) (string, error) {
	logger.Debugf("page: %+vname: %s", page, c.Name)
	// Finds the line in the incoming string with the isoName requested,
	// strips out the checksum and returns it. This is for CentOS checksums
	// which are in plaintext.
	//      checksumText  image.isoname
	// Notes: \n separate lines and two space separate the checksum and image name
	//      since this is plain text processing we don't worry about runes
	if page == "" {
		err := errors.New("the string passed to centOS.findChecksum(s string) was empty; unable to process request")
		logger.Error(err.Error())
		return "", err
	}
	pos := strings.Index(page, c.Name)
	if pos < 0 {
		err := errors.New("Unable to find ISO information while looking for the release string on the CentOS checksums page.")
		logger.Error(err.Error())
		return "", err
	}
	tmpRel := page[:pos]
	tmpSl := strings.Split(tmpRel, "\n")
	// The checksum we want is the last element in the array
	checksum := strings.TrimSpace(tmpSl[len(tmpSl)-1])
	logger.Debug("Checksum: " + checksum)
	return checksum, nil
}

func (c *centOS) setName() {
	if c.ReleaseFull == "" {
		c.ReleaseFull = c.Release
	}
	c.Name = "CentOS-" + c.ReleaseFull + "-" + c.Arch + "-" + c.Image + ".iso"
	return
}

func getStringFromURL(url string) (string, error) {
	// Get the URL resource
	res, err := http.Get(url)
	if err != nil {
		logger.Critical(err)
		return "", err
	}

	// Close the response body--its idiomatic to defer it right away
	defer res.Body.Close()

	// Read the resoponse body into page
	page, err := ioutil.ReadAll(res.Body)
	if err != nil {
		logger.Critical(err)
		return "", err
	}
	//convert the page to a string and return it
	return string(page), nil

}
<commit_msg>fixed some poorly structured switchers<commit_after>package ranchr

import (
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	_ "os"
	"strings"
	"time"
)

func init() {
	// Psuedo-random is fine here
	rand.Seed(time.Now().UTC().UnixNano())
}

type isoer interface {
	SetChecksum() string
	SetURL() string
	SetName() string
}

// Iso information. The Baseurl and ChecksumType are set during creation
type iso struct {
	BaseURL      string
	Checksum     string
	ChecksumType string
	Name         string
	URL          string
}

// Release information. Values are set during creation
type release struct {
	iso
	Arch        string
	Distro      string
	Image       string
	Release     string
	ReleaseFull string
}

type ubuntu struct {
	release
}

// Sets the ISO information for a Packer template. If any error occurs, the
// error is saved to the setting variable. This will be reflected in the
// resulting Packer template, which will render it unusable until it is fixed.
func (u *ubuntu) SetISOInfo() error {
	u.setName()
	if err := u.setChecksum(); err != nil {
		return err
	}
	u.setURL()

	return nil
}

func (u *ubuntu) setChecksum() error {
	// Don't check for ReleaseFull existence since Release will also resolve for Ubuntu dl directories.
	// if the last character of the base url isn't a /, add it
	var page string
	var err error
	if page, err = getStringFromURL(appendSlash(u.BaseURL) + appendSlash(u.Release) + strings.ToUpper(u.ChecksumType) + "SUMS"); err != nil {
		logger.Error(err.Error())
		return err
	}

	// Now that we have a page...we need to find the checksum and set it
	if u.Checksum, err = u.findChecksum(page); err != nil {
		logger.Error(err.Error())
		return err
	}

	return nil
}

func (u *ubuntu) setURL() {
	// Its ok to use Release in the directory path because Release will resolve correctly, at the directory level, for Ubuntu.
	u.URL = appendSlash(u.BaseURL) + appendSlash(u.Release) + u.Name

	return
}

// findChecksum(s string, isoName string) finds the line in the incoming string with the isoName requested, strips out the checksum and returns it
// This is for releases.ubuntu.com checksums which are in a plain text file with each line representing n iso image and checksum pair, each line is in the format of:
//      checksumText image.isoname
// Notes: \n separate lines
//      since this is plain text processing we don't worry about runes
func (u *ubuntu) findChecksum(isoName string) (string, error) {
	if isoName == "" {
		err := errors.New("the string passed to ubuntu.findChecksum(isoName string) was empty; unable to process request")
		logger.Error(err.Error())
		return "", err
	}
	pos := strings.Index(isoName, u.Name)
	if pos <= 0 {
		// if it wasn't found, there's a chance that there's an extension on the release number
		// e.g. 12.04.4 instead of 12.04. This usually affects the LTS versions, I think.
		// For this look for a line  that contains .iso.
		// Substring the release string and explode it on '-'. Update isoName
		pos = strings.Index(isoName, ".iso")
		if pos < 0 {
			err := errors.New("Unable to find ISO information while looking for the release string on the Ubuntu checksums page.")
			logger.Error(err.Error())
			return "", err
		}
		tmpRel := isoName[:pos]
		tmpSl := strings.Split(tmpRel, "-")
		if len(tmpSl) < 3 {
			err := errors.New("Unable to parse release information on the Ubuntu checksum page.")
			logger.Error(err.Error())
			return "", err
		}

		u.ReleaseFull = tmpSl[1]
		u.setName()

		pos = strings.Index(isoName, u.Name)
		if pos < 0 {
			err := errors.New("Unable to retrieve checksum while looking for " + u.Name + " on the Ubuntu checksums page.")
			logger.Error(err.Error())
			return "", err
		}
	}

	if len(isoName) < pos-2 {
		err := errors.New("Unable to retrieve checksum information for " + u.Name)
		logger.Error(err.Error())
		return "", err
	}

	if pos-66 < 1 {
		u.Checksum = isoName[:pos-2]
	} else {
		u.Checksum = isoName[pos-66 : pos-2]
	}

	return u.Checksum, nil
}

func (u *ubuntu) setName() {
	if u.ReleaseFull == "" {
		u.ReleaseFull = u.Release
	}
	u.Name = "ubuntu-" + u.ReleaseFull + "-" + u.Image + "-" + u.Arch + ".iso"

	return
}

func (u *ubuntu) getOSType(buildType string) string {
	// Get the OSType string for the provided builder
	// OS Type varies by distro and bit and builder.
	switch buildType {
	case "vmware-iso":
		switch u.Arch {
		case "amd64":
			return "ubuntu-64"
		case "i386":
			return "ubuntu-32"
		}
	case "virtualbox-iso":
		switch u.Arch {
		case "amd64":
			return "Ubuntu_64"
		case "i386":
			return "Ubuntu_32"
		}
	}
	return "linux"
}

type centOS struct {
	release
}

// For CentOS, only the whole version is needed. The latest version will be pulled from the iso name
var centOSMirrorListURL = "http://mirrorlist.centos.org/?release=%s&arch=%s&repo=os"

// Sets the ISO information for a Packer template. If any error occurs, the
// error is saved to the setting variable. This will be reflected in the
// resulting Packer template, which will render it unusable until it is fixed.
func (c *centOS) SetISOInfo() error {
	logger.Debugf("Current state: %+v", c)
	if err := c.setBaseURL(); err != nil {
		logger.Debugf("%+v", err)
		return err
	}
	if err := c.setChecksum(); err != nil {
		logger.Debugf("%+v", err)
		return err
	}
	c.setURL()
	logger.Debug("Exiting...")
	return nil
}

func (c *centOS) getOSType(buildType string) string {
	// Get the OSType string for the provided builder
	// OS Type varies by distro and bit and builder.
	switch buildType {
	case "vmware-iso":
		switch c.Arch {
		case "x86_64":
			return "centos-64"
		case "x386":
			return "centos-32"
		}
	case "virtualbox-iso":
		switch c.Arch {
		case "x86_64":
			return "RedHat_64"
		case "x386":
			return "RedHat_32"
		}
	}
	return "linux"
}

func (c *centOS) setBaseURL() error {
	// Uses the release and arch information to get the list of mirrors.
	// Picks a mirror and uses that as the baseURL. This is only done if
	// the baseURL is empty. If there is a custom value, it is assumed that
	// it represents the mirror that Rancher should use.
	var err error
	if c.BaseURL != "" {
		logger.Debug("No changes made; centOS.BaseURL was already set to " + c.BaseURL)
		return nil
	}
	if c.Arch == "" {
		err = errors.New("Unable to set BaseURL information for CentOS because the Arch was not set.")
		logger.Critical(err.Error())
		return err
	}
	if c.Release == "" {
		err = errors.New("Unable to set BaseURL information for CentOS because the Release was not set.")
		logger.Critical(err.Error())
		return err
	}
	// We only care about the version, not the release. The version release will
	// always bring up the currently supported version.
	version := strings.Split(c.Release, ".")
	mirrorURL := fmt.Sprintf(centOSMirrorListURL, version[0], c.Arch)
	var page string
	if page, err = getStringFromURL(mirrorURL); err != nil {
		logger.Error(err.Error())
		return err
	}
	mirrors := strings.Split(page, "\n")
	mirrorCount := len(mirrors)
	if mirrorCount <= 1 {
		err = errors.New("Encountered unexpected results while processing mirror results from " + mirrorURL)
		logger.Error(err.Error())
		return err
	}
	// Use the URL provided by the list as the starting point
	c.BaseURL = mirrors[rand.Intn(mirrorCount-1)]
	// But that url is not actually usable for our needs so get the URL structure
	// and modify it so that it works for isos.
	baseURL, err := url.Parse(c.BaseURL)
	if err != nil {
		logger.Error(err)
		return err
	}
	pathParts := strings.Split(baseURL.Path, "/")
	l := len(pathParts)
	path := ""
	for i := 0; i < l-4; i++ {
		path += appendSlash(pathParts[i])
	}
	if path != "" {
		baseURL.Path = trimSuffix(path, "/")
	}
	// Set the ReleaseFull
	c.ReleaseFull = pathParts[l-4]
	c.BaseURL = baseURL.String()
	c.setName()
	logger.Debugf("BaseURL:%v, Name:%v", c.BaseURL, c.Name)
	return nil
}

func (c *centOS) setChecksum() error {
	// Don't check for ReleaseFull existence since Release will also resolve for Ubuntu dl directories.
	// if the last character of the base url isn't a /, add it
	var page string
	var err error
	if page, err = getStringFromURL(appendSlash(c.BaseURL) + c.ReleaseFull + "/isos/" + appendSlash(c.Arch) + strings.ToLower(c.ChecksumType) + "sum.txt"); err != nil {
		logger.Error(err.Error())
		return err
	}
	// Now that we have a page...we need to find the checksum and set it
	if c.Checksum, err = c.findChecksum(page); err != nil {
		logger.Error(err.Error())
		return err
	}
	logger.Debug(c.Checksum)
	return nil
}

func (c *centOS) setURL() {
	logger.Debug("entering")
	// The release needs to be set to the current release, not the major version, this only
	// applies if the number is a whole number.
	releaseParts := strings.Split(c.Release, ".")
	if len(releaseParts) == 1 {
		// This is just the version, replace it with a release
		urlParts := strings.Split(c.BaseURL, "/")
		// The version is the 3rd from last
		c.Release = urlParts[len(urlParts)-1]
	}
	c.URL = appendSlash(c.BaseURL) + c.ReleaseFull + "/isos/" + appendSlash(c.Arch) + c.Name
	logger.Debug(c.URL)
	return
}

func (c *centOS) findChecksum(page string) (string, error) {
	logger.Debugf("page: %+vname: %s", page, c.Name)
	// Finds the line in the incoming string with the isoName requested,
	// strips out the checksum and returns it. This is for CentOS checksums
	// which are in plaintext.
	//      checksumText  image.isoname
	// Notes: \n separate lines and two space separate the checksum and image name
	//      since this is plain text processing we don't worry about runes
	if page == "" {
		err := errors.New("the string passed to centOS.findChecksum(s string) was empty; unable to process request")
		logger.Error(err.Error())
		return "", err
	}
	pos := strings.Index(page, c.Name)
	if pos < 0 {
		err := errors.New("Unable to find ISO information while looking for the release string on the CentOS checksums page.")
		logger.Error(err.Error())
		return "", err
	}
	tmpRel := page[:pos]
	tmpSl := strings.Split(tmpRel, "\n")
	// The checksum we want is the last element in the array
	checksum := strings.TrimSpace(tmpSl[len(tmpSl)-1])
	logger.Debug("Checksum: " + checksum)
	return checksum, nil
}

func (c *centOS) setName() {
	if c.ReleaseFull == "" {
		c.ReleaseFull = c.Release
	}
	c.Name = "CentOS-" + c.ReleaseFull + "-" + c.Arch + "-" + c.Image + ".iso"
	return
}

func getStringFromURL(url string) (string, error) {
	// Get the URL resource
	res, err := http.Get(url)
	if err != nil {
		logger.Critical(err)
		return "", err
	}

	// Close the response body--its idiomatic to defer it right away
	defer res.Body.Close()

	// Read the resoponse body into page
	page, err := ioutil.ReadAll(res.Body)
	if err != nil {
		logger.Critical(err)
		return "", err
	}
	//convert the page to a string and return it
	return string(page), nil

}
<|endoftext|>


<commit_before>package gocb

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
)

// ClusterManager provides methods for performing cluster management operations.
type ClusterManager struct {
	hosts    []string
	username string
	password string
	httpCli  *http.Client
}

// BucketType specifies the kind of bucket
type BucketType int

const (
	// Couchbase indicates a Couchbase bucket type.
	Couchbase = BucketType(0)

	// Memcached indicates a Memcached bucket type.
	Memcached = BucketType(1)

	// Ephemeral indicates an Ephemeral bucket type.
	Ephemeral = BucketType(2)
)

type bucketDataIn struct {
	Name         string `json:"name"`
	BucketType   string `json:"bucketType"`
	AuthType     string `json:"authType"`
	SaslPassword string `json:"saslPassword"`
	Quota        struct {
		Ram    int `json:"ram"`
		RawRam int `json:"rawRAM"`
	} `json:"quota"`
	ReplicaNumber int  `json:"replicaNumber"`
	ReplicaIndex  bool `json:"replicaIndex"`
	Controllers   struct {
		Flush string `json:"flush"`
	} `json:"controllers"`
}

// BucketSettings holds information about the settings for a bucket.
type BucketSettings struct {
	FlushEnabled  bool
	IndexReplicas bool
	Name          string
	Password      string
	Quota         int
	Replicas      int
	Type          BucketType
}

func (cm *ClusterManager) getMgmtEp() string {
	return cm.hosts[rand.Intn(len(cm.hosts))]
}

func (cm *ClusterManager) mgmtRequest(method, uri string, contentType string, body io.Reader) (*http.Response, error) {
	if contentType == "" && body != nil {
		panic("Content-type must be specified for non-null body.")
	}

	reqUri := cm.getMgmtEp() + uri
	req, err := http.NewRequest(method, reqUri, body)
	if contentType != "" {
		req.Header.Add("Content-Type", contentType)
	}
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth(cm.username, cm.password)
	return cm.httpCli.Do(req)
}

func bucketDataInToSettings(bucketData *bucketDataIn) *BucketSettings {
	settings := &BucketSettings{
		FlushEnabled:  bucketData.Controllers.Flush != "",
		IndexReplicas: bucketData.ReplicaIndex,
		Name:          bucketData.Name,
		Password:      bucketData.SaslPassword,
		Quota:         bucketData.Quota.Ram,
		Replicas:      bucketData.ReplicaNumber,
	}
	if bucketData.BucketType == "membase" {
		settings.Type = Couchbase
	} else if bucketData.BucketType == "memcached" {
		settings.Type = Memcached
	} else if bucketData.BucketType == "ephemeral" {
		settings.Type = Ephemeral
	} else {
		panic("Unrecognized bucket type string.")
	}
	if bucketData.AuthType != "sasl" {
		settings.Password = ""
	}
	return settings
}

// GetBuckets returns a list of all active buckets on the cluster.
func (cm *ClusterManager) GetBuckets() ([]*BucketSettings, error) {
	resp, err := cm.mgmtRequest("GET", "/pools/default/buckets", "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var bucketsData []*bucketDataIn
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&bucketsData)
	if err != nil {
		return nil, err
	}

	var buckets []*BucketSettings
	for _, bucketData := range bucketsData {
		buckets = append(buckets, bucketDataInToSettings(bucketData))
	}

	return buckets, nil
}

// InsertBucket creates a new bucket on the cluster.
func (cm *ClusterManager) InsertBucket(settings *BucketSettings) error {
	posts := url.Values{}
	posts.Add("name", settings.Name)
	if settings.Type == Couchbase {
		posts.Add("bucketType", "couchbase")
	} else if settings.Type == Memcached {
		posts.Add("bucketType", "memcached")
	} else if settings.Type == Ephemeral {
		posts.Add("bucketType", "ephemeral")
	} else {
		panic("Unrecognized bucket type.")
	}
	if settings.FlushEnabled {
		posts.Add("flushEnabled", "1")
	} else {
		posts.Add("flushEnabled", "0")
	}
	posts.Add("replicaNumber", fmt.Sprintf("%d", settings.Replicas))
	posts.Add("authType", "sasl")
	posts.Add("saslPassword", settings.Password)
	posts.Add("ramQuotaMB", fmt.Sprintf("%d", settings.Quota))
	posts.Add("proxyPort", "11210")

	data := []byte(posts.Encode())
	resp, err := cm.mgmtRequest("POST", "/pools/default/buckets", "application/x-www-form-urlencoded", bytes.NewReader(data))
	if err != nil {
		return err
	}

	if resp.StatusCode != 202 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// UpdateBucket will update the settings for a specific bucket on the cluster.
func (cm *ClusterManager) UpdateBucket(settings *BucketSettings) error {
	// Cluster-side, updates are the same as creates.
	return cm.InsertBucket(settings)
}

// RemoveBucket will delete a bucket from the cluster by name.
func (cm *ClusterManager) RemoveBucket(name string) error {
	reqUri := fmt.Sprintf("/pools/default/buckets/%s", name)

	resp, err := cm.mgmtRequest("DELETE", reqUri, "", nil)
	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// UserRole represents a role for a particular user on the server.
type UserRole struct {
	Role       string
	BucketName string
}

// User represents a user which was retrieved from the server.
type User struct {
	Id    string
	Name  string
	Type  string
	Roles []UserRole
}

// UserSettings represents a user during user creation.
type UserSettings struct {
	Name     string
	Password string
	Roles    []UserRole
}

type userRoleJson struct {
	Role       string `json:"role"`
	BucketName string `json:"bucket_name"`
}

type userJson struct {
	Id    string         `json:"id"`
	Name  string         `json:"name"`
	Type  string         `json:"type"`
	Roles []userRoleJson `json:"roles"`
}

type userSettingsJson struct {
	Name     string         `json:"name"`
	Password string         `json:"password"`
	Roles    []userRoleJson `json:"roles"`
}

// GetUsers returns a list of all users on the cluster.
func (cm *ClusterManager) GetUsers() ([]*User, error) {
	resp, err := cm.mgmtRequest("GET", "/settings/rbac/users", "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var usersData []*userJson
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&usersData)
	if err != nil {
		return nil, err
	}

	var users []*User
	for _, userData := range usersData {
		var user User
		user.Id = userData.Id
		user.Name = userData.Name
		user.Type = userData.Type
		for _, roleData := range userData.Roles {
			user.Roles = append(user.Roles, UserRole{
				Role:       roleData.Role,
				BucketName: roleData.BucketName,
			})
		}
		users = append(users, &user)
	}

	return users, nil
}

// UpsertUser updates a built-in RBAC user on the cluster.
func (cm *ClusterManager) UpsertUser(name string, settings *UserSettings) error {
	var reqRoleStrs []string
	for _, roleData := range settings.Roles {
		reqRoleStrs = append(reqRoleStrs, fmt.Sprintf("%s[%s]", roleData.Role, roleData.BucketName))
	}

	reqForm := make(url.Values)
	reqForm.Add("name", settings.Name)
	reqForm.Add("password", settings.Password)
	reqForm.Add("roles", strings.Join(reqRoleStrs, ","))

	uri := fmt.Sprintf("/settings/rbac/users/local/%s", name)
	reqBody := bytes.NewReader([]byte(reqForm.Encode()))
	resp, err := cm.mgmtRequest("PUT", uri, "application/x-www-form-urlencoded", reqBody)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// RemoveUser removes a built-in RBAC user on the cluster.
func (cm *ClusterManager) RemoveUser(name string) error {
	uri := fmt.Sprintf("/settings/rbac/users/local/%s", name)
	resp, err := cm.mgmtRequest("DELETE", uri, "", nil)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}
<commit_msg>Remove the proxyPort parameter when creating a bucket<commit_after>package gocb

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
)

// ClusterManager provides methods for performing cluster management operations.
type ClusterManager struct {
	hosts    []string
	username string
	password string
	httpCli  *http.Client
}

// BucketType specifies the kind of bucket
type BucketType int

const (
	// Couchbase indicates a Couchbase bucket type.
	Couchbase = BucketType(0)

	// Memcached indicates a Memcached bucket type.
	Memcached = BucketType(1)

	// Ephemeral indicates an Ephemeral bucket type.
	Ephemeral = BucketType(2)
)

type bucketDataIn struct {
	Name         string `json:"name"`
	BucketType   string `json:"bucketType"`
	AuthType     string `json:"authType"`
	SaslPassword string `json:"saslPassword"`
	Quota        struct {
		Ram    int `json:"ram"`
		RawRam int `json:"rawRAM"`
	} `json:"quota"`
	ReplicaNumber int  `json:"replicaNumber"`
	ReplicaIndex  bool `json:"replicaIndex"`
	Controllers   struct {
		Flush string `json:"flush"`
	} `json:"controllers"`
}

// BucketSettings holds information about the settings for a bucket.
type BucketSettings struct {
	FlushEnabled  bool
	IndexReplicas bool
	Name          string
	Password      string
	Quota         int
	Replicas      int
	Type          BucketType
}

func (cm *ClusterManager) getMgmtEp() string {
	return cm.hosts[rand.Intn(len(cm.hosts))]
}

func (cm *ClusterManager) mgmtRequest(method, uri string, contentType string, body io.Reader) (*http.Response, error) {
	if contentType == "" && body != nil {
		panic("Content-type must be specified for non-null body.")
	}

	reqUri := cm.getMgmtEp() + uri
	req, err := http.NewRequest(method, reqUri, body)
	if contentType != "" {
		req.Header.Add("Content-Type", contentType)
	}
	if err != nil {
		return nil, err
	}

	req.SetBasicAuth(cm.username, cm.password)
	return cm.httpCli.Do(req)
}

func bucketDataInToSettings(bucketData *bucketDataIn) *BucketSettings {
	settings := &BucketSettings{
		FlushEnabled:  bucketData.Controllers.Flush != "",
		IndexReplicas: bucketData.ReplicaIndex,
		Name:          bucketData.Name,
		Password:      bucketData.SaslPassword,
		Quota:         bucketData.Quota.Ram,
		Replicas:      bucketData.ReplicaNumber,
	}
	if bucketData.BucketType == "membase" {
		settings.Type = Couchbase
	} else if bucketData.BucketType == "memcached" {
		settings.Type = Memcached
	} else if bucketData.BucketType == "ephemeral" {
		settings.Type = Ephemeral
	} else {
		panic("Unrecognized bucket type string.")
	}
	if bucketData.AuthType != "sasl" {
		settings.Password = ""
	}
	return settings
}

// GetBuckets returns a list of all active buckets on the cluster.
func (cm *ClusterManager) GetBuckets() ([]*BucketSettings, error) {
	resp, err := cm.mgmtRequest("GET", "/pools/default/buckets", "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var bucketsData []*bucketDataIn
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&bucketsData)
	if err != nil {
		return nil, err
	}

	var buckets []*BucketSettings
	for _, bucketData := range bucketsData {
		buckets = append(buckets, bucketDataInToSettings(bucketData))
	}

	return buckets, nil
}

// InsertBucket creates a new bucket on the cluster.
func (cm *ClusterManager) InsertBucket(settings *BucketSettings) error {
	posts := url.Values{}
	posts.Add("name", settings.Name)
	if settings.Type == Couchbase {
		posts.Add("bucketType", "couchbase")
	} else if settings.Type == Memcached {
		posts.Add("bucketType", "memcached")
	} else if settings.Type == Ephemeral {
		posts.Add("bucketType", "ephemeral")
	} else {
		panic("Unrecognized bucket type.")
	}
	if settings.FlushEnabled {
		posts.Add("flushEnabled", "1")
	} else {
		posts.Add("flushEnabled", "0")
	}
	posts.Add("replicaNumber", fmt.Sprintf("%d", settings.Replicas))
	posts.Add("authType", "sasl")
	posts.Add("saslPassword", settings.Password)
	posts.Add("ramQuotaMB", fmt.Sprintf("%d", settings.Quota))

	data := []byte(posts.Encode())
	resp, err := cm.mgmtRequest("POST", "/pools/default/buckets", "application/x-www-form-urlencoded", bytes.NewReader(data))
	if err != nil {
		return err
	}

	if resp.StatusCode != 202 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// UpdateBucket will update the settings for a specific bucket on the cluster.
func (cm *ClusterManager) UpdateBucket(settings *BucketSettings) error {
	// Cluster-side, updates are the same as creates.
	return cm.InsertBucket(settings)
}

// RemoveBucket will delete a bucket from the cluster by name.
func (cm *ClusterManager) RemoveBucket(name string) error {
	reqUri := fmt.Sprintf("/pools/default/buckets/%s", name)

	resp, err := cm.mgmtRequest("DELETE", reqUri, "", nil)
	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// UserRole represents a role for a particular user on the server.
type UserRole struct {
	Role       string
	BucketName string
}

// User represents a user which was retrieved from the server.
type User struct {
	Id    string
	Name  string
	Type  string
	Roles []UserRole
}

// UserSettings represents a user during user creation.
type UserSettings struct {
	Name     string
	Password string
	Roles    []UserRole
}

type userRoleJson struct {
	Role       string `json:"role"`
	BucketName string `json:"bucket_name"`
}

type userJson struct {
	Id    string         `json:"id"`
	Name  string         `json:"name"`
	Type  string         `json:"type"`
	Roles []userRoleJson `json:"roles"`
}

type userSettingsJson struct {
	Name     string         `json:"name"`
	Password string         `json:"password"`
	Roles    []userRoleJson `json:"roles"`
}

// GetUsers returns a list of all users on the cluster.
func (cm *ClusterManager) GetUsers() ([]*User, error) {
	resp, err := cm.mgmtRequest("GET", "/settings/rbac/users", "", nil)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return nil, clientError{string(data)}
	}

	var usersData []*userJson
	jsonDec := json.NewDecoder(resp.Body)
	err = jsonDec.Decode(&usersData)
	if err != nil {
		return nil, err
	}

	var users []*User
	for _, userData := range usersData {
		var user User
		user.Id = userData.Id
		user.Name = userData.Name
		user.Type = userData.Type
		for _, roleData := range userData.Roles {
			user.Roles = append(user.Roles, UserRole{
				Role:       roleData.Role,
				BucketName: roleData.BucketName,
			})
		}
		users = append(users, &user)
	}

	return users, nil
}

// UpsertUser updates a built-in RBAC user on the cluster.
func (cm *ClusterManager) UpsertUser(name string, settings *UserSettings) error {
	var reqRoleStrs []string
	for _, roleData := range settings.Roles {
		reqRoleStrs = append(reqRoleStrs, fmt.Sprintf("%s[%s]", roleData.Role, roleData.BucketName))
	}

	reqForm := make(url.Values)
	reqForm.Add("name", settings.Name)
	reqForm.Add("password", settings.Password)
	reqForm.Add("roles", strings.Join(reqRoleStrs, ","))

	uri := fmt.Sprintf("/settings/rbac/users/local/%s", name)
	reqBody := bytes.NewReader([]byte(reqForm.Encode()))
	resp, err := cm.mgmtRequest("PUT", uri, "application/x-www-form-urlencoded", reqBody)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}

// RemoveUser removes a built-in RBAC user on the cluster.
func (cm *ClusterManager) RemoveUser(name string) error {
	uri := fmt.Sprintf("/settings/rbac/users/local/%s", name)
	resp, err := cm.mgmtRequest("DELETE", uri, "", nil)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		data, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		err = resp.Body.Close()
		if err != nil {
			logDebugf("Failed to close socket (%s)", err)
		}
		return clientError{string(data)}
	}

	return nil
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package cmd

import (
	"encoding/json"

	"github.com/kelseyhightower/envconfig"
	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/stats/cloud"
	"github.com/loadimpact/k6/stats/influxdb"
	"github.com/shibukawa/configdir"
	"github.com/spf13/pflag"
	null "gopkg.in/guregu/null.v3"
)

const configFilename = "config.json"

var (
	configDirs    = configdir.New("loadimpact", "k6")
	configFlagSet = pflag.NewFlagSet("", 0)
)

func init() {
	configFlagSet.SortFlags = false
	configFlagSet.StringP("out", "o", "", "`uri` for an external metrics database")
	configFlagSet.BoolP("linger", "l", false, "keep the API server alive past test end")
	configFlagSet.Bool("no-usage-report", false, "don't send anonymous stats to the developers")
}

type Config struct {
	lib.Options

	Out           null.String `json:"out" envconfig:"out"`
	Linger        null.Bool   `json:"linger" envconfig:"linger"`
	NoUsageReport null.Bool   `json:"noUsageReport" envconfig:"no_usage_report"`

	Collectors struct {
		InfluxDB influxdb.Config `json:"influxdb"`
		Cloud    cloud.Config    `json:"cloud"`
	} `json:"collectors"`
}

func (c Config) Apply(cfg Config) Config {
	c.Options = c.Options.Apply(cfg.Options)
	if cfg.Out.Valid {
		c.Out = cfg.Out
	}
	if cfg.Linger.Valid {
		c.Linger = cfg.Linger
	}
	if cfg.NoUsageReport.Valid {
		c.NoUsageReport = cfg.NoUsageReport
	}
	c.Collectors.InfluxDB = c.Collectors.InfluxDB.Apply(cfg.Collectors.InfluxDB)
	c.Collectors.Cloud = c.Collectors.Cloud.Apply(cfg.Collectors.Cloud)
	return c
}

// Gets configuration from CLI flags.
func getConfig(flags *pflag.FlagSet) (Config, error) {
	opts, err := getOptions(flags)
	if err != nil {
		return Config{}, err
	}
	return Config{
		Options:       opts,
		Out:           getNullString(flags, "out"),
		Linger:        getNullBool(flags, "linger"),
		NoUsageReport: getNullBool(flags, "no-usage-report"),
	}, nil
}

// Reads a configuration file from disk.
func readDiskConfig() (Config, *configdir.Config, error) {
	cdir := configDirs.QueryFolderContainsFile(configFilename)
	if cdir == nil {
		return Config{}, configDirs.QueryFolders(configdir.Global)[0], nil
	}
	data, err := cdir.ReadFile(configFilename)
	if err != nil {
		return Config{}, cdir, err
	}
	var conf Config
	if err := json.Unmarshal(data, &conf); err != nil {
		return conf, cdir, err
	}
	return conf, cdir, nil
}

// Writes configuration back to disk.
func writeDiskConfig(cdir *configdir.Config, conf Config) error {
	data, err := json.MarshalIndent(conf, "", "  ")
	if err != nil {
		return err
	}
	return cdir.WriteFile(configFilename, data)
}

// Reads configuration variables from the environment.
func readEnvConfig() (conf Config, err error) {
	err = envconfig.Process("k6", &conf)
	return conf, err
}
<commit_msg>Simplified for linter<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package cmd

import (
	"encoding/json"

	"github.com/kelseyhightower/envconfig"
	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/stats/cloud"
	"github.com/loadimpact/k6/stats/influxdb"
	"github.com/shibukawa/configdir"
	"github.com/spf13/pflag"
	null "gopkg.in/guregu/null.v3"
)

const configFilename = "config.json"

var (
	configDirs    = configdir.New("loadimpact", "k6")
	configFlagSet = pflag.NewFlagSet("", 0)
)

func init() {
	configFlagSet.SortFlags = false
	configFlagSet.StringP("out", "o", "", "`uri` for an external metrics database")
	configFlagSet.BoolP("linger", "l", false, "keep the API server alive past test end")
	configFlagSet.Bool("no-usage-report", false, "don't send anonymous stats to the developers")
}

type Config struct {
	lib.Options

	Out           null.String `json:"out" envconfig:"out"`
	Linger        null.Bool   `json:"linger" envconfig:"linger"`
	NoUsageReport null.Bool   `json:"noUsageReport" envconfig:"no_usage_report"`

	Collectors struct {
		InfluxDB influxdb.Config `json:"influxdb"`
		Cloud    cloud.Config    `json:"cloud"`
	} `json:"collectors"`
}

func (c Config) Apply(cfg Config) Config {
	c.Options = c.Options.Apply(cfg.Options)
	if cfg.Out.Valid {
		c.Out = cfg.Out
	}
	if cfg.Linger.Valid {
		c.Linger = cfg.Linger
	}
	if cfg.NoUsageReport.Valid {
		c.NoUsageReport = cfg.NoUsageReport
	}
	c.Collectors.InfluxDB = c.Collectors.InfluxDB.Apply(cfg.Collectors.InfluxDB)
	c.Collectors.Cloud = c.Collectors.Cloud.Apply(cfg.Collectors.Cloud)
	return c
}

// Gets configuration from CLI flags.
func getConfig(flags *pflag.FlagSet) (Config, error) {
	opts, err := getOptions(flags)
	if err != nil {
		return Config{}, err
	}
	return Config{
		Options:       opts,
		Out:           getNullString(flags, "out"),
		Linger:        getNullBool(flags, "linger"),
		NoUsageReport: getNullBool(flags, "no-usage-report"),
	}, nil
}

// Reads a configuration file from disk.
func readDiskConfig() (Config, *configdir.Config, error) {
	cdir := configDirs.QueryFolderContainsFile(configFilename)
	if cdir == nil {
		return Config{}, configDirs.QueryFolders(configdir.Global)[0], nil
	}
	data, err := cdir.ReadFile(configFilename)
	if err != nil {
		return Config{}, cdir, err
	}
	var conf Config
	err = json.Unmarshal(data, &conf)
	return conf, cdir, err
}

// Writes configuration back to disk.
func writeDiskConfig(cdir *configdir.Config, conf Config) error {
	data, err := json.MarshalIndent(conf, "", "  ")
	if err != nil {
		return err
	}
	return cdir.WriteFile(configFilename, data)
}

// Reads configuration variables from the environment.
func readEnvConfig() (conf Config, err error) {
	err = envconfig.Process("k6", &conf)
	return conf, err
}
<|endoftext|>