<commit_before>// +build linux

package linux

import (
	"bytes"
	"context"
	"io"
	"os"
	"path/filepath"

	"github.com/containerd/containerd/events"
	"github.com/containerd/containerd/linux/runcopts"
	client "github.com/containerd/containerd/linux/shim"
	"github.com/pkg/errors"
)

// loadBundle loads an existing bundle from disk
func loadBundle(id, path, workdir string) *bundle {
	return &bundle{
		id:      id,
		path:    path,
		workDir: workdir,
	}
}

// newBundle creates a new bundle on disk at the provided path for the given id
func newBundle(id, path, workDir string, spec []byte) (b *bundle, err error) {
	if err := os.MkdirAll(path, 0711); err != nil {
		return nil, err
	}
	path = filepath.Join(path, id)
	defer func() {
		if err != nil {
			os.RemoveAll(path)
		}
	}()
	workDir = filepath.Join(workDir, id)
	if err := os.MkdirAll(workDir, 0711); err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			os.RemoveAll(workDir)
		}
	}()

	if err := os.Mkdir(path, 0711); err != nil {
		return nil, err
	}
	if err := os.Mkdir(filepath.Join(path, "rootfs"), 0711); err != nil {
		return nil, err
	}
	f, err := os.Create(filepath.Join(path, configFilename))
	if err != nil {
		return nil, err
	}
	defer f.Close()
	_, err = io.Copy(f, bytes.NewReader(spec))
	return &bundle{
		id:      id,
		path:    path,
		workDir: workDir,
	}, err
}

type bundle struct {
	id      string
	path    string
	workDir string
}

type shimOpt func(*bundle, string, *runcopts.RuncOptions) (client.Config, client.ClientOpt)

// ShimRemote is a shimOpt for connecting and starting a remote shim
func ShimRemote(shim, daemonAddress, cgroup string, debug bool, exitHandler func()) shimOpt {
	return func(b *bundle, ns string, ropts *runcopts.RuncOptions) (client.Config, client.ClientOpt) {
		return b.shimConfig(ns, ropts),
			client.WithStart(shim, b.shimAddress(ns), daemonAddress, cgroup, debug, exitHandler)
	}
}

// ShimLocal is a shimOpt for using an in process shim implementation
func ShimLocal(exchange *events.Exchange) shimOpt {
	return func(b *bundle, ns string, ropts *runcopts.RuncOptions) (client.Config, client.ClientOpt) {
		return b.shimConfig(ns, ropts), client.WithLocal(exchange)
	}
}

// ShimConnect is a shimOpt for connecting to an existing remote shim
func ShimConnect() shimOpt {
	return func(b *bundle, ns string, ropts *runcopts.RuncOptions) (client.Config, client.ClientOpt) {
		return b.shimConfig(ns, ropts), client.WithConnect(b.shimAddress(ns))
	}
}

// NewShimClient connects to the shim managing the bundle and tasks creating it if needed
func (b *bundle) NewShimClient(ctx context.Context, namespace string, getClientOpts shimOpt, runcOpts *runcopts.RuncOptions) (*client.Client, error) {
	cfg, opt := getClientOpts(b, namespace, runcOpts)
	return client.New(ctx, cfg, opt)
}

// Delete deletes the bundle from disk
func (b *bundle) Delete() error {
	err := os.RemoveAll(b.path)
	if err == nil {
		return os.RemoveAll(b.workDir)
	}
	// error removing the bundle path; still attempt removing work dir
	err2 := os.RemoveAll(b.workDir)
	if err2 == nil {
		return err
	}
	return errors.Wrapf(err, "Failed to remove both bundle and workdir locations: %v", err2)
}

func (b *bundle) shimAddress(namespace string) string {
	return filepath.Join(string(filepath.Separator), "containerd-shim", namespace, b.id, "shim.sock")

}

func (b *bundle) shimConfig(namespace string, runcOptions *runcopts.RuncOptions) client.Config {
	var (
		criuPath      string
		runtimeRoot   string
		systemdCgroup bool
	)
	if runcOptions != nil {
		criuPath = runcOptions.CriuPath
		systemdCgroup = runcOptions.SystemdCgroup
		runtimeRoot = runcOptions.RuntimeRoot
	}
	return client.Config{
		Path:          b.path,
		WorkDir:       b.workDir,
		Namespace:     namespace,
		Criu:          criuPath,
		RuntimeRoot:   runtimeRoot,
		SystemdCgroup: systemdCgroup,
	}
}
<commit_msg>Remove a blank line<commit_after>// +build linux

package linux

import (
	"bytes"
	"context"
	"io"
	"os"
	"path/filepath"

	"github.com/containerd/containerd/events"
	"github.com/containerd/containerd/linux/runcopts"
	client "github.com/containerd/containerd/linux/shim"
	"github.com/pkg/errors"
)

// loadBundle loads an existing bundle from disk
func loadBundle(id, path, workdir string) *bundle {
	return &bundle{
		id:      id,
		path:    path,
		workDir: workdir,
	}
}

// newBundle creates a new bundle on disk at the provided path for the given id
func newBundle(id, path, workDir string, spec []byte) (b *bundle, err error) {
	if err := os.MkdirAll(path, 0711); err != nil {
		return nil, err
	}
	path = filepath.Join(path, id)
	defer func() {
		if err != nil {
			os.RemoveAll(path)
		}
	}()
	workDir = filepath.Join(workDir, id)
	if err := os.MkdirAll(workDir, 0711); err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			os.RemoveAll(workDir)
		}
	}()

	if err := os.Mkdir(path, 0711); err != nil {
		return nil, err
	}
	if err := os.Mkdir(filepath.Join(path, "rootfs"), 0711); err != nil {
		return nil, err
	}
	f, err := os.Create(filepath.Join(path, configFilename))
	if err != nil {
		return nil, err
	}
	defer f.Close()
	_, err = io.Copy(f, bytes.NewReader(spec))
	return &bundle{
		id:      id,
		path:    path,
		workDir: workDir,
	}, err
}

type bundle struct {
	id      string
	path    string
	workDir string
}

type shimOpt func(*bundle, string, *runcopts.RuncOptions) (client.Config, client.ClientOpt)

// ShimRemote is a shimOpt for connecting and starting a remote shim
func ShimRemote(shim, daemonAddress, cgroup string, debug bool, exitHandler func()) shimOpt {
	return func(b *bundle, ns string, ropts *runcopts.RuncOptions) (client.Config, client.ClientOpt) {
		return b.shimConfig(ns, ropts),
			client.WithStart(shim, b.shimAddress(ns), daemonAddress, cgroup, debug, exitHandler)
	}
}

// ShimLocal is a shimOpt for using an in process shim implementation
func ShimLocal(exchange *events.Exchange) shimOpt {
	return func(b *bundle, ns string, ropts *runcopts.RuncOptions) (client.Config, client.ClientOpt) {
		return b.shimConfig(ns, ropts), client.WithLocal(exchange)
	}
}

// ShimConnect is a shimOpt for connecting to an existing remote shim
func ShimConnect() shimOpt {
	return func(b *bundle, ns string, ropts *runcopts.RuncOptions) (client.Config, client.ClientOpt) {
		return b.shimConfig(ns, ropts), client.WithConnect(b.shimAddress(ns))
	}
}

// NewShimClient connects to the shim managing the bundle and tasks creating it if needed
func (b *bundle) NewShimClient(ctx context.Context, namespace string, getClientOpts shimOpt, runcOpts *runcopts.RuncOptions) (*client.Client, error) {
	cfg, opt := getClientOpts(b, namespace, runcOpts)
	return client.New(ctx, cfg, opt)
}

// Delete deletes the bundle from disk
func (b *bundle) Delete() error {
	err := os.RemoveAll(b.path)
	if err == nil {
		return os.RemoveAll(b.workDir)
	}
	// error removing the bundle path; still attempt removing work dir
	err2 := os.RemoveAll(b.workDir)
	if err2 == nil {
		return err
	}
	return errors.Wrapf(err, "Failed to remove both bundle and workdir locations: %v", err2)
}

func (b *bundle) shimAddress(namespace string) string {
	return filepath.Join(string(filepath.Separator), "containerd-shim", namespace, b.id, "shim.sock")
}

func (b *bundle) shimConfig(namespace string, runcOptions *runcopts.RuncOptions) client.Config {
	var (
		criuPath      string
		runtimeRoot   string
		systemdCgroup bool
	)
	if runcOptions != nil {
		criuPath = runcOptions.CriuPath
		systemdCgroup = runcOptions.SystemdCgroup
		runtimeRoot = runcOptions.RuntimeRoot
	}
	return client.Config{
		Path:          b.path,
		WorkDir:       b.workDir,
		Namespace:     namespace,
		Criu:          criuPath,
		RuntimeRoot:   runtimeRoot,
		SystemdCgroup: systemdCgroup,
	}
}
<|endoftext|>


<commit_before>package local

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"strconv"
	"text/template"
	"time"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/strslice"
	"github.com/docker/go-connections/nat"

	"code.cloudfoundry.org/cli/cf/formatters"
	"github.com/sclevine/cflocal/engine"
	"github.com/sclevine/cflocal/service"
)

const RunnerScript = `
	set -e
	{{with .ForwardConfig -}}
	{{if .Forwards -}}
	echo 'Forwarding:{{range .Forwards}} {{.Name}}{{end}}'
	sshpass -p '{{.Code}}' ssh -f -N \
		-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \
		-o LogLevel=ERROR -o ExitOnForwardFailure=yes \
		-o ServerAliveInterval=10 -o ServerAliveCountMax=60 \
		-p '{{.Port}}' '{{.User}}@{{.Host}}' \
		{{- range $i, $_ := .Forwards}}
		{{- if $i}} \{{end}}
		-L '{{.From}}:{{.To}}'
		{{- end}}
	{{end -}}
	{{end -}}
	{{if .RSync -}}
	rsync -a /tmp/local/ /home/vcap/app/
	{{end -}}
	if [[ ! -z $(ls -A /home/vcap/app) ]]; then
		exclude='--exclude=./app'
	fi
	tar $exclude -C /home/vcap -xzf /tmp/droplet
	chown -R vcap:vcap /home/vcap
	{{if .RSync -}}
	if [[ -z $(ls -A /tmp/local) ]]; then
		rsync -a /home/vcap/app/ /tmp/local/
	fi
	{{end -}}
	command=$1
	if [[ -z $command ]]; then
		command=$(jq -r .start_command /home/vcap/staging_info.yml)
	fi
	exec /tmp/lifecycle/launcher /home/vcap/app "$command" ''
`

type Runner struct {
	StackVersion string
	Logs         io.Writer
	UI           UI
	Engine       Engine
	Image        Image
}

type RunConfig struct {
	Droplet       engine.Stream
	Launcher      engine.Stream
	SSHPass       engine.Stream
	IP            string
	Port          uint
	AppDir        string
	RSync         bool
	Restart       <-chan time.Time
	Color         Colorizer
	AppConfig     *AppConfig
	ForwardConfig *service.ForwardConfig
}

func (r *Runner) Run(config *RunConfig) (status int64, err error) {
	if err := r.pull(); err != nil {
		return 0, err
	}

	r.setDefaults(config.AppConfig)
	containerConfig, err := r.buildContainerConfig(config.AppConfig, config.ForwardConfig, config.RSync)
	if err != nil {
		return 0, err
	}
	remoteDir := "/home/vcap/app"
	if config.RSync {
		remoteDir = "/tmp/local"
	}
	memory, err := formatters.ToMegabytes(config.AppConfig.Memory)
	if err != nil {
		return 0, err
	}
	hostConfig := r.buildHostConfig(config.IP, config.Port, memory, config.AppDir, remoteDir)
	contr, err := r.Engine.NewContainer(containerConfig, hostConfig)
	if err != nil {
		return 0, err
	}
	defer contr.Close()

	if err := contr.CopyTo(config.Launcher, "/tmp/lifecycle/launcher"); err != nil {
		return 0, err
	}
	if err := contr.CopyTo(config.Droplet, "/tmp/droplet"); err != nil {
		return 0, err
	}
	if config.SSHPass.Size > 0 {
		if err := contr.CopyTo(config.SSHPass, "/usr/bin/sshpass"); err != nil {
			return 0, err
		}
	}
	return contr.Start(config.Color("[%s] ", config.AppConfig.Name), r.Logs, config.Restart)
}

type ExportConfig struct {
	Droplet   engine.Stream
	Launcher  engine.Stream
	Ref       string
	AppConfig *AppConfig
}

func (r *Runner) Export(config *ExportConfig) (imageID string, err error) {
	if err := r.pull(); err != nil {
		return "", err
	}

	r.setDefaults(config.AppConfig)
	containerConfig, err := r.buildContainerConfig(config.AppConfig, nil, false)
	if err != nil {
		return "", err
	}
	contr, err := r.Engine.NewContainer(containerConfig, nil)
	if err != nil {
		return "", err
	}
	defer contr.Close()

	if err := contr.CopyTo(config.Launcher, "/tmp/lifecycle/launcher"); err != nil {
		return "", err
	}
	if err := contr.CopyTo(config.Droplet, "/tmp/droplet"); err != nil {
		return "", err
	}

	return contr.Commit(config.Ref)
}

func (r *Runner) pull() error {
	return r.UI.Loading("Image", r.Image.Pull("cloudfoundry/cflinuxfs2:"+r.StackVersion))
}

func (r *Runner) setDefaults(config *AppConfig) {
	if config.Memory == "" {
		config.Memory = "1024m"
	}
	if config.DiskQuota == "" {
		config.DiskQuota = "1024m"
	}
}

func (r *Runner) buildContainerConfig(config *AppConfig, forwardConfig *service.ForwardConfig, rsync bool) (*container.Config, error) {
	name := config.Name
	memory, err := formatters.ToMegabytes(config.Memory)
	if err != nil {
		return nil, err
	}
	disk, err := formatters.ToMegabytes(config.DiskQuota)
	if err != nil {
		return nil, err
	}
	vcapApp, err := json.Marshal(&vcapApplication{
		ApplicationID:      "01d31c12-d066-495e-aca2-8d3403165360",
		ApplicationName:    name,
		ApplicationURIs:    []string{"localhost"},
		ApplicationVersion: "2b860df9-a0a1-474c-b02f-5985f53ea0bb",
		Host:               "0.0.0.0",
		InstanceID:         "999db41a-508b-46eb-74d8-6f9c06c006da",
		InstanceIndex:      uintPtr(0),
		Limits:             map[string]int64{"fds": 16384, "mem": memory, "disk": disk},
		Name:               name,
		Port:               uintPtr(8080),
		SpaceID:            "18300c1c-1aa4-4ae7-81e6-ae59c6cdbaf1",
		SpaceName:          "cflocal-space",
		URIs:               []string{"localhost"},
		Version:            "18300c1c-1aa4-4ae7-81e6-ae59c6cdbaf1",
	})
	if err != nil {
		return nil, err
	}

	services := config.Services
	if services == nil {
		services = service.Services{}
	}
	vcapServices, err := json.Marshal(services)
	if err != nil {
		return nil, err
	}

	env := map[string]string{
		"CF_INSTANCE_ADDR":  "0.0.0.0:8080",
		"CF_INSTANCE_GUID":  "999db41a-508b-46eb-74d8-6f9c06c006da",
		"CF_INSTANCE_INDEX": "0",
		"CF_INSTANCE_IP":    "0.0.0.0",
		"CF_INSTANCE_PORT":  "8080",
		"CF_INSTANCE_PORTS": `[{"external":8080,"internal":8080}]`,
		"INSTANCE_GUID":     "999db41a-508b-46eb-74d8-6f9c06c006da",
		"INSTANCE_INDEX":    "0",
		"LANG":              "en_US.UTF-8",
		"MEMORY_LIMIT":      fmt.Sprintf("%dm", memory),
		"PATH":              "/usr/local/bin:/usr/bin:/bin",
		"PORT":              "8080",
		"TMPDIR":            "/home/vcap/tmp",
		"USER":              "vcap",
		"VCAP_APPLICATION":  string(vcapApp),
		"VCAP_SERVICES":     string(vcapServices),
	}

	options := struct {
		RSync         bool
		ForwardConfig *service.ForwardConfig
	}{rsync, forwardConfig}
	scriptBuf := &bytes.Buffer{}
	tmpl := template.Must(template.New("").Parse(RunnerScript))
	if err := tmpl.Execute(scriptBuf, options); err != nil {
		return nil, err
	}

	return &container.Config{
		Hostname:     "cflocal",
		User:         "vcap",
		ExposedPorts: nat.PortSet{"8080/tcp": {}},
		Env:          mapToEnv(mergeMaps(env, config.RunningEnv, config.Env)),
		Image:        "cloudfoundry/cflinuxfs2:" + r.StackVersion,
		WorkingDir:   "/home/vcap/app",
		Entrypoint: strslice.StrSlice{
			"/bin/bash", "-c", scriptBuf.String(), config.Command,
		},
	}, nil
}

func (*Runner) buildHostConfig(ip string, port uint, memory int64, appDir, remoteDir string) *container.HostConfig {
	config := &container.HostConfig{
		PortBindings: nat.PortMap{
			"8080/tcp": {{HostIP: ip, HostPort: strconv.FormatUint(uint64(port), 10)}},
		},
		Resources: container.Resources{
			Memory: memory * 1024 * 1024,
		},
	}
	if appDir != "" && remoteDir != "" {
		config.Binds = []string{appDir + ":" + remoteDir}
	}
	return config
}

func parseBytes(s string) uint64 {

	return 0
}

func mergeMaps(maps ...map[string]string) map[string]string {
	merged := map[string]string{}
	for _, m := range maps {
		for k, v := range m {
			merged[k] = v
		}
	}
	return merged
}

func mapToEnv(env map[string]string) []string {
	var out []string
	for k, v := range env {
		out = append(out, fmt.Sprintf("%s=%s", k, v))
	}
	return out
}

func uintPtr(i uint) *uint {
	return &i
}
<commit_msg>Remove dead code<commit_after>package local

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"strconv"
	"text/template"
	"time"

	"code.cloudfoundry.org/cli/cf/formatters"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/strslice"
	"github.com/docker/go-connections/nat"

	"github.com/sclevine/cflocal/engine"
	"github.com/sclevine/cflocal/service"
)

const RunnerScript = `
	set -e
	{{with .ForwardConfig -}}
	{{if .Forwards -}}
	echo 'Forwarding:{{range .Forwards}} {{.Name}}{{end}}'
	sshpass -p '{{.Code}}' ssh -f -N \
		-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \
		-o LogLevel=ERROR -o ExitOnForwardFailure=yes \
		-o ServerAliveInterval=10 -o ServerAliveCountMax=60 \
		-p '{{.Port}}' '{{.User}}@{{.Host}}' \
		{{- range $i, $_ := .Forwards}}
		{{- if $i}} \{{end}}
		-L '{{.From}}:{{.To}}'
		{{- end}}
	{{end -}}
	{{end -}}
	{{if .RSync -}}
	rsync -a /tmp/local/ /home/vcap/app/
	{{end -}}
	if [[ ! -z $(ls -A /home/vcap/app) ]]; then
		exclude='--exclude=./app'
	fi
	tar $exclude -C /home/vcap -xzf /tmp/droplet
	chown -R vcap:vcap /home/vcap
	{{if .RSync -}}
	if [[ -z $(ls -A /tmp/local) ]]; then
		rsync -a /home/vcap/app/ /tmp/local/
	fi
	{{end -}}
	command=$1
	if [[ -z $command ]]; then
		command=$(jq -r .start_command /home/vcap/staging_info.yml)
	fi
	exec /tmp/lifecycle/launcher /home/vcap/app "$command" ''
`

type Runner struct {
	StackVersion string
	Logs         io.Writer
	UI           UI
	Engine       Engine
	Image        Image
}

type RunConfig struct {
	Droplet       engine.Stream
	Launcher      engine.Stream
	SSHPass       engine.Stream
	IP            string
	Port          uint
	AppDir        string
	RSync         bool
	Restart       <-chan time.Time
	Color         Colorizer
	AppConfig     *AppConfig
	ForwardConfig *service.ForwardConfig
}

func (r *Runner) Run(config *RunConfig) (status int64, err error) {
	if err := r.pull(); err != nil {
		return 0, err
	}

	r.setDefaults(config.AppConfig)
	containerConfig, err := r.buildContainerConfig(config.AppConfig, config.ForwardConfig, config.RSync)
	if err != nil {
		return 0, err
	}
	remoteDir := "/home/vcap/app"
	if config.RSync {
		remoteDir = "/tmp/local"
	}
	memory, err := formatters.ToMegabytes(config.AppConfig.Memory)
	if err != nil {
		return 0, err
	}
	hostConfig := r.buildHostConfig(config.IP, config.Port, memory, config.AppDir, remoteDir)
	contr, err := r.Engine.NewContainer(containerConfig, hostConfig)
	if err != nil {
		return 0, err
	}
	defer contr.Close()

	if err := contr.CopyTo(config.Launcher, "/tmp/lifecycle/launcher"); err != nil {
		return 0, err
	}
	if err := contr.CopyTo(config.Droplet, "/tmp/droplet"); err != nil {
		return 0, err
	}
	if config.SSHPass.Size > 0 {
		if err := contr.CopyTo(config.SSHPass, "/usr/bin/sshpass"); err != nil {
			return 0, err
		}
	}
	return contr.Start(config.Color("[%s] ", config.AppConfig.Name), r.Logs, config.Restart)
}

type ExportConfig struct {
	Droplet   engine.Stream
	Launcher  engine.Stream
	Ref       string
	AppConfig *AppConfig
}

func (r *Runner) Export(config *ExportConfig) (imageID string, err error) {
	if err := r.pull(); err != nil {
		return "", err
	}

	r.setDefaults(config.AppConfig)
	containerConfig, err := r.buildContainerConfig(config.AppConfig, nil, false)
	if err != nil {
		return "", err
	}
	contr, err := r.Engine.NewContainer(containerConfig, nil)
	if err != nil {
		return "", err
	}
	defer contr.Close()

	if err := contr.CopyTo(config.Launcher, "/tmp/lifecycle/launcher"); err != nil {
		return "", err
	}
	if err := contr.CopyTo(config.Droplet, "/tmp/droplet"); err != nil {
		return "", err
	}

	return contr.Commit(config.Ref)
}

func (r *Runner) pull() error {
	return r.UI.Loading("Image", r.Image.Pull("cloudfoundry/cflinuxfs2:"+r.StackVersion))
}

func (r *Runner) setDefaults(config *AppConfig) {
	if config.Memory == "" {
		config.Memory = "1024m"
	}
	if config.DiskQuota == "" {
		config.DiskQuota = "1024m"
	}
}

func (r *Runner) buildContainerConfig(config *AppConfig, forwardConfig *service.ForwardConfig, rsync bool) (*container.Config, error) {
	name := config.Name
	memory, err := formatters.ToMegabytes(config.Memory)
	if err != nil {
		return nil, err
	}
	disk, err := formatters.ToMegabytes(config.DiskQuota)
	if err != nil {
		return nil, err
	}
	vcapApp, err := json.Marshal(&vcapApplication{
		ApplicationID:      "01d31c12-d066-495e-aca2-8d3403165360",
		ApplicationName:    name,
		ApplicationURIs:    []string{"localhost"},
		ApplicationVersion: "2b860df9-a0a1-474c-b02f-5985f53ea0bb",
		Host:               "0.0.0.0",
		InstanceID:         "999db41a-508b-46eb-74d8-6f9c06c006da",
		InstanceIndex:      uintPtr(0),
		Limits:             map[string]int64{"fds": 16384, "mem": memory, "disk": disk},
		Name:               name,
		Port:               uintPtr(8080),
		SpaceID:            "18300c1c-1aa4-4ae7-81e6-ae59c6cdbaf1",
		SpaceName:          "cflocal-space",
		URIs:               []string{"localhost"},
		Version:            "18300c1c-1aa4-4ae7-81e6-ae59c6cdbaf1",
	})
	if err != nil {
		return nil, err
	}

	services := config.Services
	if services == nil {
		services = service.Services{}
	}
	vcapServices, err := json.Marshal(services)
	if err != nil {
		return nil, err
	}

	env := map[string]string{
		"CF_INSTANCE_ADDR":  "0.0.0.0:8080",
		"CF_INSTANCE_GUID":  "999db41a-508b-46eb-74d8-6f9c06c006da",
		"CF_INSTANCE_INDEX": "0",
		"CF_INSTANCE_IP":    "0.0.0.0",
		"CF_INSTANCE_PORT":  "8080",
		"CF_INSTANCE_PORTS": `[{"external":8080,"internal":8080}]`,
		"INSTANCE_GUID":     "999db41a-508b-46eb-74d8-6f9c06c006da",
		"INSTANCE_INDEX":    "0",
		"LANG":              "en_US.UTF-8",
		"MEMORY_LIMIT":      fmt.Sprintf("%dm", memory),
		"PATH":              "/usr/local/bin:/usr/bin:/bin",
		"PORT":              "8080",
		"TMPDIR":            "/home/vcap/tmp",
		"USER":              "vcap",
		"VCAP_APPLICATION":  string(vcapApp),
		"VCAP_SERVICES":     string(vcapServices),
	}

	options := struct {
		RSync         bool
		ForwardConfig *service.ForwardConfig
	}{rsync, forwardConfig}
	scriptBuf := &bytes.Buffer{}
	tmpl := template.Must(template.New("").Parse(RunnerScript))
	if err := tmpl.Execute(scriptBuf, options); err != nil {
		return nil, err
	}

	return &container.Config{
		Hostname:     "cflocal",
		User:         "vcap",
		ExposedPorts: nat.PortSet{"8080/tcp": {}},
		Env:          mapToEnv(mergeMaps(env, config.RunningEnv, config.Env)),
		Image:        "cloudfoundry/cflinuxfs2:" + r.StackVersion,
		WorkingDir:   "/home/vcap/app",
		Entrypoint: strslice.StrSlice{
			"/bin/bash", "-c", scriptBuf.String(), config.Command,
		},
	}, nil
}

func (*Runner) buildHostConfig(ip string, port uint, memory int64, appDir, remoteDir string) *container.HostConfig {
	config := &container.HostConfig{
		PortBindings: nat.PortMap{
			"8080/tcp": {{HostIP: ip, HostPort: strconv.FormatUint(uint64(port), 10)}},
		},
		Resources: container.Resources{
			Memory: memory * 1024 * 1024,
		},
	}
	if appDir != "" && remoteDir != "" {
		config.Binds = []string{appDir + ":" + remoteDir}
	}
	return config
}

func mergeMaps(maps ...map[string]string) map[string]string {
	merged := map[string]string{}
	for _, m := range maps {
		for k, v := range m {
			merged[k] = v
		}
	}
	return merged
}

func mapToEnv(env map[string]string) []string {
	var out []string
	for k, v := range env {
		out = append(out, fmt.Sprintf("%s=%s", k, v))
	}
	return out
}

func uintPtr(i uint) *uint {
	return &i
}
<|endoftext|>


<commit_before>package gcp

import (
	"github.com/viant/endly"
	"github.com/viant/endly/util"
	"github.com/viant/toolbox"
	"github.com/viant/toolbox/data"
	"google.golang.org/api/gensupport"
)

//ExpandMeta expand meta data
func ExpandMeta(context *endly.Context, text string) string {
	gcpCred, err := getCredentials(context)
	if err != nil {
		return text
	}
	if gcpCred.Region == "" {
		gcpCred.Region = DefaultRegion
	}
	state := data.NewMap()
	state.SetValue("gcp.projectID", gcpCred.ProjectID)
	state.SetValue("gcp.region", gcpCred.Region)
	state.SetValue("gcp.serviceAccount", gcpCred.ClientEmail)
	return state.ExpandAsText(text)
}

//UpdateActionRequest updates raw request with project, service
func UpdateActionRequest(rawRequest map[string]interface{}, credConfig *gcpCredConfig, client CtxClient) {
	state := data.NewMap()

	if credConfig.Region == "" {
		credConfig.Region = DefaultRegion
	}

	state.SetValue("gcp.projectID", credConfig.ProjectID)
	state.SetValue("gcp.region", credConfig.Region)
	state.SetValue("gcp.serviceAccount", credConfig.ClientEmail)
	for k, v := range rawRequest {
		rawRequest[k] = state.Expand(v)
	}

	mappings := util.BuildLowerCaseMapping(rawRequest)
	if _, has := mappings["project"]; !has {
		rawRequest["project"] = credConfig.ProjectID
	}
	if _, has := mappings["region"]; !has {
		rawRequest["region"] = credConfig.Region
	}

	var URLParams = make(gensupport.URLParams)
	if paramsKey, has := mappings["urlparams"]; has {
		params := rawRequest[paramsKey]
		if toolbox.IsMap(params) {
			for k, v := range toolbox.AsMap(params) {
				URLParams[k] = []string{toolbox.AsString(v)}
			}
		}
	}

	rawRequest["urlParams_"] = URLParams
	rawRequest["s"] = client.Service()

}
<commit_msg>removed gensupport dep - https://github.com/viant/endly/issues/51<commit_after>package gcp

import (
	"github.com/viant/endly"
	"github.com/viant/endly/util"
	"github.com/viant/toolbox"
	"github.com/viant/toolbox/data"
)

type URLParams map[string][]string

//ExpandMeta expand meta data
func ExpandMeta(context *endly.Context, text string) string {
	gcpCred, err := getCredentials(context)
	if err != nil {
		return text
	}
	if gcpCred.Region == "" {
		gcpCred.Region = DefaultRegion
	}
	state := data.NewMap()
	state.SetValue("gcp.projectID", gcpCred.ProjectID)
	state.SetValue("gcp.region", gcpCred.Region)
	state.SetValue("gcp.serviceAccount", gcpCred.ClientEmail)
	return state.ExpandAsText(text)
}

//UpdateActionRequest updates raw request with project, service
func UpdateActionRequest(rawRequest map[string]interface{}, credConfig *gcpCredConfig, client CtxClient) {
	state := data.NewMap()

	if credConfig.Region == "" {
		credConfig.Region = DefaultRegion
	}

	state.SetValue("gcp.projectID", credConfig.ProjectID)
	state.SetValue("gcp.region", credConfig.Region)
	state.SetValue("gcp.serviceAccount", credConfig.ClientEmail)
	for k, v := range rawRequest {
		rawRequest[k] = state.Expand(v)
	}

	mappings := util.BuildLowerCaseMapping(rawRequest)
	if _, has := mappings["project"]; !has {
		rawRequest["project"] = credConfig.ProjectID
	}
	if _, has := mappings["region"]; !has {
		rawRequest["region"] = credConfig.Region
	}

	var URLParams = make(URLParams)
	if paramsKey, has := mappings["urlparams"]; has {
		params := rawRequest[paramsKey]
		if toolbox.IsMap(params) {
			for k, v := range toolbox.AsMap(params) {
				URLParams[k] = []string{toolbox.AsString(v)}
			}
		}
	}

	rawRequest["urlParams_"] = URLParams
	rawRequest["s"] = client.Service()

}
<|endoftext|>


<commit_before>// peekaboo registers the current machine (or provided IP) to a Rackspace load
// balancer, enabling (or disabling) itself.

package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
	"github.com/rackspace/gophercloud/rackspace"
	"github.com/rackspace/gophercloud/rackspace/lb/v1/lbs"
	"github.com/rackspace/gophercloud/rackspace/lb/v1/nodes"
)

// backoff executes a closure that performs a CLB operation and returns its error condition. If
// a 422 (immutable entity) error is returned, wait timeout seconds perturbed by a small random
// amount and try again. If a non-422 error is returned, return that error immediately. If the
// operation is successful, return nil.
func backoff(timeout int, action func() error) error {
	err := action()
	for err != nil {
		err = action()

		if casted, ok := err.(*gophercloud.UnexpectedResponseCodeError); ok {
			if casted.Actual == 422 {
				// Immutable load balancer.
				// Sleep and retry.
				base := time.Duration(timeout) * time.Second
				delta := time.Duration(-1000+rand.Intn(2000)) * time.Millisecond
				d := base + delta

				log.Printf("Load balancer is immutable. Sleeping for %s.", d)
				time.Sleep(d)
			} else {
				// Non-422 error.
				return err
			}
		} else {
			// Non-HTTP error
			return err
		}
	}
	return nil
}

// waitForReady waits for the load balancer with id loadBalancerID to become
// ACTIVE. It times out after 60 seconds and streamrolls on ahead.
func waitForReady(client *gophercloud.ServiceClient, loadBalancerID int, timeout int) {

	// Ensure the load balancer is ready
	gophercloud.WaitFor(timeout, func() (bool, error) {
		lb, err := lbs.Get(client, loadBalancerID).Extract()
		if err != nil {
			return false, err
		}

		if lb.Status != lbs.ACTIVE {
			// It has not yet reached ACTIVE
			return false, nil
		}

		// It has reached ACTIVE
		return true, nil
	})

}

// findNodeByIPPort gets a load balancer node by IP and Port
func findNodeByIPPort(
	client *gophercloud.ServiceClient,
	loadBalancerID int,
	address string,
	port int,
) *nodes.Node {

	// nil until found
	var found *nodes.Node

	pager := nodes.List(client, loadBalancerID, nil)

	pager.EachPage(func(page pagination.Page) (bool, error) {
		lbNodes, err := nodes.ExtractNodes(page)
		if err != nil {
			log.Fatalf("Error during paging load balancer: %v", err)
		}

		for _, trialNode := range lbNodes {
			if trialNode.Address == address && trialNode.Port == port {
				found = &trialNode
				return false, nil
			}

		}

		return true, nil
	})

	return found
}

// getIP tries to best guess what IP to work with
//
// Precedence for IP is determined by
//  - ipPtr being non-nil (peekaboo's -ip flag)
//  - Service Net Environment variable $RAX_SERVICENET_IPV4
//  - Public Net Environment variable $RAX_PUBLICNET_IPV4
//  - Gleaning a 10 dot out of the network interfaces (likely service net)
//  - eth0
func getIP(ipPtr *string) (string, error) {

	serviceNetIPv4 := os.Getenv("RAX_SERVICENET_IPV4")
	publicNetIPv4 := os.Getenv("RAX_PUBLICNET_IPV4")

	switch {
	case *ipPtr != "":
		return *ipPtr, nil
	case serviceNetIPv4 != "":
		return serviceNetIPv4, nil
	case publicNetIPv4 != "":
		return publicNetIPv4, nil
	}

	addrs, err := net.InterfaceAddrs()

	if err != nil {
		return "", err
	}

	for _, addr := range addrs {
		cidr := addr.String()
		ip := strings.Split(cidr, "/")[0]

		if strings.HasPrefix(ip, "10.") {
			return ip, nil
		}
	}

	// Find eth0
	eth0, err := net.InterfaceByName("eth0")
	if err != nil {
		return "", fmt.Errorf("trouble finding eth0: %v", err)
	}

	addrs, err = eth0.Addrs()
	if err != nil {
		return "", err
	}

	for _, addr := range addrs {
		cidr := addr.String()
		ip := strings.Split(cidr, "/")[0]

		// Pick out IPv4
		if strings.ContainsRune(ip, '.') {
			return ip, err
		}
	}

	return "", errors.New("unable to determine an IP for load balancing")

}

func main() {
	rand.Seed(time.Now().UTC().UnixNano())
	var err error

	disabledPtr := flag.Bool("disable", false, "Disable the node on the load balancer")
	drainingPtr := flag.Bool("drain", false, "Drain the node from the load balancer")
	deletePtr := flag.Bool("delete", false, "Delete the node from the load balancer")

	//NOTE: peekaboo allows setting the IP by using
	//        - environment variables: RAX_SERVICENET_IPV4 or RAX_PUBLICNET_IPV4
	//        - finding an ip on the system starting with 10. (service net)
	//        - locating the eth0 interface
	//        - providing an ip as a flag is fine too and will take precedence
	ipPtr := flag.String("ip", "", "IP address to register/deregister on the load balancer")
	timeout := flag.Int("timeout", 60, "Seconds to wait for the load balancer to become available")

	flag.Parse()

	username := os.Getenv("OS_USERNAME")
	APIKey := os.Getenv("OS_PASSWORD")
	region := os.Getenv("OS_REGION_NAME")

	if username == "" || APIKey == "" || region == "" {
		log.Fatalf("One or more of $OS_USERNAME, $OS_PASSWORD, and $OS_REGION_NAME not set")
	}

	// These get converted into integers later
	strLoadBalancerID := os.Getenv("LOAD_BALANCER_ID")
	strAppPort := os.Getenv("APP_PORT")

	/**
	 * Retrieve port for load balancer's node, defaulting to 80
	 */
	var nodePort = 80

	if strAppPort == "" {
		log.Printf("$APP_PORT not set, defaulting to 80")
	} else {
		nodePort, err = strconv.Atoi(strAppPort)
		if err != nil {
			log.Fatalf("Unable to parse integer from $APP_PORT: %v\n", strAppPort)
		}
	}

	/**
	 * Determine the IP Address for the load balancer's node
	 */
	nodeAddress, err := getIP(ipPtr)
	if err != nil {
		log.Fatalln(err)
	}

	/**
	 * Retrive Load Balancer ID
	 */
	if strLoadBalancerID == "" {
		log.Fatalln("$LOAD_BALANCER_ID must be set")
	}
	loadBalancerID, err := strconv.Atoi(strLoadBalancerID)
	if err != nil {
		log.Fatalf("$LOAD_BALANCER_ID not an integer: %v\n", loadBalancerID)
	}

	provider, err := rackspace.AuthenticatedClient(gophercloud.AuthOptions{
		Username: username,
		APIKey:   APIKey,
	})

	if err != nil {
		log.Fatalf("Trouble authenticating to Rackspace: %v\n", err)
	}

	client, err := rackspace.NewLBV1(provider, gophercloud.EndpointOpts{
		Region: region,
	})

	if err != nil {
		log.Fatalf("Creating load balancer client in %v failed: %v\n", region, err)
	}

	waitForReady(client, loadBalancerID, *timeout)

	nodePtr := findNodeByIPPort(client, loadBalancerID, nodeAddress, nodePort)

	if !*deletePtr {
		// Transition an existing node to the desired state. Create a new node if one doesn't exist
		// already.

		condition := nodes.ENABLED
		if *disabledPtr {
			condition = nodes.DISABLED
		} else if *drainingPtr {
			//TODO: Watch the interface on the right process/container to determine
			//      when connections have dropped, and set to DISABLED
			condition = nodes.DRAINING
		}

		log.Printf("Setting %v:%v to be %v on load balancer %v\n",
			nodeAddress, nodePort,
			condition,
			loadBalancerID)

		if nodePtr != nil {
			log.Printf("Updating existing node %v", *nodePtr)

			opts := nodes.UpdateOpts{
				Condition: condition,
			}

			updateResult := nodes.Update(client, loadBalancerID, nodePtr.ID, opts)
			err = updateResult.ExtractErr()
			if err != nil {
				log.Panicf("Updating node failed: %v", err)
			}

		} else {
			log.Printf("Creating new node")
			opts := nodes.CreateOpts{
				nodes.CreateOpt{
					Address:   nodeAddress,
					Port:      nodePort,
					Condition: condition,
				},
			}

			nodeList, err := nodes.Create(client, loadBalancerID, opts).ExtractNodes()
			if err != nil {
				log.Fatalf("Error enumerating created nodes: %v", err)
			}
			if len(nodeList) != 1 {
				log.Panicf("Something went terribly wrong on node creation: %#v\n", nodeList)
			}
			nodePtr = &nodeList[0]
		}

		waitForReady(client, loadBalancerID, *timeout)

		// After update, get the version of the node Rackspace has
		result := nodes.Get(client, loadBalancerID, nodePtr.ID)
		nodePtr, err = result.Extract()
		if err != nil {
			log.Panicln(err)
		}

		log.Printf("Final node state: %v\n", *nodePtr)
	} else {
		// Delete an existing node from the balancer. Do nothing if no node exists.
		if nodePtr != nil {
			log.Printf("Deleting existing node %v", *nodePtr)

			err := nodes.Delete(client, loadBalancerID, nodePtr.ID).ExtractErr()
			if err != nil {
				log.Fatalf("Unable to delete node %d: %v", nodePtr.ID, err)
			}

			waitForReady(client, loadBalancerID, *timeout)
			log.Println("Final node state: Deleted")
		} else {
			log.Println("Node is already gone. Hooray?")
		}
	}

}
<commit_msg>Use the backoff when updating the LB.<commit_after>// peekaboo registers the current machine (or provided IP) to a Rackspace load
// balancer, enabling (or disabling) itself.

package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
	"github.com/rackspace/gophercloud/rackspace"
	"github.com/rackspace/gophercloud/rackspace/lb/v1/lbs"
	"github.com/rackspace/gophercloud/rackspace/lb/v1/nodes"
)

// backoff executes a closure that performs a CLB operation and returns its error condition. If
// a 422 (immutable entity) error is returned, wait timeout seconds perturbed by a small random
// amount and try again. If a non-422 error is returned, return that error immediately. If the
// operation is successful, return nil.
func backoff(timeout int, action func() error) error {
	err := action()
	for err != nil {
		err = action()

		if casted, ok := err.(*gophercloud.UnexpectedResponseCodeError); ok {
			if casted.Actual == 422 {
				// Immutable load balancer.
				// Sleep and retry.
				base := time.Duration(timeout) * time.Second
				delta := time.Duration(-1000+rand.Intn(2000)) * time.Millisecond
				d := base + delta

				log.Printf("Load balancer is immutable. Sleeping for %s.", d)
				time.Sleep(d)
			} else {
				// Non-422 error.
				return err
			}
		} else {
			// Non-HTTP error
			return err
		}
	}
	return nil
}

// waitForReady waits for the load balancer with id loadBalancerID to become
// ACTIVE. It times out after 60 seconds and streamrolls on ahead.
func waitForReady(client *gophercloud.ServiceClient, loadBalancerID int, timeout int) {

	// Ensure the load balancer is ready
	gophercloud.WaitFor(timeout, func() (bool, error) {
		lb, err := lbs.Get(client, loadBalancerID).Extract()
		if err != nil {
			return false, err
		}

		if lb.Status != lbs.ACTIVE {
			// It has not yet reached ACTIVE
			return false, nil
		}

		// It has reached ACTIVE
		return true, nil
	})

}

// findNodeByIPPort gets a load balancer node by IP and Port
func findNodeByIPPort(
	client *gophercloud.ServiceClient,
	loadBalancerID int,
	address string,
	port int,
) *nodes.Node {

	// nil until found
	var found *nodes.Node

	pager := nodes.List(client, loadBalancerID, nil)

	pager.EachPage(func(page pagination.Page) (bool, error) {
		lbNodes, err := nodes.ExtractNodes(page)
		if err != nil {
			log.Fatalf("Error during paging load balancer: %v", err)
		}

		for _, trialNode := range lbNodes {
			if trialNode.Address == address && trialNode.Port == port {
				found = &trialNode
				return false, nil
			}

		}

		return true, nil
	})

	return found
}

// getIP tries to best guess what IP to work with
//
// Precedence for IP is determined by
//  - ipPtr being non-nil (peekaboo's -ip flag)
//  - Service Net Environment variable $RAX_SERVICENET_IPV4
//  - Public Net Environment variable $RAX_PUBLICNET_IPV4
//  - Gleaning a 10 dot out of the network interfaces (likely service net)
//  - eth0
func getIP(ipPtr *string) (string, error) {

	serviceNetIPv4 := os.Getenv("RAX_SERVICENET_IPV4")
	publicNetIPv4 := os.Getenv("RAX_PUBLICNET_IPV4")

	switch {
	case *ipPtr != "":
		return *ipPtr, nil
	case serviceNetIPv4 != "":
		return serviceNetIPv4, nil
	case publicNetIPv4 != "":
		return publicNetIPv4, nil
	}

	addrs, err := net.InterfaceAddrs()

	if err != nil {
		return "", err
	}

	for _, addr := range addrs {
		cidr := addr.String()
		ip := strings.Split(cidr, "/")[0]

		if strings.HasPrefix(ip, "10.") {
			return ip, nil
		}
	}

	// Find eth0
	eth0, err := net.InterfaceByName("eth0")
	if err != nil {
		return "", fmt.Errorf("trouble finding eth0: %v", err)
	}

	addrs, err = eth0.Addrs()
	if err != nil {
		return "", err
	}

	for _, addr := range addrs {
		cidr := addr.String()
		ip := strings.Split(cidr, "/")[0]

		// Pick out IPv4
		if strings.ContainsRune(ip, '.') {
			return ip, err
		}
	}

	return "", errors.New("unable to determine an IP for load balancing")

}

func main() {
	rand.Seed(time.Now().UTC().UnixNano())
	var err error

	disabledPtr := flag.Bool("disable", false, "Disable the node on the load balancer")
	drainingPtr := flag.Bool("drain", false, "Drain the node from the load balancer")
	deletePtr := flag.Bool("delete", false, "Delete the node from the load balancer")

	//NOTE: peekaboo allows setting the IP by using
	//        - environment variables: RAX_SERVICENET_IPV4 or RAX_PUBLICNET_IPV4
	//        - finding an ip on the system starting with 10. (service net)
	//        - locating the eth0 interface
	//        - providing an ip as a flag is fine too and will take precedence
	ipPtr := flag.String("ip", "", "IP address to register/deregister on the load balancer")
	timeout := flag.Int("timeout", 60, "Seconds to wait for the load balancer to become available")

	flag.Parse()

	username := os.Getenv("OS_USERNAME")
	APIKey := os.Getenv("OS_PASSWORD")
	region := os.Getenv("OS_REGION_NAME")

	if username == "" || APIKey == "" || region == "" {
		log.Fatalf("One or more of $OS_USERNAME, $OS_PASSWORD, and $OS_REGION_NAME not set")
	}

	// These get converted into integers later
	strLoadBalancerID := os.Getenv("LOAD_BALANCER_ID")
	strAppPort := os.Getenv("APP_PORT")

	/**
	 * Retrieve port for load balancer's node, defaulting to 80
	 */
	var nodePort = 80

	if strAppPort == "" {
		log.Printf("$APP_PORT not set, defaulting to 80")
	} else {
		nodePort, err = strconv.Atoi(strAppPort)
		if err != nil {
			log.Fatalf("Unable to parse integer from $APP_PORT: %v\n", strAppPort)
		}
	}

	/**
	 * Determine the IP Address for the load balancer's node
	 */
	nodeAddress, err := getIP(ipPtr)
	if err != nil {
		log.Fatalln(err)
	}

	/**
	 * Retrive Load Balancer ID
	 */
	if strLoadBalancerID == "" {
		log.Fatalln("$LOAD_BALANCER_ID must be set")
	}
	loadBalancerID, err := strconv.Atoi(strLoadBalancerID)
	if err != nil {
		log.Fatalf("$LOAD_BALANCER_ID not an integer: %v\n", loadBalancerID)
	}

	provider, err := rackspace.AuthenticatedClient(gophercloud.AuthOptions{
		Username: username,
		APIKey:   APIKey,
	})

	if err != nil {
		log.Fatalf("Trouble authenticating to Rackspace: %v\n", err)
	}

	client, err := rackspace.NewLBV1(provider, gophercloud.EndpointOpts{
		Region: region,
	})

	if err != nil {
		log.Fatalf("Creating load balancer client in %v failed: %v\n", region, err)
	}

	nodePtr := findNodeByIPPort(client, loadBalancerID, nodeAddress, nodePort)

	if !*deletePtr {
		// Transition an existing node to the desired state. Create a new node if one doesn't exist
		// already.

		condition := nodes.ENABLED
		if *disabledPtr {
			condition = nodes.DISABLED
		} else if *drainingPtr {
			//TODO: Watch the interface on the right process/container to determine
			//      when connections have dropped, and set to DISABLED
			condition = nodes.DRAINING
		}

		log.Printf("Setting %v:%v to be %v on load balancer %v\n",
			nodeAddress, nodePort,
			condition,
			loadBalancerID)

		if nodePtr != nil {
			log.Printf("Updating existing node %v", *nodePtr)

			opts := nodes.UpdateOpts{
				Condition: condition,
			}

			err = backoff(*timeout, func() error {
				return nodes.Update(client, loadBalancerID, nodePtr.ID, opts).ExtractErr()
			})
			if err != nil {
				log.Fatalf("Unable to update node: %v", err)
			}

		} else {
			log.Printf("Creating new node")
			opts := nodes.CreateOpts{
				nodes.CreateOpt{
					Address:   nodeAddress,
					Port:      nodePort,
					Condition: condition,
				},
			}

			var created []nodes.Node
			err = backoff(*timeout, func() error {
				created, err = nodes.Create(client, loadBalancerID, opts).ExtractNodes()
				return err
			})

			if err != nil {
				log.Fatalf("Error enumerating created nodes: %v", err)
			}
			if len(created) != 1 {
				log.Fatalf("Something went terribly wrong on node creation: %#v\n", created)
			}
			nodePtr = &created[0]
		}

		waitForReady(client, loadBalancerID, *timeout)

		// After the update completes, get the final version of the node.
		nodePtr, err := nodes.Get(client, loadBalancerID, nodePtr.ID).Extract()
		if err != nil {
			log.Fatalf("Update to retrieve final node state: %v", err)
		}

		log.Printf("Final node state: %v\n", *nodePtr)
	} else {
		// Delete an existing node from the balancer. Do nothing if no node exists.
		if nodePtr != nil {
			log.Printf("Deleting existing node %v", *nodePtr)

			err = backoff(*timeout, func() error {
				return nodes.Delete(client, loadBalancerID, nodePtr.ID).ExtractErr()
			})
			if err != nil {
				log.Fatalf("Unable to delete node %d: %v", nodePtr.ID, err)
			}

			waitForReady(client, loadBalancerID, *timeout)
			log.Println("Final node state: Deleted")
		} else {
			log.Println("Node is already gone. Hooray?")
		}
	}

}
<|endoftext|>


<commit_before>package lxd

import (
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/simplestreams"
	"gopkg.in/macaroon-bakery.v2/httpbakery"
)

// ConnectionArgs represents a set of common connection properties
type ConnectionArgs struct {
	// TLS certificate of the remote server. If not specified, the system CA is used.
	TLSServerCert string

	// TLS certificate to use for client authentication.
	TLSClientCert string

	// TLS key to use for client authentication.
	TLSClientKey string

	// TLS CA to validate against when in PKI mode.
	TLSCA string

	// User agent string
	UserAgent string

	// Authentication type
	AuthType string

	// Authentication interactor
	AuthInteractor []httpbakery.Interactor

	// Custom proxy
	Proxy func(*http.Request) (*url.URL, error)

	// Custom HTTP Client (used as base for the connection)
	HTTPClient *http.Client

	// Controls whether a client verifies the server's certificate chain and host name.
	InsecureSkipVerify bool

	// Cookie jar
	CookieJar http.CookieJar

	// Skip automatic GetServer request upon connection
	SkipGetServer bool
}

// ConnectLXD lets you connect to a remote LXD daemon over HTTPs.
//
// A client certificate (TLSClientCert) and key (TLSClientKey) must be provided.
//
// If connecting to a LXD daemon running in PKI mode, the PKI CA (TLSCA) must also be provided.
//
// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
func ConnectLXD(url string, args *ConnectionArgs) (InstanceServer, error) {
	logger.Debugf("Connecting to a remote LXD over HTTPs")

	// Cleanup URL
	url = strings.TrimSuffix(url, "/")

	return httpsLXD(url, args)
}

// ConnectLXDHTTP lets you connect to a VM agent over a VM socket.
func ConnectLXDHTTP(vsockID int, args *ConnectionArgs, client *http.Client) (InstanceServer, error) {
	logger.Debugf("Connecting to a VM agent over a VM socket")

	// Use empty args if not specified
	if args == nil {
		args = &ConnectionArgs{}
	}

	// Initialize the client struct
	server := ProtocolLXD{
		httpHost:      "https://custom.socket",
		httpProtocol:  "custom",
		httpUserAgent: args.UserAgent,
	}

	// Setup the HTTP client
	server.http = client

	// Test the connection and seed the server information
	if !args.SkipGetServer {
		serverStatus, _, err := server.GetServer()
		if err != nil {
			return nil, err
		}

		// Record the server certificate
		server.httpCertificate = serverStatus.Environment.Certificate
	}

	return &server, nil
}

// ConnectLXDUnix lets you connect to a remote LXD daemon over a local unix socket.
//
// If the path argument is empty, then $LXD_SOCKET will be used, if
// unset $LXD_DIR/unix.socket will be used and if that one isn't set
// either, then the path will default to /var/lib/lxd/unix.socket.
func ConnectLXDUnix(path string, args *ConnectionArgs) (InstanceServer, error) {
	logger.Debugf("Connecting to a local LXD over a Unix socket")

	// Use empty args if not specified
	if args == nil {
		args = &ConnectionArgs{}
	}

	// Initialize the client struct
	server := ProtocolLXD{
		httpHost:      "http://unix.socket",
		httpUnixPath:  path,
		httpProtocol:  "unix",
		httpUserAgent: args.UserAgent,
	}

	// Determine the socket path
	if path == "" {
		path = os.Getenv("LXD_SOCKET")
		if path == "" {
			lxdDir := os.Getenv("LXD_DIR")
			if lxdDir == "" {
				lxdDir = "/var/lib/lxd"
			}

			path = filepath.Join(lxdDir, "unix.socket")
		}
	}

	// Setup the HTTP client
	httpClient, err := unixHTTPClient(args.HTTPClient, path)
	if err != nil {
		return nil, err
	}
	server.http = httpClient

	// Test the connection and seed the server information
	if !args.SkipGetServer {
		serverStatus, _, err := server.GetServer()
		if err != nil {
			return nil, err
		}

		// Record the server certificate
		server.httpCertificate = serverStatus.Environment.Certificate
	}

	return &server, nil
}

// ConnectPublicLXD lets you connect to a remote public LXD daemon over HTTPs.
//
// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
	logger.Debugf("Connecting to a remote public LXD over HTTPs")

	// Cleanup URL
	url = strings.TrimSuffix(url, "/")

	return httpsLXD(url, args)
}

// ConnectSimpleStreams lets you connect to a remote SimpleStreams image server over HTTPs.
//
// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error) {
	logger.Debugf("Connecting to a remote simplestreams server")

	// Cleanup URL
	url = strings.TrimSuffix(url, "/")

	// Use empty args if not specified
	if args == nil {
		args = &ConnectionArgs{}
	}

	// Initialize the client struct
	server := ProtocolSimpleStreams{
		httpHost:        url,
		httpUserAgent:   args.UserAgent,
		httpCertificate: args.TLSServerCert,
	}

	// Setup the HTTP client
	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.InsecureSkipVerify, args.Proxy)
	if err != nil {
		return nil, err
	}
	server.http = httpClient

	// Get simplestreams client
	ssClient := simplestreams.NewClient(url, *httpClient, args.UserAgent)
	server.ssClient = ssClient

	return &server, nil
}

// Internal function called by ConnectLXD and ConnectPublicLXD
func httpsLXD(url string, args *ConnectionArgs) (InstanceServer, error) {
	// Use empty args if not specified
	if args == nil {
		args = &ConnectionArgs{}
	}

	// Initialize the client struct
	server := ProtocolLXD{
		httpCertificate:  args.TLSServerCert,
		httpHost:         url,
		httpProtocol:     "https",
		httpUserAgent:    args.UserAgent,
		bakeryInteractor: args.AuthInteractor,
	}
	if args.AuthType == "candid" {
		server.RequireAuthenticated(true)
	}

	// Setup the HTTP client
	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.InsecureSkipVerify, args.Proxy)
	if err != nil {
		return nil, err
	}

	if args.CookieJar != nil {
		httpClient.Jar = args.CookieJar
	}

	server.http = httpClient
	if args.AuthType == "candid" {
		server.setupBakeryClient()
	}

	// Test the connection and seed the server information
	if !args.SkipGetServer {
		_, _, err := server.GetServer()
		if err != nil {
			return nil, err
		}
	}
	return &server, nil
}
<commit_msg>client/connection: Simplifies ConnectLXDHTTP<commit_after>package lxd

import (
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/simplestreams"
	"gopkg.in/macaroon-bakery.v2/httpbakery"
)

// ConnectionArgs represents a set of common connection properties
type ConnectionArgs struct {
	// TLS certificate of the remote server. If not specified, the system CA is used.
	TLSServerCert string

	// TLS certificate to use for client authentication.
	TLSClientCert string

	// TLS key to use for client authentication.
	TLSClientKey string

	// TLS CA to validate against when in PKI mode.
	TLSCA string

	// User agent string
	UserAgent string

	// Authentication type
	AuthType string

	// Authentication interactor
	AuthInteractor []httpbakery.Interactor

	// Custom proxy
	Proxy func(*http.Request) (*url.URL, error)

	// Custom HTTP Client (used as base for the connection)
	HTTPClient *http.Client

	// Controls whether a client verifies the server's certificate chain and host name.
	InsecureSkipVerify bool

	// Cookie jar
	CookieJar http.CookieJar

	// Skip automatic GetServer request upon connection
	SkipGetServer bool
}

// ConnectLXD lets you connect to a remote LXD daemon over HTTPs.
//
// A client certificate (TLSClientCert) and key (TLSClientKey) must be provided.
//
// If connecting to a LXD daemon running in PKI mode, the PKI CA (TLSCA) must also be provided.
//
// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
func ConnectLXD(url string, args *ConnectionArgs) (InstanceServer, error) {
	logger.Debugf("Connecting to a remote LXD over HTTPs")

	// Cleanup URL
	url = strings.TrimSuffix(url, "/")

	return httpsLXD(url, args)
}

// ConnectLXDHTTP lets you connect to a VM agent over a VM socket.
func ConnectLXDHTTP(args *ConnectionArgs, client *http.Client) (InstanceServer, error) {
	logger.Debugf("Connecting to a VM agent over a VM socket")

	// Use empty args if not specified
	if args == nil {
		args = &ConnectionArgs{}
	}

	// Initialize the client struct
	server := ProtocolLXD{
		httpHost:      "https://custom.socket",
		httpProtocol:  "custom",
		httpUserAgent: args.UserAgent,
	}

	// Setup the HTTP client
	server.http = client

	// Test the connection and seed the server information
	if !args.SkipGetServer {
		serverStatus, _, err := server.GetServer()
		if err != nil {
			return nil, err
		}

		// Record the server certificate
		server.httpCertificate = serverStatus.Environment.Certificate
	}

	return &server, nil
}

// ConnectLXDUnix lets you connect to a remote LXD daemon over a local unix socket.
//
// If the path argument is empty, then $LXD_SOCKET will be used, if
// unset $LXD_DIR/unix.socket will be used and if that one isn't set
// either, then the path will default to /var/lib/lxd/unix.socket.
func ConnectLXDUnix(path string, args *ConnectionArgs) (InstanceServer, error) {
	logger.Debugf("Connecting to a local LXD over a Unix socket")

	// Use empty args if not specified
	if args == nil {
		args = &ConnectionArgs{}
	}

	// Initialize the client struct
	server := ProtocolLXD{
		httpHost:      "http://unix.socket",
		httpUnixPath:  path,
		httpProtocol:  "unix",
		httpUserAgent: args.UserAgent,
	}

	// Determine the socket path
	if path == "" {
		path = os.Getenv("LXD_SOCKET")
		if path == "" {
			lxdDir := os.Getenv("LXD_DIR")
			if lxdDir == "" {
				lxdDir = "/var/lib/lxd"
			}

			path = filepath.Join(lxdDir, "unix.socket")
		}
	}

	// Setup the HTTP client
	httpClient, err := unixHTTPClient(args.HTTPClient, path)
	if err != nil {
		return nil, err
	}
	server.http = httpClient

	// Test the connection and seed the server information
	if !args.SkipGetServer {
		serverStatus, _, err := server.GetServer()
		if err != nil {
			return nil, err
		}

		// Record the server certificate
		server.httpCertificate = serverStatus.Environment.Certificate
	}

	return &server, nil
}

// ConnectPublicLXD lets you connect to a remote public LXD daemon over HTTPs.
//
// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
	logger.Debugf("Connecting to a remote public LXD over HTTPs")

	// Cleanup URL
	url = strings.TrimSuffix(url, "/")

	return httpsLXD(url, args)
}

// ConnectSimpleStreams lets you connect to a remote SimpleStreams image server over HTTPs.
//
// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error) {
	logger.Debugf("Connecting to a remote simplestreams server")

	// Cleanup URL
	url = strings.TrimSuffix(url, "/")

	// Use empty args if not specified
	if args == nil {
		args = &ConnectionArgs{}
	}

	// Initialize the client struct
	server := ProtocolSimpleStreams{
		httpHost:        url,
		httpUserAgent:   args.UserAgent,
		httpCertificate: args.TLSServerCert,
	}

	// Setup the HTTP client
	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.InsecureSkipVerify, args.Proxy)
	if err != nil {
		return nil, err
	}
	server.http = httpClient

	// Get simplestreams client
	ssClient := simplestreams.NewClient(url, *httpClient, args.UserAgent)
	server.ssClient = ssClient

	return &server, nil
}

// Internal function called by ConnectLXD and ConnectPublicLXD
func httpsLXD(url string, args *ConnectionArgs) (InstanceServer, error) {
	// Use empty args if not specified
	if args == nil {
		args = &ConnectionArgs{}
	}

	// Initialize the client struct
	server := ProtocolLXD{
		httpCertificate:  args.TLSServerCert,
		httpHost:         url,
		httpProtocol:     "https",
		httpUserAgent:    args.UserAgent,
		bakeryInteractor: args.AuthInteractor,
	}
	if args.AuthType == "candid" {
		server.RequireAuthenticated(true)
	}

	// Setup the HTTP client
	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.InsecureSkipVerify, args.Proxy)
	if err != nil {
		return nil, err
	}

	if args.CookieJar != nil {
		httpClient.Jar = args.CookieJar
	}

	server.http = httpClient
	if args.AuthType == "candid" {
		server.setupBakeryClient()
	}

	// Test the connection and seed the server information
	if !args.SkipGetServer {
		_, _, err := server.GetServer()
		if err != nil {
			return nil, err
		}
	}
	return &server, nil
}
<|endoftext|>


<commit_before>package bubbles

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sync"
	"time"
)

const (
	// DefaultMaxDocumentsPerBatch is the number of documents a batch needs to
	// have before it is send. This is per connection.
	DefaultMaxDocumentsPerBatch = 10

	// DefaultFlushTimeout is the maximum time we batch something before we try
	// to send it to a server.
	DefaultFlushTimeout = 10 * time.Second

	// DefaultServerTimeout is the time we give ES to respond. This is also the
	// maximum time Stop() will take.
	DefaultServerTimeout = 10 * time.Second

	// DefaultConnCount is the number of connections per hosts.
	DefaultConnCount = 2

	serverErrorWait = 1 * time.Second
)

// Bubbles is the main struct to control a queue of Actions going to the
// ElasticSearch servers.
type Bubbles struct {
	q                chan Action
	retryQ           chan Action
	error            chan ActionError
	quit             chan struct{}
	wg               sync.WaitGroup
	maxDocumentCount int
	connCount        int
	flushTimeout     time.Duration
	serverTimeout    time.Duration
}

type bulkRes struct {
	Took   int                        `json:"took"`
	Items  []map[string]bulkResStatus `json:"items"`
	Errors bool                       `json:"errors"`
}

type bulkResStatus struct {
	Index   string `json:"_index"`
	Type    string `json:"_type"`
	ID      string `json:"_id"`
	Version int    `json:"_version"`
	Status  int    `json:"status"`
	Error   string `json:"error"`
}

// ActionError wraps an Action we won't retry. It implements the error interface.
type ActionError struct {
	Action Action
	Msg    string
	Server string
}

func (e ActionError) Error() string {
	return fmt.Sprintf("%s: %s", e.Server, e.Msg)
}

// opt is any option to New()
type opt func(*Bubbles)

// OptConnCount is an option to New() to specify the number of connections per
// host. The default is DefaultConnCount.
func OptConnCount(n int) opt {
	return func(b *Bubbles) {
		b.connCount = n
	}
}

// OptFlush is an option to New() to specify the flush timeout of a batch. The
// default is DefaultFlushTimeout.
func OptFlush(d time.Duration) opt {
	return func(b *Bubbles) {
		b.flushTimeout = d
	}
}

// OptServerTimeout is an option to New() to specify the timeout of a single
// batch POST to ES. This value is also the maximum time Stop() will take. All
// actions in a bulk which is timed out will be retried. The default is
// DefaultServerTimeout.
func OptServerTimeout(d time.Duration) opt {
	return func(b *Bubbles) {
		b.serverTimeout = d
	}
}

// OptMaxDocs is an option to New() to specify maximum number of documents in a
// single batch. The default is DefaultMaxDocumentsPerBatch.
func OptMaxDocs(n int) opt {
	return func(b *Bubbles) {
		b.maxDocumentCount = n
	}
}

// New makes a new ES bulk inserter. It needs a list with 'ip:port' addresses,
// options are added via the Opt* functions. Be sure to read the Errors()
// channel.
func New(addrs []string, opts ...opt) *Bubbles {
	b := Bubbles{
		q:                make(chan Action),
		error:            make(chan ActionError, 10),
		quit:             make(chan struct{}),
		maxDocumentCount: DefaultMaxDocumentsPerBatch,
		connCount:        DefaultConnCount,
		flushTimeout:     DefaultFlushTimeout,
		serverTimeout:    DefaultServerTimeout,
	}
	for _, o := range opts {
		o(&b)
	}
	b.retryQ = make(chan Action, len(addrs)*b.connCount)

	// Start a go routine per connection per host
	for _, a := range addrs {
		for i := 0; i < b.connCount; i++ {
			b.wg.Add(1)
			go func(a string) {
				client(&b, a)
				b.wg.Done()
			}(a)
		}
	}
	return &b
}

// Errors returns a channel with all actions we won't retry.
func (b *Bubbles) Errors() <-chan ActionError {
	return b.error
}

// Enqueue returns the queue to add Actions in a routine. It will block if all bulk
// processors are busy.
func (b *Bubbles) Enqueue() chan<- Action {
	return b.q
}

// Stop shuts down all ES clients. It'll return all Action entries which were
// not yet processed, or were up for a retry. It can take OptServerTimeout to
// complete.
func (b *Bubbles) Stop() []Action {
	close(b.quit)
	// There is no explicit timeout, we rely on b.serverTimeout to shut down
	// everything.
	b.wg.Wait()

	close(b.error)

	// Collect and return elements which are in flight.
	close(b.retryQ)
	close(b.q)
	pending := make([]Action, 0, len(b.q)+len(b.retryQ))
	for a := range b.q {
		pending = append(pending, a)
	}
	for a := range b.retryQ {
		pending = append(pending, a)
	}
	return pending
}

// client talks to ES. This runs in a go routine in a loop and deals with a
// single ES address.
func client(b *Bubbles, addr string) {
	url := fmt.Sprintf("http://%s/_bulk", addr) // TODO: https?
	// log.Printf("starting client to %s\n", addr)
	// defer log.Printf("stopping client to %s\n", addr)

	cl := http.Client{
		Timeout:       b.serverTimeout,
		CheckRedirect: noRedirect,
	}

	for {
		select {
		case <-b.quit:
			return
		default:
		}
		if err := runBatch(b, cl, url); err != nil {
			// runBatch only returns an error on server error.
			log.Printf("server error: %s", err)
			select {
			case <-b.quit:
			case <-time.After(serverErrorWait):
				// TODO: backoff period
			}
		}
	}

}

// runBatch gathers and deals with a batch of actions. It'll return a non-nil
// error if the whole server gave an error.
func runBatch(b *Bubbles, cl http.Client, url string) error {
	actions := make([]Action, 0, b.maxDocumentCount)
	// First use all retry actions.
retry:
	for len(actions) < b.maxDocumentCount {
		select {
		case a := <-b.retryQ:
			actions = append(actions, a)
		default:
			// no more retry actions queued
			break retry
		}
	}

	var t <-chan time.Time
gather:
	for len(actions) < b.maxDocumentCount {
		if t == nil && len(actions) > 0 {
			// Set timeout on the first element we read
			t = time.After(b.flushTimeout)
		}
		select {
		case <-b.quit:
			for _, a := range actions {
				b.retryQ <- a
			}
			return nil
		case <-t:
			// this case is not enabled until we've got an action
			break gather
		case a := <-b.retryQ:
			actions = append(actions, a)
		case a := <-b.q:
			actions = append(actions, a)
		}
	}
	if len(actions) == 0 {
		// no actions. Weird.
		return nil
	}

	res, err := postActions(cl, url, actions)
	if err != nil {
		// A server error. Retry these actions later.
		for _, a := range actions {
			b.retryQ <- a
		}
		return err
	}

	// Server has accepted the request an sich, but there can be errors in the
	// individual actions.
	if !res.Errors {
		// Simple case, no errors present.
		return nil
	}
	// Figure out which actions have errors.
	for i, e := range res.Items {
		a := actions[i] // TODO: sanity check
		el, ok := e[string(a.Type)]
		if !ok {
			// TODO: this
			fmt.Printf("Non matching action!\n")
			continue
		}

		c := el.Status
		switch {
		case c >= 200 && c < 300:
			// Document accepted by ES.
		case c >= 400 && c < 500:
			// Some error. Nothing we can do with it.
			b.error <- ActionError{
				Action: a,
				Msg:    fmt.Sprintf("error %d: %s", c, el.Error),
				Server: url,
			}
		case c >= 500 && c < 600:
			// Server error. Retry it.
			b.retryQ <- a
		default:
			// No idea.
			fmt.Printf("unexpected status: %d. Ignoring document.\n", c)
		}
	}
	return nil
}

func postActions(cl http.Client, url string, actions []Action) (*bulkRes, error) {
	// TODO: bytestring as argument
	// TODO: don't chunk.
	buf := bytes.Buffer{}
	for _, a := range actions {
		buf.Write(a.Buf())
	}

	resp, err := cl.Post(url, "application/json", &buf)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("status %d: %s", resp.StatusCode, string(body))
	}

	var bulk bulkRes
	if err := json.Unmarshal(body, &bulk); err != nil {
		return nil, err
	}
	return &bulk, nil
}

func noRedirect(req *http.Request, via []*http.Request) error {
	return errors.New("no redirect")
}
<commit_msg>Export Opt, pleases golint.<commit_after>package bubbles

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sync"
	"time"
)

const (
	// DefaultMaxDocumentsPerBatch is the number of documents a batch needs to
	// have before it is send. This is per connection.
	DefaultMaxDocumentsPerBatch = 10

	// DefaultFlushTimeout is the maximum time we batch something before we try
	// to send it to a server.
	DefaultFlushTimeout = 10 * time.Second

	// DefaultServerTimeout is the time we give ES to respond. This is also the
	// maximum time Stop() will take.
	DefaultServerTimeout = 10 * time.Second

	// DefaultConnCount is the number of connections per hosts.
	DefaultConnCount = 2

	serverErrorWait = 1 * time.Second
)

// Bubbles is the main struct to control a queue of Actions going to the
// ElasticSearch servers.
type Bubbles struct {
	q                chan Action
	retryQ           chan Action
	error            chan ActionError
	quit             chan struct{}
	wg               sync.WaitGroup
	maxDocumentCount int
	connCount        int
	flushTimeout     time.Duration
	serverTimeout    time.Duration
}

type bulkRes struct {
	Took   int                        `json:"took"`
	Items  []map[string]bulkResStatus `json:"items"`
	Errors bool                       `json:"errors"`
}

type bulkResStatus struct {
	Index   string `json:"_index"`
	Type    string `json:"_type"`
	ID      string `json:"_id"`
	Version int    `json:"_version"`
	Status  int    `json:"status"`
	Error   string `json:"error"`
}

// ActionError wraps an Action we won't retry. It implements the error interface.
type ActionError struct {
	Action Action
	Msg    string
	Server string
}

func (e ActionError) Error() string {
	return fmt.Sprintf("%s: %s", e.Server, e.Msg)
}

// Opt is any option to New()
type Opt func(*Bubbles)

// OptConnCount is an option to New() to specify the number of connections per
// host. The default is DefaultConnCount.
func OptConnCount(n int) Opt {
	return func(b *Bubbles) {
		b.connCount = n
	}
}

// OptFlush is an option to New() to specify the flush timeout of a batch. The
// default is DefaultFlushTimeout.
func OptFlush(d time.Duration) Opt {
	return func(b *Bubbles) {
		b.flushTimeout = d
	}
}

// OptServerTimeout is an option to New() to specify the timeout of a single
// batch POST to ES. This value is also the maximum time Stop() will take. All
// actions in a bulk which is timed out will be retried. The default is
// DefaultServerTimeout.
func OptServerTimeout(d time.Duration) Opt {
	return func(b *Bubbles) {
		b.serverTimeout = d
	}
}

// OptMaxDocs is an option to New() to specify maximum number of documents in a
// single batch. The default is DefaultMaxDocumentsPerBatch.
func OptMaxDocs(n int) Opt {
	return func(b *Bubbles) {
		b.maxDocumentCount = n
	}
}

// New makes a new ES bulk inserter. It needs a list with 'ip:port' addresses,
// options are added via the Opt* functions. Be sure to read the Errors()
// channel.
func New(addrs []string, opts ...Opt) *Bubbles {
	b := Bubbles{
		q:                make(chan Action),
		error:            make(chan ActionError, 10),
		quit:             make(chan struct{}),
		maxDocumentCount: DefaultMaxDocumentsPerBatch,
		connCount:        DefaultConnCount,
		flushTimeout:     DefaultFlushTimeout,
		serverTimeout:    DefaultServerTimeout,
	}
	for _, o := range opts {
		o(&b)
	}
	b.retryQ = make(chan Action, len(addrs)*b.connCount)

	// Start a go routine per connection per host
	for _, a := range addrs {
		for i := 0; i < b.connCount; i++ {
			b.wg.Add(1)
			go func(a string) {
				client(&b, a)
				b.wg.Done()
			}(a)
		}
	}
	return &b
}

// Errors returns a channel with all actions we won't retry.
func (b *Bubbles) Errors() <-chan ActionError {
	return b.error
}

// Enqueue returns the queue to add Actions in a routine. It will block if all bulk
// processors are busy.
func (b *Bubbles) Enqueue() chan<- Action {
	return b.q
}

// Stop shuts down all ES clients. It'll return all Action entries which were
// not yet processed, or were up for a retry. It can take OptServerTimeout to
// complete.
func (b *Bubbles) Stop() []Action {
	close(b.quit)
	// There is no explicit timeout, we rely on b.serverTimeout to shut down
	// everything.
	b.wg.Wait()

	close(b.error)

	// Collect and return elements which are in flight.
	close(b.retryQ)
	close(b.q)
	pending := make([]Action, 0, len(b.q)+len(b.retryQ))
	for a := range b.q {
		pending = append(pending, a)
	}
	for a := range b.retryQ {
		pending = append(pending, a)
	}
	return pending
}

// client talks to ES. This runs in a go routine in a loop and deals with a
// single ES address.
func client(b *Bubbles, addr string) {
	url := fmt.Sprintf("http://%s/_bulk", addr) // TODO: https?
	// log.Printf("starting client to %s\n", addr)
	// defer log.Printf("stopping client to %s\n", addr)

	cl := http.Client{
		Timeout:       b.serverTimeout,
		CheckRedirect: noRedirect,
	}

	for {
		select {
		case <-b.quit:
			return
		default:
		}
		if err := runBatch(b, cl, url); err != nil {
			// runBatch only returns an error on server error.
			log.Printf("server error: %s", err)
			select {
			case <-b.quit:
			case <-time.After(serverErrorWait):
				// TODO: backoff period
			}
		}
	}

}

// runBatch gathers and deals with a batch of actions. It'll return a non-nil
// error if the whole server gave an error.
func runBatch(b *Bubbles, cl http.Client, url string) error {
	actions := make([]Action, 0, b.maxDocumentCount)
	// First use all retry actions.
retry:
	for len(actions) < b.maxDocumentCount {
		select {
		case a := <-b.retryQ:
			actions = append(actions, a)
		default:
			// no more retry actions queued
			break retry
		}
	}

	var t <-chan time.Time
gather:
	for len(actions) < b.maxDocumentCount {
		if t == nil && len(actions) > 0 {
			// Set timeout on the first element we read
			t = time.After(b.flushTimeout)
		}
		select {
		case <-b.quit:
			for _, a := range actions {
				b.retryQ <- a
			}
			return nil
		case <-t:
			// this case is not enabled until we've got an action
			break gather
		case a := <-b.retryQ:
			actions = append(actions, a)
		case a := <-b.q:
			actions = append(actions, a)
		}
	}
	if len(actions) == 0 {
		// no actions. Weird.
		return nil
	}

	res, err := postActions(cl, url, actions)
	if err != nil {
		// A server error. Retry these actions later.
		for _, a := range actions {
			b.retryQ <- a
		}
		return err
	}

	// Server has accepted the request an sich, but there can be errors in the
	// individual actions.
	if !res.Errors {
		// Simple case, no errors present.
		return nil
	}
	// Figure out which actions have errors.
	for i, e := range res.Items {
		a := actions[i] // TODO: sanity check
		el, ok := e[string(a.Type)]
		if !ok {
			// TODO: this
			fmt.Printf("Non matching action!\n")
			continue
		}

		c := el.Status
		switch {
		case c >= 200 && c < 300:
			// Document accepted by ES.
		case c >= 400 && c < 500:
			// Some error. Nothing we can do with it.
			b.error <- ActionError{
				Action: a,
				Msg:    fmt.Sprintf("error %d: %s", c, el.Error),
				Server: url,
			}
		case c >= 500 && c < 600:
			// Server error. Retry it.
			b.retryQ <- a
		default:
			// No idea.
			fmt.Printf("unexpected status: %d. Ignoring document.\n", c)
		}
	}
	return nil
}

func postActions(cl http.Client, url string, actions []Action) (*bulkRes, error) {
	// TODO: bytestring as argument
	// TODO: don't chunk.
	buf := bytes.Buffer{}
	for _, a := range actions {
		buf.Write(a.Buf())
	}

	resp, err := cl.Post(url, "application/json", &buf)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("status %d: %s", resp.StatusCode, string(body))
	}

	var bulk bulkRes
	if err := json.Unmarshal(body, &bulk); err != nil {
		return nil, err
	}
	return &bulk, nil
}

func noRedirect(req *http.Request, via []*http.Request) error {
	return errors.New("no redirect")
}
<|endoftext|>


<commit_before>package bugsnag

import (
	"github.com/bugsnag/bugsnag-go/errors"
	"log"
	"net/http"
	"os"
	"sync"

	// Fixes a bug with SHA-384 intermediate certs on some platforms.
	// - https://github.com/bugsnag/bugsnag-go/issues/9
	_ "crypto/sha512"
)

// The current version of bugsnag-go.
const VERSION = "1.0.2"

var once sync.Once
var middleware middlewareStack

// The configuration for the default bugsnag notifier.
var Config Configuration

var defaultNotifier = Notifier{&Config, nil}

// Configure Bugsnag. The only required setting is the APIKey, which can be
// obtained by clicking on "Settings" in your Bugsnag dashboard. This function
// is also responsible for installing the global panic handler, so it should be
// called as early as possible in your initialization process.
func Configure(config Configuration) {
	Config.update(&config)
	once.Do(Config.PanicHandler)
}

// Notify sends an error to Bugsnag along with the current stack trace. The
// rawData is used to send extra information along with the error. For example
// you can pass the current http.Request to Bugsnag to see information about it
// in the dashboard, or set the severity of the notification.
func Notify(err error, rawData ...interface{}) error {
	return defaultNotifier.Notify(errors.New(err, 1), rawData...)
}

// AutoNotify logs a panic on a goroutine and then repanics.
// It should only be used in places that have existing panic handlers further
// up the stack. See bugsnag.Recover().  The rawData is used to send extra
// information along with any panics that are handled this way.
// Usage: defer bugsnag.AutoNotify()
func AutoNotify(rawData ...interface{}) {
	if err := recover(); err != nil {
		rawData = defaultNotifier.addDefaultSeverity(rawData, SeverityError)
		defaultNotifier.Notify(errors.New(err, 2), rawData...)
		panic(err)
	}
}

// Recover logs a panic on a goroutine and then recovers.
// The rawData is used to send extra information along with
// any panics that are handled this way
// Usage: defer bugsnag.Recover()
func Recover(rawData ...interface{}) {
	if err := recover(); err != nil {
		rawData = defaultNotifier.addDefaultSeverity(rawData, SeverityWarning)
		defaultNotifier.Notify(errors.New(err, 2), rawData...)
	}
}

// OnBeforeNotify adds a callback to be run before a notification is sent to
// Bugsnag.  It can be used to modify the event or its MetaData. Changes made
// to the configuration are local to notifying about this event. To prevent the
// event from being sent to Bugsnag return an error, this error will be
// returned from bugsnag.Notify() and the event will not be sent.
func OnBeforeNotify(callback func(event *Event, config *Configuration) error) {
	middleware.OnBeforeNotify(callback)
}

// Handler creates an http Handler that notifies Bugsnag any panics that
// happen. It then repanics so that the default http Server panic handler can
// handle the panic too. The rawData is used to send extra information along
// with any panics that are handled this way.
func Handler(h http.Handler, rawData ...interface{}) http.Handler {
	notifier := New(rawData...)
	if h == nil {
		h = http.DefaultServeMux
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer notifier.AutoNotify(r)
		h.ServeHTTP(w, r)
	})
}

// HandlerFunc creates an http HandlerFunc that notifies Bugsnag about any
// panics that happen. It then repanics so that the default http Server panic
// handler can handle the panic too. The rawData is used to send extra
// information along with any panics that are handled this way. If you have
// already wrapped your http server using bugsnag.Handler() you don't also need
// to wrap each HandlerFunc.
func HandlerFunc(h http.HandlerFunc, rawData ...interface{}) http.HandlerFunc {
	notifier := New(rawData...)

	return func(w http.ResponseWriter, r *http.Request) {
		defer notifier.AutoNotify(r)
		h(w, r)
	}
}

func init() {
	// Set up builtin middlewarez
	OnBeforeNotify(httpRequestMiddleware)

	// Default configuration
	Config.update(&Configuration{
		APIKey:        "",
		Endpoint:      "https://notify.bugsnag.com/",
		Hostname:      "",
		AppVersion:    "",
		ReleaseStage:  "",
		ParamsFilters: []string{"password", "secret"},
		// * for app-engine
		ProjectPackages:     []string{"main*"},
		NotifyReleaseStages: nil,
		Logger:              log.New(os.Stdout, log.Prefix(), log.Flags()),
		PanicHandler:        defaultPanicHandler,
		Transport:           http.DefaultTransport,
	})

	hostname, err := os.Hostname()
	if err == nil {
		Config.Hostname = hostname
	}
}
<commit_msg>v1.0.3<commit_after>package bugsnag

import (
	"github.com/bugsnag/bugsnag-go/errors"
	"log"
	"net/http"
	"os"
	"sync"

	// Fixes a bug with SHA-384 intermediate certs on some platforms.
	// - https://github.com/bugsnag/bugsnag-go/issues/9
	_ "crypto/sha512"
)

// The current version of bugsnag-go.
const VERSION = "1.0.3"

var once sync.Once
var middleware middlewareStack

// The configuration for the default bugsnag notifier.
var Config Configuration

var defaultNotifier = Notifier{&Config, nil}

// Configure Bugsnag. The only required setting is the APIKey, which can be
// obtained by clicking on "Settings" in your Bugsnag dashboard. This function
// is also responsible for installing the global panic handler, so it should be
// called as early as possible in your initialization process.
func Configure(config Configuration) {
	Config.update(&config)
	once.Do(Config.PanicHandler)
}

// Notify sends an error to Bugsnag along with the current stack trace. The
// rawData is used to send extra information along with the error. For example
// you can pass the current http.Request to Bugsnag to see information about it
// in the dashboard, or set the severity of the notification.
func Notify(err error, rawData ...interface{}) error {
	return defaultNotifier.Notify(errors.New(err, 1), rawData...)
}

// AutoNotify logs a panic on a goroutine and then repanics.
// It should only be used in places that have existing panic handlers further
// up the stack. See bugsnag.Recover().  The rawData is used to send extra
// information along with any panics that are handled this way.
// Usage: defer bugsnag.AutoNotify()
func AutoNotify(rawData ...interface{}) {
	if err := recover(); err != nil {
		rawData = defaultNotifier.addDefaultSeverity(rawData, SeverityError)
		defaultNotifier.Notify(errors.New(err, 2), rawData...)
		panic(err)
	}
}

// Recover logs a panic on a goroutine and then recovers.
// The rawData is used to send extra information along with
// any panics that are handled this way
// Usage: defer bugsnag.Recover()
func Recover(rawData ...interface{}) {
	if err := recover(); err != nil {
		rawData = defaultNotifier.addDefaultSeverity(rawData, SeverityWarning)
		defaultNotifier.Notify(errors.New(err, 2), rawData...)
	}
}

// OnBeforeNotify adds a callback to be run before a notification is sent to
// Bugsnag.  It can be used to modify the event or its MetaData. Changes made
// to the configuration are local to notifying about this event. To prevent the
// event from being sent to Bugsnag return an error, this error will be
// returned from bugsnag.Notify() and the event will not be sent.
func OnBeforeNotify(callback func(event *Event, config *Configuration) error) {
	middleware.OnBeforeNotify(callback)
}

// Handler creates an http Handler that notifies Bugsnag any panics that
// happen. It then repanics so that the default http Server panic handler can
// handle the panic too. The rawData is used to send extra information along
// with any panics that are handled this way.
func Handler(h http.Handler, rawData ...interface{}) http.Handler {
	notifier := New(rawData...)
	if h == nil {
		h = http.DefaultServeMux
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer notifier.AutoNotify(r)
		h.ServeHTTP(w, r)
	})
}

// HandlerFunc creates an http HandlerFunc that notifies Bugsnag about any
// panics that happen. It then repanics so that the default http Server panic
// handler can handle the panic too. The rawData is used to send extra
// information along with any panics that are handled this way. If you have
// already wrapped your http server using bugsnag.Handler() you don't also need
// to wrap each HandlerFunc.
func HandlerFunc(h http.HandlerFunc, rawData ...interface{}) http.HandlerFunc {
	notifier := New(rawData...)

	return func(w http.ResponseWriter, r *http.Request) {
		defer notifier.AutoNotify(r)
		h(w, r)
	}
}

func init() {
	// Set up builtin middlewarez
	OnBeforeNotify(httpRequestMiddleware)

	// Default configuration
	Config.update(&Configuration{
		APIKey:        "",
		Endpoint:      "https://notify.bugsnag.com/",
		Hostname:      "",
		AppVersion:    "",
		ReleaseStage:  "",
		ParamsFilters: []string{"password", "secret"},
		// * for app-engine
		ProjectPackages:     []string{"main*"},
		NotifyReleaseStages: nil,
		Logger:              log.New(os.Stdout, log.Prefix(), log.Flags()),
		PanicHandler:        defaultPanicHandler,
		Transport:           http.DefaultTransport,
	})

	hostname, err := os.Hostname()
	if err == nil {
		Config.Hostname = hostname
	}
}
<|endoftext|>


<commit_before>package dns

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

const normal string = `
# This file is automatically generated.
#
domain somedomain.com
nameserver 10.28.10.2
nameserver 11.28.10.1
`

const missingNewline string = `
# This file is automatically generated.
#
domain somedomain.com
nameserver 10.28.10.2
nameserver 11.28.10.1` // <- NOTE: NO newline.

func testConfig(t *testing.T, data string) {
	tempDir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatalf("TempDir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	path := filepath.Join(tempDir, "resolv.conf")
	if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}
	cc, err := ClientConfigFromFile(path)
	if err != nil {
		t.Errorf("error parsing resolv.conf: %s", err)
	}
	if l := len(cc.Servers); l != 2 {
		t.Errorf("incorrect number of nameservers detected: %d", l)
	}
	if l := len(cc.Search); l != 1 {
		t.Errorf("domain directive not parsed correctly: %v", cc.Search)
	} else {
		if cc.Search[0] != "somedomain.com" {
			t.Errorf("domain is unexpected: %v", cc.Search[0])
		}
	}
}

// Test we can read a normally formed resolv.conf.
func TestNameserver(t *testing.T) {
	testConfig(t, normal)
}

// Don't drop the last line if there is no newline before EOF.
func TestMissingFinalNewLine(t *testing.T) {
	testConfig(t, missingNewline)

}
<commit_msg>Very small cleanups<commit_after>package dns

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

const normal string = `
# Comment
domain somedomain.com
nameserver 10.28.10.2
nameserver 11.28.10.1
`

const missingNewline string = `
domain somedomain.com
nameserver 10.28.10.2
nameserver 11.28.10.1` // <- NOTE: NO newline.

func testConfig(t *testing.T, data string) {
	tempDir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatalf("TempDir: %v", err)
	}
	defer os.RemoveAll(tempDir)

	path := filepath.Join(tempDir, "resolv.conf")
	if err := ioutil.WriteFile(path, []byte(data), 0644); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}
	cc, err := ClientConfigFromFile(path)
	if err != nil {
		t.Errorf("error parsing resolv.conf: %s", err)
	}
	if l := len(cc.Servers); l != 2 {
		t.Errorf("incorrect number of nameservers detected: %d", l)
	}
	if l := len(cc.Search); l != 1 {
		t.Errorf("domain directive not parsed correctly: %v", cc.Search)
	} else {
		if cc.Search[0] != "somedomain.com" {
			t.Errorf("domain is unexpected: %v", cc.Search[0])
		}
	}
}

func TestNameserver(t *testing.T) {
	testConfig(t, normal)
}

func TestMissingFinalNewLine(t *testing.T) {
	testConfig(t, missingNewline)
}
<|endoftext|>


<commit_before>package wgsqlx

import (
	"bytes"
	"database/sql/driver"
	"errors"
	"math"
	"strconv"
	"strings"
)

var (
	IntPricePrecision int     = 2
	IntPricePow       float64 = math.Pow10(IntPricePrecision)
)

func SetIntPricePrecision(e int) {
	IntPricePrecision = e
	IntPricePow = math.Pow10(IntPricePrecision)
}

type IntPrice int64

func NewIntPrice(i int64) IntPrice {
	return IntPrice(i)
}

func NewIntPriceFloat(i float64) IntPrice {
	return IntPrice(int64(i * IntPricePow))
}

func (p *IntPrice) MarshalJSON() ([]byte, error) {
	if p == nil {
		return nil, errors.New("MarshalJSON on nil pointer")
	}
	var b bytes.Buffer
	b.WriteByte('"')
	b.WriteString(p.StringFloat())
	b.WriteByte('"')
	return b.Bytes(), nil
}

func (p *IntPrice) UnmarshalJSON(data []byte) error {
	if p == nil {
		return errors.New("UnmarshalJSON on nil pointer")
	} else if f, err := strconv.ParseFloat(strings.Replace(string(data), `"`, ``, -1), 64); err != nil {
		return err
	} else {
		p.SetFloat64(f)
	}
	return nil
}

func (p IntPrice) Value() (driver.Value, error) {
	return p.Int64(), nil
}

func (p *IntPrice) Scan(src interface{}) error {
	switch o := src.(type) {
	case int, int8, int16, int32, int64:
		*p = IntPrice(o.(int64))
	case string:
		i, err := strconv.ParseInt(o, 10, 64)
		if err != nil {
			return err
		}
		*p = IntPrice(i)
	case []byte:
		return p.Scan(string(o))
	default:
		return errors.New("Incompatible type for IntPrice")
	}
	return nil
}

func (p IntPrice) Int64() int64 {
	return int64(p)
}

func (p *IntPrice) SetInt64(i int64) *IntPrice {
	*p = IntPrice(i)
	return p
}

func (p IntPrice) Float64() float64 {
	return float64(p) / IntPricePow
}

func (p *IntPrice) SetFloat64(f float64) *IntPrice {
	*p = IntPrice(int64(f * IntPricePow))
	return p
}

func (p IntPrice) String() string {
	return strconv.FormatInt(int64(p), 10)
}

func (p IntPrice) StringFloat() string {
	return strconv.FormatFloat(p.Float64(), 'f', IntPricePrecision, 64)
}

func (p *IntPrice) IsPositive() bool {
	return p.Int64() > 0
}

func (p *IntPrice) IsNegative() bool {
	return p.Int64() < 0
}

func (p *IntPrice) Zero() {
	p.SetInt64(0)
}

func (p *IntPrice) IsZero() bool {
	return p.Int64() == 0
}

func (p *IntPrice) Equal(i int64) bool {
	return p.Int64() == i
}

func (p *IntPrice) LessThan(i int64) bool {
	return p.Int64() < i
}

func (p *IntPrice) LessEqual(i int64) bool {
	return p.Int64() <= i
}

func (p *IntPrice) GreaterThan(i int64) bool {
	return p.Int64() > i
}

func (p *IntPrice) GreaterEqual(i int64) bool {
	return p.Int64() >= i
}

// rounded p+q and returns p
func (p *IntPrice) Add(q IntPrice) *IntPrice {
	return p.SetInt64(p.Int64() + q.Int64())
}

// rounded p-q and returns p
func (p *IntPrice) Sub(q IntPrice) *IntPrice {
	return p.SetInt64(p.Int64() - q.Int64())
}

// rounded product p*q and returns p
func (p *IntPrice) Mul(q IntPrice) *IntPrice {
	return p.SetInt64(p.Int64() * q.Int64())
}

// rounded quotient p/q and returns p
func (p *IntPrice) Quo(q IntPrice) *IntPrice {
	return p.SetInt64(p.Int64() / q.Int64())
}

// rounded p+x... and returns p
func (p *IntPrice) Sum(x ...IntPrice) *IntPrice {
	for _, y := range x {
		p.Add(y)
	}
	return p
}

// rounded p-x... and returns p
func (p *IntPrice) Diff(x ...IntPrice) *IntPrice {
	for _, y := range x {
		p.Sub(y)
	}
	return p
}

// rounded p+x... and returns
func (p IntPrice) GetSum(x ...IntPrice) IntPrice {
	sum := p.Int64()
	for _, y := range x {
		sum += y.Int64()
	}
	return NewIntPrice(sum)
}

// rounded p+x... and returns
func (p IntPrice) GetDiff(x ...IntPrice) IntPrice {
	diff := p.Int64()
	for _, y := range x {
		diff -= y.Int64()
	}
	return NewIntPrice(diff)
}

// returns negation
func (p IntPrice) GetNegation() IntPrice {
	return NewIntPrice(-p.Int64())
}

// Cmp compares p and p and returns:
//
//   -1 if p <  p
//    0 if p == p (incl. -0 == 0, -Inf == -Inf, and +Inf == +Inf)
//   +1 if p >  p
//
func (p IntPrice) Cmp(q IntPrice) int {
	switch {
	case p.Int64() < q.Int64():
		return -1
	case p.Int64() > q.Int64():
		return +1
	}
	return 0
}
<commit_msg>fix: sql int price func<commit_after>package wgsqlx

import (
	"bytes"
	"database/sql/driver"
	"errors"
	"math"
	"strconv"
	"strings"
)

var (
	IntPricePrecision int     = 2
	IntPricePow       float64 = math.Pow10(IntPricePrecision)
)

func SetIntPricePrecision(e int) {
	IntPricePrecision = e
	IntPricePow = math.Pow10(IntPricePrecision)
}

type IntPrice int64

func NewIntPrice(i int64) IntPrice {
	return IntPrice(i)
}

func NewIntPriceFloat(i float64) IntPrice {
	return IntPrice(int64(i * IntPricePow))
}

func (p *IntPrice) MarshalJSON() ([]byte, error) {
	if p == nil {
		return nil, errors.New("MarshalJSON on nil pointer")
	}
	var b bytes.Buffer
	b.WriteByte('"')
	b.WriteString(p.StringFloat())
	b.WriteByte('"')
	return b.Bytes(), nil
}

func (p *IntPrice) UnmarshalJSON(data []byte) error {
	if p == nil {
		return errors.New("UnmarshalJSON on nil pointer")
	} else if f, err := strconv.ParseFloat(strings.Replace(string(data), `"`, ``, -1), 64); err != nil {
		return err
	} else {
		p.SetFloat64(f)
	}
	return nil
}

func (p IntPrice) Value() (driver.Value, error) {
	return p.Int64(), nil
}

func (p *IntPrice) Scan(src interface{}) error {
	switch o := src.(type) {
	case int, int8, int16, int32, int64:
		*p = IntPrice(o.(int64))
	case string:
		i, err := strconv.ParseInt(o, 10, 64)
		if err != nil {
			return err
		}
		*p = IntPrice(i)
	case []byte:
		return p.Scan(string(o))
	default:
		return errors.New("Incompatible type for IntPrice")
	}
	return nil
}

func (p IntPrice) Int64() int64 {
	return int64(p)
}

func (p *IntPrice) SetInt64(i int64) *IntPrice {
	*p = IntPrice(i)
	return p
}

func (p IntPrice) Float64() float64 {
	return float64(p) / IntPricePow
}

func (p *IntPrice) SetFloat64(f float64) *IntPrice {
	*p = IntPrice(int64(f * IntPricePow))
	return p
}

func (p IntPrice) String() string {
	return strconv.FormatInt(int64(p), 10)
}

func (p IntPrice) StringFloat() string {
	return strconv.FormatFloat(p.Float64(), 'f', IntPricePrecision, 64)
}

func (p *IntPrice) IsPositive() bool {
	return p.Int64() > 0
}

func (p *IntPrice) IsNegative() bool {
	return p.Int64() < 0
}

func (p *IntPrice) Zero() {
	p.SetInt64(0)
}

func (p *IntPrice) IsZero() bool {
	return p.Int64() == 0
}

func (p *IntPrice) Equal(f float64) bool {
	return p.Float64() == f
}

func (p *IntPrice) LessThan(f float64) bool {
	return p.Float64() < f
}

func (p *IntPrice) LessEqual(f float64) bool {
	return p.Float64() <= f
}

func (p *IntPrice) GreaterThan(f float64) bool {
	return p.Float64() > f
}

func (p *IntPrice) GreaterEqual(f float64) bool {
	return p.Float64() >= f
}

// rounded p+q and returns p
func (p *IntPrice) Add(q IntPrice) *IntPrice {
	return p.SetInt64(p.Int64() + q.Int64())
}

// rounded p-q and returns p
func (p *IntPrice) Sub(q IntPrice) *IntPrice {
	return p.SetInt64(p.Int64() - q.Int64())
}

// rounded product p*q and returns p
func (p *IntPrice) Mul(q IntPrice) *IntPrice {
	return p.SetInt64(p.Int64() * q.Int64())
}

// rounded quotient p/q and returns p
func (p *IntPrice) Quo(q IntPrice) *IntPrice {
	return p.SetInt64(p.Int64() / q.Int64())
}

// rounded p+x... and returns p
func (p *IntPrice) Sum(x ...IntPrice) *IntPrice {
	for _, y := range x {
		p.Add(y)
	}
	return p
}

// rounded p-x... and returns p
func (p *IntPrice) Diff(x ...IntPrice) *IntPrice {
	for _, y := range x {
		p.Sub(y)
	}
	return p
}

// rounded p+x... and returns
func (p IntPrice) GetSum(x ...IntPrice) IntPrice {
	sum := p.Int64()
	for _, y := range x {
		sum += y.Int64()
	}
	return NewIntPrice(sum)
}

// rounded p+x... and returns
func (p IntPrice) GetDiff(x ...IntPrice) IntPrice {
	diff := p.Int64()
	for _, y := range x {
		diff -= y.Int64()
	}
	return NewIntPrice(diff)
}

// returns negation
func (p IntPrice) GetNegation() IntPrice {
	return NewIntPrice(-p.Int64())
}

// Cmp compares p and p and returns:
//
//   -1 if p <  p
//    0 if p == p (incl. -0 == 0, -Inf == -Inf, and +Inf == +Inf)
//   +1 if p >  p
//
func (p IntPrice) Cmp(q IntPrice) int {
	switch {
	case p.Int64() < q.Int64():
		return -1
	case p.Int64() > q.Int64():
		return +1
	}
	return 0
}
<|endoftext|>


<commit_before>package users

import (
	"encoding/json"
	"errors"
	"net/http"

	h "github.com/ernestio/api-gateway/helpers"
	"github.com/ernestio/api-gateway/models"
)

// Update : responds to PUT /users/:id: by updating an existing
// user
func Update(au models.User, user string, body []byte) (int, []byte) {
	var u models.User
	var existing models.User

	if err := u.Map(body); err != nil {
		h.L.Error(err.Error())
		return 400, []byte(err.Error())
	}

	err := u.Validate()
	if err != nil {
		h.L.Error(err.Error())
		return http.StatusBadRequest, models.NewJSONError(err.Error())
	}

	if u.Username != user {
		return 400, models.NewJSONError("Project name does not match payload name")
	}

	// Check if authenticated user is admin or updating itself
	if !u.CanBeChangedBy(au) {
		err := errors.New("You're not allowed to perform this action, please contact your admin")
		h.L.Error(err.Error())
		return 403, models.NewJSONError(err.Error())
	}

	// Check user exists
	if err := au.FindByUserName(user, &existing); err != nil {
		if err := au.FindByID(user, &existing); err != nil {
			h.L.Error(err.Error())
			return 404, models.NewJSONError("Specified user not found")
		}
	}

	if existing.ID == 0 {
		err := errors.New("Specified user not found")
		h.L.Error(err.Error())
		return 404, models.NewJSONError(err.Error())
	}

	u.Username = existing.Username

	if !au.IsAdmin() && existing.Username != au.Username {
		err := errors.New("You're not allowed to perform this action, please contact your admin")
		h.L.Error(err.Error())
		return 403, models.NewJSONError(err.Error())
	}

	if !au.IsAdmin() && existing.IsAdmin() != u.IsAdmin() {
		err := errors.New("You're not allowed to perform this action, please contact your admin")
		h.L.Error(err.Error())
		return 403, models.NewJSONError(err.Error())
	}

	if u.Password != nil {
		err := u.Validate()
		if err != nil {
			return 400, models.NewJSONError(err.Error())
		}

		// Check the old password if it is present
		if u.OldPassword != nil && !existing.ValidPassword(*u.OldPassword) {
			err := errors.New("Provided credentials are not valid")
			h.L.Error(err.Error())
			return 403, models.NewJSONError(err.Error())
		}
	}

	if err := u.Save(); err != nil {
		h.L.Error(err.Error())
		return 500, models.NewJSONError("Error updating user")
	}

	if existing.MFA == nil {
		existing.MFA = h.Bool(false)
	}

	if u.MFA != nil {
		if *u.MFA && !*existing.MFA {
			mfaSecret := u.MFASecret
			u.Redact()
			u.MFASecret = mfaSecret
		} else {
			u.Redact()
		}
	} else {
		u.Redact()
	}

	body, err = json.Marshal(u)
	if err != nil {
		return 500, models.NewJSONError("Internal server error")
	}

	return http.StatusOK, body
}
<commit_msg>fixing user update error message<commit_after>package users

import (
	"encoding/json"
	"errors"
	"net/http"

	h "github.com/ernestio/api-gateway/helpers"
	"github.com/ernestio/api-gateway/models"
)

// Update : responds to PUT /users/:id: by updating an existing
// user
func Update(au models.User, user string, body []byte) (int, []byte) {
	var u models.User
	var existing models.User

	if err := u.Map(body); err != nil {
		h.L.Error(err.Error())
		return 400, []byte(err.Error())
	}

	err := u.Validate()
	if err != nil {
		h.L.Error(err.Error())
		return http.StatusBadRequest, models.NewJSONError(err.Error())
	}

	if u.Username != user {
		return 400, models.NewJSONError("Username does not match payload name")
	}

	// Check if authenticated user is admin or updating itself
	if !u.CanBeChangedBy(au) {
		err := errors.New("You're not allowed to perform this action, please contact your admin")
		h.L.Error(err.Error())
		return 403, models.NewJSONError(err.Error())
	}

	// Check user exists
	if err := au.FindByUserName(user, &existing); err != nil {
		if err := au.FindByID(user, &existing); err != nil {
			h.L.Error(err.Error())
			return 404, models.NewJSONError("Specified user not found")
		}
	}

	if existing.ID == 0 {
		err := errors.New("Specified user not found")
		h.L.Error(err.Error())
		return 404, models.NewJSONError(err.Error())
	}

	u.Username = existing.Username

	if !au.IsAdmin() && existing.Username != au.Username {
		err := errors.New("You're not allowed to perform this action, please contact your admin")
		h.L.Error(err.Error())
		return 403, models.NewJSONError(err.Error())
	}

	if !au.IsAdmin() && existing.IsAdmin() != u.IsAdmin() {
		err := errors.New("You're not allowed to perform this action, please contact your admin")
		h.L.Error(err.Error())
		return 403, models.NewJSONError(err.Error())
	}

	if u.Password != nil {
		err := u.Validate()
		if err != nil {
			return 400, models.NewJSONError(err.Error())
		}

		// Check the old password if it is present
		if u.OldPassword != nil && !existing.ValidPassword(*u.OldPassword) {
			err := errors.New("Provided credentials are not valid")
			h.L.Error(err.Error())
			return 403, models.NewJSONError(err.Error())
		}
	}

	if err := u.Save(); err != nil {
		h.L.Error(err.Error())
		return 500, models.NewJSONError("Error updating user")
	}

	if existing.MFA == nil {
		existing.MFA = h.Bool(false)
	}

	if u.MFA != nil {
		if *u.MFA && !*existing.MFA {
			mfaSecret := u.MFASecret
			u.Redact()
			u.MFASecret = mfaSecret
		} else {
			u.Redact()
		}
	} else {
		u.Redact()
	}

	body, err = json.Marshal(u)
	if err != nil {
		return 500, models.NewJSONError("Internal server error")
	}

	return http.StatusOK, body
}
<|endoftext|>