<commit_before>package taskrunner

import (
	"context"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
	ti "github.com/hashicorp/nomad/client/allocrunner/taskrunner/interfaces"
	"github.com/hashicorp/nomad/client/consul"
	"github.com/hashicorp/nomad/nomad/structs"
	"github.com/pkg/errors"
)

const (
	// the name of this hook, used in logs
	sidsHookName = "consul_si_token"

	// sidsBackoffBaseline is the baseline time for exponential backoff when
	// attempting to retrieve a Consul SI token
	sidsBackoffBaseline = 5 * time.Second

	// sidsBackoffLimit is the limit of the exponential backoff when attempting
	// to retrieve a Consul SI token
	sidsBackoffLimit = 3 * time.Minute

	// sidsDerivationTimeout limits the amount of time we may spend trying to
	// derive a SI token. If the hook does not get a token within this amount of
	// time, the result is a failure.
	sidsDerivationTimeout = 5 * time.Minute

	// sidsTokenFile is the name of the file holding the Consul SI token inside
	// the task's secret directory
	sidsTokenFile = "si_token"

	// sidsTokenFilePerms is the level of file permissions granted on the file
	// in the secrets directory for the task
	sidsTokenFilePerms = 0440
)

type sidsHookConfig struct {
	alloc      *structs.Allocation
	task       *structs.Task
	sidsClient consul.ServiceIdentityAPI
	lifecycle  ti.TaskLifecycle
	logger     hclog.Logger
}

// Service Identities hook for managing SI tokens of connect enabled tasks.
type sidsHook struct {
	// alloc is the allocation
	alloc *structs.Allocation

	// taskName is the name of the task
	task *structs.Task

	// sidsClient is the Consul client [proxy] for requesting SI tokens
	sidsClient consul.ServiceIdentityAPI

	// lifecycle is used to signal, restart, and kill a task
	lifecycle ti.TaskLifecycle

	// derivationTimeout is the amount of time we may wait for Consul to successfully
	// provide a SI token. Making this configurable for testing, otherwise
	// default to sidsDerivationTimeout
	derivationTimeout time.Duration

	// logger is used to log
	logger hclog.Logger

	// lock variables that can be manipulated after hook creation
	lock sync.Mutex
	// firstRun keeps track of whether the hook is being called for the first
	// time (for this task) during the lifespan of the Nomad Client process.
	firstRun bool
}

func newSIDSHook(c sidsHookConfig) *sidsHook {
	return &sidsHook{
		alloc:             c.alloc,
		task:              c.task,
		sidsClient:        c.sidsClient,
		lifecycle:         c.lifecycle,
		derivationTimeout: sidsDerivationTimeout,
		logger:            c.logger.Named(sidsHookName),
		firstRun:          true,
	}
}

func (h *sidsHook) Name() string {
	return sidsHookName
}

func (h *sidsHook) Prestart(
	ctx context.Context,
	req *interfaces.TaskPrestartRequest,
	resp *interfaces.TaskPrestartResponse) error {

	h.lock.Lock()
	defer h.lock.Unlock()

	// do nothing if we have already done things
	if h.earlyExit() {
		resp.Done = true
		return nil
	}

	// optimistically try to recover token from disk
	token, err := h.recoverToken(req.TaskDir.SecretsDir)
	if err != nil {
		return err
	}

	// need to ask for a new SI token & persist it to disk
	if token == "" {
		if token, err = h.deriveSIToken(ctx); err != nil {
			return err
		}
		if err := h.writeToken(req.TaskDir.SecretsDir, token); err != nil {
			return err
		}
	}

	h.logger.Info("derived SI token", "task", h.task.Name, "si_task", h.task.Kind.Value())

	resp.Done = true
	return nil
}

// earlyExit returns true if the Prestart hook has already been executed during
// the instantiation of this task runner.
//
// assumes h is locked
func (h *sidsHook) earlyExit() bool {
	if h.firstRun {
		h.firstRun = false
		return false
	}
	return true
}

// writeToken writes token into the secrets directory for the task.
func (h *sidsHook) writeToken(dir string, token string) error {
	tokenPath := filepath.Join(dir, sidsTokenFile)
	if err := ioutil.WriteFile(tokenPath, []byte(token), sidsTokenFilePerms); err != nil {
		return errors.Wrap(err, "failed to write SI token")
	}
	return nil
}

// recoverToken returns the token saved to disk in the secrets directory for the
// task if it exists, or the empty string if the file does not exist. an error
// is returned only for some other (e.g. disk IO) error.
func (h *sidsHook) recoverToken(dir string) (string, error) {
	tokenPath := filepath.Join(dir, sidsTokenFile)
	token, err := ioutil.ReadFile(tokenPath)
	if err != nil {
		if !os.IsNotExist(err) {
			h.logger.Error("failed to recover SI token", "error", err)
			return "", errors.Wrap(err, "failed to recover SI token")
		}
		h.logger.Trace("no pre-existing SI token to recover", "task", h.task.Name)
		return "", nil // token file does not exist yet
	}
	h.logger.Trace("recovered pre-existing SI token", "task", h.task.Name)
	return string(token), nil
}

// siDerivationResult is used to pass along the result of attempting to derive
// an SI token between the goroutine doing the derivation and its caller
type siDerivationResult struct {
	token string
	err   error
}

// deriveSIToken spawns and waits on a goroutine which will make attempts to
// derive an SI token until a token is successfully created, or ctx is signaled
// done.
func (h *sidsHook) deriveSIToken(ctx context.Context) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, h.derivationTimeout)
	defer cancel()

	resultCh := make(chan siDerivationResult)

	// keep trying to get the token in the background
	go h.tryDerive(ctx, resultCh)

	// wait until we get a token, or we get a signal to quit
	for {
		select {
		case result := <-resultCh:
			if result.err != nil {
				h.kill(ctx, errors.Wrap(result.err, "consul: failed to derive SI token"))
				return "", result.err
			}
			return result.token, nil
		case <-ctx.Done():
			return "", ctx.Err()
		}
	}
}

func (h *sidsHook) kill(ctx context.Context, err error) {
	_ = h.lifecycle.Kill(
		ctx,
		structs.NewTaskEvent(structs.TaskKilling).
			SetFailsTask().
			SetDisplayMessage(err.Error()),
	)
}

// tryDerive loops forever until a token is created, or ctx is done.
func (h *sidsHook) tryDerive(ctx context.Context, ch chan<- siDerivationResult) {
	for attempt := 0; backoff(ctx, attempt); attempt++ {

		tokens, err := h.sidsClient.DeriveSITokens(h.alloc, []string{h.task.Name})

		switch {
		case err == nil:
			token, exists := tokens[h.task.Name]
			if !exists {
				err := errors.New("response does not include token for task")
				h.logger.Error("derive SI token is missing token for task", "error", err, "task", h.task.Name)
				ch <- siDerivationResult{token: "", err: err}
				return
			}
			ch <- siDerivationResult{token: token, err: nil}
			return
		case structs.IsServerSide(err):
			// the error is known to be a server problem, just die
			h.logger.Error("failed to derive SI token", "error", err, "task", h.task.Name, "server_side", true)
			ch <- siDerivationResult{token: "", err: err}
			return
		case !structs.IsRecoverable(err):
			// the error is known not to be recoverable, just die
			h.logger.Error("failed to derive SI token", "error", err, "task", h.task.Name, "recoverable", false)
			ch <- siDerivationResult{token: "", err: err}
			return

		default:
			// the error is marked recoverable, retry after some backoff
			h.logger.Error("failed attempt to derive SI token", "error", err, "recoverable", true)
		}
	}
}

func backoff(ctx context.Context, attempt int) bool {
	next := computeBackoff(attempt)
	select {
	case <-ctx.Done():
		return false
	case <-time.After(next):
		return true
	}
}

func computeBackoff(attempt int) time.Duration {
	switch attempt {
	case 0:
		return 0
	case 1:
		// go fast on first retry, because a unit test should be fast
		return 100 * time.Millisecond
	default:
		wait := time.Duration(attempt) * sidsBackoffBaseline
		if wait > sidsBackoffLimit {
			wait = sidsBackoffLimit
		}
		return wait
	}
}
<commit_msg>client: PR cleanup - improved logging around kill task in SIDS hook<commit_after>package taskrunner

import (
	"context"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
	ti "github.com/hashicorp/nomad/client/allocrunner/taskrunner/interfaces"
	"github.com/hashicorp/nomad/client/consul"
	"github.com/hashicorp/nomad/nomad/structs"
	"github.com/pkg/errors"
)

const (
	// the name of this hook, used in logs
	sidsHookName = "consul_si_token"

	// sidsBackoffBaseline is the baseline time for exponential backoff when
	// attempting to retrieve a Consul SI token
	sidsBackoffBaseline = 5 * time.Second

	// sidsBackoffLimit is the limit of the exponential backoff when attempting
	// to retrieve a Consul SI token
	sidsBackoffLimit = 3 * time.Minute

	// sidsDerivationTimeout limits the amount of time we may spend trying to
	// derive a SI token. If the hook does not get a token within this amount of
	// time, the result is a failure.
	sidsDerivationTimeout = 5 * time.Minute

	// sidsTokenFile is the name of the file holding the Consul SI token inside
	// the task's secret directory
	sidsTokenFile = "si_token"

	// sidsTokenFilePerms is the level of file permissions granted on the file
	// in the secrets directory for the task
	sidsTokenFilePerms = 0440
)

type sidsHookConfig struct {
	alloc      *structs.Allocation
	task       *structs.Task
	sidsClient consul.ServiceIdentityAPI
	lifecycle  ti.TaskLifecycle
	logger     hclog.Logger
}

// Service Identities hook for managing SI tokens of connect enabled tasks.
type sidsHook struct {
	// alloc is the allocation
	alloc *structs.Allocation

	// taskName is the name of the task
	task *structs.Task

	// sidsClient is the Consul client [proxy] for requesting SI tokens
	sidsClient consul.ServiceIdentityAPI

	// lifecycle is used to signal, restart, and kill a task
	lifecycle ti.TaskLifecycle

	// derivationTimeout is the amount of time we may wait for Consul to successfully
	// provide a SI token. Making this configurable for testing, otherwise
	// default to sidsDerivationTimeout
	derivationTimeout time.Duration

	// logger is used to log
	logger hclog.Logger

	// lock variables that can be manipulated after hook creation
	lock sync.Mutex
	// firstRun keeps track of whether the hook is being called for the first
	// time (for this task) during the lifespan of the Nomad Client process.
	firstRun bool
}

func newSIDSHook(c sidsHookConfig) *sidsHook {
	return &sidsHook{
		alloc:             c.alloc,
		task:              c.task,
		sidsClient:        c.sidsClient,
		lifecycle:         c.lifecycle,
		derivationTimeout: sidsDerivationTimeout,
		logger:            c.logger.Named(sidsHookName),
		firstRun:          true,
	}
}

func (h *sidsHook) Name() string {
	return sidsHookName
}

func (h *sidsHook) Prestart(
	ctx context.Context,
	req *interfaces.TaskPrestartRequest,
	resp *interfaces.TaskPrestartResponse) error {

	h.lock.Lock()
	defer h.lock.Unlock()

	// do nothing if we have already done things
	if h.earlyExit() {
		resp.Done = true
		return nil
	}

	// optimistically try to recover token from disk
	token, err := h.recoverToken(req.TaskDir.SecretsDir)
	if err != nil {
		return err
	}

	// need to ask for a new SI token & persist it to disk
	if token == "" {
		if token, err = h.deriveSIToken(ctx); err != nil {
			return err
		}
		if err := h.writeToken(req.TaskDir.SecretsDir, token); err != nil {
			return err
		}
	}

	h.logger.Info("derived SI token", "task", h.task.Name, "si_task", h.task.Kind.Value())

	resp.Done = true
	return nil
}

// earlyExit returns true if the Prestart hook has already been executed during
// the instantiation of this task runner.
//
// assumes h is locked
func (h *sidsHook) earlyExit() bool {
	if h.firstRun {
		h.firstRun = false
		return false
	}
	return true
}

// writeToken writes token into the secrets directory for the task.
func (h *sidsHook) writeToken(dir string, token string) error {
	tokenPath := filepath.Join(dir, sidsTokenFile)
	if err := ioutil.WriteFile(tokenPath, []byte(token), sidsTokenFilePerms); err != nil {
		return errors.Wrap(err, "failed to write SI token")
	}
	return nil
}

// recoverToken returns the token saved to disk in the secrets directory for the
// task if it exists, or the empty string if the file does not exist. an error
// is returned only for some other (e.g. disk IO) error.
func (h *sidsHook) recoverToken(dir string) (string, error) {
	tokenPath := filepath.Join(dir, sidsTokenFile)
	token, err := ioutil.ReadFile(tokenPath)
	if err != nil {
		if !os.IsNotExist(err) {
			h.logger.Error("failed to recover SI token", "error", err)
			return "", errors.Wrap(err, "failed to recover SI token")
		}
		h.logger.Trace("no pre-existing SI token to recover", "task", h.task.Name)
		return "", nil // token file does not exist yet
	}
	h.logger.Trace("recovered pre-existing SI token", "task", h.task.Name)
	return string(token), nil
}

// siDerivationResult is used to pass along the result of attempting to derive
// an SI token between the goroutine doing the derivation and its caller
type siDerivationResult struct {
	token string
	err   error
}

// deriveSIToken spawns and waits on a goroutine which will make attempts to
// derive an SI token until a token is successfully created, or ctx is signaled
// done.
func (h *sidsHook) deriveSIToken(ctx context.Context) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, h.derivationTimeout)
	defer cancel()

	resultCh := make(chan siDerivationResult)

	// keep trying to get the token in the background
	go h.tryDerive(ctx, resultCh)

	// wait until we get a token, or we get a signal to quit
	for {
		select {
		case result := <-resultCh:
			if result.err != nil {
				h.logger.Error("failed to derive SI token", "error", result.err)
				h.kill(ctx, errors.Wrap(result.err, "failed to derive SI token"))
				return "", result.err
			}
			return result.token, nil
		case <-ctx.Done():
			return "", ctx.Err()
		}
	}
}

func (h *sidsHook) kill(ctx context.Context, reason error) {
	if err := h.lifecycle.Kill(ctx,
		structs.NewTaskEvent(structs.TaskKilling).
			SetFailsTask().
			SetDisplayMessage(reason.Error()),
	); err != nil {
		h.logger.Error("failed to kill task", "kill_reason", reason, "error", err)
	}
}

// tryDerive loops forever until a token is created, or ctx is done.
func (h *sidsHook) tryDerive(ctx context.Context, ch chan<- siDerivationResult) {
	for attempt := 0; backoff(ctx, attempt); attempt++ {

		tokens, err := h.sidsClient.DeriveSITokens(h.alloc, []string{h.task.Name})

		switch {
		case err == nil:
			token, exists := tokens[h.task.Name]
			if !exists {
				err := errors.New("response does not include token for task")
				h.logger.Error("derive SI token is missing token for task", "error", err, "task", h.task.Name)
				ch <- siDerivationResult{token: "", err: err}
				return
			}
			ch <- siDerivationResult{token: token, err: nil}
			return
		case structs.IsServerSide(err):
			// the error is known to be a server problem, just die
			h.logger.Error("failed to derive SI token", "error", err, "task", h.task.Name, "server_side", true)
			ch <- siDerivationResult{token: "", err: err}
			return
		case !structs.IsRecoverable(err):
			// the error is known not to be recoverable, just die
			h.logger.Error("failed to derive SI token", "error", err, "task", h.task.Name, "recoverable", false)
			ch <- siDerivationResult{token: "", err: err}
			return

		default:
			// the error is marked recoverable, retry after some backoff
			h.logger.Error("failed attempt to derive SI token", "error", err, "recoverable", true)
		}
	}
}

func backoff(ctx context.Context, attempt int) bool {
	next := computeBackoff(attempt)
	select {
	case <-ctx.Done():
		return false
	case <-time.After(next):
		return true
	}
}

func computeBackoff(attempt int) time.Duration {
	switch attempt {
	case 0:
		return 0
	case 1:
		// go fast on first retry, because a unit test should be fast
		return 100 * time.Millisecond
	default:
		wait := time.Duration(attempt) * sidsBackoffBaseline
		if wait > sidsBackoffLimit {
			wait = sidsBackoffLimit
		}
		return wait
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
)

func fileExists(path string) bool {
	_, err := os.Stat(path)
	if err != nil {
		return false
	}
	return true
}

func saveCurrentDir() string {
	prevDir, _ := filepath.Abs(".")
	return prevDir
}

func restoreCurrentDir(prevDir string) {
	os.Chdir(prevDir)
}

func printLastMsg(workDir, srcDir string) {
	log.Println("Complete building nginx!")

	lastMsgFormat := `Enter the following command for install nginx.

$ cd %s/%s
$ sudo make install
`
	log.Println(fmt.Sprintf(lastMsgFormat, workDir, srcDir))
}

func versionCheck(version string) {
	if version == "" {
		log.Println("[warn]nginx version is not set.")
		log.Printf("[warn]nginx-build use %s.\n", NGINX_VERSION)
	}
}

func fileGetContents(path string) (string, error) {
	conf := ""
	if path == "" {
		log.Println("[warn]configure option is empty.")
	} else {
		confb, err := ioutil.ReadFile(path)
		if err != nil {
			return "", fmt.Errorf("confPath(%s) does not exist.", path)
		}
		conf = string(confb)
	}
	return conf, nil
}
<commit_msg>adjust space for last message<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
)

func fileExists(path string) bool {
	_, err := os.Stat(path)
	if err != nil {
		return false
	}
	return true
}

func saveCurrentDir() string {
	prevDir, _ := filepath.Abs(".")
	return prevDir
}

func restoreCurrentDir(prevDir string) {
	os.Chdir(prevDir)
}

func printLastMsg(workDir, srcDir string) {
	log.Println("Complete building nginx!")

	lastMsgFormat := `Enter the following command for install nginx.

   $ cd %s/%s
   $ sudo make install
`
	log.Println(fmt.Sprintf(lastMsgFormat, workDir, srcDir))
}

func versionCheck(version string) {
	if version == "" {
		log.Println("[warn]nginx version is not set.")
		log.Printf("[warn]nginx-build use %s.\n", NGINX_VERSION)
	}
}

func fileGetContents(path string) (string, error) {
	conf := ""
	if path == "" {
		log.Println("[warn]configure option is empty.")
	} else {
		confb, err := ioutil.ReadFile(path)
		if err != nil {
			return "", fmt.Errorf("confPath(%s) does not exist.", path)
		}
		conf = string(confb)
	}
	return conf, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package resty

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/textproto"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"sort"
	"strings"
)

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Logger interface
//_______________________________________________________________________

// Logger interface is to abstract the logging from Resty. Gives control to
// the Resty users, choice of the logger.
type Logger interface {
	Errorf(format string, v ...interface{})
	Warnf(format string, v ...interface{})
	Debugf(format string, v ...interface{})
}

func createLogger() *logger {
	l := &logger{l: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)}
	return l
}

var _ Logger = (*logger)(nil)

type logger struct {
	l *log.Logger
}

func (l *logger) Errorf(format string, v ...interface{}) {
	l.output("ERROR RESTY "+format, v...)
}

func (l *logger) Warnf(format string, v ...interface{}) {
	l.output("WARN RESTY "+format, v...)
}

func (l *logger) Debugf(format string, v ...interface{}) {
	l.output("DEBUG RESTY "+format, v...)
}

func (l *logger) output(format string, v ...interface{}) {
	if len(v) == 0 {
		l.l.Print(format)
		return
	}
	l.l.Printf(format, v...)
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package Helper methods
//_______________________________________________________________________

// IsStringEmpty method tells whether given string is empty or not
func IsStringEmpty(str string) bool {
	return len(strings.TrimSpace(str)) == 0
}

// DetectContentType method is used to figure out `Request.Body` content type for request header
func DetectContentType(body interface{}) string {
	contentType := plainTextType
	kind := kindOf(body)
	switch kind {
	case reflect.Struct, reflect.Map:
		contentType = jsonContentType
	case reflect.String:
		contentType = plainTextType
	default:
		if b, ok := body.([]byte); ok {
			contentType = http.DetectContentType(b)
		} else if kind == reflect.Slice {
			contentType = jsonContentType
		}
	}

	return contentType
}

// IsJSONType method is to check JSON content type or not
func IsJSONType(ct string) bool {
	return jsonCheck.MatchString(ct)
}

// IsXMLType method is to check XML content type or not
func IsXMLType(ct string) bool {
	return xmlCheck.MatchString(ct)
}

// Unmarshalc content into object from JSON or XML
func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
	if IsJSONType(ct) {
		err = c.JSONUnmarshal(b, d)
	} else if IsXMLType(ct) {
		err = xml.Unmarshal(b, d)
	}

	return
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// RequestLog and ResponseLog type
//_______________________________________________________________________

// RequestLog struct is used to collected information from resty request
// instance for debug logging. It sent to request log callback before resty
// actually logs the information.
type RequestLog struct {
	Header http.Header
	Body   string
}

// ResponseLog struct is used to collected information from resty response
// instance for debug logging. It sent to response log callback before resty
// actually logs the information.
type ResponseLog struct {
	Header http.Header
	Body   string
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package Unexported methods
//_______________________________________________________________________

// way to disable the HTML escape as opt-in
func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) {
	if !r.jsonEscapeHTML {
		return noescapeJSONMarshal(d)
	} else if !c.jsonEscapeHTML {
		return noescapeJSONMarshal(d)
	}
	return c.JSONMarshal(d)
}

func firstNonEmpty(v ...string) string {
	for _, s := range v {
		if !IsStringEmpty(s) {
			return s
		}
	}
	return ""
}

var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")

func escapeQuotes(s string) string {
	return quoteEscaper.Replace(s)
}

func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
	hdr := make(textproto.MIMEHeader)

	var contentDispositionValue string
	if IsStringEmpty(fileName) {
		contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param)
	} else {
		contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
			param, escapeQuotes(fileName))
	}
	hdr.Set("Content-Disposition", contentDispositionValue)

	if !IsStringEmpty(contentType) {
		hdr.Set(hdrContentTypeKey, contentType)
	}
	return hdr
}

func addMultipartFormField(w *multipart.Writer, mf *MultipartField) error {
	partWriter, err := w.CreatePart(createMultipartHeader(mf.Param, mf.FileName, mf.ContentType))
	if err != nil {
		return err
	}

	_, err = io.Copy(partWriter, mf.Reader)
	return err
}

func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error {
	// Auto detect actual multipart content type
	cbuf := make([]byte, 512)
	size, err := r.Read(cbuf)
	if err != nil {
		return err
	}

	partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf)))
	if err != nil {
		return err
	}

	if _, err = partWriter.Write(cbuf[:size]); err != nil {
		return err
	}

	_, err = io.Copy(partWriter, r)
	return err
}

func addFile(w *multipart.Writer, fieldName, path string) error {
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer closeq(file)
	return writeMultipartFormFile(w, fieldName, filepath.Base(path), file)
}

func addFileReader(w *multipart.Writer, f *File) error {
	return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader)
}

func getPointer(v interface{}) interface{} {
	vv := valueOf(v)
	if vv.Kind() == reflect.Ptr {
		return v
	}
	return reflect.New(vv.Type()).Interface()
}

func isPayloadSupported(m string, allowMethodGet bool) bool {
	return !(m == MethodHead || m == MethodOptions || (m == MethodGet && !allowMethodGet))
}

func typeOf(i interface{}) reflect.Type {
	return indirect(valueOf(i)).Type()
}

func valueOf(i interface{}) reflect.Value {
	return reflect.ValueOf(i)
}

func indirect(v reflect.Value) reflect.Value {
	return reflect.Indirect(v)
}

func kindOf(v interface{}) reflect.Kind {
	return typeOf(v).Kind()
}

func createDirectory(dir string) (err error) {
	if _, err = os.Stat(dir); err != nil {
		if os.IsNotExist(err) {
			if err = os.MkdirAll(dir, 0755); err != nil {
				return
			}
		}
	}
	return
}

func canJSONMarshal(contentType string, kind reflect.Kind) bool {
	return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice)
}

func functionName(i interface{}) string {
	return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}

func acquireBuffer() *bytes.Buffer {
	return bufPool.Get().(*bytes.Buffer)
}

func releaseBuffer(buf *bytes.Buffer) {
	if buf != nil {
		buf.Reset()
		bufPool.Put(buf)
	}
}

func closeq(v interface{}) {
	if c, ok := v.(io.Closer); ok {
		sliently(c.Close())
	}
}

func sliently(_ ...interface{}) {}

func composeHeaders(c *Client, r *Request, hdrs http.Header) string {
	var str []string
	for _, k := range sortHeaderKeys(hdrs) {
		var v string
		if k == "Cookie" {
			cv := strings.TrimSpace(strings.Join(hdrs[k], ", "))
			for _, c := range c.GetClient().Jar.Cookies(r.RawRequest.URL) {
				if cv != "" {
					cv = cv + "; " + c.String()
				} else {
					cv = c.String()
				}
			}
			v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, cv))
		} else {
			v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", ")))
		}
		if v != "" {
			str = append(str, "\t"+v)
		}
	}
	return strings.Join(str, "\n")
}

func sortHeaderKeys(hdrs http.Header) []string {
	var keys []string
	for key := range hdrs {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func copyHeaders(hdrs http.Header) http.Header {
	nh := http.Header{}
	for k, v := range hdrs {
		nh[k] = v
	}
	return nh
}
<commit_msg>debug log optimize - Added capacity to slice creation, when capacity is known (#277)<commit_after>// Copyright (c) 2015-2019 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package resty

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"net/http"
	"net/textproto"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"sort"
	"strings"
)

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Logger interface
//_______________________________________________________________________

// Logger interface is to abstract the logging from Resty. Gives control to
// the Resty users, choice of the logger.
type Logger interface {
	Errorf(format string, v ...interface{})
	Warnf(format string, v ...interface{})
	Debugf(format string, v ...interface{})
}

func createLogger() *logger {
	l := &logger{l: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)}
	return l
}

var _ Logger = (*logger)(nil)

type logger struct {
	l *log.Logger
}

func (l *logger) Errorf(format string, v ...interface{}) {
	l.output("ERROR RESTY "+format, v...)
}

func (l *logger) Warnf(format string, v ...interface{}) {
	l.output("WARN RESTY "+format, v...)
}

func (l *logger) Debugf(format string, v ...interface{}) {
	l.output("DEBUG RESTY "+format, v...)
}

func (l *logger) output(format string, v ...interface{}) {
	if len(v) == 0 {
		l.l.Print(format)
		return
	}
	l.l.Printf(format, v...)
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package Helper methods
//_______________________________________________________________________

// IsStringEmpty method tells whether given string is empty or not
func IsStringEmpty(str string) bool {
	return len(strings.TrimSpace(str)) == 0
}

// DetectContentType method is used to figure out `Request.Body` content type for request header
func DetectContentType(body interface{}) string {
	contentType := plainTextType
	kind := kindOf(body)
	switch kind {
	case reflect.Struct, reflect.Map:
		contentType = jsonContentType
	case reflect.String:
		contentType = plainTextType
	default:
		if b, ok := body.([]byte); ok {
			contentType = http.DetectContentType(b)
		} else if kind == reflect.Slice {
			contentType = jsonContentType
		}
	}

	return contentType
}

// IsJSONType method is to check JSON content type or not
func IsJSONType(ct string) bool {
	return jsonCheck.MatchString(ct)
}

// IsXMLType method is to check XML content type or not
func IsXMLType(ct string) bool {
	return xmlCheck.MatchString(ct)
}

// Unmarshalc content into object from JSON or XML
func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
	if IsJSONType(ct) {
		err = c.JSONUnmarshal(b, d)
	} else if IsXMLType(ct) {
		err = xml.Unmarshal(b, d)
	}

	return
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// RequestLog and ResponseLog type
//_______________________________________________________________________

// RequestLog struct is used to collected information from resty request
// instance for debug logging. It sent to request log callback before resty
// actually logs the information.
type RequestLog struct {
	Header http.Header
	Body   string
}

// ResponseLog struct is used to collected information from resty response
// instance for debug logging. It sent to response log callback before resty
// actually logs the information.
type ResponseLog struct {
	Header http.Header
	Body   string
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Package Unexported methods
//_______________________________________________________________________

// way to disable the HTML escape as opt-in
func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) {
	if !r.jsonEscapeHTML {
		return noescapeJSONMarshal(d)
	} else if !c.jsonEscapeHTML {
		return noescapeJSONMarshal(d)
	}
	return c.JSONMarshal(d)
}

func firstNonEmpty(v ...string) string {
	for _, s := range v {
		if !IsStringEmpty(s) {
			return s
		}
	}
	return ""
}

var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")

func escapeQuotes(s string) string {
	return quoteEscaper.Replace(s)
}

func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader {
	hdr := make(textproto.MIMEHeader)

	var contentDispositionValue string
	if IsStringEmpty(fileName) {
		contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param)
	} else {
		contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
			param, escapeQuotes(fileName))
	}
	hdr.Set("Content-Disposition", contentDispositionValue)

	if !IsStringEmpty(contentType) {
		hdr.Set(hdrContentTypeKey, contentType)
	}
	return hdr
}

func addMultipartFormField(w *multipart.Writer, mf *MultipartField) error {
	partWriter, err := w.CreatePart(createMultipartHeader(mf.Param, mf.FileName, mf.ContentType))
	if err != nil {
		return err
	}

	_, err = io.Copy(partWriter, mf.Reader)
	return err
}

func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error {
	// Auto detect actual multipart content type
	cbuf := make([]byte, 512)
	size, err := r.Read(cbuf)
	if err != nil {
		return err
	}

	partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf)))
	if err != nil {
		return err
	}

	if _, err = partWriter.Write(cbuf[:size]); err != nil {
		return err
	}

	_, err = io.Copy(partWriter, r)
	return err
}

func addFile(w *multipart.Writer, fieldName, path string) error {
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer closeq(file)
	return writeMultipartFormFile(w, fieldName, filepath.Base(path), file)
}

func addFileReader(w *multipart.Writer, f *File) error {
	return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader)
}

func getPointer(v interface{}) interface{} {
	vv := valueOf(v)
	if vv.Kind() == reflect.Ptr {
		return v
	}
	return reflect.New(vv.Type()).Interface()
}

func isPayloadSupported(m string, allowMethodGet bool) bool {
	return !(m == MethodHead || m == MethodOptions || (m == MethodGet && !allowMethodGet))
}

func typeOf(i interface{}) reflect.Type {
	return indirect(valueOf(i)).Type()
}

func valueOf(i interface{}) reflect.Value {
	return reflect.ValueOf(i)
}

func indirect(v reflect.Value) reflect.Value {
	return reflect.Indirect(v)
}

func kindOf(v interface{}) reflect.Kind {
	return typeOf(v).Kind()
}

func createDirectory(dir string) (err error) {
	if _, err = os.Stat(dir); err != nil {
		if os.IsNotExist(err) {
			if err = os.MkdirAll(dir, 0755); err != nil {
				return
			}
		}
	}
	return
}

func canJSONMarshal(contentType string, kind reflect.Kind) bool {
	return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice)
}

func functionName(i interface{}) string {
	return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}

func acquireBuffer() *bytes.Buffer {
	return bufPool.Get().(*bytes.Buffer)
}

func releaseBuffer(buf *bytes.Buffer) {
	if buf != nil {
		buf.Reset()
		bufPool.Put(buf)
	}
}

func closeq(v interface{}) {
	if c, ok := v.(io.Closer); ok {
		sliently(c.Close())
	}
}

func sliently(_ ...interface{}) {}

func composeHeaders(c *Client, r *Request, hdrs http.Header) string {
	str := make([]string, 0, len(hdrs))
	for _, k := range sortHeaderKeys(hdrs) {
		var v string
		if k == "Cookie" {
			cv := strings.TrimSpace(strings.Join(hdrs[k], ", "))
			for _, c := range c.GetClient().Jar.Cookies(r.RawRequest.URL) {
				if cv != "" {
					cv = cv + "; " + c.String()
				} else {
					cv = c.String()
				}
			}
			v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, cv))
		} else {
			v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", ")))
		}
		if v != "" {
			str = append(str, "\t"+v)
		}
	}
	return strings.Join(str, "\n")
}

func sortHeaderKeys(hdrs http.Header) []string {
	keys := make([]string, 0, len(hdrs))
	for key := range hdrs {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func copyHeaders(hdrs http.Header) http.Header {
	nh := http.Header{}
	for k, v := range hdrs {
		nh[k] = v
	}
	return nh
}
<|endoftext|>


<commit_before>package services

import (
	"fmt"
	"strings"

	"github.com/racker/perigee"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
	ToCDNServiceListQuery() (string, error)
}

// ListOpts allows the filtering and sorting of paginated collections through
// the API. Marker and Limit are used for pagination.
type ListOpts struct {
	Marker string `q:"marker"`
	Limit  int    `q:"limit"`
}

// ToCDNServiceListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToCDNServiceListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// List returns a Pager which allows you to iterate over a collection of
// CDN services. It accepts a ListOpts struct, which allows for pagination via
// marker and limit.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
	url := listURL(c)
	if opts != nil {
		query, err := opts.ToCDNServiceListQuery()
		if err != nil {
			return pagination.Pager{Err: err}
		}
		url += query
	}

	createPage := func(r pagination.PageResult) pagination.Page {
		p := ServicePage{pagination.MarkerPageBase{PageResult: r}}
		p.MarkerPageBase.Owner = p
		return p
	}

	pager := pagination.NewPager(c, url, createPage)
	return pager
}

// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
	ToCDNServiceCreateMap() (map[string]interface{}, error)
}

// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
	// REQUIRED. Specifies the name of the service. The minimum length for name is
	// 3. The maximum length is 256.
	Name string
	// REQUIRED. Specifies a list of domains used by users to access their website.
	Domains []Domain
	// REQUIRED. Specifies a list of origin domains or IP addresses where the
	// original assets are stored.
	Origins []Origin
	// REQUIRED. Specifies the CDN provider flavor ID to use. For a list of
	// flavors, see the operation to list the available flavors. The minimum
	// length for flavor_id is 1. The maximum length is 256.
	FlavorID string
	// OPTIONAL. Specifies the TTL rules for the assets under this service. Supports wildcards for fine-grained control.
	Caching []CacheRule
	// OPTIONAL. Specifies the restrictions that define who can access assets (content from the CDN cache).
	Restrictions []Restriction
}

// ToCDNServiceCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToCDNServiceCreateMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.Name == "" {
		return nil, no("Name")
	}
	s["name"] = opts.Name

	if opts.Domains == nil {
		return nil, no("Domains")
	}
	for _, domain := range opts.Domains {
		if domain.Domain == "" {
			return nil, no("Domains[].Domain")
		}
	}
	s["domains"] = opts.Domains

	if opts.Origins == nil {
		return nil, no("Origins")
	}
	for _, origin := range opts.Origins {
		if origin.Origin == "" {
			return nil, no("Origins[].Origin")
		}
		if origin.Rules == nil && len(opts.Origins) > 1 {
			return nil, no("Origins[].Rules")
		}
		for _, rule := range origin.Rules {
			if rule.Name == "" {
				return nil, no("Origins[].Rules[].Name")
			}
			if rule.RequestURL == "" {
				return nil, no("Origins[].Rules[].RequestURL")
			}
		}
	}
	s["origins"] = opts.Origins

	if opts.FlavorID == "" {
		return nil, no("FlavorID")
	}
	s["flavor_id"] = opts.FlavorID

	if opts.Caching != nil {
		for _, cache := range opts.Caching {
			if cache.Name == "" {
				return nil, no("Caching[].Name")
			}
			if cache.Rules != nil {
				for _, rule := range cache.Rules {
					if rule.Name == "" {
						return nil, no("Caching[].Rules[].Name")
					}
					if rule.RequestURL == "" {
						return nil, no("Caching[].Rules[].RequestURL")
					}
				}
			}
		}
		s["caching"] = opts.Caching
	}

	if opts.Restrictions != nil {
		for _, restriction := range opts.Restrictions {
			if restriction.Name == "" {
				return nil, no("Restrictions[].Name")
			}
			if restriction.Rules != nil {
				for _, rule := range restriction.Rules {
					if rule.Name == "" {
						return nil, no("Restrictions[].Rules[].Name")
					}
				}
			}
		}
		s["restrictions"] = opts.Restrictions
	}

	return s, nil
}

// Create accepts a CreateOpts struct and creates a new CDN service using the
// values provided.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
	var res CreateResult

	reqBody, err := opts.ToCDNServiceCreateMap()
	if err != nil {
		res.Err = err
		return res
	}

	// Send request to API
	resp, err := perigee.Request("POST", createURL(c), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		OkCodes:     []int{202},
	})
	res.Header = resp.HttpResponse.Header
	res.Err = err
	return res
}

// Get retrieves a specific service based on its URL or its unique ID. For
// example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Get(c *gophercloud.ServiceClient, idOrURL string) GetResult {
	var url string
	if strings.Contains(idOrURL, "/") {
		url = idOrURL
	} else {
		url = getURL(c, idOrURL)
	}

	var res GetResult
	_, res.Err = perigee.Request("GET", url, perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		Results:     &res.Body,
		OkCodes:     []int{200},
	})
	return res
}

// UpdateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Update operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type UpdateOptsBuilder interface {
	ToCDNServiceUpdateMap() ([]map[string]interface{}, error)
}

// Op represents an update operation.
type Op string

var (
	// Add is a constant used for performing a "add" operation when updating.
	Add Op = "add"
	// Remove is a constant used for performing a "remove" operation when updating.
	Remove Op = "remove"
	// Replace is a constant used for performing a "replace" operation when updating.
	Replace Op = "replace"
)

// Path is a JSON pointer location that indicates which service parameter is being added, replaced,
// or removed.
type Path struct {
	baseElement string
}

func (p Path) renderDash() string {
	return fmt.Sprintf("/%s/-", p.baseElement)
}

func (p Path) renderIndex(index int64) string {
	return fmt.Sprintf("/%s/%d", p.baseElement, index)
}

var (
	// PathDomains indicates that an update operation is to be performed on a Domain.
	PathDomains = Path{baseElement: "domains"}

	// PathOrigins indicates that an update operation is to be performed on an Origin.
	PathOrigins = Path{baseElement: "origins"}

	// PathCaching indicates that an update operation is to be performed on a CacheRule.
	PathCaching = Path{baseElement: "caching"}
)

// UpdateOpts represents the attributes used when updating an existing CDN service.
type UpdateOpts []UpdateOpt

// UpdateOpt represents a single update to an existing service. Multiple updates
// to a service can be submitted at the same time. See UpdateOpts.
type UpdateOpt struct {
	// Specifies the update operation to perform.
	Op Op `json:"op"`
	// Specifies the JSON Pointer location within the service's JSON representation
	// of the service parameter being added, replaced or removed.
	Path string `json:"path"`
	// Specifies the actual value to be added or replaced. It is not required for
	// the remove operation.
	Value map[string]interface{} `json:"value,omitempty"`
}

type value interface {
	toPatchValue() map[string]interface{}
	appropriatePath() Path
}

// Patch represents a single update to an existing Service. Multiple updates to a service can be
// submitted at the same time.
type Patch interface {
	ToCDNServiceUpdateMap() map[string]interface{}
}

// Addition is a Patch that requests the addition of one or more values (Domains, Origins, or
// CacheRules) to a Service. Pass it to the Update function as part of the Patch slice.
type Addition struct {
	Value value
}

// ToCDNServiceUpdateMap converts an Addition into a request body fragment suitable for the
// Update call.
func (a Addition) ToCDNServiceUpdateMap() map[string]interface{} {
	return map[string]interface{}{
		"op":    "add",
		"path":  a.Value.appropriatePath().renderDash(),
		"value": a.Value.toPatchValue(),
	}
}

// Replacement is a Patch that alters a specific service parameter (Domain, Origin, or CacheRule)
// in-place by index. Pass it to the Update function as part of the Patch slice.
type Replacement struct {
	Value value
	Index int64
}

// ToCDNServiceUpdateMap converts a Replacement into a request body fragment suitable for the
// Update call.
func (r Replacement) ToCDNServiceUpdateMap() map[string]interface{} {
	return map[string]interface{}{
		"op":    "replace",
		"path":  r.Value.appropriatePath().renderIndex(r.Index),
		"value": r.Value.toPatchValue(),
	}
}

// Removal is a Patch that requests the removal of a service parameter (Domain, Origin, or
// CacheRule) by index. Pass it to the Update function as part of the Patch slice.
type Removal struct {
	Path  Path
	Index int64
}

// ToCDNServiceUpdateMap converts a Removal into a request body fragment suitable for the
// Update call.
func (r Removal) ToCDNServiceUpdateMap() map[string]interface{} {
	return map[string]interface{}{
		"op":   "remove",
		"path": r.Path.renderIndex(r.Index),
	}
}

// ToCDNServiceUpdateMap casts an UpdateOpts struct to a map.
func (opts UpdateOpts) ToCDNServiceUpdateMap() ([]map[string]interface{}, error) {
	s := make([]map[string]interface{}, len(opts))

	for i, opt := range opts {
		if opt.Op != Add && opt.Op != Remove && opt.Op != Replace {
			return nil, fmt.Errorf("Invalid Op: %v", opt.Op)
		}
		if opt.Op == "" {
			return nil, no("Op")
		}
		if opt.Path == "" {
			return nil, no("Path")
		}
		if opt.Op != Remove && opt.Value == nil {
			return nil, no("Value")
		}
		s[i] = map[string]interface{}{
			"op":    opt.Op,
			"path":  opt.Path,
			"value": opt.Value,
		}
	}

	return s, nil
}

// Update accepts a UpdateOpts struct and updates an existing CDN service using
// the values provided. idOrURL can be either the service's URL or its ID. For
// example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Update(c *gophercloud.ServiceClient, idOrURL string, opts UpdateOptsBuilder) UpdateResult {
	var url string
	if strings.Contains(idOrURL, "/") {
		url = idOrURL
	} else {
		url = updateURL(c, idOrURL)
	}

	var res UpdateResult
	reqBody, err := opts.ToCDNServiceUpdateMap()
	if err != nil {
		res.Err = err
		return res
	}

	resp, err := perigee.Request("PATCH", url, perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		OkCodes:     []int{202},
	})
	res.Header = resp.HttpResponse.Header
	res.Err = err
	return res
}

// Delete accepts a service's ID or its URL and deletes the CDN service
// associated with it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Delete(c *gophercloud.ServiceClient, idOrURL string) DeleteResult {
	var url string
	if strings.Contains(idOrURL, "/") {
		url = idOrURL
	} else {
		url = deleteURL(c, idOrURL)
	}

	var res DeleteResult
	_, res.Err = perigee.Request("DELETE", url, perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return res
}
<commit_msg>Accept a []Patch in Update.<commit_after>package services

import (
	"fmt"
	"strings"

	"github.com/racker/perigee"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
	ToCDNServiceListQuery() (string, error)
}

// ListOpts allows the filtering and sorting of paginated collections through
// the API. Marker and Limit are used for pagination.
type ListOpts struct {
	Marker string `q:"marker"`
	Limit  int    `q:"limit"`
}

// ToCDNServiceListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToCDNServiceListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// List returns a Pager which allows you to iterate over a collection of
// CDN services. It accepts a ListOpts struct, which allows for pagination via
// marker and limit.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
	url := listURL(c)
	if opts != nil {
		query, err := opts.ToCDNServiceListQuery()
		if err != nil {
			return pagination.Pager{Err: err}
		}
		url += query
	}

	createPage := func(r pagination.PageResult) pagination.Page {
		p := ServicePage{pagination.MarkerPageBase{PageResult: r}}
		p.MarkerPageBase.Owner = p
		return p
	}

	pager := pagination.NewPager(c, url, createPage)
	return pager
}

// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
	ToCDNServiceCreateMap() (map[string]interface{}, error)
}

// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
	// REQUIRED. Specifies the name of the service. The minimum length for name is
	// 3. The maximum length is 256.
	Name string
	// REQUIRED. Specifies a list of domains used by users to access their website.
	Domains []Domain
	// REQUIRED. Specifies a list of origin domains or IP addresses where the
	// original assets are stored.
	Origins []Origin
	// REQUIRED. Specifies the CDN provider flavor ID to use. For a list of
	// flavors, see the operation to list the available flavors. The minimum
	// length for flavor_id is 1. The maximum length is 256.
	FlavorID string
	// OPTIONAL. Specifies the TTL rules for the assets under this service. Supports wildcards for fine-grained control.
	Caching []CacheRule
	// OPTIONAL. Specifies the restrictions that define who can access assets (content from the CDN cache).
	Restrictions []Restriction
}

// ToCDNServiceCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToCDNServiceCreateMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.Name == "" {
		return nil, no("Name")
	}
	s["name"] = opts.Name

	if opts.Domains == nil {
		return nil, no("Domains")
	}
	for _, domain := range opts.Domains {
		if domain.Domain == "" {
			return nil, no("Domains[].Domain")
		}
	}
	s["domains"] = opts.Domains

	if opts.Origins == nil {
		return nil, no("Origins")
	}
	for _, origin := range opts.Origins {
		if origin.Origin == "" {
			return nil, no("Origins[].Origin")
		}
		if origin.Rules == nil && len(opts.Origins) > 1 {
			return nil, no("Origins[].Rules")
		}
		for _, rule := range origin.Rules {
			if rule.Name == "" {
				return nil, no("Origins[].Rules[].Name")
			}
			if rule.RequestURL == "" {
				return nil, no("Origins[].Rules[].RequestURL")
			}
		}
	}
	s["origins"] = opts.Origins

	if opts.FlavorID == "" {
		return nil, no("FlavorID")
	}
	s["flavor_id"] = opts.FlavorID

	if opts.Caching != nil {
		for _, cache := range opts.Caching {
			if cache.Name == "" {
				return nil, no("Caching[].Name")
			}
			if cache.Rules != nil {
				for _, rule := range cache.Rules {
					if rule.Name == "" {
						return nil, no("Caching[].Rules[].Name")
					}
					if rule.RequestURL == "" {
						return nil, no("Caching[].Rules[].RequestURL")
					}
				}
			}
		}
		s["caching"] = opts.Caching
	}

	if opts.Restrictions != nil {
		for _, restriction := range opts.Restrictions {
			if restriction.Name == "" {
				return nil, no("Restrictions[].Name")
			}
			if restriction.Rules != nil {
				for _, rule := range restriction.Rules {
					if rule.Name == "" {
						return nil, no("Restrictions[].Rules[].Name")
					}
				}
			}
		}
		s["restrictions"] = opts.Restrictions
	}

	return s, nil
}

// Create accepts a CreateOpts struct and creates a new CDN service using the
// values provided.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
	var res CreateResult

	reqBody, err := opts.ToCDNServiceCreateMap()
	if err != nil {
		res.Err = err
		return res
	}

	// Send request to API
	resp, err := perigee.Request("POST", createURL(c), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		OkCodes:     []int{202},
	})
	res.Header = resp.HttpResponse.Header
	res.Err = err
	return res
}

// Get retrieves a specific service based on its URL or its unique ID. For
// example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Get(c *gophercloud.ServiceClient, idOrURL string) GetResult {
	var url string
	if strings.Contains(idOrURL, "/") {
		url = idOrURL
	} else {
		url = getURL(c, idOrURL)
	}

	var res GetResult
	_, res.Err = perigee.Request("GET", url, perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		Results:     &res.Body,
		OkCodes:     []int{200},
	})
	return res
}

// UpdateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Update operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type UpdateOptsBuilder interface {
	ToCDNServiceUpdateMap() ([]map[string]interface{}, error)
}

// Op represents an update operation.
type Op string

var (
	// Add is a constant used for performing a "add" operation when updating.
	Add Op = "add"
	// Remove is a constant used for performing a "remove" operation when updating.
	Remove Op = "remove"
	// Replace is a constant used for performing a "replace" operation when updating.
	Replace Op = "replace"
)

// Path is a JSON pointer location that indicates which service parameter is being added, replaced,
// or removed.
type Path struct {
	baseElement string
}

func (p Path) renderDash() string {
	return fmt.Sprintf("/%s/-", p.baseElement)
}

func (p Path) renderIndex(index int64) string {
	return fmt.Sprintf("/%s/%d", p.baseElement, index)
}

var (
	// PathDomains indicates that an update operation is to be performed on a Domain.
	PathDomains = Path{baseElement: "domains"}

	// PathOrigins indicates that an update operation is to be performed on an Origin.
	PathOrigins = Path{baseElement: "origins"}

	// PathCaching indicates that an update operation is to be performed on a CacheRule.
	PathCaching = Path{baseElement: "caching"}
)

// UpdateOpts represents the attributes used when updating an existing CDN service.
type UpdateOpts []UpdateOpt

// UpdateOpt represents a single update to an existing service. Multiple updates
// to a service can be submitted at the same time. See UpdateOpts.
type UpdateOpt struct {
	// Specifies the update operation to perform.
	Op Op `json:"op"`
	// Specifies the JSON Pointer location within the service's JSON representation
	// of the service parameter being added, replaced or removed.
	Path string `json:"path"`
	// Specifies the actual value to be added or replaced. It is not required for
	// the remove operation.
	Value map[string]interface{} `json:"value,omitempty"`
}

type value interface {
	toPatchValue() map[string]interface{}
	appropriatePath() Path
}

// Patch represents a single update to an existing Service. Multiple updates to a service can be
// submitted at the same time.
type Patch interface {
	ToCDNServiceUpdateMap() map[string]interface{}
}

// Addition is a Patch that requests the addition of one or more values (Domains, Origins, or
// CacheRules) to a Service. Pass it to the Update function as part of the Patch slice.
type Addition struct {
	Value value
}

// ToCDNServiceUpdateMap converts an Addition into a request body fragment suitable for the
// Update call.
func (a Addition) ToCDNServiceUpdateMap() map[string]interface{} {
	return map[string]interface{}{
		"op":    "add",
		"path":  a.Value.appropriatePath().renderDash(),
		"value": a.Value.toPatchValue(),
	}
}

// Replacement is a Patch that alters a specific service parameter (Domain, Origin, or CacheRule)
// in-place by index. Pass it to the Update function as part of the Patch slice.
type Replacement struct {
	Value value
	Index int64
}

// ToCDNServiceUpdateMap converts a Replacement into a request body fragment suitable for the
// Update call.
func (r Replacement) ToCDNServiceUpdateMap() map[string]interface{} {
	return map[string]interface{}{
		"op":    "replace",
		"path":  r.Value.appropriatePath().renderIndex(r.Index),
		"value": r.Value.toPatchValue(),
	}
}

// Removal is a Patch that requests the removal of a service parameter (Domain, Origin, or
// CacheRule) by index. Pass it to the Update function as part of the Patch slice.
type Removal struct {
	Path  Path
	Index int64
}

// ToCDNServiceUpdateMap converts a Removal into a request body fragment suitable for the
// Update call.
func (r Removal) ToCDNServiceUpdateMap() map[string]interface{} {
	return map[string]interface{}{
		"op":   "remove",
		"path": r.Path.renderIndex(r.Index),
	}
}

// ToCDNServiceUpdateMap casts an UpdateOpts struct to a map.
func (opts UpdateOpts) ToCDNServiceUpdateMap() ([]map[string]interface{}, error) {
	s := make([]map[string]interface{}, len(opts))

	for i, opt := range opts {
		if opt.Op != Add && opt.Op != Remove && opt.Op != Replace {
			return nil, fmt.Errorf("Invalid Op: %v", opt.Op)
		}
		if opt.Op == "" {
			return nil, no("Op")
		}
		if opt.Path == "" {
			return nil, no("Path")
		}
		if opt.Op != Remove && opt.Value == nil {
			return nil, no("Value")
		}
		s[i] = map[string]interface{}{
			"op":    opt.Op,
			"path":  opt.Path,
			"value": opt.Value,
		}
	}

	return s, nil
}

// Update accepts a slice of Patch operations (Addition, Replacement or Removal) and updates an
// existing CDN service using the values provided. idOrURL can be either the service's URL or its
// ID. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Update(c *gophercloud.ServiceClient, idOrURL string, patches []Patch) UpdateResult {
	var url string
	if strings.Contains(idOrURL, "/") {
		url = idOrURL
	} else {
		url = updateURL(c, idOrURL)
	}

	reqBody := make([]map[string]interface{}, len(patches))
	for i, patch := range patches {
		reqBody[i] = patch.ToCDNServiceUpdateMap()
	}

	resp, err := perigee.Request("PATCH", url, perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		OkCodes:     []int{202},
	})
	var result UpdateResult
	result.Header = resp.HttpResponse.Header
	result.Err = err
	return result
}

// Delete accepts a service's ID or its URL and deletes the CDN service
// associated with it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Delete(c *gophercloud.ServiceClient, idOrURL string) DeleteResult {
	var url string
	if strings.Contains(idOrURL, "/") {
		url = idOrURL
	} else {
		url = deleteURL(c, idOrURL)
	}

	var res DeleteResult
	_, res.Err = perigee.Request("DELETE", url, perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return res
}
<|endoftext|>


<commit_before>package avs

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"net/textproto"
	"strings"

	"github.com/rogertalk/go-avs/multipart2"
)

var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")

// UUID holds a 16 byte unique identifier.
type UUID []byte

// String returns a string representation of the UUID.
func (uuid UUID) String() string {
	if len(uuid) != 16 {
		return ""
	}
	b := []byte(uuid)
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}

// NewUUID returns a randomly generated UUID.
func NewUUID() (UUID, error) {
	b := make([]byte, 16)
	n, err := io.ReadFull(rand.Reader, b)
	if n != len(b) || err != nil {
		return nil, err
	}
	b[8] = b[8]&^0xc0 | 0x80
	b[6] = b[6]&^0xf0 | 0x40
	return UUID(b), nil
}

func GetUUIDString() string {
	uuid, err := NewUUID()
	if err != nil {
		panic("GetUUIDString: unable to create UUID")
	}
	return uuid.String()
}

func newMultipartReaderFromResponse(resp *http.Response) (*multipart2.Reader, error) {
	// Work around bug in Amazon's downchannel server.
	contentType := strings.Replace(resp.Header.Get("Content-Type"), "type=application/json", `type="application/json"`, 1)
	mediatype, params, err := mime.ParseMediaType(contentType)
	if err != nil {
		return nil, err
	}
	if !strings.HasPrefix(mediatype, "multipart/") {
		return nil, fmt.Errorf("unexpected content type %s", mediatype)
	}
	return multipart2.NewReader(resp.Body, params["boundary"]), nil
}

func escapeQuotes(s string) string {
	return quoteEscaper.Replace(s)
}

// Encodes a JSON value and writes it to a field with the provided multipart writer.
func writeJSON(writer *multipart.Writer, fieldname string, value interface{}) error {
	h := make(textproto.MIMEHeader)
	h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname)))
	h.Set("Content-Type", "application/json; charset=UTF-8")
	p, err := writer.CreatePart(h)
	if err != nil {
		return err
	}
	data, err := json.Marshal(value)
	if err != nil {
		return err
	}
	_, err = p.Write(data)
	return err
}
<commit_msg>Rename GetUUIDString to RandomUUIDString<commit_after>package avs

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"net/textproto"
	"strings"

	"github.com/rogertalk/go-avs/multipart2"
)

var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")

// UUID holds a 16 byte unique identifier.
type UUID []byte

// String returns a string representation of the UUID.
func (uuid UUID) String() string {
	if len(uuid) != 16 {
		return ""
	}
	b := []byte(uuid)
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}

// NewUUID returns a randomly generated UUID.
func NewUUID() (UUID, error) {
	b := make([]byte, 16)
	n, err := io.ReadFull(rand.Reader, b)
	if n != len(b) || err != nil {
		return nil, err
	}
	b[8] = b[8]&^0xc0 | 0x80
	b[6] = b[6]&^0xf0 | 0x40
	return UUID(b), nil
}

func RandomUUIDString() string {
	uuid, err := NewUUID()
	if err != nil {
		panic("RandomUUIDString: unable to create UUID")
	}
	return uuid.String()
}

func newMultipartReaderFromResponse(resp *http.Response) (*multipart2.Reader, error) {
	// Work around bug in Amazon's downchannel server.
	contentType := strings.Replace(resp.Header.Get("Content-Type"), "type=application/json", `type="application/json"`, 1)
	mediatype, params, err := mime.ParseMediaType(contentType)
	if err != nil {
		return nil, err
	}
	if !strings.HasPrefix(mediatype, "multipart/") {
		return nil, fmt.Errorf("unexpected content type %s", mediatype)
	}
	return multipart2.NewReader(resp.Body, params["boundary"]), nil
}

func escapeQuotes(s string) string {
	return quoteEscaper.Replace(s)
}

// Encodes a JSON value and writes it to a field with the provided multipart writer.
func writeJSON(writer *multipart.Writer, fieldname string, value interface{}) error {
	h := make(textproto.MIMEHeader)
	h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"`, escapeQuotes(fieldname)))
	h.Set("Content-Type", "application/json; charset=UTF-8")
	p, err := writer.CreatePart(h)
	if err != nil {
		return err
	}
	data, err := json.Marshal(value)
	if err != nil {
		return err
	}
	_, err = p.Write(data)
	return err
}
<|endoftext|>


<commit_before>// Copyright 2016 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package factom

import (
	"encoding/json"
	"fmt"
)

// Diagnostics represents a set of diagnostic/debugging information about
// factomd and the Factom Network.
type Diagnostics struct {
	Name                  string `json:"name"`
	ID                    string `json:"id,omitempty"`
	PublicKey             string `json:"publickey,omitempty"`
	Role                  string `json:"role"`
	LeaderHeight          int    `json:"leaderheight"`
	CurrentMinute         int    `json:"currentminute"`
	CurrentMinuteDuration int64  `json:"currentminuteduration"`
	PrevMinuteDuration    int64  `json:"previousminuteduration"`
	BalanceHash           string `json:"balancehash"`
	TempBalanceHash       string `json:"tempbalancehash"`
	LastBlockFromDBState  bool   `json:"lastblockfromdbstate"`

	SyncInfo struct {
		Status   string   `json:"status"`
		Received int      `json:"received,omitempty"`
		Expected int      `json:"expected,omitempty"`
		Missing  []string `json:"missing,omitempty"`
	} `json:"syncing"`

	AuthSet struct {
		Leaders []struct {
			ID                string `json:"id"`
			VM                int    `json:"vm"`
			ProcessListHeight int    `json:"listheight"`
			ListLength        int    `json:"listlength"`
			NextNil           int    `json:"nextnil"`
		} `json:"leaders"`

		Audits []struct {
			ID     string `json:"id"`
			Online bool   `json:"online"`
		} `json:"audits"`
	} `json:"authset"`

	ElectionInfo struct {
		InProgress bool   `json:"inprogress"`
		VMIndex    int    `json:"vmindex,omitempty"`
		FedIndex   int    `json:"fedindex,omitempty"`
		FedID      string `json:"fedid,omitempty"`
		Round      int    `json:"round,omitempty"`
	} `json:"elections"`
}

func (d *Diagnostics) String() string {
	var s string

	s += fmt.Sprintln("Name:", d.Name)
	s += fmt.Sprintln("ID:", d.ID)
	s += fmt.Sprintln("PublicKey:", d.PublicKey)
	s += fmt.Sprintln("Role:", d.Role)
	s += fmt.Sprintln("LeaderHeight:", d.LeaderHeight)
	s += fmt.Sprintln("CurrentMinute:", d.CurrentMinute)
	s += fmt.Sprintln("CurrentMinuteDuration:", d.CurrentMinuteDuration)
	s += fmt.Sprintln("PrevMinuteDuration:", d.PrevMinuteDuration)
	s += fmt.Sprintln("BalanceHash:", d.BalanceHash)
	s += fmt.Sprintln("TempBalanceHash:", d.TempBalanceHash)
	s += fmt.Sprintln("LastBlockFromDBState:", d.LastBlockFromDBState)
	// SyncInfo
	s += fmt.Sprintln("Status:", d.SyncInfo.Status)
	s += fmt.Sprintln("Received:", d.SyncInfo.Received)
	s += fmt.Sprintln("Expected:", d.SyncInfo.Expected)
	for _, m := range d.SyncInfo.Missing {
		s += fmt.Sprintln("Missing:", m)
	}
	// ElectionInfo
	s += fmt.Sprintln("InProgress:", d.ElectionInfo.InProgress)
	s += fmt.Sprintln("VMIndex:", d.ElectionInfo.VMIndex)
	s += fmt.Sprintln("FedIndex:", d.ElectionInfo.FedIndex)
	s += fmt.Sprintln("FedID:", d.ElectionInfo.FedID)
	s += fmt.Sprintln("Round:", d.ElectionInfo.Round)
	// AuthSet
	s += fmt.Sprintln("Leaders {")
	for _, v := range d.AuthSet.Leaders {
		s += fmt.Sprintln(" ID:", v.ID)
		s += fmt.Sprintln(" VM:", v.VM)
		s += fmt.Sprintln(" ProcessListHeight:", v.ProcessListHeight)
		s += fmt.Sprintln(" ListLength:", v.ListLength)
		s += fmt.Sprintln(" NextNil:", v.NextNil)
	}
	s += fmt.Sprintln("}") // Leaders
	s += fmt.Sprintln("Audits {")
	for _, v := range d.AuthSet.Audits {
		s += fmt.Sprintln(" ID:", v.ID)
		s += fmt.Sprintln(" Online:", v.Online)
	}
	s += fmt.Sprintln("}") // Audits

	return s
}

// GetDiagnostics requests diagnostic information from factomd.
func GetDiagnostics() (*Diagnostics, error) {
	req := NewJSON2Request("diagnostics", APICounter(), nil)
	resp, err := factomdRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	d := new(Diagnostics)
	if err := json.Unmarshal(resp.JSONResult(), d); err != nil {
		return nil, err
	}

	return d, nil
}
<commit_msg>comments for diagnostics<commit_after>// Copyright 2016 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package factom

import (
	"encoding/json"
	"fmt"
)

// Diagnostics represents a set of diagnostic/debugging information about
// factomd and the Factom Network.
type Diagnostics struct {
	Name                  string `json:"name"`
	ID                    string `json:"id,omitempty"`
	PublicKey             string `json:"publickey,omitempty"`
	Role                  string `json:"role"`
	LeaderHeight          int    `json:"leaderheight"`
	CurrentMinute         int    `json:"currentminute"`
	CurrentMinuteDuration int64  `json:"currentminuteduration"`
	PrevMinuteDuration    int64  `json:"previousminuteduration"`
	BalanceHash           string `json:"balancehash"`
	TempBalanceHash       string `json:"tempbalancehash"`
	LastBlockFromDBState  bool   `json:"lastblockfromdbstate"`

	SyncInfo struct {
		Status   string   `json:"status"`
		Received int      `json:"received,omitempty"`
		Expected int      `json:"expected,omitempty"`
		Missing  []string `json:"missing,omitempty"`
	} `json:"syncing"`

	AuthSet struct {
		Leaders []struct {
			ID                string `json:"id"`
			VM                int    `json:"vm"`
			ProcessListHeight int    `json:"listheight"`
			ListLength        int    `json:"listlength"`
			NextNil           int    `json:"nextnil"`
		} `json:"leaders"`

		Audits []struct {
			ID     string `json:"id"`
			Online bool   `json:"online"`
		} `json:"audits"`
	} `json:"authset"`

	ElectionInfo struct {
		InProgress bool   `json:"inprogress"`
		VMIndex    int    `json:"vmindex,omitempty"`
		FedIndex   int    `json:"fedindex,omitempty"`
		FedID      string `json:"fedid,omitempty"`
		Round      int    `json:"round,omitempty"`
	} `json:"elections"`
}

func (d *Diagnostics) String() string {
	var s string

	// ServerInfo
	s += fmt.Sprintln("Name:", d.Name)
	s += fmt.Sprintln("ID:", d.ID)
	s += fmt.Sprintln("PublicKey:", d.PublicKey)
	s += fmt.Sprintln("Role:", d.Role)
	// NetworkInfo
	s += fmt.Sprintln("LeaderHeight:", d.LeaderHeight)
	s += fmt.Sprintln("CurrentMinute:", d.CurrentMinute)
	s += fmt.Sprintln("CurrentMinuteDuration:", d.CurrentMinuteDuration)
	s += fmt.Sprintln("PrevMinuteDuration:", d.PrevMinuteDuration)
	s += fmt.Sprintln("BalanceHash:", d.BalanceHash)
	s += fmt.Sprintln("TempBalanceHash:", d.TempBalanceHash)
	s += fmt.Sprintln("LastBlockFromDBState:", d.LastBlockFromDBState)
	// SyncInfo
	s += fmt.Sprintln("Status:", d.SyncInfo.Status)
	s += fmt.Sprintln("Received:", d.SyncInfo.Received)
	s += fmt.Sprintln("Expected:", d.SyncInfo.Expected)
	for _, m := range d.SyncInfo.Missing {
		s += fmt.Sprintln("Missing:", m)
	}
	// ElectionInfo
	s += fmt.Sprintln("InProgress:", d.ElectionInfo.InProgress)
	s += fmt.Sprintln("VMIndex:", d.ElectionInfo.VMIndex)
	s += fmt.Sprintln("FedIndex:", d.ElectionInfo.FedIndex)
	s += fmt.Sprintln("FedID:", d.ElectionInfo.FedID)
	s += fmt.Sprintln("Round:", d.ElectionInfo.Round)
	// AuthSet
	s += fmt.Sprintln("Leaders {")
	for _, v := range d.AuthSet.Leaders {
		s += fmt.Sprintln(" ID:", v.ID)
		s += fmt.Sprintln(" VM:", v.VM)
		s += fmt.Sprintln(" ProcessListHeight:", v.ProcessListHeight)
		s += fmt.Sprintln(" ListLength:", v.ListLength)
		s += fmt.Sprintln(" NextNil:", v.NextNil)
	}
	s += fmt.Sprintln("}") // Leaders
	s += fmt.Sprintln("Audits {")
	for _, v := range d.AuthSet.Audits {
		s += fmt.Sprintln(" ID:", v.ID)
		s += fmt.Sprintln(" Online:", v.Online)
	}
	s += fmt.Sprintln("}") // Audits

	return s
}

// GetDiagnostics requests diagnostic information from factomd.
func GetDiagnostics() (*Diagnostics, error) {
	req := NewJSON2Request("diagnostics", APICounter(), nil)
	resp, err := factomdRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.Error != nil {
		return nil, resp.Error
	}

	d := new(Diagnostics)
	if err := json.Unmarshal(resp.JSONResult(), d); err != nil {
		return nil, err
	}

	return d, nil
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
 *
 * 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 gopmod

import (
	"bytes"
	"crypto/sha1"
	"log"
	"os"
	"path/filepath"
	"sort"

	"github.com/goplus/gop/cl"
	"github.com/goplus/gop/parser"
	"github.com/goplus/gop/token"
	"github.com/goplus/gox"
)

// -----------------------------------------------------------------------------

type gopFiles struct {
	files []string
}

func (p *Context) openFromGopFiles(files []string) (proj *Project, err error) {
	proj = &Project{
		Source: &gopFiles{files: files},
	}
	return
}

func (p *gopFiles) Fingerp() [20]byte { // source code fingerprint
	files := make([]string, len(p.files))
	for i, file := range p.files {
		files[i], _ = filepath.Abs(file)
	}
	sort.Strings(files)
	var buf bytes.Buffer
	for _, file := range files {
		fi, err := os.Stat(file)
		if err != nil {
			log.Panicln(err)
		}
		buf.WriteString(file)
		buf.WriteByte('\t')
		buf.WriteString(fi.ModTime().UTC().String())
		buf.WriteByte('\n')
	}
	return sha1.Sum(buf.Bytes())
}

func (p *gopFiles) IsDirty(outFile string, temp bool) bool {
	return true
}

const (
	parserMode = parser.ParseComments
)

func (p *gopFiles) GenGo(outFile, modFile string) error {
	fset := token.NewFileSet()
	pkgs, err := parser.ParseFiles(fset, p.files, parserMode)
	if err != nil {
		return err
	}
	if len(pkgs) != 1 {
		log.Panicln("TODO: mutli packages -", len(pkgs))
	}
	mainPkg, ok := pkgs["main"]
	if !ok {
		panic("TODO: main package not found")
	}

	srcDir, _ := filepath.Split(outFile)
	modDir, _ := filepath.Split(modFile)
	conf := &cl.Config{
		Dir: modDir, TargetDir: srcDir, Fset: fset, CacheLoadPkgs: true, PersistLoadPkgs: true}
	out, err := cl.NewPackage("", mainPkg, conf)
	if err != nil {
		return err
	}
	err = gox.WriteFile(outFile, out, false)
	if err != nil {
		return err
	}
	conf.PkgsLoader.Save()
	return nil
}

// -----------------------------------------------------------------------------
<commit_msg>gopmod.IsDirty<commit_after>/*
 * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
 *
 * 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 gopmod

import (
	"bytes"
	"crypto/sha1"
	"log"
	"os"
	"path/filepath"
	"sort"

	"github.com/goplus/gop/cl"
	"github.com/goplus/gop/parser"
	"github.com/goplus/gop/token"
	"github.com/goplus/gox"
)

// -----------------------------------------------------------------------------

type gopFiles struct {
	files []string
}

func (p *Context) openFromGopFiles(files []string) (proj *Project, err error) {
	proj = &Project{
		Source: &gopFiles{files: files},
	}
	return
}

func (p *gopFiles) Fingerp() [20]byte { // source code fingerprint
	files := make([]string, len(p.files))
	for i, file := range p.files {
		files[i], _ = filepath.Abs(file)
	}
	sort.Strings(files)
	var buf bytes.Buffer
	for _, file := range files {
		fi, err := os.Stat(file)
		if err != nil {
			log.Panicln(err)
		}
		buf.WriteString(file)
		buf.WriteByte('\t')
		buf.WriteString(fi.ModTime().UTC().String())
		buf.WriteByte('\n')
	}
	return sha1.Sum(buf.Bytes())
}

func (p *gopFiles) IsDirty(outFile string, temp bool) bool {
	if fi, err := os.Lstat(outFile); err == nil { // TODO: more strictly
		return fi.IsDir()
	}
	return true
}

const (
	parserMode = parser.ParseComments
)

func (p *gopFiles) GenGo(outFile, modFile string) error {
	fset := token.NewFileSet()
	pkgs, err := parser.ParseFiles(fset, p.files, parserMode)
	if err != nil {
		return err
	}
	if len(pkgs) != 1 {
		log.Panicln("TODO: mutli packages -", len(pkgs))
	}
	mainPkg, ok := pkgs["main"]
	if !ok {
		panic("TODO: main package not found")
	}

	srcDir, _ := filepath.Split(outFile)
	modDir, _ := filepath.Split(modFile)
	conf := &cl.Config{
		Dir: modDir, TargetDir: srcDir, Fset: fset, CacheLoadPkgs: true, PersistLoadPkgs: true}
	out, err := cl.NewPackage("", mainPkg, conf)
	if err != nil {
		return err
	}
	err = gox.WriteFile(outFile, out, false)
	if err != nil {
		return err
	}
	conf.PkgsLoader.Save()
	return nil
}

// -----------------------------------------------------------------------------
<|endoftext|>


<commit_before>package docconv

import (
	"encoding/csv"
	"fmt"
	"io"
	"io/ioutil"
	"strconv"

	"github.com/tealeg/xlsx"
	"github.com/xuri/excelize"
)

// convertWithTealegXlsx used tealeg/xlsx to convert XLSX to CSV
func convertWithTealegXlsx(f *LocalFile) (body string, err error) {
	fileStat, err := f.Stat()
	if err != nil {
		err = fmt.Errorf("error convert with 'tealeg/xlsx' on getting file stats: %v", err)
		return
	}
	xlsFile, err := xlsx.OpenReaderAt(f, fileStat.Size())
	if err != nil {
		err = fmt.Errorf("error convert with 'tealeg/xlsx' on xlsx parsing: %v", err)
		return
	}
	for _, sheet := range xlsFile.Sheets {
		for rowIndex, row := range sheet.Rows {
			for cellIndex, cell := range row.Cells {
				text := cell.String()
				body += text
				if cellIndex < len(row.Cells)-1 {
					body += ","
				}
			}
			if rowIndex < len(sheet.Rows)-1 {
				body += "\n"
			}
		}
	}
	return
}

// convertWithExcelize used excelize to convert XLSX to CSV
func convertWithExcelize(f *LocalFile) (body string, err error) {
	xlsFile, err := excelize.OpenFile(f.Name())
	if err != nil {
		err = fmt.Errorf("error convert with 'excelize' on xlsx parsing: %v", err)
		return
	}
	for sheetIndex, _ := range xlsFile.GetSheetMap() {
		rows := xlsFile.GetRows("sheet" + strconv.Itoa(sheetIndex))
		if len(rows) == 0 {
			err = fmt.Errorf("error on convert with excelize")
			return
		}
		for rowIndex, row := range rows {
			for cellIndex, colCell := range row {
				text := colCell
				body += text
				if cellIndex < len(row)-1 {
					body += ","
				}
			}
			if rowIndex < len(rows)-1 {
				body += "\n"
			}
		}
	}
	return
}

// ConvertXLSX Excel Spreadsheet
func ConvertXLSX(r io.Reader) (string, map[string]string, error) {
	f, err := NewLocalFile(r, "/tmp", "sajari-convert-")
	if err != nil {
		return "", nil, fmt.Errorf("error creating local file: %v", err)
	}
	defer f.Done()

	fileStat, err := f.Stat()
	if err != nil {
		return "", nil, fmt.Errorf("error on getting file stats: %v", err)
	}

	// Meta data
	meta := make(map[string]string)
	meta["ModifiedDate"] = fmt.Sprintf("%d", fileStat.ModTime().Unix())

	body, err := convertWithExcelize(f)
	if err != nil {
		// TODO: Generates error in some types of Excel, we need to contribute to xuri/excelize
		body, err := convertWithTealegXlsx(f)
		if err != nil {
			return body, meta, err
		}
	}

	return body, meta, nil
}

// CSVtoXLSX convert CSV data to XLSX
func CSVtoXLSX(r io.Reader) (xlsByte string, err error) {
	reader := csv.NewReader(r)

	var file *xlsx.File
	var sheet *xlsx.Sheet

	file = xlsx.NewFile()
	sheet, err = file.AddSheet("Sheet1")
	if err != nil {
		return "", fmt.Errorf("error try add sheet: %s", err.Error())
	}

	// write others rows
	for {
		rec, err := reader.Read()
		if err != nil {
			if err == io.EOF {
				break
			}
		}
		rows := sheet.AddRow()

		for _, h := range rec {
			row := rows.AddCell()
			row.Value = h
		}
	}

	f, err := NewLocalFile(r, "/tmp", "sajari-convert-")
	if err != nil {
		return "", fmt.Errorf("error creating local file: %v", err)
	}
	defer f.Done()

	err = file.Write(f)
	if err != nil {
		return "", fmt.Errorf("error try write xlsx: %s", err.Error())
	}

	xlsxB, err := ioutil.ReadFile(f.Name())
	if err != nil {
		return
	}
	xlsByte = string(xlsxB)
	return
}
<commit_msg>xlsx: fixed typo body xlsx declare variable body out of scope and reuse<commit_after>package docconv

import (
	"encoding/csv"
	"fmt"
	"io"
	"io/ioutil"
	"strconv"

	"github.com/tealeg/xlsx"
	"github.com/xuri/excelize"
)

// convertWithTealegXlsx used tealeg/xlsx to convert XLSX to CSV
func convertWithTealegXlsx(f *LocalFile) (body string, err error) {
	fileStat, err := f.Stat()
	if err != nil {
		err = fmt.Errorf("error convert with 'tealeg/xlsx' on getting file stats: %v", err)
		return
	}
	xlsFile, err := xlsx.OpenReaderAt(f, fileStat.Size())
	if err != nil {
		err = fmt.Errorf("error convert with 'tealeg/xlsx' on xlsx parsing: %v", err)
		return
	}
	for _, sheet := range xlsFile.Sheets {
		for rowIndex, row := range sheet.Rows {
			for cellIndex, cell := range row.Cells {
				text := cell.String()
				body += text
				if cellIndex < len(row.Cells)-1 {
					body += ","
				}
			}
			if rowIndex < len(sheet.Rows)-1 {
				body += "\n"
			}
		}
	}
	return
}

// convertWithExcelize used excelize to convert XLSX to CSV
func convertWithExcelize(f *LocalFile) (body string, err error) {
	xlsFile, err := excelize.OpenFile(f.Name())
	if err != nil {
		err = fmt.Errorf("error convert with 'excelize' on xlsx parsing: %v", err)
		return
	}
	for sheetIndex, _ := range xlsFile.GetSheetMap() {
		rows := xlsFile.GetRows("sheet" + strconv.Itoa(sheetIndex))
		if len(rows) == 0 {
			err = fmt.Errorf("error on convert with excelize")
			return
		}
		for rowIndex, row := range rows {
			for cellIndex, colCell := range row {
				text := colCell
				body += text
				if cellIndex < len(row)-1 {
					body += ","
				}
			}
			if rowIndex < len(rows)-1 {
				body += "\n"
			}
		}
	}
	return
}

// ConvertXLSX Excel Spreadsheet
func ConvertXLSX(r io.Reader) (string, map[string]string, error) {
	f, err := NewLocalFile(r, "/tmp", "sajari-convert-")
	if err != nil {
		return "", nil, fmt.Errorf("error creating local file: %v", err)
	}
	defer f.Done()

	fileStat, err := f.Stat()
	if err != nil {
		return "", nil, fmt.Errorf("error on getting file stats: %v", err)
	}

	// Meta data
	meta := make(map[string]string)
	meta["ModifiedDate"] = fmt.Sprintf("%d", fileStat.ModTime().Unix())

	var body string
	body, err = convertWithExcelize(f)
	if err != nil {
		// TODO: Generates error in some types of Excel, we need to contribute to xuri/excelize
		body, err = convertWithTealegXlsx(f)
		if err != nil {
			return body, meta, err
		}
	}

	return body, meta, nil
}

// CSVtoXLSX convert CSV data to XLSX
func CSVtoXLSX(r io.Reader) (xlsByte string, err error) {
	reader := csv.NewReader(r)

	var file *xlsx.File
	var sheet *xlsx.Sheet

	file = xlsx.NewFile()
	sheet, err = file.AddSheet("Sheet1")
	if err != nil {
		return "", fmt.Errorf("error try add sheet: %s", err.Error())
	}

	// write others rows
	for {
		rec, err := reader.Read()
		if err != nil {
			if err == io.EOF {
				break
			}
		}
		rows := sheet.AddRow()

		for _, h := range rec {
			row := rows.AddCell()
			row.Value = h
		}
	}

	f, err := NewLocalFile(r, "/tmp", "sajari-convert-")
	if err != nil {
		return "", fmt.Errorf("error creating local file: %v", err)
	}
	defer f.Done()

	err = file.Write(f)
	if err != nil {
		return "", fmt.Errorf("error try write xlsx: %s", err.Error())
	}

	xlsxB, err := ioutil.ReadFile(f.Name())
	if err != nil {
		return
	}
	xlsByte = string(xlsxB)
	return
}
<|endoftext|>


<commit_before>package sms

import (
	"encoding/xml"
	"fmt"
	"log"
	"os"

	xco "github.com/mndrix/go-xco"
)

// xmppProcess is the piece which interacts with the XMPP network and
// converts those communications into values which the rest of
// sms-over-xmpp can understand.
type xmppProcess struct {
	// where to connect to the XMPP server
	host string
	port int

	// credentials for XMPP auth
	name   string
	secret string

	// channels for communicating with the Gateway process
	gatewayRx chan<- *xco.Message
	gatewayTx <-chan *xco.Message
}

// runXmppComponent creates a goroutine for sending and receiving XMPP
// stanzas.  it returns a channel for monitoring the goroutine's
// health.  if that channel closes, the XMPP process has died.
func (x *xmppProcess) run() <-chan struct{} {
	opts := xco.Options{
		Name:         x.name,
		SharedSecret: x.secret,
		Address:      fmt.Sprintf("%s:%d", x.host, x.port),
		Logger:       log.New(os.Stderr, "", log.LstdFlags),
	}

	healthCh := make(chan struct{})
	go x.loop(opts, healthCh)
	return healthCh
}

func (x *xmppProcess) loop(opts xco.Options, healthCh chan<- struct{}) {
	defer func() { close(healthCh) }()

	c, err := xco.NewComponent(opts)
	if err != nil {
		log.Printf("can't create internal XMPP component: %s", err)
		return
	}

	tx, rx, errx := c.RunAsync()
	for {
		select {
		case st := <-rx:
			switch stanza := st.(type) {
			case *xco.Message:
				log.Printf("Message: %+v", stanza)
				if stanza.Body == "" {
					log.Printf("  ignoring message with empty body")
					break
				}
				go func() { x.gatewayRx <- stanza }()
			case *xco.Iq:
				if stanza.IsDiscoInfo() {
					log.Printf("Disco: %+v", stanza)
					ids, features := x.describeService()
					stanza, err = stanza.DiscoInfoReply(ids, features)
					if err == nil {
						go func() { tx <- stanza }()
					}
				} else {
					log.Printf("Iq: %+v", stanza)
				}
			case *xco.Presence:
				log.Printf("Presence: %+v", stanza)
				switch stanza.Type {
				case "probe":
					stanza, err = x.presenceAvailable(stanza)
					if err == nil {
						go func() { tx <- stanza }()
					}
				case "subscribe", "unsubscribe":
					stanza, err = x.handleSubscription(stanza)
					if err == nil {
						go func() { tx <- stanza }()
					}
				}
			case *xml.StartElement:
				log.Printf("Unknown: %+v", stanza)
			default:
				panic(fmt.Sprintf("Unexpected stanza type: %#v", st))
			}
		case stanza := <-x.gatewayTx:
			go func() { tx <- stanza }()
		case err = <-errx:
		}

		if err != nil {
			break
		}
	}

	log.Printf("lost XMPP connection: %s", err)
}

func (x *xmppProcess) describeService() ([]xco.DiscoIdentity, []xco.DiscoFeature) {
	ids := []xco.DiscoIdentity{
		{
			Category: "gateway",
			Type:     "sms",
			Name:     "SMS over XMPP",
		},
	}
	features := []xco.DiscoFeature{
		{
			Var: "urn:xmpp:receipts",
		},
	}
	return ids, features
}

func (x *xmppProcess) presenceAvailable(p *xco.Presence) (*xco.Presence, error) {
	stanza := &xco.Presence{
		Header: xco.Header{
			From: p.Header.To,
			To:   p.Header.From,
			ID:   NewId(),
		},
	}
	return stanza, nil
}

func (x *xmppProcess) handleSubscription(p *xco.Presence) (*xco.Presence, error) {
	// RFC says to use full JIDs
	p.Header.To.ResourcePart = ""
	p.Header.From.ResourcePart = ""

	stanza := &xco.Presence{
		Header: xco.Header{
			From: p.Header.To,
			To:   p.Header.From,
			ID:   NewId(),
		},
	}
	switch p.Type {
	case "subscribe":
		stanza.Type = "subscribed"
	case "unsubscribe":
		stanza.Type = "unavailable"
	}
	return stanza, nil
}
<commit_msg>Reply to "subscribe" with an available presence too<commit_after>package sms

import (
	"encoding/xml"
	"fmt"
	"log"
	"os"

	xco "github.com/mndrix/go-xco"
)

// xmppProcess is the piece which interacts with the XMPP network and
// converts those communications into values which the rest of
// sms-over-xmpp can understand.
type xmppProcess struct {
	// where to connect to the XMPP server
	host string
	port int

	// credentials for XMPP auth
	name   string
	secret string

	// channels for communicating with the Gateway process
	gatewayRx chan<- *xco.Message
	gatewayTx <-chan *xco.Message
}

// runXmppComponent creates a goroutine for sending and receiving XMPP
// stanzas.  it returns a channel for monitoring the goroutine's
// health.  if that channel closes, the XMPP process has died.
func (x *xmppProcess) run() <-chan struct{} {
	opts := xco.Options{
		Name:         x.name,
		SharedSecret: x.secret,
		Address:      fmt.Sprintf("%s:%d", x.host, x.port),
		Logger:       log.New(os.Stderr, "", log.LstdFlags),
	}

	healthCh := make(chan struct{})
	go x.loop(opts, healthCh)
	return healthCh
}

func (x *xmppProcess) loop(opts xco.Options, healthCh chan<- struct{}) {
	defer func() { close(healthCh) }()

	c, err := xco.NewComponent(opts)
	if err != nil {
		log.Printf("can't create internal XMPP component: %s", err)
		return
	}

	tx, rx, errx := c.RunAsync()
	for {
		select {
		case st := <-rx:
			switch stanza := st.(type) {
			case *xco.Message:
				log.Printf("Message: %+v", stanza)
				if stanza.Body == "" {
					log.Printf("  ignoring message with empty body")
					break
				}
				go func() { x.gatewayRx <- stanza }()
			case *xco.Iq:
				if stanza.IsDiscoInfo() {
					log.Printf("Disco: %+v", stanza)
					ids, features := x.describeService()
					stanza, err = stanza.DiscoInfoReply(ids, features)
					if err == nil {
						go func() { tx <- stanza }()
					}
				} else {
					log.Printf("Iq: %+v", stanza)
				}
			case *xco.Presence:
				log.Printf("Presence: %+v", stanza)
				switch stanza.Type {
				case "probe":
					stanza, err = x.presenceAvailable(stanza)
					if err == nil {
						go func() { tx <- stanza }()
					}
				case "subscribe", "unsubscribe":
					var stanzas []*xco.Presence
					stanzas, err = x.handleSubscription(stanza)
					if err == nil {
						go func() {
							for _, stanza := range stanzas {
								tx <- stanza
							}
						}()
					}
				}
			case *xml.StartElement:
				log.Printf("Unknown: %+v", stanza)
			default:
				panic(fmt.Sprintf("Unexpected stanza type: %#v", st))
			}
		case stanza := <-x.gatewayTx:
			go func() { tx <- stanza }()
		case err = <-errx:
		}

		if err != nil {
			break
		}
	}

	log.Printf("lost XMPP connection: %s", err)
}

func (x *xmppProcess) describeService() ([]xco.DiscoIdentity, []xco.DiscoFeature) {
	ids := []xco.DiscoIdentity{
		{
			Category: "gateway",
			Type:     "sms",
			Name:     "SMS over XMPP",
		},
	}
	features := []xco.DiscoFeature{
		{
			Var: "urn:xmpp:receipts",
		},
	}
	return ids, features
}

func (x *xmppProcess) presenceAvailable(p *xco.Presence) (*xco.Presence, error) {
	stanza := &xco.Presence{
		Header: xco.Header{
			From: p.Header.To,
			To:   p.Header.From,
			ID:   NewId(),
		},
	}
	return stanza, nil
}

func (x *xmppProcess) handleSubscription(p *xco.Presence) ([]*xco.Presence, error) {
	var err error

	// RFC says to use full JIDs
	p.Header.To.ResourcePart = ""
	p.Header.From.ResourcePart = ""

	stanzas := make([]*xco.Presence, 0, 2)
	stanza := &xco.Presence{
		Header: xco.Header{
			From: p.Header.To,
			To:   p.Header.From,
			ID:   NewId(),
		},
	}
	switch p.Type {
	case "subscribe":
		stanza.Type = "subscribed"
		stanzas = append(stanzas, stanza)

		// let user know that we're available
		stanza, err = x.presenceAvailable(p)
	case "unsubscribe":
		stanza.Type = "unavailable"
	}
	stanzas = append(stanzas, stanza)

	if err != nil {
		return nil, err
	}
	return stanzas, nil
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"

	"github.com/codegangsta/cli"
	"github.com/nlopes/slack"
	"github.com/vivace-io/evelib/zkill"
	"github.com/vivace-io/gonfig"
	"github.com/vivace-io/zk2s/util"
)

/* zk2s.go
 * Main entrypoint and controller for zk2s
 */

const VERSION = "0.3"

var CONTRIBUTORS = []cli.Author{
	cli.Author{
		Name: "Nathan \"Vivace Naaris\" Morley",
	},
	cli.Author{
		Name: "\"Zuke\"",
	},
}

var config *util.Configuration
var bot *slack.Client

func main() {
	app := cli.NewApp()
	app.Authors = CONTRIBUTORS
	app.Version = VERSION
	app.Commands = []cli.Command{
		cli.Command{
			Name:   "start",
			Usage:  "start zk2s application",
			Action: Run,
		},
		cli.Command{
			Name:   "configure",
			Usage:  "configure zk2s application to be run",
			Action: util.RunConfigure,
		},
	}
	app.Run(os.Args)
}

// Run zk2s
func Run(c *cli.Context) {
	log.Printf("%v version %v", c.App.Name, c.App.Version)
	var err error

	// 1 - Load Configuration file
	config = new(util.Configuration)
	config.FileName = util.ConfigFileName
	err = gonfig.Load(config)
	if err != nil {
		log.Fatalf("Unable to read %v with error %v", util.ConfigFileName, err)
		os.Exit(1)
	}
	// 2 - Setup a new Slack Bot
	bot = slack.New(config.BotToken)
	authResp, err := bot.AuthTest()
	if err != nil {
		log.Fatalf("Unable to authenticate with Slack - %v", err)
		os.Exit(1)
	}
	log.Printf("Connected to Slack Team %v as user %v", authResp.Team, authResp.User)

	// 3 - Watch for new kills and log errors
	errc := make(chan error, 5)
	killc := make(chan zkill.Kill, 10)
	zClient := zkill.NewRedisQ()
	zClient.UserAgent = config.UserAgent
	zClient.FetchKillmails(killc, errc)
	handleKills(killc)
	handleErrors(errc)
	select {}
}

// handleKills sends the kill to be filtered/processed before posting to slack.
func handleKills(killChan chan zkill.Kill) {
	go func() {
		for {
			select {
			case kill := <-killChan:
				PostKill(&kill)
			}
		}
	}()
}

// handleErrors logs errors returned in Zkillboard queries
func handleErrors(errChan chan error) {
	go func() {
		for {
			select {
			case err := <-errChan:
				log.Printf("ERROR - %v", err.Error())
			}
		}
	}()
}
<commit_msg>fixed app name/usage<commit_after>package main

import (
	"log"
	"os"

	"github.com/codegangsta/cli"
	"github.com/nlopes/slack"
	"github.com/vivace-io/evelib/zkill"
	"github.com/vivace-io/gonfig"
	"github.com/vivace-io/zk2s/util"
)

/* zk2s.go
 * Main entrypoint and controller for zk2s
 */

const VERSION = "0.3"

var CONTRIBUTORS = []cli.Author{
	cli.Author{
		Name: "Nathan \"Vivace Naaris\" Morley",
	},
	cli.Author{
		Name: "\"Zuke\"",
	},
}

var config *util.Configuration
var bot *slack.Client

func main() {
	app := cli.NewApp()
	app.Authors = CONTRIBUTORS
	app.Version = VERSION
	app.Name = "zk2s"
	app.Usage = "a Slack bot for posting kills from zKillboard to slack in near-real time"
	app.Commands = []cli.Command{
		cli.Command{
			Name:   "start",
			Usage:  "start zk2s application",
			Action: Run,
		},
		cli.Command{
			Name:   "configure",
			Usage:  "configure zk2s application to be run",
			Action: util.RunConfigure,
		},
	}
	app.Run(os.Args)
}

// Run zk2s
func Run(c *cli.Context) {
	log.Printf("%v version %v", c.App.Name, c.App.Version)
	var err error

	// 1 - Load Configuration file
	config = new(util.Configuration)
	config.FileName = util.ConfigFileName
	err = gonfig.Load(config)
	if err != nil {
		log.Fatalf("Unable to read %v with error %v", util.ConfigFileName, err)
		os.Exit(1)
	}
	// 2 - Setup a new Slack Bot
	bot = slack.New(config.BotToken)
	authResp, err := bot.AuthTest()
	if err != nil {
		log.Fatalf("Unable to authenticate with Slack - %v", err)
		os.Exit(1)
	}
	log.Printf("Connected to Slack Team %v as user %v", authResp.Team, authResp.User)

	// 3 - Watch for new kills and log errors
	errc := make(chan error, 5)
	killc := make(chan zkill.Kill, 10)
	zClient := zkill.NewRedisQ()
	zClient.UserAgent = config.UserAgent
	zClient.FetchKillmails(killc, errc)
	handleKills(killc)
	handleErrors(errc)
	select {}
}

// handleKills sends the kill to be filtered/processed before posting to slack.
func handleKills(killChan chan zkill.Kill) {
	go func() {
		for {
			select {
			case kill := <-killChan:
				PostKill(&kill)
			}
		}
	}()
}

// handleErrors logs errors returned in Zkillboard queries
func handleErrors(errChan chan error) {
	go func() {
		for {
			select {
			case err := <-errChan:
				log.Printf("ERROR - %v", err.Error())
			}
		}
	}()
}
<|endoftext|>