<commit_before>package monitor

import (
	"fmt"
	"sync"
	"time"

	"github.com/libopenstorage/stork/drivers/volume"
	storklog "github.com/libopenstorage/stork/pkg/log"
	"github.com/portworx/sched-ops/k8s"
	log "github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/kubernetes/pkg/scheduler/algorithm"
	"k8s.io/kubernetes/pkg/util/node"
)

const (
	defaultIntervalSec = 120
	minimumIntervalSec = 30
)

// Monitor Storage driver monitor
type Monitor struct {
	Driver      volume.Driver
	IntervalSec int64
	lock        sync.Mutex
	started     bool
	stopChannel chan int
	done        chan int
}

// Start Starts the monitor
func (m *Monitor) Start() error {
	m.lock.Lock()
	defer m.lock.Unlock()

	if m.started {
		return fmt.Errorf("Monitor has already been started")
	}

	if m.IntervalSec == 0 {
		m.IntervalSec = defaultIntervalSec
	} else if m.IntervalSec < minimumIntervalSec {
		return fmt.Errorf("minimum interval for health monitor is %v seconds", minimumIntervalSec)
	}

	m.stopChannel = make(chan int)
	m.done = make(chan int)

	if err := m.podMonitor(); err != nil {
		return err
	}

	go m.driverMonitor()

	m.started = true

	return nil
}

// Stop Stops the monitor
func (m *Monitor) Stop() error {
	m.lock.Lock()
	defer m.lock.Unlock()

	if !m.started {
		return fmt.Errorf("Monitor has not been started")
	}

	close(m.stopChannel)
	<-m.done

	m.started = false
	return nil
}

func (m *Monitor) isSameNode(k8sNodeName string, driverNode *volume.NodeInfo) bool {
	if k8sNodeName == driverNode.Hostname {
		return true
	}
	node, err := k8s.Instance().GetNodeByName(k8sNodeName)
	if err != nil {
		log.Errorf("Error getting node %v: %v", k8sNodeName, err)
		return false
	}
	return volume.IsNodeMatch(node, driverNode)
}

func (m *Monitor) podMonitor() error {
	fn := func(object runtime.Object) error {
		pod, ok := object.(*v1.Pod)
		if !ok {
			err := fmt.Errorf("invalid object type on pod watch: %v", object)
			return err
		}

		podUnknownState := false

		if pod.Status.Reason == node.NodeUnreachablePodReason {
			podUnknownState = true
		} else if pod.ObjectMeta.DeletionTimestamp != nil {
			n, err := k8s.Instance().GetNodeByName(pod.Spec.NodeName)
			if err != nil {
				return err
			}

			// Check if node has eviction taint
			for _, taint := range n.Spec.Taints {
				if taint.Key == algorithm.TaintNodeUnreachable &&
					taint.Effect == v1.TaintEffectNoExecute {
					podUnknownState = true
					break
				}
			}
		}

		if podUnknownState {
			owns, err := m.doesDriverOwnPodVolumes(pod)
			if err != nil || !owns {
				return nil
			}

			storklog.PodLog(pod).Infof("Force deleting pod as it's in unknown state.")

			// force delete the pod
			err = k8s.Instance().DeletePods([]v1.Pod{*pod}, true)
			if err != nil {
				if errors.IsNotFound(err) {
					return nil
				}

				storklog.PodLog(pod).Errorf("Error deleting pod: %v", err)
				return err
			}
		}

		return nil
	}

	if err := k8s.Instance().WatchPods("", fn, metav1.ListOptions{}); err != nil {
		log.Errorf("failed to watch pods due to: %v", err)
		return err
	}

	return nil
}

func (m *Monitor) driverMonitor() {
	defer close(m.done)

	for {
		select {
		default:
			log.Debugf("Monitoring storage nodes")
			nodes, err := m.Driver.GetNodes()
			if err != nil {
				log.Errorf("Error getting nodes: %v", err)
				time.Sleep(2 * time.Second)
			}
			for _, node := range nodes {
				// Check if nodes are reported online by the storage driver
				// If not online, look at all the pods on that node
				// For any Running pod on that node using volume by the driver, kill the pod
				if node.Status != volume.NodeOnline {
					pods, err := k8s.Instance().GetPods("", nil)
					if err != nil {
						log.Errorf("Error getting pods: %v", err)
						continue
					}
					for _, pod := range pods.Items {
						owns, err := m.doesDriverOwnPodVolumes(&pod)
						if err != nil || !owns {
							continue
						}

						if m.isSameNode(pod.Spec.NodeName, node) &&
							(pod.Status.Phase == v1.PodRunning || pod.Status.Phase == v1.PodFailed) {
							storklog.PodLog(&pod).Infof("Deleting Pod from Node: %v", pod.Spec.NodeName)
							err = k8s.Instance().DeletePods([]v1.Pod{pod}, true)
							if err != nil {
								storklog.PodLog(&pod).Errorf("Error deleting pod: %v", err)
								continue
							}
						}
					}
				}
			}
			time.Sleep(time.Duration(m.IntervalSec) * time.Second)
		case <-m.stopChannel:
			return
		}
	}
}

func (m *Monitor) doesDriverOwnPodVolumes(pod *v1.Pod) (bool, error) {
	volumes, err := m.Driver.GetPodVolumes(&pod.Spec, pod.Namespace)
	if err != nil {
		storklog.PodLog(pod).Errorf("Error getting volumes for pod: %v", err)
		return false, err
	}

	if len(volumes) == 0 {
		storklog.PodLog(pod).Debugf("Pod doesn't have any volumes by driver")
		return false, nil
	}

	return true, nil
}
<commit_msg>[Monitor] Delete pods from node if it is in any phase<commit_after>package monitor

import (
	"fmt"
	"sync"
	"time"

	"github.com/libopenstorage/stork/drivers/volume"
	storklog "github.com/libopenstorage/stork/pkg/log"
	"github.com/portworx/sched-ops/k8s"
	log "github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/kubernetes/pkg/scheduler/algorithm"
	"k8s.io/kubernetes/pkg/util/node"
)

const (
	defaultIntervalSec = 120
	minimumIntervalSec = 30
)

// Monitor Storage driver monitor
type Monitor struct {
	Driver      volume.Driver
	IntervalSec int64
	lock        sync.Mutex
	started     bool
	stopChannel chan int
	done        chan int
}

// Start Starts the monitor
func (m *Monitor) Start() error {
	m.lock.Lock()
	defer m.lock.Unlock()

	if m.started {
		return fmt.Errorf("Monitor has already been started")
	}

	if m.IntervalSec == 0 {
		m.IntervalSec = defaultIntervalSec
	} else if m.IntervalSec < minimumIntervalSec {
		return fmt.Errorf("minimum interval for health monitor is %v seconds", minimumIntervalSec)
	}

	m.stopChannel = make(chan int)
	m.done = make(chan int)

	if err := m.podMonitor(); err != nil {
		return err
	}

	go m.driverMonitor()

	m.started = true

	return nil
}

// Stop Stops the monitor
func (m *Monitor) Stop() error {
	m.lock.Lock()
	defer m.lock.Unlock()

	if !m.started {
		return fmt.Errorf("Monitor has not been started")
	}

	close(m.stopChannel)
	<-m.done

	m.started = false
	return nil
}

func (m *Monitor) isSameNode(k8sNodeName string, driverNode *volume.NodeInfo) bool {
	if k8sNodeName == driverNode.Hostname {
		return true
	}
	node, err := k8s.Instance().GetNodeByName(k8sNodeName)
	if err != nil {
		log.Errorf("Error getting node %v: %v", k8sNodeName, err)
		return false
	}
	return volume.IsNodeMatch(node, driverNode)
}

func (m *Monitor) podMonitor() error {
	fn := func(object runtime.Object) error {
		pod, ok := object.(*v1.Pod)
		if !ok {
			err := fmt.Errorf("invalid object type on pod watch: %v", object)
			return err
		}

		podUnknownState := false

		if pod.Status.Reason == node.NodeUnreachablePodReason {
			podUnknownState = true
		} else if pod.ObjectMeta.DeletionTimestamp != nil {
			n, err := k8s.Instance().GetNodeByName(pod.Spec.NodeName)
			if err != nil {
				return err
			}

			// Check if node has eviction taint
			for _, taint := range n.Spec.Taints {
				if taint.Key == algorithm.TaintNodeUnreachable &&
					taint.Effect == v1.TaintEffectNoExecute {
					podUnknownState = true
					break
				}
			}
		}

		if podUnknownState {
			owns, err := m.doesDriverOwnPodVolumes(pod)
			if err != nil || !owns {
				return nil
			}

			storklog.PodLog(pod).Infof("Force deleting pod as it's in unknown state.")

			// force delete the pod
			err = k8s.Instance().DeletePods([]v1.Pod{*pod}, true)
			if err != nil {
				if errors.IsNotFound(err) {
					return nil
				}

				storklog.PodLog(pod).Errorf("Error deleting pod: %v", err)
				return err
			}
		}

		return nil
	}

	if err := k8s.Instance().WatchPods("", fn, metav1.ListOptions{}); err != nil {
		log.Errorf("failed to watch pods due to: %v", err)
		return err
	}

	return nil
}

func (m *Monitor) driverMonitor() {
	defer close(m.done)

	for {
		select {
		default:
			log.Debugf("Monitoring storage nodes")
			nodes, err := m.Driver.GetNodes()
			if err != nil {
				log.Errorf("Error getting nodes: %v", err)
				time.Sleep(2 * time.Second)
			}
			for _, node := range nodes {
				// Check if nodes are reported online by the storage driver
				// If not online, look at all the pods on that node
				// For any Running pod on that node using volume by the driver, kill the pod
				if node.Status != volume.NodeOnline {
					pods, err := k8s.Instance().GetPods("", nil)
					if err != nil {
						log.Errorf("Error getting pods: %v", err)
						continue
					}
					for _, pod := range pods.Items {
						owns, err := m.doesDriverOwnPodVolumes(&pod)
						if err != nil || !owns {
							continue
						}

						if m.isSameNode(pod.Spec.NodeName, node) {
							storklog.PodLog(&pod).Infof("Deleting Pod from Node: %v", pod.Spec.NodeName)
							err = k8s.Instance().DeletePods([]v1.Pod{pod}, true)
							if err != nil {
								storklog.PodLog(&pod).Errorf("Error deleting pod: %v", err)
								continue
							}
						}
					}
				}
			}
			time.Sleep(time.Duration(m.IntervalSec) * time.Second)
		case <-m.stopChannel:
			return
		}
	}
}

func (m *Monitor) doesDriverOwnPodVolumes(pod *v1.Pod) (bool, error) {
	volumes, err := m.Driver.GetPodVolumes(&pod.Spec, pod.Namespace)
	if err != nil {
		storklog.PodLog(pod).Errorf("Error getting volumes for pod: %v", err)
		return false, err
	}

	if len(volumes) == 0 {
		storklog.PodLog(pod).Debugf("Pod doesn't have any volumes by driver")
		return false, nil
	}

	return true, nil
}
<|endoftext|>


<commit_before>// +build go1.10

package mutagen

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

const (
	// VersionMajor represents the current major version of Mutagen.
	VersionMajor = 0
	// VersionMinor represents the current minor version of Mutagen.
	VersionMinor = 4
	// VersionPatch represents the current patch version of Mutagen.
	VersionPatch = 1
)

var Version string

func init() {
	Version = fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch)
}

type versionBytes [12]byte

func SendVersion(writer io.Writer) error {
	// Compute the version bytes.
	var data versionBytes
	binary.BigEndian.PutUint32(data[:4], VersionMajor)
	binary.BigEndian.PutUint32(data[4:8], VersionMinor)
	binary.BigEndian.PutUint32(data[8:], VersionPatch)

	// Transmit the bytes.
	_, err := writer.Write(data[:])
	return err
}

func ReceiveVersion(reader io.Reader) (uint32, uint32, uint32, error) {
	// Read the bytes.
	var data versionBytes
	if _, err := io.ReadFull(reader, data[:]); err != nil {
		return 0, 0, 0, err
	}

	// Decode components.
	major := binary.BigEndian.Uint32(data[:4])
	minor := binary.BigEndian.Uint32(data[4:8])
	patch := binary.BigEndian.Uint32(data[8:])

	// Done.
	return major, minor, patch, nil
}

func ReceiveAndCompareVersion(reader io.Reader) (bool, error) {
	// Receive the version.
	major, minor, patch, err := ReceiveVersion(reader)
	if err != nil {
		return false, err
	}

	// Compare the version.
	return major == VersionMajor &&
		minor == VersionMinor &&
		patch == VersionPatch, nil
}
<commit_msg>Bumped development version to v0.4.2.<commit_after>// +build go1.10

package mutagen

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

const (
	// VersionMajor represents the current major version of Mutagen.
	VersionMajor = 0
	// VersionMinor represents the current minor version of Mutagen.
	VersionMinor = 4
	// VersionPatch represents the current patch version of Mutagen.
	VersionPatch = 2
)

var Version string

func init() {
	Version = fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch)
}

type versionBytes [12]byte

func SendVersion(writer io.Writer) error {
	// Compute the version bytes.
	var data versionBytes
	binary.BigEndian.PutUint32(data[:4], VersionMajor)
	binary.BigEndian.PutUint32(data[4:8], VersionMinor)
	binary.BigEndian.PutUint32(data[8:], VersionPatch)

	// Transmit the bytes.
	_, err := writer.Write(data[:])
	return err
}

func ReceiveVersion(reader io.Reader) (uint32, uint32, uint32, error) {
	// Read the bytes.
	var data versionBytes
	if _, err := io.ReadFull(reader, data[:]); err != nil {
		return 0, 0, 0, err
	}

	// Decode components.
	major := binary.BigEndian.Uint32(data[:4])
	minor := binary.BigEndian.Uint32(data[4:8])
	patch := binary.BigEndian.Uint32(data[8:])

	// Done.
	return major, minor, patch, nil
}

func ReceiveAndCompareVersion(reader io.Reader) (bool, error) {
	// Receive the version.
	major, minor, patch, err := ReceiveVersion(reader)
	if err != nil {
		return false, err
	}

	// Compare the version.
	return major == VersionMajor &&
		minor == VersionMinor &&
		patch == VersionPatch, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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 notes

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

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"gopkg.in/yaml.v2"
)

// MapProvider interface that obtains release notes maps from a source
type MapProvider interface {
	GetMapsForPR(int) (map[int]*ReleaseNotesMap, error)
}

// NewProviderFromInitString creates a new map provider from an initialization string
func NewProviderFromInitString(initString string) (MapProvider, error) {
	// If init string starts with gs:// return a CloudStorageProvider
	if initString[0:5] == "gs://" {
		// Currently for illustration purposes
		return nil, errors.New("CloudStorageProvider is not yet implemented")
	}

	// Otherwise, build a DirectoryMapProvider using the
	// whole init string as the path
	fileStat, err := os.Stat(initString)
	if os.IsNotExist(err) {
		return nil, errors.New("release notes map path does not exist")
	}
	if !fileStat.IsDir() {
		return nil, errors.New("release notes map path is not a directory")
	}

	return &DirectoryMapProvider{Path: initString}, nil
}

// ParseReleaseNotesMap Parses a Release Notes Map
func ParseReleaseNotesMap(mapPath string) (*[]ReleaseNotesMap, error) {
	notemaps := []ReleaseNotesMap{}
	yamlReader, err := os.Open(mapPath)
	if err != nil {
		return nil, errors.Wrap(err, "opening maps")
	}

	decoder := yaml.NewDecoder(yamlReader)
	noteMap := ReleaseNotesMap{}

	for {
		if err := decoder.Decode(&noteMap); err == io.EOF {
			break
		} else if err != nil {
			return nil, errors.Wrap(err, "decoding note map")
		}
		notemaps = append(notemaps, noteMap)
	}

	return &notemaps, nil
}

// ReleaseNotesMap holds the changes that will be applied to the notes
// the struct has double annotation because of this: https://play.golang.org/p/C7QM1varozy
type ReleaseNotesMap struct {
	// Pull request where the note was published
	PR int `json:"pr"`
	// SHA of the notes commit
	Commit      string `json:"commit,omitempty" yaml:"commit,omitempty"`
	ReleaseNote struct {
		// Text is the actual content of the release note
		Text *string `json:"text,omitempty" yaml:"text,omitempty"`

		// Docs is additional documentation for the release note
		Documentation *[]*Documentation `json:"documentation,omitempty" yaml:"documentation,omitempty"`

		// Author is the GitHub username of the commit author
		Author *string `json:"author,omitempty" yaml:"author,omitempty"`

		// Areas is a list of the labels beginning with area/
		Areas *[]string `json:"areas,omitempty" yaml:"areas,omitempty"`

		// Kinds is a list of the labels beginning with kind/
		Kinds *[]string `json:"kinds,omitempty" yaml:"kinds,omitempty"`

		// SIGs is a list of the labels beginning with sig/
		SIGs *[]string `json:"sigs,omitempty" yaml:"sigs,omitempty"`

		// Indicates whether or not a note will appear as a new feature
		Feature *bool `json:"feature,omitempty" yaml:"feature,omitempty"`

		// ActionRequired indicates whether or not the release-note-action-required
		// label was set on the PR
		ActionRequired *bool `json:"action_required,omitempty" yaml:"action_required,omitempty"`
	} `json:"releasenote"`

	DataFields map[string]ReleaseNotesDataField `json:"datafields,omitempty" yaml:"datafields,omitempty"`
}

// ReleaseNotesDataField extra data added to a release note
type ReleaseNotesDataField interface{}

// DirectoryMapProvider is a provider that gets maps from a directory
type DirectoryMapProvider struct {
	Path string
	Maps map[int]map[int]*ReleaseNotesMap
}

// readMaps Open the dir and read dir notes
func (mp *DirectoryMapProvider) readMaps() error {
	var fileList []string
	mp.Maps = map[int]map[int]*ReleaseNotesMap{}

	err := filepath.Walk(mp.Path, func(path string, info os.FileInfo, err error) error {
		if filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" {
			fileList = append(fileList, path)
		}
		return nil
	})

	counter := 0
	for _, fileName := range fileList {
		notemaps, err := ParseReleaseNotesMap(fileName)
		if err != nil {
			return errors.Wrapf(err, "while parsing note map in %s", fileName)
		}
		for _, notemap := range *notemaps {
			if _, ok := mp.Maps[notemap.PR]; !ok {
				mp.Maps[notemap.PR] = map[int]*ReleaseNotesMap{}
			}
			mp.Maps[notemap.PR][len(mp.Maps[notemap.PR])] = &notemap
			counter++
		}
	}
	logrus.Infof("Successfully parsed %d release notes maps for %d PRs from %s", counter, len(mp.Maps), mp.Path)
	return err
}

// GetMapsForPR get the release notes maps for a specific PR number
func (mp *DirectoryMapProvider) GetMapsForPR(pr int) (notesMap map[int]*ReleaseNotesMap, err error) {
	if mp.Maps == nil {
		err := mp.readMaps()
		if err != nil {
			return nil, errors.Wrap(err, "while reading release notes maps")
		}
	}
	if notesMap, ok := mp.Maps[pr]; ok {
		return notesMap, nil
	}
	return nil, nil
}
<commit_msg>Change maps data struct to use a slice instead of old int map<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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 notes

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

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"gopkg.in/yaml.v2"
)

// MapProvider interface that obtains release notes maps from a source
type MapProvider interface {
	GetMapsForPR(int) ([]*ReleaseNotesMap, error)
}

// NewProviderFromInitString creates a new map provider from an initialization string
func NewProviderFromInitString(initString string) (MapProvider, error) {
	// If init string starts with gs:// return a CloudStorageProvider
	if initString[0:5] == "gs://" {
		// Currently for illustration purposes
		return nil, errors.New("CloudStorageProvider is not yet implemented")
	}

	// Otherwise, build a DirectoryMapProvider using the
	// whole init string as the path
	fileStat, err := os.Stat(initString)
	if os.IsNotExist(err) {
		return nil, errors.New("release notes map path does not exist")
	}
	if !fileStat.IsDir() {
		return nil, errors.New("release notes map path is not a directory")
	}

	return &DirectoryMapProvider{Path: initString}, nil
}

// ParseReleaseNotesMap Parses a Release Notes Map
func ParseReleaseNotesMap(mapPath string) (*[]ReleaseNotesMap, error) {
	notemaps := []ReleaseNotesMap{}
	yamlReader, err := os.Open(mapPath)
	if err != nil {
		return nil, errors.Wrap(err, "opening maps")
	}

	decoder := yaml.NewDecoder(yamlReader)
	noteMap := ReleaseNotesMap{}

	for {
		if err := decoder.Decode(&noteMap); err == io.EOF {
			break
		} else if err != nil {
			return nil, errors.Wrap(err, "decoding note map")
		}
		notemaps = append(notemaps, noteMap)
	}

	return &notemaps, nil
}

// ReleaseNotesMap holds the changes that will be applied to the notes
// the struct has double annotation because of this: https://play.golang.org/p/C7QM1varozy
type ReleaseNotesMap struct {
	// Pull request where the note was published
	PR int `json:"pr"`
	// SHA of the notes commit
	Commit      string `json:"commit,omitempty" yaml:"commit,omitempty"`
	ReleaseNote struct {
		// Text is the actual content of the release note
		Text *string `json:"text,omitempty" yaml:"text,omitempty"`

		// Docs is additional documentation for the release note
		Documentation *[]*Documentation `json:"documentation,omitempty" yaml:"documentation,omitempty"`

		// Author is the GitHub username of the commit author
		Author *string `json:"author,omitempty" yaml:"author,omitempty"`

		// Areas is a list of the labels beginning with area/
		Areas *[]string `json:"areas,omitempty" yaml:"areas,omitempty"`

		// Kinds is a list of the labels beginning with kind/
		Kinds *[]string `json:"kinds,omitempty" yaml:"kinds,omitempty"`

		// SIGs is a list of the labels beginning with sig/
		SIGs *[]string `json:"sigs,omitempty" yaml:"sigs,omitempty"`

		// Indicates whether or not a note will appear as a new feature
		Feature *bool `json:"feature,omitempty" yaml:"feature,omitempty"`

		// ActionRequired indicates whether or not the release-note-action-required
		// label was set on the PR
		ActionRequired *bool `json:"action_required,omitempty" yaml:"action_required,omitempty"`
	} `json:"releasenote"`

	DataFields map[string]ReleaseNotesDataField `json:"datafields,omitempty" yaml:"datafields,omitempty"`
}

// ReleaseNotesDataField extra data added to a release note
type ReleaseNotesDataField interface{}

// DirectoryMapProvider is a provider that gets maps from a directory
type DirectoryMapProvider struct {
	Path string
	Maps map[int][]*ReleaseNotesMap
}

// readMaps Open the dir and read dir notes
func (mp *DirectoryMapProvider) readMaps() error {
	var fileList []string
	mp.Maps = map[int][]*ReleaseNotesMap{}

	err := filepath.Walk(mp.Path, func(path string, info os.FileInfo, err error) error {
		if filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" {
			fileList = append(fileList, path)
		}
		return nil
	})

	for _, fileName := range fileList {
		notemaps, err := ParseReleaseNotesMap(fileName)
		if err != nil {
			return errors.Wrapf(err, "while parsing note map in %s", fileName)
		}
		for i, notemap := range *notemaps {
			if _, ok := mp.Maps[notemap.PR]; !ok {
				mp.Maps[notemap.PR] = make([]*ReleaseNotesMap, 0)
			}
			mp.Maps[notemap.PR] = append(mp.Maps[notemap.PR], &(*notemaps)[i])
		}
	}
	logrus.Infof("Successfully parsed release notes maps for %d PRs from %s", len(mp.Maps), mp.Path)
	return err
}

// GetMapsForPR get the release notes maps for a specific PR number
func (mp *DirectoryMapProvider) GetMapsForPR(pr int) (notesMap []*ReleaseNotesMap, err error) {
	if mp.Maps == nil {
		err := mp.readMaps()
		if err != nil {
			return nil, errors.Wrap(err, "while reading release notes maps")
		}
	}
	if notesMap, ok := mp.Maps[pr]; ok {
		return notesMap, nil
	}
	return nil, nil
}
<|endoftext|>


<commit_before>package pkg

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/davecgh/go-spew/spew"
	"github.com/ghodss/yaml"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"k8s.io/client-go/pkg/api"
	"k8s.io/client-go/pkg/api/resource"
	"k8s.io/client-go/pkg/runtime"

	log "github.com/Sirupsen/logrus"
	api_v1 "k8s.io/client-go/pkg/api/v1"
	ext_v1beta1 "k8s.io/client-go/pkg/apis/extensions/v1beta1"

	// install api

	_ "k8s.io/client-go/pkg/api/install"
	_ "k8s.io/client-go/pkg/apis/extensions/install"
)

type Volume struct {
	api_v1.Volume `yaml:",inline"`
	Size          string   `yaml:"size"`
	AccessModes   []string `yaml:"accessModes"`
}

type Service struct {
	Name               string `yaml:"name,omitempty"`
	api_v1.ServiceSpec `yaml:",inline"`
}

type App struct {
	Name              string            `yaml:"name"`
	Replicas          *int32            `yaml:"replicas,omitempty"`
	Expose            bool              `yaml:"expose,omitempty"`
	Labels            map[string]string `yaml:"labels,omitempty"`
	PersistentVolumes []Volume          `yaml:"persistentVolumes,omitempty"`
	ConfigData        map[string]string `yaml:"configData,omitempty"`
	Services          []Service         `yaml:"services,omitempty"`
	api_v1.PodSpec    `yaml:",inline"`
}

func ReadFile(f string) ([]byte, error) {
	data, err := ioutil.ReadFile(f)
	if err != nil {
		return nil, errors.Wrap(err, "file reading failed")
	}
	return data, nil
}

func Convert(v *viper.Viper, cmd *cobra.Command) error {

	for _, file := range strings.Split(v.GetStringSlice("files")[0], ",") {
		d, err := ReadFile(file)
		if err != nil {
			return errors.New(err.Error())
		}

		var app App
		err = yaml.Unmarshal(d, &app)
		if err != nil {
			return errors.Wrap(err, "could not unmarshal into internal struct")
		}
		log.Debugf("file: %s, object unmrashalled: %#v", file, app)

		runtimeObjects, err := CreateK8sObjects(&app)

		for _, runtimeObject := range runtimeObjects {
			gvk, isUnversioned, err := api.Scheme.ObjectKind(runtimeObject)
			if err != nil {
				return errors.Wrap(err, "ConvertToVersion failed")
			}
			if isUnversioned {
				return errors.New(fmt.Sprintf("ConvertToVersion failed: can't output unversioned type: %T", runtimeObject))
			}

			runtimeObject.GetObjectKind().SetGroupVersionKind(gvk)

			data, err := yaml.Marshal(runtimeObject)
			if err != nil {
				return errors.Wrap(err, "failed to marshal object")
			}

			writeObject := func(o runtime.Object, data []byte) error {
				_, err := fmt.Fprintln(os.Stdout, "---")
				if err != nil {
					return errors.Wrap(err, "could not print to STDOUT")
				}

				_, err = os.Stdout.Write(data)
				return errors.Wrap(err, "could not write to STDOUT")
			}

			err = writeObject(runtimeObject, data)
			if err != nil {
				return errors.Wrap(err, "failed to write object")
			}
		}
	}

	return nil
}

func getLabels(app *App) map[string]string {
	labels := map[string]string{"app": app.Name}
	return labels
}

func createServices(app *App) []runtime.Object {
	var svcs []runtime.Object
	for _, s := range app.Services {
		svc := &api_v1.Service{
			ObjectMeta: api_v1.ObjectMeta{
				Name:   s.Name,
				Labels: app.Labels,
			},
			Spec: s.ServiceSpec,
		}
		if len(svc.Spec.Selector) == 0 {
			svc.Spec.Selector = app.Labels
		}
		svcs = append(svcs, svc)

		if s.Type == api_v1.ServiceTypeLoadBalancer {
			// TODO: create a ingress resource
		}
	}
	return svcs
}

func createDeployment(app *App) *ext_v1beta1.Deployment {
	// bare minimum deployment
	return &ext_v1beta1.Deployment{
		ObjectMeta: api_v1.ObjectMeta{
			Name:   app.Name,
			Labels: app.Labels,
		},
		Spec: ext_v1beta1.DeploymentSpec{
			Replicas: app.Replicas,
			Template: api_v1.PodTemplateSpec{
				ObjectMeta: api_v1.ObjectMeta{
					Name:   app.Name,
					Labels: app.Labels,
				},
				// get pod spec out of the original info
				Spec: api_v1.PodSpec(app.PodSpec),
			},
		},
	}
}

func isVolumeDefined(app *App, name string) bool {
	for _, v := range app.PersistentVolumes {
		if name == v.Name {
			return true
		}
	}
	return false
}

func searchVolumeIndex(app *App, name string) int {
	for i := 0; i < len(app.PersistentVolumes); i++ {
		if name == app.PersistentVolumes[i].Name {
			return i
		}
	}
	return -1
}

func createPVC(v *Volume) (*api_v1.PersistentVolumeClaim, error) {
	// create pvc
	if v.Size == "" {
		v.Size = "100Mi"
	}
	size, err := resource.ParseQuantity(v.Size)
	if err != nil {
		return nil, errors.Wrap(err, "could not read volume size")
	}

	pvc := &api_v1.PersistentVolumeClaim{
		ObjectMeta: api_v1.ObjectMeta{
			Name: v.Name,
		},
		Spec: api_v1.PersistentVolumeClaimSpec{
			Resources: api_v1.ResourceRequirements{
				Requests: api_v1.ResourceList{
					api_v1.ResourceStorage: size,
				},
			},
		},
	}
	for _, mode := range v.AccessModes {
		switch mode {
		case "ReadWriteOnce":
			pvc.Spec.AccessModes = append(pvc.Spec.AccessModes, api_v1.ReadWriteOnce)
		case "ReadOnlyMany":
			pvc.Spec.AccessModes = append(pvc.Spec.AccessModes, api_v1.ReadOnlyMany)
		case "ReadWriteMany":
			pvc.Spec.AccessModes = append(pvc.Spec.AccessModes, api_v1.ReadWriteMany)
		}
	}
	if len(v.AccessModes) == 0 {
		pvc.Spec.AccessModes = []api_v1.PersistentVolumeAccessMode{api_v1.ReadWriteOnce}
	}

	return pvc, nil
}

func add(objects *[]runtime.Object, object runtime.Object) {
	if object != nil {
		*objects = append(*objects, object)
		log.Debugf("%s\n", spew.Sprint(object))
	}
}

func CreateK8sObjects(app *App) ([]runtime.Object, error) {

	var objects []runtime.Object

	if app.Labels == nil {
		app.Labels = getLabels(app)
	}

	svcs := createServices(app)

	var pvcs []runtime.Object
	for _, c := range app.Containers {
		for _, vm := range c.VolumeMounts {

			// User won't be giving this so we have to create it
			// so that the pod spec is complete
			podVolume := api_v1.Volume{
				Name: vm.Name,
				VolumeSource: api_v1.VolumeSource{
					PersistentVolumeClaim: &api_v1.PersistentVolumeClaimVolumeSource{
						ClaimName: vm.Name,
					},
				},
			}
			app.Volumes = append(app.Volumes, podVolume)

			if isVolumeDefined(app, vm.Name) {
				i := searchVolumeIndex(app, vm.Name)
				pvc, err := createPVC(&app.PersistentVolumes[i])
				if err != nil {
					return nil, errors.Wrap(err, "cannot create pvc")
				}
				pvcs = append(pvcs, pvc)
				continue
			}

			v := Volume{podVolume, "100Mi", []string{"ReadWriteOnce"}}
			app.PersistentVolumes = append(app.PersistentVolumes, v)
			pvc, err := createPVC(&v)
			if err != nil {
				return nil, errors.Wrap(err, "cannot create pvc")
			}
			pvcs = append(pvcs, pvc)
		}
	}

	// if only one container set name of it as app name
	if len(app.Containers) == 1 && app.Containers[0].Name == "" {
		app.Containers[0].Name = app.Name
	}

	//var configMap *api_v1.ConfigMap
	//if len(app.ConfigData) > 0 {
	//	configMap = &api_v1.ConfigMap{
	//		ObjectMeta: api_v1.ObjectMeta{
	//			Name: app.Name,
	//		},
	//		Data: app.ConfigData,
	//	}
	//}

	deployment := createDeployment(app)

	add(&objects, deployment)
	log.Debugf("app: %s, deployment: %s", app.Name, spew.Sprint(deployment))
	//add(&objects, configMap)
	//log.Debugf("app: %s, configMap: %s", spew.Sprint(configMap))
	for _, svc := range svcs {
		add(&objects, svc)
	}
	log.Debugf("app: %s, service: %s", app.Name, spew.Sprint(svcs))
	for _, pvc := range pvcs {
		add(&objects, pvc)
	}
	log.Debugf("app: %s, pvc: %s", app.Name, spew.Sprint(pvcs))

	return objects, nil
}

// how to expose certain service using ingress
//
<commit_msg>make search efficient<commit_after>package pkg

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/davecgh/go-spew/spew"
	"github.com/ghodss/yaml"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"k8s.io/client-go/pkg/api"
	"k8s.io/client-go/pkg/api/resource"
	"k8s.io/client-go/pkg/runtime"

	log "github.com/Sirupsen/logrus"
	api_v1 "k8s.io/client-go/pkg/api/v1"
	ext_v1beta1 "k8s.io/client-go/pkg/apis/extensions/v1beta1"

	// install api

	_ "k8s.io/client-go/pkg/api/install"
	_ "k8s.io/client-go/pkg/apis/extensions/install"
)

type Volume struct {
	api_v1.Volume `yaml:",inline"`
	Size          string   `yaml:"size"`
	AccessModes   []string `yaml:"accessModes"`
}

type Service struct {
	Name               string `yaml:"name,omitempty"`
	api_v1.ServiceSpec `yaml:",inline"`
}

type App struct {
	Name              string            `yaml:"name"`
	Replicas          *int32            `yaml:"replicas,omitempty"`
	Expose            bool              `yaml:"expose,omitempty"`
	Labels            map[string]string `yaml:"labels,omitempty"`
	PersistentVolumes []Volume          `yaml:"persistentVolumes,omitempty"`
	ConfigData        map[string]string `yaml:"configData,omitempty"`
	Services          []Service         `yaml:"services,omitempty"`
	api_v1.PodSpec    `yaml:",inline"`
}

func ReadFile(f string) ([]byte, error) {
	data, err := ioutil.ReadFile(f)
	if err != nil {
		return nil, errors.Wrap(err, "file reading failed")
	}
	return data, nil
}

func Convert(v *viper.Viper, cmd *cobra.Command) error {

	for _, file := range strings.Split(v.GetStringSlice("files")[0], ",") {
		d, err := ReadFile(file)
		if err != nil {
			return errors.New(err.Error())
		}

		var app App
		err = yaml.Unmarshal(d, &app)
		if err != nil {
			return errors.Wrap(err, "could not unmarshal into internal struct")
		}
		log.Debugf("file: %s, object unmrashalled: %#v", file, app)

		runtimeObjects, err := CreateK8sObjects(&app)

		for _, runtimeObject := range runtimeObjects {
			gvk, isUnversioned, err := api.Scheme.ObjectKind(runtimeObject)
			if err != nil {
				return errors.Wrap(err, "ConvertToVersion failed")
			}
			if isUnversioned {
				return errors.New(fmt.Sprintf("ConvertToVersion failed: can't output unversioned type: %T", runtimeObject))
			}

			runtimeObject.GetObjectKind().SetGroupVersionKind(gvk)

			data, err := yaml.Marshal(runtimeObject)
			if err != nil {
				return errors.Wrap(err, "failed to marshal object")
			}

			writeObject := func(o runtime.Object, data []byte) error {
				_, err := fmt.Fprintln(os.Stdout, "---")
				if err != nil {
					return errors.Wrap(err, "could not print to STDOUT")
				}

				_, err = os.Stdout.Write(data)
				return errors.Wrap(err, "could not write to STDOUT")
			}

			err = writeObject(runtimeObject, data)
			if err != nil {
				return errors.Wrap(err, "failed to write object")
			}
		}
	}

	return nil
}

func getLabels(app *App) map[string]string {
	labels := map[string]string{"app": app.Name}
	return labels
}

func createServices(app *App) []runtime.Object {
	var svcs []runtime.Object
	for _, s := range app.Services {
		svc := &api_v1.Service{
			ObjectMeta: api_v1.ObjectMeta{
				Name:   s.Name,
				Labels: app.Labels,
			},
			Spec: s.ServiceSpec,
		}
		if len(svc.Spec.Selector) == 0 {
			svc.Spec.Selector = app.Labels
		}
		svcs = append(svcs, svc)

		if s.Type == api_v1.ServiceTypeLoadBalancer {
			// TODO: create a ingress resource
		}
	}
	return svcs
}

func createDeployment(app *App) *ext_v1beta1.Deployment {
	// bare minimum deployment
	return &ext_v1beta1.Deployment{
		ObjectMeta: api_v1.ObjectMeta{
			Name:   app.Name,
			Labels: app.Labels,
		},
		Spec: ext_v1beta1.DeploymentSpec{
			Replicas: app.Replicas,
			Template: api_v1.PodTemplateSpec{
				ObjectMeta: api_v1.ObjectMeta{
					Name:   app.Name,
					Labels: app.Labels,
				},
				// get pod spec out of the original info
				Spec: api_v1.PodSpec(app.PodSpec),
			},
		},
	}
}

func isVolumeDefined(app *App, name string) bool {
	if i := searchVolumeIndex(app, name); i != -1 {
		return true
	}
	return false
}

func searchVolumeIndex(app *App, name string) int {
	for i, v := range app.PersistentVolumes {
		if name == v.Name {
			return i
		}
	}
	return -1
}

func createPVC(v *Volume) (*api_v1.PersistentVolumeClaim, error) {
	// create pvc
	if v.Size == "" {
		v.Size = "100Mi"
	}
	size, err := resource.ParseQuantity(v.Size)
	if err != nil {
		return nil, errors.Wrap(err, "could not read volume size")
	}

	pvc := &api_v1.PersistentVolumeClaim{
		ObjectMeta: api_v1.ObjectMeta{
			Name: v.Name,
		},
		Spec: api_v1.PersistentVolumeClaimSpec{
			Resources: api_v1.ResourceRequirements{
				Requests: api_v1.ResourceList{
					api_v1.ResourceStorage: size,
				},
			},
		},
	}
	for _, mode := range v.AccessModes {
		switch mode {
		case "ReadWriteOnce":
			pvc.Spec.AccessModes = append(pvc.Spec.AccessModes, api_v1.ReadWriteOnce)
		case "ReadOnlyMany":
			pvc.Spec.AccessModes = append(pvc.Spec.AccessModes, api_v1.ReadOnlyMany)
		case "ReadWriteMany":
			pvc.Spec.AccessModes = append(pvc.Spec.AccessModes, api_v1.ReadWriteMany)
		}
	}
	if len(v.AccessModes) == 0 {
		pvc.Spec.AccessModes = []api_v1.PersistentVolumeAccessMode{api_v1.ReadWriteOnce}
	}

	return pvc, nil
}

func add(objects *[]runtime.Object, object runtime.Object) {
	if object != nil {
		*objects = append(*objects, object)
		log.Debugf("%s\n", spew.Sprint(object))
	}
}

func CreateK8sObjects(app *App) ([]runtime.Object, error) {

	var objects []runtime.Object

	if app.Labels == nil {
		app.Labels = getLabels(app)
	}

	svcs := createServices(app)

	var pvcs []runtime.Object
	for _, c := range app.Containers {
		for _, vm := range c.VolumeMounts {

			// User won't be giving this so we have to create it
			// so that the pod spec is complete
			podVolume := api_v1.Volume{
				Name: vm.Name,
				VolumeSource: api_v1.VolumeSource{
					PersistentVolumeClaim: &api_v1.PersistentVolumeClaimVolumeSource{
						ClaimName: vm.Name,
					},
				},
			}
			app.Volumes = append(app.Volumes, podVolume)

			if isVolumeDefined(app, vm.Name) {
				i := searchVolumeIndex(app, vm.Name)
				pvc, err := createPVC(&app.PersistentVolumes[i])
				if err != nil {
					return nil, errors.Wrap(err, "cannot create pvc")
				}
				pvcs = append(pvcs, pvc)
				continue
			}

			v := Volume{podVolume, "100Mi", []string{"ReadWriteOnce"}}
			app.PersistentVolumes = append(app.PersistentVolumes, v)
			pvc, err := createPVC(&v)
			if err != nil {
				return nil, errors.Wrap(err, "cannot create pvc")
			}
			pvcs = append(pvcs, pvc)
		}
	}

	// if only one container set name of it as app name
	if len(app.Containers) == 1 && app.Containers[0].Name == "" {
		app.Containers[0].Name = app.Name
	}

	//var configMap *api_v1.ConfigMap
	//if len(app.ConfigData) > 0 {
	//	configMap = &api_v1.ConfigMap{
	//		ObjectMeta: api_v1.ObjectMeta{
	//			Name: app.Name,
	//		},
	//		Data: app.ConfigData,
	//	}
	//}

	deployment := createDeployment(app)

	add(&objects, deployment)
	log.Debugf("app: %s, deployment: %s", app.Name, spew.Sprint(deployment))
	//add(&objects, configMap)
	//log.Debugf("app: %s, configMap: %s", spew.Sprint(configMap))
	for _, svc := range svcs {
		add(&objects, svc)
	}
	log.Debugf("app: %s, service: %s", app.Name, spew.Sprint(svcs))
	for _, pvc := range pvcs {
		add(&objects, pvc)
	}
	log.Debugf("app: %s, pvc: %s", app.Name, spew.Sprint(pvcs))

	return objects, nil
}

// how to expose certain service using ingress
//
<|endoftext|>


<commit_before>package plugins

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"strings"
	"text/template"

	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/util"
)

var (
	DataSources  map[string]*DataSourcePlugin
	Panels       map[string]*PanelPlugin
	ApiPlugins   map[string]*ApiPlugin
	StaticRoutes []*PluginStaticRoute
	Apps         map[string]*AppPlugin
	PluginTypes  map[string]interface{}
)

type PluginScanner struct {
	pluginPath string
	errors     []error
}

func Init() error {
	DataSources = make(map[string]*DataSourcePlugin)
	ApiPlugins = make(map[string]*ApiPlugin)
	StaticRoutes = make([]*PluginStaticRoute, 0)
	Panels = make(map[string]*PanelPlugin)
	Apps = make(map[string]*AppPlugin)
	PluginTypes = map[string]interface{}{
		"panel":      PanelPlugin{},
		"datasource": DataSourcePlugin{},
		"api":        ApiPlugin{},
		"app":        AppPlugin{},
	}

	scan(path.Join(setting.StaticRootPath, "app/plugins"))
	checkPluginPaths()
	// checkDependencies()
	return nil
}

// func checkDependencies() {
// 	for appType, app := range Apps {
// 		for _, reqPanel := range app.PanelPlugins {
// 			if _, ok := Panels[reqPanel]; !ok {
// 				log.Fatal(4, "App %s requires Panel type %s, but it is not present.", appType, reqPanel)
// 			}
// 		}
// 		for _, reqDataSource := range app.DatasourcePlugins {
// 			if _, ok := DataSources[reqDataSource]; !ok {
// 				log.Fatal(4, "App %s requires DataSource type %s, but it is not present.", appType, reqDataSource)
// 			}
// 		}
// 		for _, reqApiPlugin := range app.ApiPlugins {
// 			if _, ok := ApiPlugins[reqApiPlugin]; !ok {
// 				log.Fatal(4, "App %s requires ApiPlugin type %s, but it is not present.", appType, reqApiPlugin)
// 			}
// 		}
// 	}
// }

func checkPluginPaths() error {
	for _, section := range setting.Cfg.Sections() {
		if strings.HasPrefix(section.Name(), "plugin.") {
			path := section.Key("path").String()
			if path != "" {
				log.Info("Plugin: Scaning dir %s", path)
				scan(path)
			}
		}
	}
	return nil
}

func scan(pluginDir string) error {
	scanner := &PluginScanner{
		pluginPath: pluginDir,
	}

	if err := util.Walk(pluginDir, true, true, scanner.walker); err != nil {
		if pluginDir != "data/plugins" {
			log.Warn("Could not scan dir \"%v\" error: %s", pluginDir, err)
		}
		return err
	}

	if len(scanner.errors) > 0 {
		return errors.New("Some plugins failed to load")
	}

	return nil
}

func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err error) error {
	if err != nil {
		return err
	}

	if f.IsDir() {
		return nil
	}

	if f.Name() == "plugin.json" {
		err := scanner.loadPluginJson(currentPath)
		if err != nil {
			log.Error(3, "Failed to load plugin json file: %v,  err: %v", currentPath, err)
			scanner.errors = append(scanner.errors, err)
		}
	}
	return nil
}

func interpolatePluginJson(reader io.Reader, pluginCommon *PluginBase) (io.Reader, error) {
	buf := new(bytes.Buffer)
	buf.ReadFrom(reader)
	jsonStr := buf.String() //

	tmpl, err := template.New("json").Parse(jsonStr)
	if err != nil {
		return nil, err
	}

	data := map[string]interface{}{
		"PluginPublicRoot": "public/plugins/" + pluginCommon.Id,
	}

	var resultBuffer bytes.Buffer
	if err := tmpl.ExecuteTemplate(&resultBuffer, "json", data); err != nil {
		return nil, err
	}

	return bytes.NewReader(resultBuffer.Bytes()), nil
}

func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
	currentDir := filepath.Dir(pluginJsonFilePath)
	reader, err := os.Open(pluginJsonFilePath)
	if err != nil {
		return err
	}

	defer reader.Close()

	jsonParser := json.NewDecoder(reader)
	pluginCommon := PluginBase{}
	if err := jsonParser.Decode(&pluginCommon); err != nil {
		return err
	}

	if pluginCommon.Id == "" || pluginCommon.Type == "" {
		return errors.New("Did not find type and id property in plugin.json")
	}

	reader.Seek(0, 0)
	if newReader, err := interpolatePluginJson(reader, &pluginCommon); err != nil {
		return err
	} else {
		jsonParser = json.NewDecoder(newReader)
	}

	var loader PluginLoader

	if pluginGoType, exists := PluginTypes[pluginCommon.Type]; !exists {
		return errors.New("Unkown plugin type " + pluginCommon.Type)
	} else {
		loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader)
	}

	return loader.Load(jsonParser, currentDir)
}
<commit_msg>fix(plugins): scans default plugin path<commit_after>package plugins

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"strings"
	"text/template"

	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/util"
)

var (
	DataSources  map[string]*DataSourcePlugin
	Panels       map[string]*PanelPlugin
	ApiPlugins   map[string]*ApiPlugin
	StaticRoutes []*PluginStaticRoute
	Apps         map[string]*AppPlugin
	PluginTypes  map[string]interface{}
)

type PluginScanner struct {
	pluginPath string
	errors     []error
}

func Init() error {
	DataSources = make(map[string]*DataSourcePlugin)
	ApiPlugins = make(map[string]*ApiPlugin)
	StaticRoutes = make([]*PluginStaticRoute, 0)
	Panels = make(map[string]*PanelPlugin)
	Apps = make(map[string]*AppPlugin)
	PluginTypes = map[string]interface{}{
		"panel":      PanelPlugin{},
		"datasource": DataSourcePlugin{},
		"api":        ApiPlugin{},
		"app":        AppPlugin{},
	}

	scan(path.Join(setting.StaticRootPath, "app/plugins"))
	scan(path.Join(setting.StaticRootPath, setting.PluginsPath))
	checkPluginPaths()
	// checkDependencies()
	return nil
}

// func checkDependencies() {
// 	for appType, app := range Apps {
// 		for _, reqPanel := range app.PanelPlugins {
// 			if _, ok := Panels[reqPanel]; !ok {
// 				log.Fatal(4, "App %s requires Panel type %s, but it is not present.", appType, reqPanel)
// 			}
// 		}
// 		for _, reqDataSource := range app.DatasourcePlugins {
// 			if _, ok := DataSources[reqDataSource]; !ok {
// 				log.Fatal(4, "App %s requires DataSource type %s, but it is not present.", appType, reqDataSource)
// 			}
// 		}
// 		for _, reqApiPlugin := range app.ApiPlugins {
// 			if _, ok := ApiPlugins[reqApiPlugin]; !ok {
// 				log.Fatal(4, "App %s requires ApiPlugin type %s, but it is not present.", appType, reqApiPlugin)
// 			}
// 		}
// 	}
// }

func checkPluginPaths() error {
	for _, section := range setting.Cfg.Sections() {
		if strings.HasPrefix(section.Name(), "plugin.") {
			path := section.Key("path").String()
			if path != "" {
				log.Info("Plugin: Scaning dir %s", path)
				scan(path)
			}
		}
	}
	return nil
}

func scan(pluginDir string) error {
	scanner := &PluginScanner{
		pluginPath: pluginDir,
	}

	if err := util.Walk(pluginDir, true, true, scanner.walker); err != nil {
		if pluginDir != "data/plugins" {
			log.Warn("Could not scan dir \"%v\" error: %s", pluginDir, err)
		}
		return err
	}

	if len(scanner.errors) > 0 {
		return errors.New("Some plugins failed to load")
	}

	return nil
}

func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err error) error {
	if err != nil {
		return err
	}

	if f.IsDir() {
		return nil
	}

	if f.Name() == "plugin.json" {
		err := scanner.loadPluginJson(currentPath)
		if err != nil {
			log.Error(3, "Failed to load plugin json file: %v,  err: %v", currentPath, err)
			scanner.errors = append(scanner.errors, err)
		}
	}
	return nil
}

func interpolatePluginJson(reader io.Reader, pluginCommon *PluginBase) (io.Reader, error) {
	buf := new(bytes.Buffer)
	buf.ReadFrom(reader)
	jsonStr := buf.String() //

	tmpl, err := template.New("json").Parse(jsonStr)
	if err != nil {
		return nil, err
	}

	data := map[string]interface{}{
		"PluginPublicRoot": "public/plugins/" + pluginCommon.Id,
	}

	var resultBuffer bytes.Buffer
	if err := tmpl.ExecuteTemplate(&resultBuffer, "json", data); err != nil {
		return nil, err
	}

	return bytes.NewReader(resultBuffer.Bytes()), nil
}

func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
	currentDir := filepath.Dir(pluginJsonFilePath)
	reader, err := os.Open(pluginJsonFilePath)
	if err != nil {
		return err
	}

	defer reader.Close()

	jsonParser := json.NewDecoder(reader)
	pluginCommon := PluginBase{}
	if err := jsonParser.Decode(&pluginCommon); err != nil {
		return err
	}

	if pluginCommon.Id == "" || pluginCommon.Type == "" {
		return errors.New("Did not find type and id property in plugin.json")
	}

	reader.Seek(0, 0)
	if newReader, err := interpolatePluginJson(reader, &pluginCommon); err != nil {
		return err
	} else {
		jsonParser = json.NewDecoder(newReader)
	}

	var loader PluginLoader

	if pluginGoType, exists := PluginTypes[pluginCommon.Type]; !exists {
		return errors.New("Unkown plugin type " + pluginCommon.Type)
	} else {
		loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader)
	}

	return loader.Load(jsonParser, currentDir)
}
<|endoftext|>


<commit_before>package project

var (
	description        = "The aws-operator manages Kubernetes clusters running on AWS."
	gitSHA             = "n/a"
	name        string = "aws-operator"
	source      string = "https://github.com/giantswarm/aws-operator"
	version            = "12.0.0"
)

func Description() string {
	return description
}

func GitSHA() string {
	return gitSHA
}

func Name() string {
	return name
}

func Source() string {
	return source
}

func Version() string {
	return version
}
<commit_msg>Bump version to 12.0.1-dev (#3368)<commit_after>package project

var (
	description        = "The aws-operator manages Kubernetes clusters running on AWS."
	gitSHA             = "n/a"
	name        string = "aws-operator"
	source      string = "https://github.com/giantswarm/aws-operator"
	version            = "12.0.1-dev"
)

func Description() string {
	return description
}

func GitSHA() string {
	return gitSHA
}

func Name() string {
	return name
}

func Source() string {
	return source
}

func Version() string {
	return version
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"html/template"
	"path/filepath"

	"github.com/yursan9/tulis/pkg/post"
)

var (
	t        *template.Template
	funcMaps template.FuncMap
)

func init() {
	// Define custom filter for template
	funcMaps = template.FuncMap{
		"inc": func(i uint8) uint8 { return i + 1 },
		"dec": func(i uint8) uint8 { return i - 1 },
	}
	// Initialize map of template
	t = template.New("").Funcs(funcMaps)
	template.Must(t.ParseGlob(filepath.Join(templateDir, "*")))
}

// PageData contain struct for Page and Index template
type PageData struct {
	PageNow uint8
	PageMax uint8
	Posts   []*post.Post
}

func newPageData(n uint8) (*PageData, error) {
	pd := new(PageData)

	// Initialize page number
	pd.PageMax = uint8(len(all))/maxPost + 1
	if n < 1 || n > pd.PageMax {
		return nil, fmt.Errorf("There is no page %d", n)
	}
	pd.PageNow = n

	// Initialize array of posts
	s := maxPost * (n - 1)
	pd.Posts = all[s:]
	f := maxPost * n
	if f < uint8(len(all)) {
		pd.Posts = all[s:f]
	}

	return pd, nil
}

// PostData contain struct for Post template
type PostData struct {
	*post.Post
	Next *post.Post
	Prev *post.Post
}

func newPostData(slug string) (*PostData, error) {
	pd := new(PostData)
	for i, p := range all {
		if p.Slug == slug {
			pd.Post = p
			if i > 0 {
				pd.Prev = all[i-1]
			}
			if i < len(all)-1 {
				pd.Next = all[i+1]
			}
			return pd, nil
		}
	}
	return nil, fmt.Errorf("Can't find post with given slug: %s", slug)
}

// ByTagData contain struct for Search By Tag template
type ByTagData struct {
	PageNow uint8
	PageMax uint8
	Tag     string
	Posts   []*post.Post
}

func newByTagData(n uint8, tag string) *ByTagData {
	pd := new(ByTagData)
	pd.Tag = tag
	// Search post with given tag
OUTER:
	for _, p := range all {
		for _, t := range p.Tag {
			if tag == t {
				pd.Posts = append(pd.Posts, p)
				continue OUTER
			}
		}
	}
	if len(pd.Posts) == 0 {
		pd.PageNow = 1
		pd.PageMax = 1
		return pd
	}

	// Initialize Page number
	pd.PageNow = n
	pd.PageMax = uint8(len(pd.Posts))/maxPost + 1

	// Initialize array of posts
	s := maxPost * (n - 1)
	pd.Posts = pd.Posts[s:]
	f := maxPost * n
	if f < uint8(len(pd.Posts)) {
		pd.Posts = pd.Posts[s:f]
	}

	return pd
}

<commit_msg>Change the logic to define number of max page<commit_after>package server

import (
	"fmt"
	"html/template"
	"math"
	"path/filepath"

	"github.com/yursan9/tulis/pkg/post"
)

var (
	t        *template.Template
	funcMaps template.FuncMap
)

func init() {
	// Define custom filter for template
	funcMaps = template.FuncMap{
		"inc": func(i uint8) uint8 { return i + 1 },
		"dec": func(i uint8) uint8 { return i - 1 },
	}
	// Initialize map of template
	t = template.New("").Funcs(funcMaps)
	template.Must(t.ParseGlob(filepath.Join(templateDir, "*")))
}

// PageData contain struct for Page and Index template
type PageData struct {
	PageNow uint8
	PageMax uint8
	Posts   []*post.Post
}

func newPageData(n uint8) (*PageData, error) {
	pd := new(PageData)

	// Initialize page number
	pd.PageMax = uint8(math.Ceil(float64(len(all)) / float64(maxPost)))
	if n < 1 || n > pd.PageMax {
		return nil, fmt.Errorf("There is no page %d", n)
	}
	pd.PageNow = n

	// Initialize array of posts
	s := maxPost * (n - 1)
	pd.Posts = all[s:]
	f := maxPost * n
	if f < uint8(len(all)) {
		pd.Posts = all[s:f]
	}

	return pd, nil
}

// PostData contain struct for Post template
type PostData struct {
	*post.Post
	Next *post.Post
	Prev *post.Post
}

func newPostData(slug string) (*PostData, error) {
	pd := new(PostData)
	for i, p := range all {
		if p.Slug == slug {
			pd.Post = p
			if i > 0 {
				pd.Prev = all[i-1]
			}
			if i < len(all)-1 {
				pd.Next = all[i+1]
			}
			return pd, nil
		}
	}
	return nil, fmt.Errorf("Can't find post with given slug: %s", slug)
}

// ByTagData contain struct for Search By Tag template
type ByTagData struct {
	PageNow uint8
	PageMax uint8
	Tag     string
	Posts   []*post.Post
}

func newByTagData(n uint8, tag string) *ByTagData {
	pd := new(ByTagData)
	pd.Tag = tag
	// Search post with given tag
OUTER:
	for _, p := range all {
		for _, t := range p.Tag {
			if tag == t {
				pd.Posts = append(pd.Posts, p)
				// Stop inner loop and continue outer loop
				continue OUTER
			}
		}
	}
	if len(pd.Posts) == 0 {
		pd.PageNow = 1
		pd.PageMax = 1
		return pd
	}

	// Initialize Page number
	pd.PageNow = n
	pd.PageMax = uint8(math.Ceil(float64(len(all)) / float64(maxPost)))

	// Initialize array of posts
	s := maxPost * (n - 1)
	pd.Posts = pd.Posts[s:]
	f := maxPost * n
	if f < uint8(len(pd.Posts)) {
		pd.Posts = pd.Posts[s:f]
	}

	return pd
}

<|endoftext|>


<commit_before>package session

import (
	"sort"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/golang/protobuf/ptypes"

	"github.com/havoc-io/mutagen/pkg/filesystem"
	"github.com/havoc-io/mutagen/pkg/rpc"
	"github.com/havoc-io/mutagen/pkg/ssh"
	"github.com/havoc-io/mutagen/pkg/state"
)

const (
	MethodCreate    = "session.Create"
	MethodList      = "session.List"
	MethodPause     = "session.Pause"
	MethodResume    = "session.Resume"
	MethodTerminate = "session.Terminate"

	listThrottleInterval = 100 * time.Millisecond
)

type Service struct {
	// sshService performs registration and deregistration of prompters.
	sshService *ssh.Service
	// tracker tracks changes to session states.
	tracker *state.Tracker
	// sessionLock locks the sessions registry.
	sessionsLock *state.TrackingLock
	// sessions maps sessions to their respective controllers.
	sessions map[string]*controller
}

func NewService(sshService *ssh.Service) (*Service, error) {
	// Create a tracker and corresponding lock to watch for state changes.
	tracker := state.NewTracker()
	sessionsLock := state.NewTrackingLock(tracker)

	// Create the session registry.
	sessions := make(map[string]*controller)

	// Load existing sessions.
	sessionsDirectory, err := pathForSession("")
	if err != nil {
		return nil, errors.Wrap(err, "unable to compute sessions directory")
	}
	identifiers, err := filesystem.DirectoryContents(sessionsDirectory)
	if err != nil {
		return nil, errors.Wrap(err, "unable to read contents of sessions directory")
	}
	for _, identifier := range identifiers {
		if controller, err := loadSession(tracker, identifier); err != nil {
			continue
		} else {
			sessions[identifier] = controller
		}
	}

	// Success.
	return &Service{
		sshService:   sshService,
		tracker:      tracker,
		sessionsLock: sessionsLock,
		sessions:     sessions,
	}, nil
}

const (
	minimumSessionMatchLength = 5
)

// findSession provides fuzzy session matching by converting a query string into
// a full session id. The query string is tried as a full session id, session id
// prefix, URL substring, and then host substring. If the specified query
// matches more than one session, it is considered ambiguous and an error is
// returned.
func (s *Service) findSession(query string) (string, error) {
	// Don't allow empty or short strings to match anything.
	if query == "" {
		return "", errors.New("empty session specification is invalid")
	} else if len(query) < minimumSessionMatchLength {
		return "", errors.Errorf(
			"session specification must be at least %d characters",
			minimumSessionMatchLength,
		)
	}

	// Grab the registry lock and defer its release.
	s.sessionsLock.Lock()
	defer s.sessionsLock.UnlockWithoutNotify()

	// Track a match.
	result := ""

	// Search for matches.
	for identifier, controller := range s.sessions {
		match := identifier == query ||
			strings.HasPrefix(identifier, query) ||
			strings.Contains(controller.session.Alpha.Path, query) ||
			strings.Contains(controller.session.Beta.Path, query) ||
			strings.Contains(controller.session.Alpha.Hostname, query) ||
			strings.Contains(controller.session.Beta.Hostname, query)
		if match {
			if result != "" {
				return "", errors.Errorf("query \"%s\" matches multiple sessions", query)
			}
			result = identifier
		}
	}

	// Success.
	return result, nil
}

func (s *Service) Methods() map[string]rpc.Handler {
	return map[string]rpc.Handler{
		MethodCreate:    s.create,
		MethodList:      s.list,
		MethodPause:     s.pause,
		MethodResume:    s.resume,
		MethodTerminate: s.terminate,
	}
}

func (s *Service) Shutdown() {
	// Poison state tracking to terminate monitoring.
	s.tracker.Poison()

	// Grab the registry lock and defer its release.
	s.sessionsLock.Lock()
	defer s.sessionsLock.UnlockWithoutNotify()

	// Attempt to halt each session so that it can shutdown cleanly. Ignore but
	// log any that fail to halt.
	for _, controller := range s.sessions {
		if err := controller.halt(haltModeShutdown); err != nil {
			// TODO: Log this halt failure.
		}
	}
}

type streamPrompter struct {
	stream rpc.HandlerStream
}

func (p *streamPrompter) Prompt(message, prompt string) (string, error) {
	// Create the request.
	request := PromptRequest{
		Message: message,
		Prompt:  prompt,
	}

	// Send the request.
	if err := p.stream.Send(request); err != nil {
		return "", errors.Wrap(err, "unable to send challenge")
	}

	// Receive the response.
	var response PromptResponse
	if err := p.stream.Receive(&response); err != nil {
		return "", errors.Wrap(err, "unable to receive response")
	}

	// Success.
	return response.Response, nil
}

func (s *Service) create(stream rpc.HandlerStream) error {
	// Receive the request.
	var request CreateRequest
	if err := stream.Receive(&request); err != nil {
		return errors.Wrap(err, "unable to receive request")
	}

	//  Wrap the stream in a prompter and register it with the SSH service.
	prompter, err := s.sshService.RegisterPrompter(&streamPrompter{stream})
	if err != nil {
		return errors.Wrap(err, "unable to register prompter")
	}

	// Attempt to create a session.
	controller, err := newSession(
		s.tracker,
		request.Alpha, request.Beta,
		request.Ignores,
		prompter,
	)

	// Unregister the prompter.
	s.sshService.UnregisterPrompter(prompter)

	// Handle any creation error.
	if err != nil {
		return err
	}

	// Extract the session identifier.
	sessionId := controller.session.Identifier

	// Register the controller.
	s.sessionsLock.Lock()
	s.sessions[sessionId] = controller
	s.sessionsLock.Unlock()

	// Signal prompt completion.
	if err := stream.Send(PromptRequest{Done: true}); err != nil {
		return errors.Wrap(err, "unable to terminate prompting stream")
	}

	// Send the response.
	if err := stream.Send(CreateResponse{Session: sessionId}); err != nil {
		return errors.Wrap(err, "unable to send create response")
	}

	// Success.
	return nil
}

func (s *Service) list(stream rpc.HandlerStream) error {
	// Receive the request.
	var request ListRequest
	if err := stream.Receive(&request); err != nil {
		return errors.Wrap(err, "unable to receive request")
	}

	// Determine the session query of interest and whether or not this is a
	// repeated request. An empty session query after this point indicates that
	// all sessions are of interest.
	var sessionQuery string
	var repeated bool
	switch request.Kind {
	case ListRequestKindSingle:
		sessionQuery = request.SessionQuery
	case ListRequestKindRepeated:
		sessionQuery = request.SessionQuery
		repeated = true
	case ListRequestKindRepeatedLatest:
		var mostRecentSessionCreationTime time.Time
		s.sessionsLock.Lock()
		for _, controller := range s.sessions {
			state := controller.currentState()
			creationTime, err := ptypes.Timestamp(state.Session.CreationTime)
			if err != nil {
				s.sessionsLock.Unlock()
				return errors.Wrap(err, "unable to convert creation time format")
			}
			if creationTime.After(mostRecentSessionCreationTime) {
				sessionQuery = state.Session.Identifier
				mostRecentSessionCreationTime = creationTime
			}
		}
		s.sessionsLock.Unlock()
		if sessionQuery == "" {
			return errors.New("no sessions present")
		}
		repeated = true
	default:
		return errors.New("unknown list request kind")
	}

	// If a session query has been provided, resolve the full session identifier
	// to which it refers.
	session := ""
	if sessionQuery != "" {
		if s, err := s.findSession(sessionQuery); err != nil {
			return errors.Wrap(err, "unable to identify session")
		} else {
			session = s
		}
	}

	// Loop indefinitely and track state changes. We'll bail after a single
	// response if monitoring wasn't requested.
	previousStateIndex := uint64(0)
	var poisoned bool
	for {
		// Wait for a state change.
		// TODO: If the client disconnects while this handler is polling for
		// changes, this Goroutine will wait here until there's another change,
		// and will then exit when it tries (and fails) to send a response. This
		// will be fine in practice, but it's not elegant.
		previousStateIndex, poisoned = s.tracker.WaitForChange(previousStateIndex)
		if poisoned {
			return errors.New("state tracking terminated")
		}

		// Lock the session registry.
		s.sessionsLock.Lock()

		// Create a snapshot of the necessary session states.
		var sessions []SessionState
		var err error
		if session != "" {
			if controller, ok := s.sessions[session]; ok {
				sessions = append(sessions, controller.currentState())
			} else {
				err = errors.New("requested session no longer exists")
			}
		} else {
			for _, controller := range s.sessions {
				sessions = append(sessions, controller.currentState())
			}
		}

		// Unlock the session registry. It's very important that we unlock
		// without a notification here, otherwise we'll trigger an infinite
		// cycle of state changes.
		s.sessionsLock.UnlockWithoutNotify()

		// Handle errors.
		if err != nil {
			return err
		}

		// Sort sessions by creation time.
		sort.Slice(sessions, func(i, j int) bool {
			iTime := sessions[i].Session.CreationTime
			jTime := sessions[j].Session.CreationTime
			return iTime.Seconds < jTime.Seconds ||
				(iTime.Seconds == jTime.Seconds && iTime.Nanos < jTime.Nanos)
		})

		// Send this response.
		if err := stream.Send(ListResponse{Sessions: sessions}); err != nil {
			return errors.Wrap(err, "unable to send list response")
		}

		// If repeated listings weren't requested, then we're done.
		if !repeated {
			return nil
		}

		// Perform a sleep to throttle list requests and wait for another
		// (empty) request from the client as a backpressure mechanism. Both of
		// these operations are necessary. The sleep protects the daemon and the
		// backpressure protects the client. In reality the sleep is probably
		// sufficient to protect the client, but you need a backpressure
		// mechanism to be sure.
		time.Sleep(listThrottleInterval)
		var readyRequest ListRequest
		if err := stream.Receive(&readyRequest); err != nil {
			return errors.Wrap(err, "unable to receive ready request")
		}
	}
}

func (s *Service) pause(stream rpc.HandlerStream) error {
	// Receive the request.
	var request PauseRequest
	if err := stream.Receive(&request); err != nil {
		return errors.Wrap(err, "unable to receive request")
	}

	// Resolve the session query to a full session identifier.
	session, err := s.findSession(request.SessionQuery)
	if err != nil {
		return errors.Wrap(err, "unable to identify session")
	}

	// Lock the session registry and try to find the specified controller.
	s.sessionsLock.Lock()
	controller, ok := s.sessions[session]
	s.sessionsLock.UnlockWithoutNotify()

	// If we couldn't find the controller, abort.
	if !ok {
		return errors.New("unable to find session")
	}

	// Attempt to pause the session.
	if err := controller.halt(haltModePause); err != nil {
		return errors.Wrap(err, "unable to pause session")
	}

	// Send the response.
	if err := stream.Send(PauseResponse{}); err != nil {
		return errors.Wrap(err, "unable to send pause response")
	}

	// Success.
	return nil
}

func (s *Service) resume(stream rpc.HandlerStream) error {
	// Receive the request.
	var request ResumeRequest
	if err := stream.Receive(&request); err != nil {
		return errors.Wrap(err, "unable to receive request")
	}

	// Resolve the session query to a full session identifier.
	session, err := s.findSession(request.SessionQuery)
	if err != nil {
		return errors.Wrap(err, "unable to identify session")
	}

	// Lock the session registry and try to find the specified controller.
	s.sessionsLock.Lock()
	controller, ok := s.sessions[session]
	s.sessionsLock.UnlockWithoutNotify()

	// If we couldn't find the controller, abort.
	if !ok {
		return errors.New("unable to find session")
	}

	//  Wrap the stream in a prompter and register it with the SSH service.
	prompter, err := s.sshService.RegisterPrompter(&streamPrompter{stream})
	if err != nil {
		return errors.Wrap(err, "unable to register prompter")
	}

	// Attempt to resume.
	err = controller.resume(prompter)

	// Unregister the prompter.
	s.sshService.UnregisterPrompter(prompter)

	// Handle any resume error.
	if err != nil {
		return err
	}

	// Signal prompt completion.
	if err := stream.Send(PromptRequest{Done: true}); err != nil {
		return errors.Wrap(err, "unable to terminate prompting stream")
	}

	// Send the response.
	if err := stream.Send(ResumeResponse{}); err != nil {
		return errors.Wrap(err, "unable to send resume response")
	}

	// Success.
	return nil
}

func (s *Service) terminate(stream rpc.HandlerStream) error {
	// Receive the request.
	var request TerminateRequest
	if err := stream.Receive(&request); err != nil {
		return errors.Wrap(err, "unable to receive request")
	}

	// Resolve the session query to a full session identifier.
	session, err := s.findSession(request.SessionQuery)
	if err != nil {
		return errors.Wrap(err, "unable to identify session")
	}

	// Lock the session registry and try to find the specified controller.
	s.sessionsLock.Lock()
	controller, ok := s.sessions[session]
	s.sessionsLock.UnlockWithoutNotify()

	// If we couldn't find the controller, abort.
	if !ok {
		return errors.New("unable to find session")
	}

	// Attempt to terminate the session.
	if err := controller.halt(haltModeTerminate); err != nil {
		return errors.Wrap(err, "unable to terminate session")
	}

	// Since we terminated the session, we're responsible for unregistering it.
	s.sessionsLock.Lock()
	delete(s.sessions, session)
	s.sessionsLock.Unlock()

	// Send the response.
	if err := stream.Send(TerminateResponse{}); err != nil {
		return errors.Wrap(err, "unable to send terminate response")
	}

	// Success.
	return nil
}
<commit_msg>Modified fuzzy session matching logic to find exact matches.<commit_after>package session

import (
	"sort"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/golang/protobuf/ptypes"

	"github.com/havoc-io/mutagen/pkg/filesystem"
	"github.com/havoc-io/mutagen/pkg/rpc"
	"github.com/havoc-io/mutagen/pkg/ssh"
	"github.com/havoc-io/mutagen/pkg/state"
)

const (
	MethodCreate    = "session.Create"
	MethodList      = "session.List"
	MethodPause     = "session.Pause"
	MethodResume    = "session.Resume"
	MethodTerminate = "session.Terminate"

	listThrottleInterval = 100 * time.Millisecond
)

type Service struct {
	// sshService performs registration and deregistration of prompters.
	sshService *ssh.Service
	// tracker tracks changes to session states.
	tracker *state.Tracker
	// sessionLock locks the sessions registry.
	sessionsLock *state.TrackingLock
	// sessions maps sessions to their respective controllers.
	sessions map[string]*controller
}

func NewService(sshService *ssh.Service) (*Service, error) {
	// Create a tracker and corresponding lock to watch for state changes.
	tracker := state.NewTracker()
	sessionsLock := state.NewTrackingLock(tracker)

	// Create the session registry.
	sessions := make(map[string]*controller)

	// Load existing sessions.
	sessionsDirectory, err := pathForSession("")
	if err != nil {
		return nil, errors.Wrap(err, "unable to compute sessions directory")
	}
	identifiers, err := filesystem.DirectoryContents(sessionsDirectory)
	if err != nil {
		return nil, errors.Wrap(err, "unable to read contents of sessions directory")
	}
	for _, identifier := range identifiers {
		if controller, err := loadSession(tracker, identifier); err != nil {
			continue
		} else {
			sessions[identifier] = controller
		}
	}

	// Success.
	return &Service{
		sshService:   sshService,
		tracker:      tracker,
		sessionsLock: sessionsLock,
		sessions:     sessions,
	}, nil
}

const (
	minimumSessionMatchLength = 5
)

// findSession provides fuzzy session matching by converting a query string into
// a full session id. The query string is tried as a full session id, session id
// prefix, URL substring, and then host substring. If the specified query
// matches more than one session, it is considered ambiguous and an error is
// returned.
func (s *Service) findSession(query string) (string, error) {
	// Don't allow empty or short strings to match anything.
	if query == "" {
		return "", errors.New("empty session specification is invalid")
	} else if len(query) < minimumSessionMatchLength {
		return "", errors.Errorf(
			"session specification must be at least %d characters",
			minimumSessionMatchLength,
		)
	}

	// Grab the registry lock and defer its release.
	s.sessionsLock.Lock()
	defer s.sessionsLock.UnlockWithoutNotify()

	// Track a match.
	result := ""

	// Search for matches.
	for identifier, controller := range s.sessions {
		// If the query matches an identifier exactly, then treat that as the
		// only match. In theory someone could be weird and use a session id as
		// an element of an endpoint URL, in which case the match would be
		// ambiguous, and in that case I don't want to disable the ability to
		// end the session.
		if identifier == query {
			return identifier, nil
		}

		// Otherwise attempt fuzzy matching.
		match := strings.HasPrefix(identifier, query) ||
			strings.Contains(controller.session.Alpha.Path, query) ||
			strings.Contains(controller.session.Beta.Path, query) ||
			strings.Contains(controller.session.Alpha.Hostname, query) ||
			strings.Contains(controller.session.Beta.Hostname, query)
		if match {
			if result != "" {
				return "", errors.Errorf("query \"%s\" matches multiple sessions", query)
			}
			result = identifier
		}
	}

	// Success.
	return result, nil
}

func (s *Service) Methods() map[string]rpc.Handler {
	return map[string]rpc.Handler{
		MethodCreate:    s.create,
		MethodList:      s.list,
		MethodPause:     s.pause,
		MethodResume:    s.resume,
		MethodTerminate: s.terminate,
	}
}

func (s *Service) Shutdown() {
	// Poison state tracking to terminate monitoring.
	s.tracker.Poison()

	// Grab the registry lock and defer its release.
	s.sessionsLock.Lock()
	defer s.sessionsLock.UnlockWithoutNotify()

	// Attempt to halt each session so that it can shutdown cleanly. Ignore but
	// log any that fail to halt.
	for _, controller := range s.sessions {
		if err := controller.halt(haltModeShutdown); err != nil {
			// TODO: Log this halt failure.
		}
	}
}

type streamPrompter struct {
	stream rpc.HandlerStream
}

func (p *streamPrompter) Prompt(message, prompt string) (string, error) {
	// Create the request.
	request := PromptRequest{
		Message: message,
		Prompt:  prompt,
	}

	// Send the request.
	if err := p.stream.Send(request); err != nil {
		return "", errors.Wrap(err, "unable to send challenge")
	}

	// Receive the response.
	var response PromptResponse
	if err := p.stream.Receive(&response); err != nil {
		return "", errors.Wrap(err, "unable to receive response")
	}

	// Success.
	return response.Response, nil
}

func (s *Service) create(stream rpc.HandlerStream) error {
	// Receive the request.
	var request CreateRequest
	if err := stream.Receive(&request); err != nil {
		return errors.Wrap(err, "unable to receive request")
	}

	//  Wrap the stream in a prompter and register it with the SSH service.
	prompter, err := s.sshService.RegisterPrompter(&streamPrompter{stream})
	if err != nil {
		return errors.Wrap(err, "unable to register prompter")
	}

	// Attempt to create a session.
	controller, err := newSession(
		s.tracker,
		request.Alpha, request.Beta,
		request.Ignores,
		prompter,
	)

	// Unregister the prompter.
	s.sshService.UnregisterPrompter(prompter)

	// Handle any creation error.
	if err != nil {
		return err
	}

	// Extract the session identifier.
	sessionId := controller.session.Identifier

	// Register the controller.
	s.sessionsLock.Lock()
	s.sessions[sessionId] = controller
	s.sessionsLock.Unlock()

	// Signal prompt completion.
	if err := stream.Send(PromptRequest{Done: true}); err != nil {
		return errors.Wrap(err, "unable to terminate prompting stream")
	}

	// Send the response.
	if err := stream.Send(CreateResponse{Session: sessionId}); err != nil {
		return errors.Wrap(err, "unable to send create response")
	}

	// Success.
	return nil
}

func (s *Service) list(stream rpc.HandlerStream) error {
	// Receive the request.
	var request ListRequest
	if err := stream.Receive(&request); err != nil {
		return errors.Wrap(err, "unable to receive request")
	}

	// Determine the session query of interest and whether or not this is a
	// repeated request. An empty session query after this point indicates that
	// all sessions are of interest.
	var sessionQuery string
	var repeated bool
	switch request.Kind {
	case ListRequestKindSingle:
		sessionQuery = request.SessionQuery
	case ListRequestKindRepeated:
		sessionQuery = request.SessionQuery
		repeated = true
	case ListRequestKindRepeatedLatest:
		var mostRecentSessionCreationTime time.Time
		s.sessionsLock.Lock()
		for _, controller := range s.sessions {
			state := controller.currentState()
			creationTime, err := ptypes.Timestamp(state.Session.CreationTime)
			if err != nil {
				s.sessionsLock.Unlock()
				return errors.Wrap(err, "unable to convert creation time format")
			}
			if creationTime.After(mostRecentSessionCreationTime) {
				sessionQuery = state.Session.Identifier
				mostRecentSessionCreationTime = creationTime
			}
		}
		s.sessionsLock.Unlock()
		if sessionQuery == "" {
			return errors.New("no sessions present")
		}
		repeated = true
	default:
		return errors.New("unknown list request kind")
	}

	// If a session query has been provided, resolve the full session identifier
	// to which it refers.
	session := ""
	if sessionQuery != "" {
		if s, err := s.findSession(sessionQuery); err != nil {
			return errors.Wrap(err, "unable to identify session")
		} else {
			session = s
		}
	}

	// Loop indefinitely and track state changes. We'll bail after a single
	// response if monitoring wasn't requested.
	previousStateIndex := uint64(0)
	var poisoned bool
	for {
		// Wait for a state change.
		// TODO: If the client disconnects while this handler is polling for
		// changes, this Goroutine will wait here until there's another change,
		// and will then exit when it tries (and fails) to send a response. This
		// will be fine in practice, but it's not elegant.
		previousStateIndex, poisoned = s.tracker.WaitForChange(previousStateIndex)
		if poisoned {
			return errors.New("state tracking terminated")
		}

		// Lock the session registry.
		s.sessionsLock.Lock()

		// Create a snapshot of the necessary session states.
		var sessions []SessionState
		var err error
		if session != "" {
			if controller, ok := s.sessions[session]; ok {
				sessions = append(sessions, controller.currentState())
			} else {
				err = errors.New("requested session no longer exists")
			}
		} else {
			for _, controller := range s.sessions {
				sessions = append(sessions, controller.currentState())
			}
		}

		// Unlock the session registry. It's very important that we unlock
		// without a notification here, otherwise we'll trigger an infinite
		// cycle of state changes.
		s.sessionsLock.UnlockWithoutNotify()

		// Handle errors.
		if err != nil {
			return err
		}

		// Sort sessions by creation time.
		sort.Slice(sessions, func(i, j int) bool {
			iTime := sessions[i].Session.CreationTime
			jTime := sessions[j].Session.CreationTime
			return iTime.Seconds < jTime.Seconds ||
				(iTime.Seconds == jTime.Seconds && iTime.Nanos < jTime.Nanos)
		})

		// Send this response.
		if err := stream.Send(ListResponse{Sessions: sessions}); err != nil {
			return errors.Wrap(err, "unable to send list response")
		}

		// If repeated listings weren't requested, then we're done.
		if !repeated {
			return nil
		}

		// Perform a sleep to throttle list requests and wait for another
		// (empty) request from the client as a backpressure mechanism. Both of
		// these operations are necessary. The sleep protects the daemon and the
		// backpressure protects the client. In reality the sleep is probably
		// sufficient to protect the client, but you need a backpressure
		// mechanism to be sure.
		time.Sleep(listThrottleInterval)
		var readyRequest ListRequest
		if err := stream.Receive(&readyRequest); err != nil {
			return errors.Wrap(err, "unable to receive ready request")
		}
	}
}

func (s *Service) pause(stream rpc.HandlerStream) error {
	// Receive the request.
	var request PauseRequest
	if err := stream.Receive(&request); err != nil {
		return errors.Wrap(err, "unable to receive request")
	}

	// Resolve the session query to a full session identifier.
	session, err := s.findSession(request.SessionQuery)
	if err != nil {
		return errors.Wrap(err, "unable to identify session")
	}

	// Lock the session registry and try to find the specified controller.
	s.sessionsLock.Lock()
	controller, ok := s.sessions[session]
	s.sessionsLock.UnlockWithoutNotify()

	// If we couldn't find the controller, abort.
	if !ok {
		return errors.New("unable to find session")
	}

	// Attempt to pause the session.
	if err := controller.halt(haltModePause); err != nil {
		return errors.Wrap(err, "unable to pause session")
	}

	// Send the response.
	if err := stream.Send(PauseResponse{}); err != nil {
		return errors.Wrap(err, "unable to send pause response")
	}

	// Success.
	return nil
}

func (s *Service) resume(stream rpc.HandlerStream) error {
	// Receive the request.
	var request ResumeRequest
	if err := stream.Receive(&request); err != nil {
		return errors.Wrap(err, "unable to receive request")
	}

	// Resolve the session query to a full session identifier.
	session, err := s.findSession(request.SessionQuery)
	if err != nil {
		return errors.Wrap(err, "unable to identify session")
	}

	// Lock the session registry and try to find the specified controller.
	s.sessionsLock.Lock()
	controller, ok := s.sessions[session]
	s.sessionsLock.UnlockWithoutNotify()

	// If we couldn't find the controller, abort.
	if !ok {
		return errors.New("unable to find session")
	}

	//  Wrap the stream in a prompter and register it with the SSH service.
	prompter, err := s.sshService.RegisterPrompter(&streamPrompter{stream})
	if err != nil {
		return errors.Wrap(err, "unable to register prompter")
	}

	// Attempt to resume.
	err = controller.resume(prompter)

	// Unregister the prompter.
	s.sshService.UnregisterPrompter(prompter)

	// Handle any resume error.
	if err != nil {
		return err
	}

	// Signal prompt completion.
	if err := stream.Send(PromptRequest{Done: true}); err != nil {
		return errors.Wrap(err, "unable to terminate prompting stream")
	}

	// Send the response.
	if err := stream.Send(ResumeResponse{}); err != nil {
		return errors.Wrap(err, "unable to send resume response")
	}

	// Success.
	return nil
}

func (s *Service) terminate(stream rpc.HandlerStream) error {
	// Receive the request.
	var request TerminateRequest
	if err := stream.Receive(&request); err != nil {
		return errors.Wrap(err, "unable to receive request")
	}

	// Resolve the session query to a full session identifier.
	session, err := s.findSession(request.SessionQuery)
	if err != nil {
		return errors.Wrap(err, "unable to identify session")
	}

	// Lock the session registry and try to find the specified controller.
	s.sessionsLock.Lock()
	controller, ok := s.sessions[session]
	s.sessionsLock.UnlockWithoutNotify()

	// If we couldn't find the controller, abort.
	if !ok {
		return errors.New("unable to find session")
	}

	// Attempt to terminate the session.
	if err := controller.halt(haltModeTerminate); err != nil {
		return errors.Wrap(err, "unable to terminate session")
	}

	// Since we terminated the session, we're responsible for unregistering it.
	s.sessionsLock.Lock()
	delete(s.sessions, session)
	s.sessionsLock.Unlock()

	// Send the response.
	if err := stream.Send(TerminateResponse{}); err != nil {
		return errors.Wrap(err, "unable to send terminate response")
	}

	// Success.
	return nil
}
<|endoftext|>


<commit_before>package platform

import (
	"fmt"
	"strings"

	"github.com/scrapli/scrapligo/assets"

	"github.com/scrapli/scrapligo/driver/generic"
	"github.com/scrapli/scrapligo/driver/network"
	"github.com/scrapli/scrapligo/driver/options"

	"github.com/scrapli/scrapligo/util"

	"gopkg.in/yaml.v3"
)

const (
	// AristaEos is a constant representing the platform string/name for Arista EOS devices.
	AristaEos = "arista_eos"
	// CiscoIosxe is a constant representing the platform string/name for Cisco IOSXE devices.
	CiscoIosxe = "cisco_iosxe"
	// CiscoIosxr is a constant representing the platform string/name for Cisco IOSXR devices.
	CiscoIosxr = "cisco_iosxr"
	// CiscoNxos is a constant representing the platform string/name for Cisco NXOS devices.
	CiscoNxos = "cisco_nxos"
	// JuniperJunos is a constant representing the platform string/name for Juniper JunOS devices.
	JuniperJunos = "juniper_junos"
	// NokiaSrl is a constant representing the platform string/name for Nokia SRL/SRLinux devices.
	NokiaSrl = "nokia_srl"
)

// GetPlatformNames is used to get the "core" (as in embedded in assets and used in testing)
// platform names.
func GetPlatformNames() []string {
	return []string{
		AristaEos, CiscoIosxe, CiscoIosxr, CiscoNxos, JuniperJunos, NokiaSrl,
	}
}

func loadPlatformDefinitionFromAssets(f string) ([]byte, error) {
	if !strings.HasSuffix(f, ".yaml") {
		f += ".yaml"
	}

	return assets.Assets.ReadFile(fmt.Sprintf("platforms/%s", f))
}

func loadPlatformDefinition(f string) (*Definition, error) {
	b, err := loadPlatformDefinitionFromAssets(f)
	if err != nil {
		b, err = util.ResolveAtFileOrURL(f) //nolint:gosec
		if err != nil {
			return nil, err
		}
	}

	pd := &Definition{}

	err = yaml.Unmarshal(b, pd)
	if err != nil {
		return nil, err
	}

	return pd, nil
}

func setDriver(host string, p *Platform, opts ...util.Option) error {
	finalOpts := p.AsOptions()
	finalOpts = append(finalOpts, opts...)

	var err error

	switch p.DriverType {
	case "generic":
		var d *generic.Driver

		d, err = generic.NewDriver(host, finalOpts...)
		if err != nil {
			return err
		}

		p.genericDriver = d
	case "network":
		var d *network.Driver

		d, err = network.NewDriver(host, finalOpts...)
		if err != nil {
			return err
		}

		p.networkDriver = d
	}

	return err
}

// NewPlatformVariant returns an instance of Platform from the platform definition file f. The
// provided variant data is merged back into the "base" platform definition. The host and
// any provided options are stored and will be applied when fetching the generic or network driver
// via the GetGenericDriver or GetNetworkDriver methods.
func NewPlatformVariant(f, variant, host string, opts ...util.Option) (*Platform, error) {
	pd, err := loadPlatformDefinition(f)
	if err != nil {
		return nil, err
	}

	p := pd.Default

	vp, ok := pd.Variants[variant]
	if !ok {
		return nil, fmt.Errorf("%w: no variant '%s' in platform", util.ErrPlatformError, variant)
	}

	p.mergeVariant(vp)

	err = setDriver(host, p, opts...)
	if err != nil {
		return nil, err
	}

	return p, nil
}

// NewPlatform returns an instance of Platform from the platform definition file f. The host and
// any provided options are stored and will be applied when fetching the generic or network driver
// via the GetGenericDriver or GetNetworkDriver methods.
func NewPlatform(f, host string, opts ...util.Option) (*Platform, error) {
	pd, err := loadPlatformDefinition(f)
	if err != nil {
		return nil, err
	}

	p := pd.Default

	err = setDriver(host, p, opts...)
	if err != nil {
		return nil, err
	}

	return p, nil
}

// Definition is a struct representing a JSON or YAML platform definition file.
type Definition struct {
	Default  *Platform            `json:"default"  yaml:"default"`
	Variants map[string]*Platform `json:"variants" yaml:"variants"`
}

// Platform is a struct that contains JSON or YAML data that represent the attributes required to
// create a generic or network driver to connect to a given device type.
type Platform struct {
	// DriverType generic||network
	DriverType string `yaml:"driver-type"`

	FailedWhenContains []string       `json:"failed-when-contains" yaml:"failed-when-contains"`
	OnOpen             onXDefinitions `json:"on-open"              yaml:"on-open"`
	OnClose            onXDefinitions `json:"on-close"             yaml:"on-close"`

	PrivilegeLevels              network.PrivilegeLevels `json:"privilege-levels"                yaml:"privilege-levels"`
	DefaultDesiredPrivilegeLevel string                  `json:"default-desired-privilege-level" yaml:"default-desired-privilege-level"`
	NetworkOnOpen                onXDefinitions          `json:"network-on-open"                 yaml:"network-on-open"`
	NetworkOnClose               onXDefinitions          `json:"network-on-close"                yaml:"network-on-close"`

	Options optionDefinitions `json:"options" yaml:"options"`

	genericDriver *generic.Driver
	networkDriver *network.Driver
}

func (p *Platform) mergeVariant(v *Platform) {
	if v.DriverType != "" {
		p.DriverType = v.DriverType
	}

	if len(v.FailedWhenContains) > 0 {
		p.FailedWhenContains = v.FailedWhenContains
	}

	if v.OnOpen != nil {
		p.OnOpen = v.OnOpen
	}

	if v.OnClose != nil {
		p.OnClose = v.OnClose
	}

	if len(v.PrivilegeLevels) > 0 {
		p.PrivilegeLevels = v.PrivilegeLevels
	}

	if v.DefaultDesiredPrivilegeLevel != "" {
		p.DefaultDesiredPrivilegeLevel = v.DefaultDesiredPrivilegeLevel
	}

	if v.NetworkOnOpen != nil {
		p.NetworkOnOpen = v.NetworkOnOpen
	}

	if v.NetworkOnClose != nil {
		p.NetworkOnClose = v.NetworkOnClose
	}
}

// GetGenericDriver returns an instance of generic.Driver built from the Platform data. If the
// platform data (JSON/YAML) specifies a network driver type this will return an error.
func (p *Platform) GetGenericDriver() (*generic.Driver, error) {
	if p.genericDriver == nil {
		return nil, fmt.Errorf(
			"%w: requested generic driver, but generic driver is nil",
			util.ErrPlatformError,
		)
	}

	return p.genericDriver, nil
}

// GetNetworkDriver returns an instance of network.Driver built from the Platform data. If the
// platform data (JSON/YAML) specifies a generic driver type this will return an error.
func (p *Platform) GetNetworkDriver() (*network.Driver, error) {
	if p.networkDriver == nil {
		return nil, fmt.Errorf(
			"%w: requested network driver, but network driver is nil",
			util.ErrPlatformError,
		)
	}

	return p.networkDriver, nil
}

func (p *Platform) genericOptions() []util.Option {
	opts := make([]util.Option, 0)

	if len(p.FailedWhenContains) > 0 {
		opts = append(opts, options.WithFailedWhenContains(p.FailedWhenContains))
	}

	if len(p.OnOpen) > 0 {
		opts = append(opts, options.WithOnOpen(p.OnOpen.asGenericOnX()))
	}

	if len(p.OnClose) > 0 {
		opts = append(opts, options.WithOnClose(p.OnClose.asGenericOnX()))
	}

	return opts
}

// AsOptions returns a slice of options that the platform represents.
func (p *Platform) AsOptions() []util.Option {
	opts := p.genericOptions()

	opts = append(
		opts,
		options.WithPrivilegeLevels(p.PrivilegeLevels),
		options.WithDefaultDesiredPriv(p.DefaultDesiredPrivilegeLevel),
	)

	if len(p.NetworkOnOpen) > 0 {
		opts = append(opts, options.WithNetworkOnOpen(p.NetworkOnOpen.asNetworkOnX()))
	}

	if len(p.NetworkOnClose) > 0 {
		opts = append(opts, options.WithNetworkOnClose(p.NetworkOnClose.asNetworkOnX()))
	}

	opts = append(opts, p.Options.asOptions()...)

	return opts
}
<commit_msg>remove unnecessary nolint<commit_after>package platform

import (
	"fmt"
	"strings"

	"github.com/scrapli/scrapligo/assets"

	"github.com/scrapli/scrapligo/driver/generic"
	"github.com/scrapli/scrapligo/driver/network"
	"github.com/scrapli/scrapligo/driver/options"

	"github.com/scrapli/scrapligo/util"

	"gopkg.in/yaml.v3"
)

const (
	// AristaEos is a constant representing the platform string/name for Arista EOS devices.
	AristaEos = "arista_eos"
	// CiscoIosxe is a constant representing the platform string/name for Cisco IOSXE devices.
	CiscoIosxe = "cisco_iosxe"
	// CiscoIosxr is a constant representing the platform string/name for Cisco IOSXR devices.
	CiscoIosxr = "cisco_iosxr"
	// CiscoNxos is a constant representing the platform string/name for Cisco NXOS devices.
	CiscoNxos = "cisco_nxos"
	// JuniperJunos is a constant representing the platform string/name for Juniper JunOS devices.
	JuniperJunos = "juniper_junos"
	// NokiaSrl is a constant representing the platform string/name for Nokia SRL/SRLinux devices.
	NokiaSrl = "nokia_srl"
)

// GetPlatformNames is used to get the "core" (as in embedded in assets and used in testing)
// platform names.
func GetPlatformNames() []string {
	return []string{
		AristaEos, CiscoIosxe, CiscoIosxr, CiscoNxos, JuniperJunos, NokiaSrl,
	}
}

func loadPlatformDefinitionFromAssets(f string) ([]byte, error) {
	if !strings.HasSuffix(f, ".yaml") {
		f += ".yaml"
	}

	return assets.Assets.ReadFile(fmt.Sprintf("platforms/%s", f))
}

func loadPlatformDefinition(f string) (*Definition, error) {
	b, err := loadPlatformDefinitionFromAssets(f)
	if err != nil {
		b, err = util.ResolveAtFileOrURL(f)
		if err != nil {
			return nil, err
		}
	}

	pd := &Definition{}

	err = yaml.Unmarshal(b, pd)
	if err != nil {
		return nil, err
	}

	return pd, nil
}

func setDriver(host string, p *Platform, opts ...util.Option) error {
	finalOpts := p.AsOptions()
	finalOpts = append(finalOpts, opts...)

	var err error

	switch p.DriverType {
	case "generic":
		var d *generic.Driver

		d, err = generic.NewDriver(host, finalOpts...)
		if err != nil {
			return err
		}

		p.genericDriver = d
	case "network":
		var d *network.Driver

		d, err = network.NewDriver(host, finalOpts...)
		if err != nil {
			return err
		}

		p.networkDriver = d
	}

	return err
}

// NewPlatformVariant returns an instance of Platform from the platform definition file f. The
// provided variant data is merged back into the "base" platform definition. The host and
// any provided options are stored and will be applied when fetching the generic or network driver
// via the GetGenericDriver or GetNetworkDriver methods.
func NewPlatformVariant(f, variant, host string, opts ...util.Option) (*Platform, error) {
	pd, err := loadPlatformDefinition(f)
	if err != nil {
		return nil, err
	}

	p := pd.Default

	vp, ok := pd.Variants[variant]
	if !ok {
		return nil, fmt.Errorf("%w: no variant '%s' in platform", util.ErrPlatformError, variant)
	}

	p.mergeVariant(vp)

	err = setDriver(host, p, opts...)
	if err != nil {
		return nil, err
	}

	return p, nil
}

// NewPlatform returns an instance of Platform from the platform definition file f. The host and
// any provided options are stored and will be applied when fetching the generic or network driver
// via the GetGenericDriver or GetNetworkDriver methods.
func NewPlatform(f, host string, opts ...util.Option) (*Platform, error) {
	pd, err := loadPlatformDefinition(f)
	if err != nil {
		return nil, err
	}

	p := pd.Default

	err = setDriver(host, p, opts...)
	if err != nil {
		return nil, err
	}

	return p, nil
}

// Definition is a struct representing a JSON or YAML platform definition file.
type Definition struct {
	Default  *Platform            `json:"default"  yaml:"default"`
	Variants map[string]*Platform `json:"variants" yaml:"variants"`
}

// Platform is a struct that contains JSON or YAML data that represent the attributes required to
// create a generic or network driver to connect to a given device type.
type Platform struct {
	// DriverType generic||network
	DriverType string `yaml:"driver-type"`

	FailedWhenContains []string       `json:"failed-when-contains" yaml:"failed-when-contains"`
	OnOpen             onXDefinitions `json:"on-open"              yaml:"on-open"`
	OnClose            onXDefinitions `json:"on-close"             yaml:"on-close"`

	PrivilegeLevels              network.PrivilegeLevels `json:"privilege-levels"                yaml:"privilege-levels"`
	DefaultDesiredPrivilegeLevel string                  `json:"default-desired-privilege-level" yaml:"default-desired-privilege-level"`
	NetworkOnOpen                onXDefinitions          `json:"network-on-open"                 yaml:"network-on-open"`
	NetworkOnClose               onXDefinitions          `json:"network-on-close"                yaml:"network-on-close"`

	Options optionDefinitions `json:"options" yaml:"options"`

	genericDriver *generic.Driver
	networkDriver *network.Driver
}

func (p *Platform) mergeVariant(v *Platform) {
	if v.DriverType != "" {
		p.DriverType = v.DriverType
	}

	if len(v.FailedWhenContains) > 0 {
		p.FailedWhenContains = v.FailedWhenContains
	}

	if v.OnOpen != nil {
		p.OnOpen = v.OnOpen
	}

	if v.OnClose != nil {
		p.OnClose = v.OnClose
	}

	if len(v.PrivilegeLevels) > 0 {
		p.PrivilegeLevels = v.PrivilegeLevels
	}

	if v.DefaultDesiredPrivilegeLevel != "" {
		p.DefaultDesiredPrivilegeLevel = v.DefaultDesiredPrivilegeLevel
	}

	if v.NetworkOnOpen != nil {
		p.NetworkOnOpen = v.NetworkOnOpen
	}

	if v.NetworkOnClose != nil {
		p.NetworkOnClose = v.NetworkOnClose
	}
}

// GetGenericDriver returns an instance of generic.Driver built from the Platform data. If the
// platform data (JSON/YAML) specifies a network driver type this will return an error.
func (p *Platform) GetGenericDriver() (*generic.Driver, error) {
	if p.genericDriver == nil {
		return nil, fmt.Errorf(
			"%w: requested generic driver, but generic driver is nil",
			util.ErrPlatformError,
		)
	}

	return p.genericDriver, nil
}

// GetNetworkDriver returns an instance of network.Driver built from the Platform data. If the
// platform data (JSON/YAML) specifies a generic driver type this will return an error.
func (p *Platform) GetNetworkDriver() (*network.Driver, error) {
	if p.networkDriver == nil {
		return nil, fmt.Errorf(
			"%w: requested network driver, but network driver is nil",
			util.ErrPlatformError,
		)
	}

	return p.networkDriver, nil
}

func (p *Platform) genericOptions() []util.Option {
	opts := make([]util.Option, 0)

	if len(p.FailedWhenContains) > 0 {
		opts = append(opts, options.WithFailedWhenContains(p.FailedWhenContains))
	}

	if len(p.OnOpen) > 0 {
		opts = append(opts, options.WithOnOpen(p.OnOpen.asGenericOnX()))
	}

	if len(p.OnClose) > 0 {
		opts = append(opts, options.WithOnClose(p.OnClose.asGenericOnX()))
	}

	return opts
}

// AsOptions returns a slice of options that the platform represents.
func (p *Platform) AsOptions() []util.Option {
	opts := p.genericOptions()

	opts = append(
		opts,
		options.WithPrivilegeLevels(p.PrivilegeLevels),
		options.WithDefaultDesiredPriv(p.DefaultDesiredPrivilegeLevel),
	)

	if len(p.NetworkOnOpen) > 0 {
		opts = append(opts, options.WithNetworkOnOpen(p.NetworkOnOpen.asNetworkOnX()))
	}

	if len(p.NetworkOnClose) > 0 {
		opts = append(opts, options.WithNetworkOnClose(p.NetworkOnClose.asNetworkOnX()))
	}

	opts = append(opts, p.Options.asOptions()...)

	return opts
}
<|endoftext|>


<commit_before>package cuckoo

import (
	"errors"
	"fmt"
	"sync/atomic"
)

const MAX_HASHES = 10

type keyt []byte

type Cuckoo struct{ *cmap }

func New() Cuckoo {
	m := new(cmap)
	m.bins = make([]cbin, 1e4 /* TODO: all of main memory */)
	m.hashes = 2
	return Cuckoo{m}
}

func (c Cuckoo) Insert(key string, value interface{}) error {
	h := c.hashes
	pathl := c.insert(keyt(key), value)

	for pathl == -1 && h < MAX_HASHES {
		sw := atomic.CompareAndSwapUint32(&c.hashes, h, h+1)
		if sw {
			fmt.Println("insert failed, so upped # hashes to", h+1)
		}

		h = c.hashes
		pathl = c.insert(keyt(key), value)
	}

	if pathl == -1 {
		return errors.New("insert failed, table must be full (or have bad cycles)")
	}
	return nil
}

func (c Cuckoo) Get(key string) (interface{}, bool) {
	return c.get(keyt(key))
}

func (c Cuckoo) Iterate() <-chan interface{} {
	ch := make(chan interface{})
	go func() {
		for v := range c.iterate() {
			ch <- v.val
		}
		close(ch)
	}()
	return ch
}

func (c Cuckoo) IterateKeys() <-chan string {
	ch := make(chan string)
	go func() {
		for v := range c.iterate() {
			ch <- string(v.key)
		}
		close(ch)
	}()
	return ch
}
<commit_msg>Indicate key that caused #hashes++<commit_after>package cuckoo

import (
	"errors"
	"fmt"
	"sync/atomic"
)

const MAX_HASHES = 10

type keyt []byte

type Cuckoo struct{ *cmap }

func New() Cuckoo {
	m := new(cmap)
	m.bins = make([]cbin, 1e4 /* TODO: all of main memory */)
	m.hashes = 2
	return Cuckoo{m}
}

func (c Cuckoo) Insert(key string, value interface{}) error {
	h := c.hashes
	pathl := c.insert(keyt(key), value)

	for pathl == -1 && h < MAX_HASHES {
		sw := atomic.CompareAndSwapUint32(&c.hashes, h, h+1)
		if sw {
			fmt.Println("insert failed on key", key, ", so upped # hashes to", h+1)
		}

		h = c.hashes
		pathl = c.insert(keyt(key), value)
	}

	if pathl == -1 {
		return errors.New("insert failed, table must be full (or have bad cycles)")
	}
	return nil
}

func (c Cuckoo) Get(key string) (interface{}, bool) {
	return c.get(keyt(key))
}

func (c Cuckoo) Iterate() <-chan interface{} {
	ch := make(chan interface{})
	go func() {
		for v := range c.iterate() {
			ch <- v.val
		}
		close(ch)
	}()
	return ch
}

func (c Cuckoo) IterateKeys() <-chan string {
	ch := make(chan string)
	go func() {
		for v := range c.iterate() {
			ch <- string(v.key)
		}
		close(ch)
	}()
	return ch
}
<|endoftext|>