<commit_before>// +build experimental

package plugin

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/libcontainerd"
	"github.com/docker/docker/pkg/ioutils"
	"github.com/docker/docker/pkg/plugins"
	"github.com/docker/docker/reference"
	"github.com/docker/docker/registry"
	"github.com/docker/docker/restartmanager"
	"github.com/docker/engine-api/types"
)

const defaultPluginRuntimeDestination = "/run/docker/plugins"

var manager *Manager

// ErrNotFound indicates that a plugin was not found locally.
type ErrNotFound string

func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }

// ErrInadequateCapability indicates that a plugin was found but did not have the requested capability.
type ErrInadequateCapability struct {
	name       string
	capability string
}

func (e ErrInadequateCapability) Error() string {
	return fmt.Sprintf("plugin %q found, but not with %q capability", e.name, e.capability)
}

type plugin struct {
	//sync.RWMutex TODO
	PluginObj         types.Plugin `json:"plugin"`
	client            *plugins.Client
	restartManager    restartmanager.RestartManager
	runtimeSourcePath string
	exitChan          chan bool
}

func (p *plugin) Client() *plugins.Client {
	return p.client
}

// IsLegacy returns true for legacy plugins and false otherwise.
func (p *plugin) IsLegacy() bool {
	return false
}

func (p *plugin) Name() string {
	name := p.PluginObj.Name
	if len(p.PluginObj.Tag) > 0 {
		// TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
		name += ":" + p.PluginObj.Tag
	}
	return name
}

func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin {
	p := &plugin{
		PluginObj: types.Plugin{
			Name: ref.Name(),
			ID:   id,
		},
		runtimeSourcePath: filepath.Join(pm.runRoot, id),
	}
	if ref, ok := ref.(reference.NamedTagged); ok {
		p.PluginObj.Tag = ref.Tag()
	}
	return p
}

func (pm *Manager) restorePlugin(p *plugin) error {
	p.runtimeSourcePath = filepath.Join(pm.runRoot, p.PluginObj.ID)
	if p.PluginObj.Active {
		return pm.restore(p)
	}
	return nil
}

type pluginMap map[string]*plugin
type eventLogger func(id, name, action string)

// Manager controls the plugin subsystem.
type Manager struct {
	sync.RWMutex
	libRoot           string
	runRoot           string
	plugins           pluginMap // TODO: figure out why save() doesn't json encode *plugin object
	nameToID          map[string]string
	handlers          map[string]func(string, *plugins.Client)
	containerdClient  libcontainerd.Client
	registryService   registry.Service
	handleLegacy      bool
	liveRestore       bool
	shutdown          bool
	pluginEventLogger eventLogger
}

// GetManager returns the singleton plugin Manager
func GetManager() *Manager {
	return manager
}

// Init (was NewManager) instantiates the singleton Manager.
// TODO: revert this to NewManager once we get rid of all the singletons.
func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRestore bool, evL eventLogger) (err error) {
	if manager != nil {
		return nil
	}

	root = filepath.Join(root, "plugins")
	manager = &Manager{
		libRoot:           root,
		runRoot:           "/run/docker",
		plugins:           make(map[string]*plugin),
		nameToID:          make(map[string]string),
		handlers:          make(map[string]func(string, *plugins.Client)),
		registryService:   rs,
		handleLegacy:      true,
		liveRestore:       liveRestore,
		pluginEventLogger: evL,
	}
	if err := os.MkdirAll(manager.runRoot, 0700); err != nil {
		return err
	}
	manager.containerdClient, err = remote.Client(manager)
	if err != nil {
		return err
	}
	if err := manager.init(); err != nil {
		return err
	}
	return nil
}

// Handle sets a callback for a given capability. The callback will be called for every plugin with a given capability.
// TODO: append instead of set?
func Handle(capability string, callback func(string, *plugins.Client)) {
	pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability))
	manager.handlers[pluginType] = callback
	if manager.handleLegacy {
		plugins.Handle(capability, callback)
	}
}

func (pm *Manager) get(name string) (*plugin, error) {
	pm.RLock()
	defer pm.RUnlock()

	id, nameOk := pm.nameToID[name]
	if !nameOk {
		return nil, ErrNotFound(name)
	}

	p, idOk := pm.plugins[id]
	if !idOk {
		return nil, ErrNotFound(name)
	}

	return p, nil
}

// FindWithCapability returns a list of plugins matching the given capability.
func FindWithCapability(capability string) ([]Plugin, error) {
	handleLegacy := true
	result := make([]Plugin, 0, 1)
	if manager != nil {
		handleLegacy = manager.handleLegacy
		manager.RLock()
		defer manager.RUnlock()
	pluginLoop:
		for _, p := range manager.plugins {
			for _, typ := range p.PluginObj.Manifest.Interface.Types {
				if typ.Capability != capability || typ.Prefix != "docker" {
					continue pluginLoop
				}
			}
			result = append(result, p)
		}
	}
	if handleLegacy {
		pl, err := plugins.GetAll(capability)
		if err != nil {
			return nil, fmt.Errorf("legacy plugin: %v", err)
		}
		for _, p := range pl {
			if _, ok := manager.nameToID[p.Name()]; !ok {
				result = append(result, p)
			}
		}
	}
	return result, nil
}

// LookupWithCapability returns a plugin matching the given name and capability.
func LookupWithCapability(name, capability string) (Plugin, error) {
	var (
		p   *plugin
		err error
	)
	handleLegacy := true
	if manager != nil {
		fullName := name
		if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate
			if reference.IsNameOnly(named) {
				named = reference.WithDefaultTag(named)
			}
			ref, ok := named.(reference.NamedTagged)
			if !ok {
				return nil, fmt.Errorf("invalid name: %s", named.String())
			}
			fullName = ref.String()
		}
		p, err = manager.get(fullName)
		if err != nil {
			if _, ok := err.(ErrNotFound); !ok {
				return nil, err
			}
			handleLegacy = manager.handleLegacy
		} else {
			handleLegacy = false
		}
	}
	if handleLegacy {
		p, err := plugins.Get(name, capability)
		if err != nil {
			return nil, fmt.Errorf("legacy plugin: %v", err)
		}
		return p, nil
	} else if err != nil {
		return nil, err
	}

	capability = strings.ToLower(capability)
	for _, typ := range p.PluginObj.Manifest.Interface.Types {
		if typ.Capability == capability && typ.Prefix == "docker" {
			return p, nil
		}
	}
	return nil, ErrInadequateCapability{name, capability}
}

// StateChanged updates plugin internals using from libcontainerd events.
func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
	logrus.Debugf("plugin state changed %s %#v", id, e)

	switch e.State {
	case libcontainerd.StateExit:
		pm.RLock()
		p, idOk := pm.plugins[id]
		pm.RUnlock()
		if !idOk {
			return ErrNotFound(id)
		}
		if pm.shutdown == true {
			p.exitChan <- true
		}
	}

	return nil
}

// AttachStreams attaches io streams to the plugin
func (pm *Manager) AttachStreams(id string, iop libcontainerd.IOPipe) error {
	iop.Stdin.Close()

	logger := logrus.New()
	logger.Hooks.Add(logHook{id})
	// TODO: cache writer per id
	w := logger.Writer()
	go func() {
		io.Copy(w, iop.Stdout)
	}()
	go func() {
		// TODO: update logrus and use logger.WriterLevel
		io.Copy(w, iop.Stderr)
	}()
	return nil
}

func (pm *Manager) init() error {
	dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json"))
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}

	if err := json.NewDecoder(dt).Decode(&pm.plugins); err != nil {
		return err
	}

	var group sync.WaitGroup
	group.Add(len(pm.plugins))
	for _, p := range pm.plugins {
		go func(p *plugin) {
			defer group.Done()
			if err := pm.restorePlugin(p); err != nil {
				logrus.Errorf("Error restoring plugin '%s': %s", p.Name(), err)
				return
			}

			pm.Lock()
			pm.nameToID[p.Name()] = p.PluginObj.ID
			requiresManualRestore := !pm.liveRestore && p.PluginObj.Active
			pm.Unlock()

			if requiresManualRestore {
				// if liveRestore is not enabled, the plugin will be stopped now so we should enable it
				if err := pm.enable(p); err != nil {
					logrus.Errorf("Error enabling plugin '%s': %s", p.Name(), err)
				}
			}
		}(p)
		group.Wait()
	}
	return pm.save()
}

func (pm *Manager) initPlugin(p *plugin) error {
	dt, err := os.Open(filepath.Join(pm.libRoot, p.PluginObj.ID, "manifest.json"))
	if err != nil {
		return err
	}
	err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest)
	dt.Close()
	if err != nil {
		return err
	}

	p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts))
	for i, mount := range p.PluginObj.Manifest.Mounts {
		p.PluginObj.Config.Mounts[i] = mount
	}
	p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env))
	for _, env := range p.PluginObj.Manifest.Env {
		if env.Value != nil {
			p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
		}
	}
	copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value)

	f, err := os.Create(filepath.Join(pm.libRoot, p.PluginObj.ID, "plugin-config.json"))
	if err != nil {
		return err
	}
	err = json.NewEncoder(f).Encode(&p.PluginObj.Config)
	f.Close()
	return err
}

func (pm *Manager) remove(p *plugin) error {
	if p.PluginObj.Active {
		return fmt.Errorf("plugin %s is active", p.Name())
	}
	pm.Lock() // fixme: lock single record
	defer pm.Unlock()
	delete(pm.plugins, p.PluginObj.ID)
	delete(pm.nameToID, p.Name())
	pm.save()
	return os.RemoveAll(filepath.Join(pm.libRoot, p.PluginObj.ID))
}

func (pm *Manager) set(p *plugin, args []string) error {
	m := make(map[string]string, len(args))
	for _, arg := range args {
		i := strings.Index(arg, "=")
		if i < 0 {
			return fmt.Errorf("No equal sign '=' found in %s", arg)
		}
		m[arg[:i]] = arg[i+1:]
	}
	return errors.New("not implemented")
}

// fixme: not safe
func (pm *Manager) save() error {
	filePath := filepath.Join(pm.libRoot, "plugins.json")

	jsonData, err := json.Marshal(pm.plugins)
	if err != nil {
		logrus.Debugf("Error in json.Marshal: %v", err)
		return err
	}
	ioutils.AtomicWriteFile(filePath, jsonData, 0600)
	return nil
}

type logHook struct{ id string }

func (logHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (l logHook) Fire(entry *logrus.Entry) error {
	entry.Data = logrus.Fields{"plugin": l.id}
	return nil
}

func computePrivileges(m *types.PluginManifest) types.PluginPrivileges {
	var privileges types.PluginPrivileges
	if m.Network.Type != "null" && m.Network.Type != "bridge" {
		privileges = append(privileges, types.PluginPrivilege{
			Name:        "network",
			Description: "",
			Value:       []string{m.Network.Type},
		})
	}
	for _, mount := range m.Mounts {
		if mount.Source != nil {
			privileges = append(privileges, types.PluginPrivilege{
				Name:        "mount",
				Description: "",
				Value:       []string{*mount.Source},
			})
		}
	}
	for _, device := range m.Devices {
		if device.Path != nil {
			privileges = append(privileges, types.PluginPrivilege{
				Name:        "device",
				Description: "",
				Value:       []string{*device.Path},
			})
		}
	}
	if len(m.Capabilities) > 0 {
		privileges = append(privileges, types.PluginPrivilege{
			Name:        "capabilities",
			Description: "",
			Value:       m.Capabilities,
		})
	}
	return privileges
}
<commit_msg>fix deadlock when more than 1 plugin is installed<commit_after>// +build experimental

package plugin

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/libcontainerd"
	"github.com/docker/docker/pkg/ioutils"
	"github.com/docker/docker/pkg/plugins"
	"github.com/docker/docker/reference"
	"github.com/docker/docker/registry"
	"github.com/docker/docker/restartmanager"
	"github.com/docker/engine-api/types"
)

const defaultPluginRuntimeDestination = "/run/docker/plugins"

var manager *Manager

// ErrNotFound indicates that a plugin was not found locally.
type ErrNotFound string

func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }

// ErrInadequateCapability indicates that a plugin was found but did not have the requested capability.
type ErrInadequateCapability struct {
	name       string
	capability string
}

func (e ErrInadequateCapability) Error() string {
	return fmt.Sprintf("plugin %q found, but not with %q capability", e.name, e.capability)
}

type plugin struct {
	//sync.RWMutex TODO
	PluginObj         types.Plugin `json:"plugin"`
	client            *plugins.Client
	restartManager    restartmanager.RestartManager
	runtimeSourcePath string
	exitChan          chan bool
}

func (p *plugin) Client() *plugins.Client {
	return p.client
}

// IsLegacy returns true for legacy plugins and false otherwise.
func (p *plugin) IsLegacy() bool {
	return false
}

func (p *plugin) Name() string {
	name := p.PluginObj.Name
	if len(p.PluginObj.Tag) > 0 {
		// TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
		name += ":" + p.PluginObj.Tag
	}
	return name
}

func (pm *Manager) newPlugin(ref reference.Named, id string) *plugin {
	p := &plugin{
		PluginObj: types.Plugin{
			Name: ref.Name(),
			ID:   id,
		},
		runtimeSourcePath: filepath.Join(pm.runRoot, id),
	}
	if ref, ok := ref.(reference.NamedTagged); ok {
		p.PluginObj.Tag = ref.Tag()
	}
	return p
}

func (pm *Manager) restorePlugin(p *plugin) error {
	p.runtimeSourcePath = filepath.Join(pm.runRoot, p.PluginObj.ID)
	if p.PluginObj.Active {
		return pm.restore(p)
	}
	return nil
}

type pluginMap map[string]*plugin
type eventLogger func(id, name, action string)

// Manager controls the plugin subsystem.
type Manager struct {
	sync.RWMutex
	libRoot           string
	runRoot           string
	plugins           pluginMap // TODO: figure out why save() doesn't json encode *plugin object
	nameToID          map[string]string
	handlers          map[string]func(string, *plugins.Client)
	containerdClient  libcontainerd.Client
	registryService   registry.Service
	handleLegacy      bool
	liveRestore       bool
	shutdown          bool
	pluginEventLogger eventLogger
}

// GetManager returns the singleton plugin Manager
func GetManager() *Manager {
	return manager
}

// Init (was NewManager) instantiates the singleton Manager.
// TODO: revert this to NewManager once we get rid of all the singletons.
func Init(root string, remote libcontainerd.Remote, rs registry.Service, liveRestore bool, evL eventLogger) (err error) {
	if manager != nil {
		return nil
	}

	root = filepath.Join(root, "plugins")
	manager = &Manager{
		libRoot:           root,
		runRoot:           "/run/docker",
		plugins:           make(map[string]*plugin),
		nameToID:          make(map[string]string),
		handlers:          make(map[string]func(string, *plugins.Client)),
		registryService:   rs,
		handleLegacy:      true,
		liveRestore:       liveRestore,
		pluginEventLogger: evL,
	}
	if err := os.MkdirAll(manager.runRoot, 0700); err != nil {
		return err
	}
	manager.containerdClient, err = remote.Client(manager)
	if err != nil {
		return err
	}
	if err := manager.init(); err != nil {
		return err
	}
	return nil
}

// Handle sets a callback for a given capability. The callback will be called for every plugin with a given capability.
// TODO: append instead of set?
func Handle(capability string, callback func(string, *plugins.Client)) {
	pluginType := fmt.Sprintf("docker.%s/1", strings.ToLower(capability))
	manager.handlers[pluginType] = callback
	if manager.handleLegacy {
		plugins.Handle(capability, callback)
	}
}

func (pm *Manager) get(name string) (*plugin, error) {
	pm.RLock()
	defer pm.RUnlock()

	id, nameOk := pm.nameToID[name]
	if !nameOk {
		return nil, ErrNotFound(name)
	}

	p, idOk := pm.plugins[id]
	if !idOk {
		return nil, ErrNotFound(name)
	}

	return p, nil
}

// FindWithCapability returns a list of plugins matching the given capability.
func FindWithCapability(capability string) ([]Plugin, error) {
	handleLegacy := true
	result := make([]Plugin, 0, 1)
	if manager != nil {
		handleLegacy = manager.handleLegacy
		manager.RLock()
		defer manager.RUnlock()
	pluginLoop:
		for _, p := range manager.plugins {
			for _, typ := range p.PluginObj.Manifest.Interface.Types {
				if typ.Capability != capability || typ.Prefix != "docker" {
					continue pluginLoop
				}
			}
			result = append(result, p)
		}
	}
	if handleLegacy {
		pl, err := plugins.GetAll(capability)
		if err != nil {
			return nil, fmt.Errorf("legacy plugin: %v", err)
		}
		for _, p := range pl {
			if _, ok := manager.nameToID[p.Name()]; !ok {
				result = append(result, p)
			}
		}
	}
	return result, nil
}

// LookupWithCapability returns a plugin matching the given name and capability.
func LookupWithCapability(name, capability string) (Plugin, error) {
	var (
		p   *plugin
		err error
	)
	handleLegacy := true
	if manager != nil {
		fullName := name
		if named, err := reference.ParseNamed(fullName); err == nil { // FIXME: validate
			if reference.IsNameOnly(named) {
				named = reference.WithDefaultTag(named)
			}
			ref, ok := named.(reference.NamedTagged)
			if !ok {
				return nil, fmt.Errorf("invalid name: %s", named.String())
			}
			fullName = ref.String()
		}
		p, err = manager.get(fullName)
		if err != nil {
			if _, ok := err.(ErrNotFound); !ok {
				return nil, err
			}
			handleLegacy = manager.handleLegacy
		} else {
			handleLegacy = false
		}
	}
	if handleLegacy {
		p, err := plugins.Get(name, capability)
		if err != nil {
			return nil, fmt.Errorf("legacy plugin: %v", err)
		}
		return p, nil
	} else if err != nil {
		return nil, err
	}

	capability = strings.ToLower(capability)
	for _, typ := range p.PluginObj.Manifest.Interface.Types {
		if typ.Capability == capability && typ.Prefix == "docker" {
			return p, nil
		}
	}
	return nil, ErrInadequateCapability{name, capability}
}

// StateChanged updates plugin internals using from libcontainerd events.
func (pm *Manager) StateChanged(id string, e libcontainerd.StateInfo) error {
	logrus.Debugf("plugin state changed %s %#v", id, e)

	switch e.State {
	case libcontainerd.StateExit:
		pm.RLock()
		p, idOk := pm.plugins[id]
		pm.RUnlock()
		if !idOk {
			return ErrNotFound(id)
		}
		if pm.shutdown == true {
			p.exitChan <- true
		}
	}

	return nil
}

// AttachStreams attaches io streams to the plugin
func (pm *Manager) AttachStreams(id string, iop libcontainerd.IOPipe) error {
	iop.Stdin.Close()

	logger := logrus.New()
	logger.Hooks.Add(logHook{id})
	// TODO: cache writer per id
	w := logger.Writer()
	go func() {
		io.Copy(w, iop.Stdout)
	}()
	go func() {
		// TODO: update logrus and use logger.WriterLevel
		io.Copy(w, iop.Stderr)
	}()
	return nil
}

func (pm *Manager) init() error {
	dt, err := os.Open(filepath.Join(pm.libRoot, "plugins.json"))
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}

	if err := json.NewDecoder(dt).Decode(&pm.plugins); err != nil {
		return err
	}

	var group sync.WaitGroup
	group.Add(len(pm.plugins))
	for _, p := range pm.plugins {
		go func(p *plugin) {
			defer group.Done()
			if err := pm.restorePlugin(p); err != nil {
				logrus.Errorf("Error restoring plugin '%s': %s", p.Name(), err)
				return
			}

			pm.Lock()
			pm.nameToID[p.Name()] = p.PluginObj.ID
			requiresManualRestore := !pm.liveRestore && p.PluginObj.Active
			pm.Unlock()

			if requiresManualRestore {
				// if liveRestore is not enabled, the plugin will be stopped now so we should enable it
				if err := pm.enable(p); err != nil {
					logrus.Errorf("Error enabling plugin '%s': %s", p.Name(), err)
				}
			}
		}(p)
	}
	group.Wait()
	return pm.save()
}

func (pm *Manager) initPlugin(p *plugin) error {
	dt, err := os.Open(filepath.Join(pm.libRoot, p.PluginObj.ID, "manifest.json"))
	if err != nil {
		return err
	}
	err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest)
	dt.Close()
	if err != nil {
		return err
	}

	p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts))
	for i, mount := range p.PluginObj.Manifest.Mounts {
		p.PluginObj.Config.Mounts[i] = mount
	}
	p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env))
	for _, env := range p.PluginObj.Manifest.Env {
		if env.Value != nil {
			p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
		}
	}
	copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value)

	f, err := os.Create(filepath.Join(pm.libRoot, p.PluginObj.ID, "plugin-config.json"))
	if err != nil {
		return err
	}
	err = json.NewEncoder(f).Encode(&p.PluginObj.Config)
	f.Close()
	return err
}

func (pm *Manager) remove(p *plugin) error {
	if p.PluginObj.Active {
		return fmt.Errorf("plugin %s is active", p.Name())
	}
	pm.Lock() // fixme: lock single record
	defer pm.Unlock()
	delete(pm.plugins, p.PluginObj.ID)
	delete(pm.nameToID, p.Name())
	pm.save()
	return os.RemoveAll(filepath.Join(pm.libRoot, p.PluginObj.ID))
}

func (pm *Manager) set(p *plugin, args []string) error {
	m := make(map[string]string, len(args))
	for _, arg := range args {
		i := strings.Index(arg, "=")
		if i < 0 {
			return fmt.Errorf("No equal sign '=' found in %s", arg)
		}
		m[arg[:i]] = arg[i+1:]
	}
	return errors.New("not implemented")
}

// fixme: not safe
func (pm *Manager) save() error {
	filePath := filepath.Join(pm.libRoot, "plugins.json")

	jsonData, err := json.Marshal(pm.plugins)
	if err != nil {
		logrus.Debugf("Error in json.Marshal: %v", err)
		return err
	}
	ioutils.AtomicWriteFile(filePath, jsonData, 0600)
	return nil
}

type logHook struct{ id string }

func (logHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (l logHook) Fire(entry *logrus.Entry) error {
	entry.Data = logrus.Fields{"plugin": l.id}
	return nil
}

func computePrivileges(m *types.PluginManifest) types.PluginPrivileges {
	var privileges types.PluginPrivileges
	if m.Network.Type != "null" && m.Network.Type != "bridge" {
		privileges = append(privileges, types.PluginPrivilege{
			Name:        "network",
			Description: "",
			Value:       []string{m.Network.Type},
		})
	}
	for _, mount := range m.Mounts {
		if mount.Source != nil {
			privileges = append(privileges, types.PluginPrivilege{
				Name:        "mount",
				Description: "",
				Value:       []string{*mount.Source},
			})
		}
	}
	for _, device := range m.Devices {
		if device.Path != nil {
			privileges = append(privileges, types.PluginPrivilege{
				Name:        "device",
				Description: "",
				Value:       []string{*device.Path},
			})
		}
	}
	if len(m.Capabilities) > 0 {
		privileges = append(privileges, types.PluginPrivilege{
			Name:        "capabilities",
			Description: "",
			Value:       m.Capabilities,
		})
	}
	return privileges
}
<|endoftext|>


<commit_before>package apigateway

import (
	"encoding/json"
	"net/http"
	"strings"
)

// Error represents an error to return in response to an
// API Gateway request
type Error struct {
	Code    int                    `json:"code"`
	Err     string                 `json:"err"`
	Message string                 `json:"message"`
	Context map[string]interface{} `json:"context,omitempty"`
}

// Error returns the JSONified version of this error which will
// trigger the appropriate integration mapping.
func (apigError *Error) Error() string {
	bytes, bytesErr := json.Marshal(apigError)
	if bytesErr != nil {
		bytes = []byte(http.StatusText(http.StatusInternalServerError))
	}
	return string(bytes)
}

// NewErrorResponse returns a response that satisfies
// the regular expression used to determine integration mappings
// via the API Gateway
func NewErrorResponse(statusCode int, messages ...string) *Error {
	err := &Error{
		Code:    statusCode,
		Err:     http.StatusText(statusCode),
		Message: strings.Join(messages, " "),
		Context: make(map[string]interface{}),
	}
	if len(err.Err) <= 0 {
		err.Code = http.StatusInternalServerError
		err.Err = http.StatusText(http.StatusInternalServerError)
	}
	return err
}
<commit_msg>Add APIGateway response type<commit_after>package apigateway

import (
	"encoding/json"
	"net/http"
	"strings"
)

// Error represents an error to return in response to an
// API Gateway request
type Error struct {
	Code    int                    `json:"code"`
	Err     string                 `json:"err"`
	Message string                 `json:"message"`
	Context map[string]interface{} `json:"context,omitempty"`
}

// Error returns the JSONified version of this error which will
// trigger the appropriate integration mapping.
func (apigError *Error) Error() string {
	bytes, bytesErr := json.Marshal(apigError)
	if bytesErr != nil {
		bytes = []byte(http.StatusText(http.StatusInternalServerError))
	}
	return string(bytes)
}

// NewErrorResponse returns a response that satisfies
// the regular expression used to determine integration mappings
// via the API Gateway
func NewErrorResponse(statusCode int, messages ...string) *Error {
	err := &Error{
		Code:    statusCode,
		Err:     http.StatusText(statusCode),
		Message: strings.Join(messages, " "),
		Context: make(map[string]interface{}),
	}
	if len(err.Err) <= 0 {
		err.Code = http.StatusInternalServerError
		err.Err = http.StatusText(http.StatusInternalServerError)
	}
	return err
}

// Response is the type returned by an API Gateway function
type Response struct {
	Code    int               `json:"code,omitempty"`
	Body    interface{}       `json:"body,omitempty"`
	Headers map[string]string `json:"headers,omitempty"`
}

// canonicalResponse is the type for the canonicalized response with the
// headers ensured to be lowercase
type canonicalResponse struct {
	Code    int               `json:"code,omitempty"`
	Body    interface{}       `json:"body,omitempty"`
	Headers map[string]string `json:"headers,omitempty"`
}

// MarshalJSON is a custom marshaller to ensure that the marshalled
// headers are always lowercase
func (resp *Response) MarshalJSON() ([]byte, error) {
	canonicalResponse := canonicalResponse{
		Code: resp.Code,
		Body: resp.Body,
	}
	if len(resp.Headers) != 0 {
		canonicalResponse.Headers = make(map[string]string)
		for eachKey, eachValue := range resp.Headers {
			canonicalResponse.Headers[strings.ToLower(eachKey)] = eachValue
		}
	}
	return json.Marshal(&canonicalResponse)
}

// NewResponse returns an API Gateway response object
func NewResponse(code int, body interface{}, headers ...map[string]string) *Response {
	response := &Response{
		Code: code,
		Body: body,
	}
	if len(headers) != 0 {
		response.Headers = make(map[string]string)
		for _, eachHeaderMap := range headers {
			for eachKey, eachValue := range eachHeaderMap {
				response.Headers[eachKey] = eachValue
			}
		}
	}
	return response
}
<|endoftext|>


<commit_before>package pggameday

import (
	"database/sql"
	"github.com/ecopony/gamedayapi"
	_ "github.com/lib/pq" // PostgreSQL driver
	"log"
	s "strings"
)

// CreateTables creates the database tables used by the importer. For now it's just the pitches table.
// This will drop tables if they already exist.
func CreateTables() {
	log.Println("Creating database tables.")

	// Assumes a pg database exists named go-gameday, a role that can access it.
	db, err := sql.Open("postgres", "user=go-gameday dbname=go-gameday sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	log.Println("\t-Creating pitches table")
	db.Exec("DROP INDEX IF EXISTS pitches_game_id")
	db.Exec("DROP TABLE IF EXISTS pitches")
	db.Exec(`CREATE TABLE pitches (pitchid SERIAL PRIMARY KEY, game_id varchar(40), year int, inning int, half varchar(6),
		at_bat_num int, at_bat_b int, at_bat_s int, at_bat_o int, at_bat_start_tfs int, batter int, stand char(1), b_height
		varchar(4), pitcher int, p_throws char(1), at_bat_des varchar(400), at_bat_event varchar(20), pitch_des varchar(40),
		pitch_id int, pitch_type char(1), pitch_pitch_type char(2), type_confidence DECIMAL(4, 3), pitch_tfs int,
	  	pitch_x DECIMAL(5, 2), pitch_y DECIMAL(5, 2), pitch_sv_id varchar(40), pitch_start_speed DECIMAL(4, 1),
	  	pitch_end_speed DECIMAL(4, 1), sz_top DECIMAL(3, 2), sz_bottom DECIMAL(3, 2), pfx_x DECIMAL(4, 2), pfx_z
	  	DECIMAL(4, 2), px DECIMAL(4, 3), pz DECIMAL(4, 3), x0 DECIMAL(5, 3), y0 DECIMAL(5, 3), z0 DECIMAL(5, 3), vx0
	  	DECIMAL(4, 2), vy0 DECIMAL(6, 3), vz0 DECIMAL(5, 3), ax DECIMAL(5, 3), ay DECIMAL(5, 3), az DECIMAL(5, 3), break_y
	  	DECIMAL(3, 1), break_angle DECIMAL(4, 1), break_length DECIMAL(3, 1), zone int, spin_dir DECIMAL(6, 3), spin_rate
	  	DECIMAL(7, 3), nasty int)`)
	log.Println("\t-Creating pitches index")
	db.Exec("CREATE INDEX pitches_game_id ON pitches (game_id)")
	db.Exec("CREATE UNIQUE INDEX pitches_game_id_pitch_id ON pitches (game_id, pitch_id)")

	log.Println("\t-Creating players table")
	db.Exec("DROP INDEX IF EXISTS players_id")
	db.Exec("DROP TABLE IF EXISTS players")
	db.Exec(`CREATE TABLE players (playerid SERIAL PRIMARY KEY, id int, first varchar(40), last varchar(40), num int,
		boxname varchar(40), rl varchar(1), bats varchar(1), position varchar(2), current_position varchar(2), status
		varchar(1), team_abbrev varchar(3), team_id int, parent_team_abbrev varchar(3), parent_team_id int, bat_order int,
		game_position varchar(2), avg DECIMAL(4, 3), hr int, rbi int, wins int, losses int, era DECIMAL(5, 2))`)
	log.Println("\t-Creating players index")
	db.Exec("CREATE UNIQUE INDEX players_id ON players (id)")

	log.Println("Done.")
}

// ImportPitchesForTeamAndYears saves all pitch data fields for a team and season.
func ImportPitchesForTeamAndYears(teamCode string, years []int) {
	log.Println("Importing pitches for " + teamCode)

	// Assumes a pg database exists named go-gameday, a role that can access it.
	db, err := sql.Open("postgres", "user=go-gameday dbname=go-gameday sslmode=disable")
	issue := db.Ping()
	log.Println(issue)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	fetchFunction := func(game *gamedayapi.Game) {
		log.Println(">>>> " + game.ID + " <<<<")

		var pitchCount int
		err := db.QueryRow("SELECT count(*) FROM pitches WHERE game_id = $1", game.ID).Scan(&pitchCount)
		if err != nil {
			log.Fatal(err)
		}
		if pitchCount > 0 {
			return
		}

		for _, inning := range game.AllInnings().Innings {
			half := "top"
			for _, atBat := range inning.AtBats() {
				if atBat.O == "3" {
					half = "bottom"
				}
				for _, pitch := range atBat.Pitches {
					// log.Println("> " + pitch.SzTop)

					res, err := db.Query(`INSERT INTO pitches
						(game_id, year, inning, half, at_bat_num, at_bat_b, at_bat_s, at_bat_o, at_bat_start_tfs,
						batter, stand, b_height, pitcher, p_throws, at_bat_des, at_bat_event,
						pitch_des, pitch_id, pitch_type, pitch_pitch_type, type_confidence,
						pitch_tfs, pitch_x, pitch_y, pitch_sv_id, pitch_start_speed, pitch_end_speed,
						sz_top, sz_bottom, pfx_x, pfx_z,
						px, pz, x0, y0,
						z0, vx0, vy0, vz0,
						ax, ay, az, break_y,
						break_angle, break_length, zone,
						spin_dir, spin_rate, nasty
						)
						VALUES
						($1, $2, $3, $4, $5, $6, $7, $8, $9,
						$10, $11, $12, $13, $14, $15, $16,
						$17, $18, $19, $20,
						$21, $22, $23, $24, $25, $26,
						$27, $28, $29, $30,
						$31, $32, $33, $34,
						$35, $36, $37, $38,
						$39, $40, $41, $42,
						$43, $44, $45,
						$46, $47, $48, $49)`,
						game.ID, game.Year(), inning.Num, half, atBat.Num, atBat.B, atBat.S, atBat.O, nullableString(atBat.StartTFS),
						atBat.Batter, atBat.Stand, atBat.BHeight, atBat.Pitcher, atBat.PThrows, atBat.Des, atBat.Event,
						pitch.Des, pitch.ID, pitch.Type, nullableString(pitch.PitchType), nullableString(pitch.TypeConfidence),
						nullableString(pitch.TFS), pitch.X, pitch.Y, pitch.SvID, nullableString(pitch.StartSpeed), nullableString(pitch.EndSpeed),
						nullableString(pitch.SzTop), nullableString(pitch.SzBottom), nullableString(pitch.PFXX), nullableString(pitch.PFXZ),
						nullableString(pitch.PX), nullableString(pitch.PZ), nullableString(pitch.X0), nullableString(pitch.Y0),
						nullableString(pitch.Z0), nullableString(pitch.VX0), nullableString(pitch.VY0), nullableString(pitch.VZ0),
						nullableString(pitch.AX), nullableString(pitch.AY), nullableString(pitch.AZ), nullableString(pitch.BreakY),
						nullableString(pitch.BreakAngle), nullableString(pitch.BreakLength), nullableString(pitch.Zone),
						nullableString(pitch.SpinDir), nullableString(pitch.SpinRate), nullableString(pitch.Nasty))

					if err != nil {
						if !s.Contains(err.Error(), "duplicate key") {
							log.Fatal(err)
						}
					} else {
						res.Close()
					}
				}
			}
		}
	}
	gamedayapi.FetchByTeamAndYears(teamCode, years, fetchFunction)
}

func ImportPlayersForTeamAndYears(teamCode string, years []int) {
	log.Println("Importing players for " + teamCode)

	// Assumes a pg database exists named go-gameday, a role that can access it.
	db, err := sql.Open("postgres", "user=go-gameday dbname=go-gameday sslmode=disable")
	issue := db.Ping()
	log.Println(issue)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	fetchFunction := func(game *gamedayapi.Game) {
		log.Println(">>>> " + game.ID + " <<<<")

		for _, team := range game.Players().Teams {
			for _, player := range team.Players {
				res, err := db.Query(`INSERT INTO players
					(id, first, last, num, boxname, rl, bats,
					position, current_position, status, team_abbrev, team_id,
					parent_team_abbrev, parent_team_id, bat_order, game_position, avg,
					hr, rbi, wins, losses, era)
					VALUES
					($1, $2, $3, $4, $5, $6, $7,
					$8, $9, $10, $11, $12,
					$13, $14, $15, $16, $17,
					$18, $19, $20, $21, $22)`,
					player.ID, player.First, player.Last, nullableString(player.Num), player.Boxname, player.Rl, player.Bats,
					player.Position, player.CurrentPosition, player.Status, player.TeamAbbrev, player.TeamID,
					player.ParentTeamAbbrev, nullableString(player.ParentTeamID), nullableString(player.BatOrder), player.GamePosition, player.Avg,
					player.HR, player.RBI, nullableString(player.Wins), nullableString(player.Losses), nullableString(player.ERA))

				if err != nil {
					if !s.Contains(err.Error(), "duplicate key") {
						log.Fatal(err)
					}
				} else {
					res.Close()
				}
			}
		}
	}
	gamedayapi.FetchByTeamAndYears(teamCode, years, fetchFunction)
}

func nullableString(value string) interface{} {
	if value == "" || value == "-" || value == "-.--" {
		return nil
	}
	return value
}
<commit_msg>Now paying attention to the team on the player import.<commit_after>package pggameday

import (
	"database/sql"
	"github.com/ecopony/gamedayapi"
	_ "github.com/lib/pq" // PostgreSQL driver
	"log"
	s "strings"
)

// CreateTables creates the database tables used by the importer. For now it's just the pitches table.
// This will drop tables if they already exist.
func CreateTables() {
	log.Println("Creating database tables.")

	// Assumes a pg database exists named go-gameday, a role that can access it.
	db, err := sql.Open("postgres", "user=go-gameday dbname=go-gameday sslmode=disable")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	log.Println("\t-Creating pitches table")
	db.Exec("DROP INDEX IF EXISTS pitches_game_id")
	db.Exec("DROP TABLE IF EXISTS pitches")
	db.Exec(`CREATE TABLE pitches (pitchid SERIAL PRIMARY KEY, game_id varchar(40), year int, inning int, half varchar(6),
		at_bat_num int, at_bat_b int, at_bat_s int, at_bat_o int, at_bat_start_tfs int, batter int, stand char(1), b_height
		varchar(4), pitcher int, p_throws char(1), at_bat_des varchar(400), at_bat_event varchar(20), pitch_des varchar(40),
		pitch_id int, pitch_type char(1), pitch_pitch_type char(2), type_confidence DECIMAL(4, 3), pitch_tfs int,
	  	pitch_x DECIMAL(5, 2), pitch_y DECIMAL(5, 2), pitch_sv_id varchar(40), pitch_start_speed DECIMAL(4, 1),
	  	pitch_end_speed DECIMAL(4, 1), sz_top DECIMAL(3, 2), sz_bottom DECIMAL(3, 2), pfx_x DECIMAL(4, 2), pfx_z
	  	DECIMAL(4, 2), px DECIMAL(4, 3), pz DECIMAL(4, 3), x0 DECIMAL(5, 3), y0 DECIMAL(5, 3), z0 DECIMAL(5, 3), vx0
	  	DECIMAL(4, 2), vy0 DECIMAL(6, 3), vz0 DECIMAL(5, 3), ax DECIMAL(5, 3), ay DECIMAL(5, 3), az DECIMAL(5, 3), break_y
	  	DECIMAL(3, 1), break_angle DECIMAL(4, 1), break_length DECIMAL(3, 1), zone int, spin_dir DECIMAL(6, 3), spin_rate
	  	DECIMAL(7, 3), nasty int)`)
	log.Println("\t-Creating pitches index")
	db.Exec("CREATE INDEX pitches_game_id ON pitches (game_id)")
	db.Exec("CREATE UNIQUE INDEX pitches_game_id_pitch_id ON pitches (game_id, pitch_id)")

	log.Println("\t-Creating players table")
	db.Exec("DROP INDEX IF EXISTS players_id")
	db.Exec("DROP TABLE IF EXISTS players")
	db.Exec(`CREATE TABLE players (playerid SERIAL PRIMARY KEY, id int, first varchar(40), last varchar(40), num int,
		boxname varchar(40), rl varchar(1), bats varchar(1), position varchar(2), current_position varchar(2), status
		varchar(1), team_abbrev varchar(3), team_id int, parent_team_abbrev varchar(3), parent_team_id int, bat_order int,
		game_position varchar(2), avg DECIMAL(4, 3), hr int, rbi int, wins int, losses int, era DECIMAL(5, 2))`)
	log.Println("\t-Creating players index")
	db.Exec("CREATE UNIQUE INDEX players_id ON players (id)")

	log.Println("Done.")
}

// ImportPitchesForTeamAndYears saves all pitch data fields for a team and season.
func ImportPitchesForTeamAndYears(teamCode string, years []int) {
	log.Println("Importing pitches for " + teamCode)

	// Assumes a pg database exists named go-gameday, a role that can access it.
	db, err := sql.Open("postgres", "user=go-gameday dbname=go-gameday sslmode=disable")
	issue := db.Ping()
	log.Println(issue)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	fetchFunction := func(game *gamedayapi.Game) {
		log.Println(">>>> " + game.ID + " <<<<")

		var pitchCount int
		err := db.QueryRow("SELECT count(*) FROM pitches WHERE game_id = $1", game.ID).Scan(&pitchCount)
		if err != nil {
			log.Fatal(err)
		}
		if pitchCount > 0 {
			return
		}

		for _, inning := range game.AllInnings().Innings {
			half := "top"
			for _, atBat := range inning.AtBats() {
				if atBat.O == "3" {
					half = "bottom"
				}
				for _, pitch := range atBat.Pitches {
					// log.Println("> " + pitch.SzTop)

					res, err := db.Query(`INSERT INTO pitches
						(game_id, year, inning, half, at_bat_num, at_bat_b, at_bat_s, at_bat_o, at_bat_start_tfs,
						batter, stand, b_height, pitcher, p_throws, at_bat_des, at_bat_event,
						pitch_des, pitch_id, pitch_type, pitch_pitch_type, type_confidence,
						pitch_tfs, pitch_x, pitch_y, pitch_sv_id, pitch_start_speed, pitch_end_speed,
						sz_top, sz_bottom, pfx_x, pfx_z,
						px, pz, x0, y0,
						z0, vx0, vy0, vz0,
						ax, ay, az, break_y,
						break_angle, break_length, zone,
						spin_dir, spin_rate, nasty
						)
						VALUES
						($1, $2, $3, $4, $5, $6, $7, $8, $9,
						$10, $11, $12, $13, $14, $15, $16,
						$17, $18, $19, $20,
						$21, $22, $23, $24, $25, $26,
						$27, $28, $29, $30,
						$31, $32, $33, $34,
						$35, $36, $37, $38,
						$39, $40, $41, $42,
						$43, $44, $45,
						$46, $47, $48, $49)`,
						game.ID, game.Year(), inning.Num, half, atBat.Num, atBat.B, atBat.S, atBat.O, nullableString(atBat.StartTFS),
						atBat.Batter, atBat.Stand, atBat.BHeight, atBat.Pitcher, atBat.PThrows, atBat.Des, atBat.Event,
						pitch.Des, pitch.ID, pitch.Type, nullableString(pitch.PitchType), nullableString(pitch.TypeConfidence),
						nullableString(pitch.TFS), pitch.X, pitch.Y, pitch.SvID, nullableString(pitch.StartSpeed), nullableString(pitch.EndSpeed),
						nullableString(pitch.SzTop), nullableString(pitch.SzBottom), nullableString(pitch.PFXX), nullableString(pitch.PFXZ),
						nullableString(pitch.PX), nullableString(pitch.PZ), nullableString(pitch.X0), nullableString(pitch.Y0),
						nullableString(pitch.Z0), nullableString(pitch.VX0), nullableString(pitch.VY0), nullableString(pitch.VZ0),
						nullableString(pitch.AX), nullableString(pitch.AY), nullableString(pitch.AZ), nullableString(pitch.BreakY),
						nullableString(pitch.BreakAngle), nullableString(pitch.BreakLength), nullableString(pitch.Zone),
						nullableString(pitch.SpinDir), nullableString(pitch.SpinRate), nullableString(pitch.Nasty))

					if err != nil {
						if !s.Contains(err.Error(), "duplicate key") {
							log.Fatal(err)
						}
					} else {
						res.Close()
					}
				}
			}
		}
	}
	gamedayapi.FetchByTeamAndYears(teamCode, years, fetchFunction)
}

// ImportPlayersForTeamAndYears saves all player data fields for a team and season.
func ImportPlayersForTeamAndYears(teamCode string, years []int) {
	log.Println("Importing players for " + teamCode)

	// Assumes a pg database exists named go-gameday, a role that can access it.
	db, err := sql.Open("postgres", "user=go-gameday dbname=go-gameday sslmode=disable")
	issue := db.Ping()
	log.Println(issue)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	fetchFunction := func(game *gamedayapi.Game) {
		log.Println(">>>> " + game.ID + " <<<<")
		var teamID string

		if game.HomeCode == teamCode {
			teamID = game.HomeTeamID
		} else {
			teamID = game.AwayTeamID
		}

		for _, team := range game.Players().Teams {
			for _, player := range team.Players {
				if player.TeamID == teamID {
					res, err := db.Query(`INSERT INTO players
					(id, first, last, num, boxname, rl, bats,
					position, current_position, status, team_abbrev, team_id,
					parent_team_abbrev, parent_team_id, bat_order, game_position, avg,
					hr, rbi, wins, losses, era)
					VALUES
					($1, $2, $3, $4, $5, $6, $7,
					$8, $9, $10, $11, $12,
					$13, $14, $15, $16, $17,
					$18, $19, $20, $21, $22)`,
						player.ID, player.First, player.Last, nullableString(player.Num), player.Boxname, player.Rl, player.Bats,
						player.Position, player.CurrentPosition, player.Status, player.TeamAbbrev, player.TeamID,
						player.ParentTeamAbbrev, nullableString(player.ParentTeamID), nullableString(player.BatOrder), player.GamePosition, player.Avg,
						player.HR, player.RBI, nullableString(player.Wins), nullableString(player.Losses), nullableString(player.ERA))

					if err != nil {
						if !s.Contains(err.Error(), "duplicate key") {
							log.Fatal(err)
						}
					} else {
						res.Close()
					}
				}
			}
		}
	}
	gamedayapi.FetchByTeamAndYears(teamCode, years, fetchFunction)
}

func nullableString(value string) interface{} {
	if value == "" || value == "-" || value == "-.--" {
		return nil
	}
	return value
}
<|endoftext|>


<commit_before>package config

import (
	"net"
	"os"
	"strings"
	"time"

	"github.com/urfave/cli"
)

type ManagerConfig struct {
	LogLevel          string   `json:"logLevel"`
	DataDir           string   `json:"dataDir"`
	RaftAdvertiseAddr string   `json:"raftAdvertiseAddr"`
	RaftListenAddr    string   `json:"raftListenAddr"`
	ListenAddr        string   `json:"listenAddr"`
	AdvertiseAddr     string   `json:"advertiseAddr"`
	JoinAddrs         []string `json:"joinAddrs"`

	Scheduler Scheduler `json:"scheduler"`
}

type AgentConfig struct {
	DataDir          string   `json:"dataDir"`
	LogLevel         string   `json:"logLevel"`
	ListenAddr       string   `json:"listenAddr"`
	AdvertiseAddr    string   `json:"advertiseAddr"`
	GossipListenAddr string   `json:"gossipListenAddr"`
	GossipJoinAddr   string   `json:"gossipJoinAddr"`
	JoinAddrs        []string `json:"joinAddrs"`
	DNS              DNS      `json:"dns"`
	Janitor          Janitor  `json:"janitor"`
}

type Scheduler struct {
	ZkPath             string `json:"zkpath"`
	MesosFrameworkUser string `json:"mesos-framwork-user"`
	Hostname           string `json:"hostname"`
}

type DNS struct {
	Domain     string `json:"domain"`
	RecurseOn  bool   `json:"recurse_on"`
	ListenAddr string `json:"listenAddr"`

	SOARname   string `json:"soarname"`
	SOAMname   string `json:"soamname"`
	SOASerial  uint32 `json:"soaserial"`
	SOARefresh uint32 `json:"soarefresh"`
	SOARetry   uint32 `json:"soaretry"`
	SOAExpire  uint32 `json:"soaexpire"`

	TTL int `json:"ttl"`

	Resolvers       []string      `json:"resolvers"`
	ExchangeTimeout time.Duration `json:"exchange_timeout"`
}

type Janitor struct {
	ListenAddr  string `json:"listenAddr"`
	Domain      string `json:"domain"`
	AdvertiseIP string `json:"ddvertiseIP"`
}

func NewAgentConfig(c *cli.Context) AgentConfig {
	agentConfig := AgentConfig{
		LogLevel:         "info",
		DataDir:          "./data/",
		ListenAddr:       "0.0.0.0:9999",
		JoinAddrs:        []string{"0.0.0.0:9999"},
		GossipListenAddr: "0.0.0.0:5000",

		DNS: DNS{
			Domain:     "swan.com",
			ListenAddr: "0.0.0.0:53",

			RecurseOn:       true,
			TTL:             3,
			Resolvers:       []string{"114.114.114.114"},
			ExchangeTimeout: time.Second * 3,
		},

		Janitor: Janitor{
			ListenAddr: "0.0.0.0:80",
			Domain:     "swan.com",
		},
	}

	if c.String("log-level") != "" {
		agentConfig.LogLevel = c.String("log-level")
	}

	if c.String("data-dir") != "" {
		agentConfig.DataDir = c.String("data-dir")
		if !strings.HasSuffix(agentConfig.DataDir, "/") {
			agentConfig.DataDir = agentConfig.DataDir + "/"
		}
	}

	if c.String("domain") != "" {
		agentConfig.DNS.Domain = c.String("domain")
		agentConfig.Janitor.Domain = c.String("domain")
	}

	if c.String("listen-addr") != "" {
		agentConfig.ListenAddr = c.String("listen-addr")
	}

	agentConfig.AdvertiseAddr = c.String("advertise-addr")
	if agentConfig.AdvertiseAddr == "" {
		agentConfig.AdvertiseAddr = agentConfig.ListenAddr
	}

	if c.String("janitor-advertise-ip") != "" {
		agentConfig.Janitor.AdvertiseIP = c.String("janitor-advertise-ip")
	}

	if c.String("janitor-listen-addr") != "" {
		agentConfig.Janitor.ListenAddr = c.String("janitor-listen-addr")

		if agentConfig.Janitor.AdvertiseIP == "" {
			agentConfig.Janitor.AdvertiseIP, _, _ = net.SplitHostPort(agentConfig.Janitor.ListenAddr)
		}
	}

	if c.String("dns-listen-addr") != "" {
		agentConfig.DNS.ListenAddr = c.String("dns-listen-addr")
	}

	if c.String("dns-resolvers") != "" {
		agentConfig.DNS.Resolvers = strings.Split(c.String("dns-resolvers"), ",")
	}

	if c.String("join-addrs") != "" {
		agentConfig.JoinAddrs = strings.Split(c.String("join-addrs"), ",")
	}

	if c.String("gossip-listen-addr") != "" {
		agentConfig.GossipListenAddr = c.String("gossip-listen-addr")
	}

	if c.String("gossip-join-addr") != "" {
		agentConfig.GossipJoinAddr = c.String("gossip-join-addr")
	}

	return agentConfig
}

func NewManagerConfig(c *cli.Context) ManagerConfig {
	managerConfig := ManagerConfig{
		LogLevel:       "info",
		DataDir:        "./data/",
		ListenAddr:     "0.0.0.0:9999",
		RaftListenAddr: "0.0.0.0:2111",
		JoinAddrs:      []string{"0.0.0.0:9999"},

		Scheduler: Scheduler{
			ZkPath:             "0.0.0.0:2181",
			MesosFrameworkUser: "root",
			Hostname:           Hostname(),
		},
	}

	if c.String("log-level") != "" {
		managerConfig.LogLevel = c.String("log-level")
	}

	if c.String("data-dir") != "" {
		managerConfig.DataDir = c.String("data-dir")
		if !strings.HasSuffix(managerConfig.DataDir, "/") {
			managerConfig.DataDir = managerConfig.DataDir + "/"
		}
	}

	if c.String("zk-path") != "" {
		managerConfig.Scheduler.ZkPath = c.String("zk-path")
	}

	if c.String("listen-addr") != "" {
		managerConfig.ListenAddr = c.String("listen-addr")
	}

	managerConfig.AdvertiseAddr = c.String("advertise-addr")
	if managerConfig.AdvertiseAddr == "" {
		managerConfig.AdvertiseAddr = managerConfig.ListenAddr
	}

	if c.String("raft-listen-addr") != "" {
		managerConfig.RaftListenAddr = c.String("raft-listen-addr")
	}

	if strings.Index(managerConfig.RaftListenAddr, "//") == 0 {
		// Leading double slashes (any scheme). Force http.
		managerConfig.RaftListenAddr = "http:" + managerConfig.RaftListenAddr
	}
	if strings.Index(managerConfig.RaftListenAddr, "://") == -1 {
		// Missing scheme. Force http.
		managerConfig.RaftListenAddr = "http://" + managerConfig.RaftListenAddr
	}

	if c.String("raft-advertise-addr") != "" {
		managerConfig.RaftAdvertiseAddr = c.String("raft-advertise-addr")
	}

	if managerConfig.RaftAdvertiseAddr == "" {
		managerConfig.RaftAdvertiseAddr = managerConfig.RaftListenAddr
	}

	if c.String("join-addrs") != "" {
		managerConfig.JoinAddrs = strings.Split(c.String("join-addrs"), ",")
	}

	SchedulerConfig = managerConfig.Scheduler

	return managerConfig
}

func Hostname() string {
	hostname, err := os.Hostname()
	if err != nil {
		hostname = "UNKNOWN"
	}

	return hostname
}

var SchedulerConfig Scheduler
<commit_msg>add raft-advertise-addr scheme<commit_after>package config

import (
	"net"
	"os"
	"strings"
	"time"

	"github.com/urfave/cli"
)

type ManagerConfig struct {
	LogLevel          string   `json:"logLevel"`
	DataDir           string   `json:"dataDir"`
	RaftAdvertiseAddr string   `json:"raftAdvertiseAddr"`
	RaftListenAddr    string   `json:"raftListenAddr"`
	ListenAddr        string   `json:"listenAddr"`
	AdvertiseAddr     string   `json:"advertiseAddr"`
	JoinAddrs         []string `json:"joinAddrs"`

	Scheduler Scheduler `json:"scheduler"`
}

type AgentConfig struct {
	DataDir          string   `json:"dataDir"`
	LogLevel         string   `json:"logLevel"`
	ListenAddr       string   `json:"listenAddr"`
	AdvertiseAddr    string   `json:"advertiseAddr"`
	GossipListenAddr string   `json:"gossipListenAddr"`
	GossipJoinAddr   string   `json:"gossipJoinAddr"`
	JoinAddrs        []string `json:"joinAddrs"`
	DNS              DNS      `json:"dns"`
	Janitor          Janitor  `json:"janitor"`
}

type Scheduler struct {
	ZkPath             string `json:"zkpath"`
	MesosFrameworkUser string `json:"mesos-framwork-user"`
	Hostname           string `json:"hostname"`
}

type DNS struct {
	Domain     string `json:"domain"`
	RecurseOn  bool   `json:"recurse_on"`
	ListenAddr string `json:"listenAddr"`

	SOARname   string `json:"soarname"`
	SOAMname   string `json:"soamname"`
	SOASerial  uint32 `json:"soaserial"`
	SOARefresh uint32 `json:"soarefresh"`
	SOARetry   uint32 `json:"soaretry"`
	SOAExpire  uint32 `json:"soaexpire"`

	TTL int `json:"ttl"`

	Resolvers       []string      `json:"resolvers"`
	ExchangeTimeout time.Duration `json:"exchange_timeout"`
}

type Janitor struct {
	ListenAddr  string `json:"listenAddr"`
	Domain      string `json:"domain"`
	AdvertiseIP string `json:"ddvertiseIP"`
}

func NewAgentConfig(c *cli.Context) AgentConfig {
	agentConfig := AgentConfig{
		LogLevel:         "info",
		DataDir:          "./data/",
		ListenAddr:       "0.0.0.0:9999",
		JoinAddrs:        []string{"0.0.0.0:9999"},
		GossipListenAddr: "0.0.0.0:5000",

		DNS: DNS{
			Domain:     "swan.com",
			ListenAddr: "0.0.0.0:53",

			RecurseOn:       true,
			TTL:             3,
			Resolvers:       []string{"114.114.114.114"},
			ExchangeTimeout: time.Second * 3,
		},

		Janitor: Janitor{
			ListenAddr: "0.0.0.0:80",
			Domain:     "swan.com",
		},
	}

	if c.String("log-level") != "" {
		agentConfig.LogLevel = c.String("log-level")
	}

	if c.String("data-dir") != "" {
		agentConfig.DataDir = c.String("data-dir")
		if !strings.HasSuffix(agentConfig.DataDir, "/") {
			agentConfig.DataDir = agentConfig.DataDir + "/"
		}
	}

	if c.String("domain") != "" {
		agentConfig.DNS.Domain = c.String("domain")
		agentConfig.Janitor.Domain = c.String("domain")
	}

	if c.String("listen-addr") != "" {
		agentConfig.ListenAddr = c.String("listen-addr")
	}

	agentConfig.AdvertiseAddr = c.String("advertise-addr")
	if agentConfig.AdvertiseAddr == "" {
		agentConfig.AdvertiseAddr = agentConfig.ListenAddr
	}

	if c.String("janitor-advertise-ip") != "" {
		agentConfig.Janitor.AdvertiseIP = c.String("janitor-advertise-ip")
	}

	if c.String("janitor-listen-addr") != "" {
		agentConfig.Janitor.ListenAddr = c.String("janitor-listen-addr")

		if agentConfig.Janitor.AdvertiseIP == "" {
			agentConfig.Janitor.AdvertiseIP, _, _ = net.SplitHostPort(agentConfig.Janitor.ListenAddr)
		}
	}

	if c.String("dns-listen-addr") != "" {
		agentConfig.DNS.ListenAddr = c.String("dns-listen-addr")
	}

	if c.String("dns-resolvers") != "" {
		agentConfig.DNS.Resolvers = strings.Split(c.String("dns-resolvers"), ",")
	}

	if c.String("join-addrs") != "" {
		agentConfig.JoinAddrs = strings.Split(c.String("join-addrs"), ",")
	}

	if c.String("gossip-listen-addr") != "" {
		agentConfig.GossipListenAddr = c.String("gossip-listen-addr")
	}

	if c.String("gossip-join-addr") != "" {
		agentConfig.GossipJoinAddr = c.String("gossip-join-addr")
	}

	return agentConfig
}

func NewManagerConfig(c *cli.Context) ManagerConfig {
	managerConfig := ManagerConfig{
		LogLevel:       "info",
		DataDir:        "./data/",
		ListenAddr:     "0.0.0.0:9999",
		RaftListenAddr: "0.0.0.0:2111",
		JoinAddrs:      []string{"0.0.0.0:9999"},

		Scheduler: Scheduler{
			ZkPath:             "0.0.0.0:2181",
			MesosFrameworkUser: "root",
			Hostname:           Hostname(),
		},
	}

	if c.String("log-level") != "" {
		managerConfig.LogLevel = c.String("log-level")
	}

	if c.String("data-dir") != "" {
		managerConfig.DataDir = c.String("data-dir")
		if !strings.HasSuffix(managerConfig.DataDir, "/") {
			managerConfig.DataDir = managerConfig.DataDir + "/"
		}
	}

	if c.String("zk-path") != "" {
		managerConfig.Scheduler.ZkPath = c.String("zk-path")
	}

	if c.String("listen-addr") != "" {
		managerConfig.ListenAddr = c.String("listen-addr")
	}

	managerConfig.AdvertiseAddr = c.String("advertise-addr")
	if managerConfig.AdvertiseAddr == "" {
		managerConfig.AdvertiseAddr = managerConfig.ListenAddr
	}

	if c.String("raft-listen-addr") != "" {
		managerConfig.RaftListenAddr = c.String("raft-listen-addr")
	}

	if strings.Index(managerConfig.RaftListenAddr, "://") == -1 {
		// Missing scheme. Force http.
		managerConfig.RaftListenAddr = "http://" + managerConfig.RaftListenAddr
	}

	if c.String("raft-advertise-addr") != "" {
		managerConfig.RaftAdvertiseAddr = c.String("raft-advertise-addr")
	}

	if managerConfig.RaftAdvertiseAddr == "" {
		managerConfig.RaftAdvertiseAddr = managerConfig.RaftListenAddr
	}

	if strings.Index(managerConfig.RaftAdvertiseAddr, "://") == -1 {
		// Missing scheme. Force http.
		managerConfig.RaftAdvertiseAddr = "http://" + managerConfig.RaftAdvertiseAddr
	}

	if c.String("join-addrs") != "" {
		managerConfig.JoinAddrs = strings.Split(c.String("join-addrs"), ",")
	}

	SchedulerConfig = managerConfig.Scheduler

	return managerConfig
}

func Hostname() string {
	hostname, err := os.Hostname()
	if err != nil {
		hostname = "UNKNOWN"
	}

	return hostname
}

var SchedulerConfig Scheduler
<|endoftext|>


<commit_before>//
// Copyright 2021, Igor Varavko
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package gitlab

import (
	"fmt"
	"net/http"
	"time"
)

// PipelinesService handles communication with the repositories related
// methods of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
type PipelinesService struct {
	client *Client
}

// PipelineVariable represents a pipeline variable.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
type PipelineVariable struct {
	Key          string `json:"key"`
	Value        string `json:"value"`
	VariableType string `json:"variable_type"`
}

// Pipeline represents a GitLab pipeline.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
type Pipeline struct {
	ID             int             `json:"id"`
	Status         string          `json:"status"`
	Ref            string          `json:"ref"`
	SHA            string          `json:"sha"`
	BeforeSHA      string          `json:"before_sha"`
	Tag            bool            `json:"tag"`
	YamlErrors     string          `json:"yaml_errors"`
	User           *BasicUser      `json:"user"`
	UpdatedAt      *time.Time      `json:"updated_at"`
	CreatedAt      *time.Time      `json:"created_at"`
	StartedAt      *time.Time      `json:"started_at"`
	FinishedAt     *time.Time      `json:"finished_at"`
	CommittedAt    *time.Time      `json:"committed_at"`
	Duration       int             `json:"duration"`
	Coverage       string          `json:"coverage"`
	WebURL         string          `json:"web_url"`
	DetailedStatus *DetailedStatus `json:"detailed_status"`
}

// DetailedStatus contains detailed information about the status of a pipeline.
type DetailedStatus struct {
	Icon         string `json:"icon"`
	Text         string `json:"text"`
	Label        string `json:"label"`
	Group        string `json:"group"`
	Tooltip      string `json:"tooltip"`
	HasDetails   bool   `json:"has_details"`
	DetailsPath  string `json:"details_path"`
	Illustration struct {
		Image string `json:"image"`
	} `json:"illustration"`
	Favicon string `json:"favicon"`
}

func (p Pipeline) String() string {
	return Stringify(p)
}

// PipelineTestReport contains a detailed report of a test run.
type PipelineTestReport struct {
	TotalTime    float64              `json:"total_time"`
	TotalCount   int                  `json:"total_count"`
	SuccessCount int                  `json:"success_count"`
	FailedCount  int                  `json:"failed_count"`
	SkippedCount int                  `json:"skipped_count"`
	ErrorCount   int                  `json:"error_count"`
	TestSuites   []PipelineTestSuites `json:"test_suites"`
}

// PipelineTestSuites contains test suites results.
type PipelineTestSuites struct {
	Name         string              `json:"name"`
	TotalTime    float64             `json:"total_time"`
	TotalCount   int                 `json:"total_count"`
	SuccessCount int                 `json:"success_count"`
	FailedCount  int                 `json:"failed_count"`
	SkippedCount int                 `json:"skipped_count"`
	ErrorCount   int                 `json:"error_count"`
	TestCases    []PipelineTestCases `json:"test_cases"`
}

// PipelineTestCases contains test cases details.
type PipelineTestCases struct {
	Status         string         `json:"status"`
	Name           string         `json:"name"`
	Classname      string         `json:"classname"`
	File           string         `json:"file"`
	ExecutionTime  float64        `json:"execution_time"`
	SystemOutput   string         `json:"system_output"`
	StackTrace     string         `json:"stack_trace"`
	AttachmentURL  string         `json:"attachment_url"`
	RecentFailures RecentFailures `json:"recent_failures"`
}

// RecentFailures contains failures count for the project's default branch.
type RecentFailures struct {
	Count      int    `json:"count"`
	BaseBranch string `json:"base_branch"`
}

func (p PipelineTestReport) String() string {
	return Stringify(p)
}

// PipelineInfo shows the basic entities of a pipeline, mostly used as fields
// on other assets, like Commit.
type PipelineInfo struct {
	ID        int        `json:"id"`
	Status    string     `json:"status"`
	Ref       string     `json:"ref"`
	SHA       string     `json:"sha"`
	WebURL    string     `json:"web_url"`
	UpdatedAt *time.Time `json:"updated_at"`
	CreatedAt *time.Time `json:"created_at"`
}

func (p PipelineInfo) String() string {
	return Stringify(p)
}

// ListProjectPipelinesOptions represents the available ListProjectPipelines() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines
type ListProjectPipelinesOptions struct {
	ListOptions
	Scope         *string          `url:"scope,omitempty" json:"scope,omitempty"`
	Status        *BuildStateValue `url:"status,omitempty" json:"status,omitempty"`
	Ref           *string          `url:"ref,omitempty" json:"ref,omitempty"`
	SHA           *string          `url:"sha,omitempty" json:"sha,omitempty"`
	YamlErrors    *bool            `url:"yaml_errors,omitempty" json:"yaml_errors,omitempty"`
	Name          *string          `url:"name,omitempty" json:"name,omitempty"`
	Username      *string          `url:"username,omitempty" json:"username,omitempty"`
	UpdatedAfter  *time.Time       `url:"updated_after,omitempty" json:"updated_after,omitempty"`
	UpdatedBefore *time.Time       `url:"updated_before,omitempty" json:"updated_before,omitempty"`
	OrderBy       *string          `url:"order_by,omitempty" json:"order_by,omitempty"`
	Sort          *string          `url:"sort,omitempty" json:"sort,omitempty"`
}

// ListProjectPipelines gets a list of project piplines.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines
func (s *PipelinesService) ListProjectPipelines(pid interface{}, opt *ListProjectPipelinesOptions, options ...RequestOptionFunc) ([]*PipelineInfo, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines", pathEscape(project))

	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var p []*PipelineInfo
	resp, err := s.client.Do(req, &p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// GetPipeline gets a single project pipeline.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#get-a-single-pipeline
func (s *PipelinesService) GetPipeline(pid interface{}, pipeline int, options ...RequestOptionFunc) (*Pipeline, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(Pipeline)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// GetPipelineVariables gets the variables of a single project pipeline.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#get-variables-of-a-pipeline
func (s *PipelinesService) GetPipelineVariables(pid interface{}, pipeline int, options ...RequestOptionFunc) ([]*PipelineVariable, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d/variables", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var p []*PipelineVariable
	resp, err := s.client.Do(req, &p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// GetPipelineTestReport gets the test report of a single project pipeline.
//
// GitLab API docs: https://docs.gitlab.com/ee/api/pipelines.html#get-a-pipelines-test-report
func (s *PipelinesService) GetPipelineTestReport(pid interface{}, pipeline int) (*PipelineTestReport, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d/test_report", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodGet, u, nil, nil)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineTestReport)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// CreatePipelineOptions represents the available CreatePipeline() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#create-a-new-pipeline
type CreatePipelineOptions struct {
	Ref       *string             `url:"ref" json:"ref"`
	Variables []*PipelineVariable `url:"variables,omitempty" json:"variables,omitempty"`
}

// CreatePipeline creates a new project pipeline.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#create-a-new-pipeline
func (s *PipelinesService) CreatePipeline(pid interface{}, opt *CreatePipelineOptions, options ...RequestOptionFunc) (*Pipeline, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline", pathEscape(project))

	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(Pipeline)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// RetryPipelineBuild retries failed builds in a pipeline
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipelines.html#retry-failed-builds-in-a-pipeline
func (s *PipelinesService) RetryPipelineBuild(pid interface{}, pipeline int, options ...RequestOptionFunc) (*Pipeline, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d/retry", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(Pipeline)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// CancelPipelineBuild cancels a pipeline builds
//
// GitLab API docs:
//https://docs.gitlab.com/ce/api/pipelines.html#cancel-a-pipelines-builds
func (s *PipelinesService) CancelPipelineBuild(pid interface{}, pipeline int, options ...RequestOptionFunc) (*Pipeline, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d/cancel", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(Pipeline)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// DeletePipeline deletes an existing pipeline.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipelines.html#delete-a-pipeline
func (s *PipelinesService) DeletePipeline(pid interface{}, pipeline int, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<commit_msg>added missing project_id on the PipelineInfo struct<commit_after>//
// Copyright 2021, Igor Varavko
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package gitlab

import (
	"fmt"
	"net/http"
	"time"
)

// PipelinesService handles communication with the repositories related
// methods of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
type PipelinesService struct {
	client *Client
}

// PipelineVariable represents a pipeline variable.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
type PipelineVariable struct {
	Key          string `json:"key"`
	Value        string `json:"value"`
	VariableType string `json:"variable_type"`
}

// Pipeline represents a GitLab pipeline.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html
type Pipeline struct {
	ID             int             `json:"id"`
	Status         string          `json:"status"`
	Ref            string          `json:"ref"`
	SHA            string          `json:"sha"`
	BeforeSHA      string          `json:"before_sha"`
	Tag            bool            `json:"tag"`
	YamlErrors     string          `json:"yaml_errors"`
	User           *BasicUser      `json:"user"`
	UpdatedAt      *time.Time      `json:"updated_at"`
	CreatedAt      *time.Time      `json:"created_at"`
	StartedAt      *time.Time      `json:"started_at"`
	FinishedAt     *time.Time      `json:"finished_at"`
	CommittedAt    *time.Time      `json:"committed_at"`
	Duration       int             `json:"duration"`
	Coverage       string          `json:"coverage"`
	WebURL         string          `json:"web_url"`
	DetailedStatus *DetailedStatus `json:"detailed_status"`
}

// DetailedStatus contains detailed information about the status of a pipeline.
type DetailedStatus struct {
	Icon         string `json:"icon"`
	Text         string `json:"text"`
	Label        string `json:"label"`
	Group        string `json:"group"`
	Tooltip      string `json:"tooltip"`
	HasDetails   bool   `json:"has_details"`
	DetailsPath  string `json:"details_path"`
	Illustration struct {
		Image string `json:"image"`
	} `json:"illustration"`
	Favicon string `json:"favicon"`
}

func (p Pipeline) String() string {
	return Stringify(p)
}

// PipelineTestReport contains a detailed report of a test run.
type PipelineTestReport struct {
	TotalTime    float64              `json:"total_time"`
	TotalCount   int                  `json:"total_count"`
	SuccessCount int                  `json:"success_count"`
	FailedCount  int                  `json:"failed_count"`
	SkippedCount int                  `json:"skipped_count"`
	ErrorCount   int                  `json:"error_count"`
	TestSuites   []PipelineTestSuites `json:"test_suites"`
}

// PipelineTestSuites contains test suites results.
type PipelineTestSuites struct {
	Name         string              `json:"name"`
	TotalTime    float64             `json:"total_time"`
	TotalCount   int                 `json:"total_count"`
	SuccessCount int                 `json:"success_count"`
	FailedCount  int                 `json:"failed_count"`
	SkippedCount int                 `json:"skipped_count"`
	ErrorCount   int                 `json:"error_count"`
	TestCases    []PipelineTestCases `json:"test_cases"`
}

// PipelineTestCases contains test cases details.
type PipelineTestCases struct {
	Status         string         `json:"status"`
	Name           string         `json:"name"`
	Classname      string         `json:"classname"`
	File           string         `json:"file"`
	ExecutionTime  float64        `json:"execution_time"`
	SystemOutput   string         `json:"system_output"`
	StackTrace     string         `json:"stack_trace"`
	AttachmentURL  string         `json:"attachment_url"`
	RecentFailures RecentFailures `json:"recent_failures"`
}

// RecentFailures contains failures count for the project's default branch.
type RecentFailures struct {
	Count      int    `json:"count"`
	BaseBranch string `json:"base_branch"`
}

func (p PipelineTestReport) String() string {
	return Stringify(p)
}

// PipelineInfo shows the basic entities of a pipeline, mostly used as fields
// on other assets, like Commit.
type PipelineInfo struct {
	ID        int        `json:"id"`
	ProjectID int        `json:"project_id"`
	Status    string     `json:"status"`
	Ref       string     `json:"ref"`
	SHA       string     `json:"sha"`
	WebURL    string     `json:"web_url"`
	UpdatedAt *time.Time `json:"updated_at"`
	CreatedAt *time.Time `json:"created_at"`
}

func (p PipelineInfo) String() string {
	return Stringify(p)
}

// ListProjectPipelinesOptions represents the available ListProjectPipelines() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines
type ListProjectPipelinesOptions struct {
	ListOptions
	Scope         *string          `url:"scope,omitempty" json:"scope,omitempty"`
	Status        *BuildStateValue `url:"status,omitempty" json:"status,omitempty"`
	Ref           *string          `url:"ref,omitempty" json:"ref,omitempty"`
	SHA           *string          `url:"sha,omitempty" json:"sha,omitempty"`
	YamlErrors    *bool            `url:"yaml_errors,omitempty" json:"yaml_errors,omitempty"`
	Name          *string          `url:"name,omitempty" json:"name,omitempty"`
	Username      *string          `url:"username,omitempty" json:"username,omitempty"`
	UpdatedAfter  *time.Time       `url:"updated_after,omitempty" json:"updated_after,omitempty"`
	UpdatedBefore *time.Time       `url:"updated_before,omitempty" json:"updated_before,omitempty"`
	OrderBy       *string          `url:"order_by,omitempty" json:"order_by,omitempty"`
	Sort          *string          `url:"sort,omitempty" json:"sort,omitempty"`
}

// ListProjectPipelines gets a list of project piplines.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#list-project-pipelines
func (s *PipelinesService) ListProjectPipelines(pid interface{}, opt *ListProjectPipelinesOptions, options ...RequestOptionFunc) ([]*PipelineInfo, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines", pathEscape(project))

	req, err := s.client.NewRequest(http.MethodGet, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var p []*PipelineInfo
	resp, err := s.client.Do(req, &p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// GetPipeline gets a single project pipeline.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#get-a-single-pipeline
func (s *PipelinesService) GetPipeline(pid interface{}, pipeline int, options ...RequestOptionFunc) (*Pipeline, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(Pipeline)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// GetPipelineVariables gets the variables of a single project pipeline.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#get-variables-of-a-pipeline
func (s *PipelinesService) GetPipelineVariables(pid interface{}, pipeline int, options ...RequestOptionFunc) ([]*PipelineVariable, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d/variables", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodGet, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	var p []*PipelineVariable
	resp, err := s.client.Do(req, &p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// GetPipelineTestReport gets the test report of a single project pipeline.
//
// GitLab API docs: https://docs.gitlab.com/ee/api/pipelines.html#get-a-pipelines-test-report
func (s *PipelinesService) GetPipelineTestReport(pid interface{}, pipeline int) (*PipelineTestReport, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d/test_report", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodGet, u, nil, nil)
	if err != nil {
		return nil, nil, err
	}

	p := new(PipelineTestReport)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// CreatePipelineOptions represents the available CreatePipeline() options.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#create-a-new-pipeline
type CreatePipelineOptions struct {
	Ref       *string             `url:"ref" json:"ref"`
	Variables []*PipelineVariable `url:"variables,omitempty" json:"variables,omitempty"`
}

// CreatePipeline creates a new project pipeline.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pipelines.html#create-a-new-pipeline
func (s *PipelinesService) CreatePipeline(pid interface{}, opt *CreatePipelineOptions, options ...RequestOptionFunc) (*Pipeline, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipeline", pathEscape(project))

	req, err := s.client.NewRequest(http.MethodPost, u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(Pipeline)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// RetryPipelineBuild retries failed builds in a pipeline
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipelines.html#retry-failed-builds-in-a-pipeline
func (s *PipelinesService) RetryPipelineBuild(pid interface{}, pipeline int, options ...RequestOptionFunc) (*Pipeline, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d/retry", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(Pipeline)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// CancelPipelineBuild cancels a pipeline builds
//
// GitLab API docs:
//https://docs.gitlab.com/ce/api/pipelines.html#cancel-a-pipelines-builds
func (s *PipelinesService) CancelPipelineBuild(pid interface{}, pipeline int, options ...RequestOptionFunc) (*Pipeline, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d/cancel", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodPost, u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	p := new(Pipeline)
	resp, err := s.client.Do(req, p)
	if err != nil {
		return nil, resp, err
	}

	return p, resp, err
}

// DeletePipeline deletes an existing pipeline.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pipelines.html#delete-a-pipeline
func (s *PipelinesService) DeletePipeline(pid interface{}, pipeline int, options ...RequestOptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d", pathEscape(project), pipeline)

	req, err := s.client.NewRequest(http.MethodDelete, u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<|endoftext|>


<commit_before>package lnwire

// code derived from https://github .com/btcsuite/btcd/blob/master/wire/message.go

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
)

// MessageHeaderSize is the number of bytes in a lightning message header.
// The bytes are allocated as follows: network magic 4 bytes + command 4
// bytes + payload length 4 bytes. Note that a checksum is omitted as lightning
// messages are assumed to be transmitted over an AEAD secured connection which
// provides integrity over the entire message.
const MessageHeaderSize = 12

// MaxMessagePayload is the maximum bytes a message can be regardless of other
// individual limits imposed by messages themselves.
const MaxMessagePayload = 65535 // 65KB

// MessageType is the unique 2 byte big-endian integer that indicates the type
// of message on the wire. All messages have a very simple header which
// consists simply of 2-byte message type. We omit a length field, and checksum
// as the Lighting Protocol is intended to be encapsulated within a
// confidential+authenticated cryptographic messaging protocol.
type MessageType uint16

const (
	MsgInit                      MessageType = 16
	MsgError                                 = 17
	MsgPing                                  = 18
	MsgPong                                  = 19
	MsgSingleFundingRequest                  = 32
	MsgSingleFundingResponse                 = 33
	MsgSingleFundingComplete                 = 34
	MsgSingleFundingSignComplete             = 35
	MsgFundingLocked                         = 36
	MsgCloseRequest                          = 39
	MsgCloseComplete                         = 40
	MsgUpdateAddHTLC                         = 128
	MsgUpdateFufillHTLC                      = 130
	MsgUpdateFailHTLC                        = 131
	MsgCommitSig                             = 132
	MsgRevokeAndAck                          = 133
	MsgChannelAnnouncement                   = 256
	MsgNodeAnnouncement                      = 257
	MsgChannelUpdate                         = 258
	MsgAnnounceSignatures                    = 259
)

// UnknownMessage is an implementation of the error interface that allows the
// creation of an error in response to an unknown message.
type UnknownMessage struct {
	messageType MessageType
}

// Error returns a human readable string describing the error.
//
// This is part of the error interface.
func (u *UnknownMessage) Error() string {
	return fmt.Sprintf("unable to parse message of unknown type: %v",
		u.messageType)
}

// Message is an interface that defines a lightning wire protocol message. The
// interface is general in order to allow implementing types full control over
// the representation of its data.
type Message interface {
	Decode(io.Reader, uint32) error
	Encode(io.Writer, uint32) error
	MsgType() MessageType
	MaxPayloadLength(uint32) uint32
}

// makeEmptyMessage creates a new empty message of the proper concrete type
// based on the passed message type.
func makeEmptyMessage(msgType MessageType) (Message, error) {
	var msg Message

	switch msgType {
	case MsgInit:
		msg = &Init{}
	case MsgSingleFundingRequest:
		msg = &SingleFundingRequest{}
	case MsgSingleFundingResponse:
		msg = &SingleFundingResponse{}
	case MsgSingleFundingComplete:
		msg = &SingleFundingComplete{}
	case MsgSingleFundingSignComplete:
		msg = &SingleFundingSignComplete{}
	case MsgFundingLocked:
		msg = &FundingLocked{}
	case MsgCloseRequest:
		msg = &CloseRequest{}
	case MsgCloseComplete:
		msg = &CloseComplete{}
	case MsgUpdateAddHTLC:
		msg = &UpdateAddHTLC{}
	case MsgUpdateFailHTLC:
		msg = &UpdateFailHTLC{}
	case MsgUpdateFufillHTLC:
		msg = &UpdateFufillHTLC{}
	case MsgCommitSig:
		msg = &CommitSig{}
	case MsgRevokeAndAck:
		msg = &RevokeAndAck{}
	case MsgError:
		msg = &Error{}
	case MsgChannelAnnouncement:
		msg = &ChannelAnnouncement{}
	case MsgChannelUpdate:
		msg = &ChannelUpdate{}
	case MsgNodeAnnouncement:
		msg = &NodeAnnouncement{}
	case MsgPing:
		msg = &Ping{}
	case MsgAnnounceSignatures:
		msg = &AnnounceSignatures{}
	case MsgPong:
		msg = &Pong{}
	default:
		return nil, fmt.Errorf("unknown message type [%d]", msgType)
	}

	return msg, nil
}

// messageHeader represents the header structure for all lightning protocol
// messages.
type messageHeader struct {
	// magic represents Which Blockchain Technology(TM) to use.
	// NOTE(j): We don't need to worry about the magic overlapping with
	// bitcoin since this is inside encrypted comms anyway, but maybe we
	// should use the XOR (^wire.TestNet3) just in case???
	magic   wire.BitcoinNet // 4 bytes
	command uint32          // 4 bytes
	length  uint32          // 4 bytes
}

// readMessageHeader reads a lightning protocol message header from r.
func readMessageHeader(r io.Reader) (int, *messageHeader, error) {
	// As the message header is a fixed size structure, read bytes for the
	// entire header at once.
	var headerBytes [MessageHeaderSize]byte
	n, err := io.ReadFull(r, headerBytes[:])
	if err != nil {
		return n, nil, err
	}
	hr := bytes.NewReader(headerBytes[:])

	// Create and populate the message header from the raw header bytes.
	hdr := messageHeader{}
	err = readElements(hr,
		&hdr.magic,
		&hdr.command,
		&hdr.length)
	if err != nil {
		return n, nil, err
	}

	return n, &hdr, nil
}

// discardInput reads n bytes from reader r in chunks and discards the read
// bytes. This is used to skip payloads when various errors occur and helps
// prevent rogue nodes from causing massive memory allocation through forging
// header length.
func discardInput(r io.Reader, n uint32) {
	maxSize := uint32(10 * 1024) // 10k at a time
	numReads := n / maxSize
	bytesRemaining := n % maxSize
	if n > 0 {
		buf := make([]byte, maxSize)
		for i := uint32(0); i < numReads; i++ {
			io.ReadFull(r, buf)
		}
	}
	if bytesRemaining > 0 {
		buf := make([]byte, bytesRemaining)
		io.ReadFull(r, buf)
	}
}

// WriteMessage writes a lightning Message to w including the necessary header
// information and returns the number of bytes written.
func WriteMessage(w io.Writer, msg Message, pver uint32, btcnet wire.BitcoinNet) (int, error) {
	totalBytes := 0

	cmd := msg.Command()

	// Encode the message payload
	var bw bytes.Buffer
	err := msg.Encode(&bw, pver)
	if err != nil {
		return totalBytes, err
	}
	payload := bw.Bytes()
	lenp := len(payload)

	// Enforce maximum overall message payload
	if lenp > MaxMessagePayload {
		return totalBytes, fmt.Errorf("message payload is too large - "+
			"encoded %d bytes, but maximum message payload is %d bytes",
			lenp, MaxMessagePayload)
	}

	// Enforce maximum message payload on the message type
	mpl := msg.MaxPayloadLength(pver)
	if uint32(lenp) > mpl {
		return totalBytes, fmt.Errorf("message payload is too large - "+
			"encoded %d bytes, but maximum message payload of "+
			"type %x is %d bytes", lenp, cmd, mpl)
	}

	// Create header for the message.
	hdr := messageHeader{magic: btcnet, command: cmd, length: uint32(lenp)}

	// Encode the header for the message. This is done to a buffer
	// rather than directly to the writer since writeElements doesn't
	// return the number of bytes written.
	hw := bytes.NewBuffer(make([]byte, 0, MessageHeaderSize))
	if err := writeElements(hw, hdr.magic, hdr.command, hdr.length); err != nil {
		return 0, nil
	}

	// Write the header first.
	n, err := w.Write(hw.Bytes())
	totalBytes += n
	if err != nil {
		return totalBytes, err
	}

	// Write payload the payload itself after the header.
	n, err = w.Write(payload)
	totalBytes += n
	return totalBytes, err
}

// ReadMessage reads, validates, and parses the next bitcoin Message from r for
// the provided protocol version and bitcoin network.  It returns the number of
// bytes read in addition to the parsed Message and raw bytes which comprise the
// message.  This function is the same as ReadMessage except it also returns the
// number of bytes read.
func ReadMessage(r io.Reader, pver uint32, btcnet wire.BitcoinNet) (int, Message, []byte, error) {
	totalBytes := 0
	n, hdr, err := readMessageHeader(r)
	totalBytes += n
	if err != nil {
		return totalBytes, nil, nil, err
	}

	// Enforce maximum message payload
	if hdr.length > MaxMessagePayload {
		return totalBytes, nil, nil, fmt.Errorf("message payload is "+
			"too large - header indicates %d bytes, but max "+
			"message payload is %d bytes.", hdr.length,
			MaxMessagePayload)
	}

	// Check for messages in the wrong network.
	if hdr.magic != btcnet {
		discardInput(r, hdr.length)
		return totalBytes, nil, nil, fmt.Errorf("message from other "+
			"network [%v]", hdr.magic)
	}

	// Create struct of appropriate message type based on the command.
	command := hdr.command
	msg, err := makeEmptyMessage(command)
	if err != nil {
		discardInput(r, hdr.length)
		return totalBytes, nil, nil, &UnknownMessage{
			messageType: command,
		}
	}

	// Check for maximum length based on the message type.
	mpl := msg.MaxPayloadLength(pver)
	if hdr.length > mpl {
		discardInput(r, hdr.length)
		return totalBytes, nil, nil, fmt.Errorf("payload exceeds max "+
			"length. indicates %v bytes, but max of message type %v is %v.",
			hdr.length, command, mpl)
	}

	// Read payload.
	payload := make([]byte, hdr.length)
	n, err = io.ReadFull(r, payload)
	totalBytes += n
	if err != nil {
		return totalBytes, nil, nil, err
	}

	// Unmarshal message.
	pr := bytes.NewBuffer(payload)
	if err = msg.Decode(pr, pver); err != nil {
		return totalBytes, nil, nil, err
	}

	// Validate the data.
	if err = msg.Validate(); err != nil {
		return totalBytes, nil, nil, err
	}

	return totalBytes, msg, payload, nil
}
<commit_msg>lnwire: convert message parsing to use the new minimal type header<commit_after>package lnwire

// code derived from https://github .com/btcsuite/btcd/blob/master/wire/message.go

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
)

// MaxMessagePayload is the maximum bytes a message can be regardless of other
// individual limits imposed by messages themselves.
const MaxMessagePayload = 65535 // 65KB

// MessageType is the unique 2 byte big-endian integer that indicates the type
// of message on the wire. All messages have a very simple header which
// consists simply of 2-byte message type. We omit a length field, and checksum
// as the Lighting Protocol is intended to be encapsulated within a
// confidential+authenticated cryptographic messaging protocol.
type MessageType uint16

const (
	MsgInit                      MessageType = 16
	MsgError                                 = 17
	MsgPing                                  = 18
	MsgPong                                  = 19
	MsgSingleFundingRequest                  = 32
	MsgSingleFundingResponse                 = 33
	MsgSingleFundingComplete                 = 34
	MsgSingleFundingSignComplete             = 35
	MsgFundingLocked                         = 36
	MsgCloseRequest                          = 39
	MsgCloseComplete                         = 40
	MsgUpdateAddHTLC                         = 128
	MsgUpdateFufillHTLC                      = 130
	MsgUpdateFailHTLC                        = 131
	MsgCommitSig                             = 132
	MsgRevokeAndAck                          = 133
	MsgChannelAnnouncement                   = 256
	MsgNodeAnnouncement                      = 257
	MsgChannelUpdate                         = 258
	MsgAnnounceSignatures                    = 259
)

// UnknownMessage is an implementation of the error interface that allows the
// creation of an error in response to an unknown message.
type UnknownMessage struct {
	messageType MessageType
}

// Error returns a human readable string describing the error.
//
// This is part of the error interface.
func (u *UnknownMessage) Error() string {
	return fmt.Sprintf("unable to parse message of unknown type: %v",
		u.messageType)
}

// Message is an interface that defines a lightning wire protocol message. The
// interface is general in order to allow implementing types full control over
// the representation of its data.
type Message interface {
	Decode(io.Reader, uint32) error
	Encode(io.Writer, uint32) error
	MsgType() MessageType
	MaxPayloadLength(uint32) uint32
}

// makeEmptyMessage creates a new empty message of the proper concrete type
// based on the passed message type.
func makeEmptyMessage(msgType MessageType) (Message, error) {
	var msg Message

	switch msgType {
	case MsgInit:
		msg = &Init{}
	case MsgSingleFundingRequest:
		msg = &SingleFundingRequest{}
	case MsgSingleFundingResponse:
		msg = &SingleFundingResponse{}
	case MsgSingleFundingComplete:
		msg = &SingleFundingComplete{}
	case MsgSingleFundingSignComplete:
		msg = &SingleFundingSignComplete{}
	case MsgFundingLocked:
		msg = &FundingLocked{}
	case MsgCloseRequest:
		msg = &CloseRequest{}
	case MsgCloseComplete:
		msg = &CloseComplete{}
	case MsgUpdateAddHTLC:
		msg = &UpdateAddHTLC{}
	case MsgUpdateFailHTLC:
		msg = &UpdateFailHTLC{}
	case MsgUpdateFufillHTLC:
		msg = &UpdateFufillHTLC{}
	case MsgCommitSig:
		msg = &CommitSig{}
	case MsgRevokeAndAck:
		msg = &RevokeAndAck{}
	case MsgError:
		msg = &Error{}
	case MsgChannelAnnouncement:
		msg = &ChannelAnnouncement{}
	case MsgChannelUpdate:
		msg = &ChannelUpdate{}
	case MsgNodeAnnouncement:
		msg = &NodeAnnouncement{}
	case MsgPing:
		msg = &Ping{}
	case MsgAnnounceSignatures:
		msg = &AnnounceSignatures{}
	case MsgPong:
		msg = &Pong{}
	default:
		return nil, fmt.Errorf("unknown message type [%d]", msgType)
	}

	return msg, nil
}

// WriteMessage writes a lightning Message to w including the necessary header
// information and returns the number of bytes written.
func WriteMessage(w io.Writer, msg Message, pver uint32) (int, error) {
	totalBytes := 0

	// Encode the message payload itself into a temporary buffer.
	// TODO(roasbeef): create buffer pool
	var bw bytes.Buffer
	if err := msg.Encode(&bw, pver); err != nil {
		return totalBytes, err
	}
	payload := bw.Bytes()
	lenp := len(payload)

	// Enforce maximum overall message payload.
	if lenp > MaxMessagePayload {
		return totalBytes, fmt.Errorf("message payload is too large - "+
			"encoded %d bytes, but maximum message payload is %d bytes",
			lenp, MaxMessagePayload)
	}

	// Enforce maximum message payload on the message type.
	mpl := msg.MaxPayloadLength(pver)
	if uint32(lenp) > mpl {
		return totalBytes, fmt.Errorf("message payload is too large - "+
			"encoded %d bytes, but maximum message payload of "+
			"type %x is %d bytes", lenp, msg.MsgType(), mpl)
	}

	// With the initial sanity checks complete, we'll now write out the
	// message type itself.
	var mType [2]byte
	binary.BigEndian.PutUint16(mType[:], uint16(msg.MsgType()))
	n, err := w.Write(mType[:])
	totalBytes += n
	if err != nil {
		return totalBytes, err
	}

	// With the message type written, we'll now write out the raw payload
	// itself.
	n, err = w.Write(payload)
	totalBytes += n

	return totalBytes, err
}

// ReadMessage reads, validates, and parses the next bitcoin Message from r for
// the provided protocol version.  It returns the number of bytes read in
// addition to the parsed Message and raw bytes which comprise the message.
func ReadMessage(r io.Reader, pver uint32) (int, Message, error) {
	// TODO(roasbeef): need to explicitly enforce max message payload, or
	// just allow it to be done by the MaxPayloadLength?
	totalBytes := 0

	// First, we'll read out the first two bytes of the message so we can
	// create the proper empty message.
	var mType [2]byte
	n, err := io.ReadFull(r, mType[:])
	totalBytes += n
	if err != nil {
		return totalBytes, nil, err
	}

	msgType := MessageType(binary.BigEndian.Uint16(mType[:]))

	// Now that we know the target message type, we can create the proper
	// empty message type and decode the message into it.
	msg, err := makeEmptyMessage(msgType)
	if err != nil {
		return totalBytes, nil, err
	}
	if err := msg.Decode(r, pver); err != nil {
		return totalBytes, nil, err
	}
	totalBytes += int(msg.MaxPayloadLength(pver))

	return totalBytes, msg, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Netflix, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package chunked

import (
	"math"
	"strconv"
)

func metaKey(key []byte) []byte {
	keyCopy := make([]byte, len(key))
	copy(keyCopy, key)
	return append(keyCopy, ([]byte("_meta"))...)
}

func chunkKey(key []byte, chunk int) []byte {
	// TODO: POOL ME PLEASE
	// or maybe not since pooling adds interface{} conversion overhead anyway
	// +4 to account for the '_###' at the end
	keyCopy := make([]byte, len(key), len(key)+4)
	copy(keyCopy, key)
	keyCopy = append(keyCopy, '_')
	return strconv.AppendInt(keyCopy, int64(chunk), 10)
}

func chunkSliceIndices(chunkSize, chunkNum, totalLength int) (int, int) {
	// Indices for slicing. End is exclusive
	start := chunkSize * chunkNum
	end := int(math.Min(float64(start+chunkSize), float64(totalLength)))

	return start, end
}
<commit_msg>even better meta key and chunk key creation<commit_after>// Copyright 2015 Netflix, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package chunked

import (
	"math"
	"strconv"
)

func metaKey(key []byte) []byte {
	// no need to copy, the header returned will point to the same array
	// just with a longer len. It might get copied if the runtime decides
	// to grow the slice.
	return append(key, ([]byte("-meta"))...)
}

func chunkKey(key []byte, chunk int) []byte {
	// TODO: POOL ME PLEASE
	// or maybe not since pooling adds interface{} conversion overhead anyway
	//
	// no need to copy, the header returned will point to the same array
	// just with a longer len. It might get copied if the runtime decides
	// to grow the slice.
	return strconv.AppendInt(key, int64(-chunk), 10)
}

func chunkSliceIndices(chunkSize, chunkNum, totalLength int) (int, int) {
	// Indices for slicing. End is exclusive
	start := chunkSize * chunkNum
	end := int(math.Min(float64(start+chunkSize), float64(totalLength)))

	return start, end
}
<|endoftext|>