<commit_before>package daemon

import (
	"fmt"
	"regexp"
	"sync/atomic"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/distribution/reference"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	timetypes "github.com/docker/docker/api/types/time"
	"github.com/docker/docker/image"
	"github.com/docker/docker/layer"
	"github.com/docker/docker/pkg/directory"
	"github.com/docker/docker/runconfig"
	"github.com/docker/docker/volume"
	"github.com/docker/libnetwork"
	digest "github.com/opencontainers/go-digest"
	"golang.org/x/net/context"
)

var (
	// errPruneRunning is returned when a prune request is received while
	// one is in progress
	errPruneRunning = fmt.Errorf("a prune operation is already running")

	containersAcceptedFilters = map[string]bool{
		"label":  true,
		"label!": true,
		"until":  true,
	}
	volumesAcceptedFilters = map[string]bool{
		"label":  true,
		"label!": true,
	}
	imagesAcceptedFilters = map[string]bool{
		"dangling": true,
		"label":    true,
		"label!":   true,
		"until":    true,
	}
	networksAcceptedFilters = map[string]bool{
		"label":  true,
		"label!": true,
		"until":  true,
	}
)

// ContainersPrune removes unused containers
func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
		return nil, errPruneRunning
	}
	defer atomic.StoreInt32(&daemon.pruneRunning, 0)

	rep := &types.ContainersPruneReport{}

	// make sure that only accepted filters have been received
	err := pruneFilters.Validate(containersAcceptedFilters)
	if err != nil {
		return nil, err
	}

	until, err := getUntilFromPruneFilters(pruneFilters)
	if err != nil {
		return nil, err
	}

	allContainers := daemon.List()
	for _, c := range allContainers {
		select {
		case <-ctx.Done():
			logrus.Warnf("ContainersPrune operation cancelled: %#v", *rep)
			return rep, ctx.Err()
		default:
		}

		if !c.IsRunning() {
			if !until.IsZero() && c.Created.After(until) {
				continue
			}
			if !matchLabels(pruneFilters, c.Config.Labels) {
				continue
			}
			cSize, _ := daemon.getSize(c.ID)
			// TODO: sets RmLink to true?
			err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{})
			if err != nil {
				logrus.Warnf("failed to prune container %s: %v", c.ID, err)
				continue
			}
			if cSize > 0 {
				rep.SpaceReclaimed += uint64(cSize)
			}
			rep.ContainersDeleted = append(rep.ContainersDeleted, c.ID)
		}
	}

	return rep, nil
}

// VolumesPrune removes unused local volumes
func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error) {
	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
		return nil, errPruneRunning
	}
	defer atomic.StoreInt32(&daemon.pruneRunning, 0)

	// make sure that only accepted filters have been received
	err := pruneFilters.Validate(volumesAcceptedFilters)
	if err != nil {
		return nil, err
	}

	rep := &types.VolumesPruneReport{}

	pruneVols := func(v volume.Volume) error {
		select {
		case <-ctx.Done():
			logrus.Warnf("VolumesPrune operation cancelled: %#v", *rep)
			return ctx.Err()
		default:
		}

		name := v.Name()
		refs := daemon.volumes.Refs(v)

		if len(refs) == 0 {
			detailedVolume, ok := v.(volume.DetailedVolume)
			if ok {
				if !matchLabels(pruneFilters, detailedVolume.Labels()) {
					return nil
				}
			}
			vSize, err := directory.Size(v.Path())
			if err != nil {
				logrus.Warnf("could not determine size of volume %s: %v", name, err)
			}
			err = daemon.volumes.Remove(v)
			if err != nil {
				logrus.Warnf("could not remove volume %s: %v", name, err)
				return nil
			}
			rep.SpaceReclaimed += uint64(vSize)
			rep.VolumesDeleted = append(rep.VolumesDeleted, name)
		}

		return nil
	}

	err = daemon.traverseLocalVolumes(pruneVols)

	return rep, err
}

// ImagesPrune removes unused images
func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
		return nil, errPruneRunning
	}
	defer atomic.StoreInt32(&daemon.pruneRunning, 0)

	// make sure that only accepted filters have been received
	err := pruneFilters.Validate(imagesAcceptedFilters)
	if err != nil {
		return nil, err
	}

	rep := &types.ImagesPruneReport{}

	danglingOnly := true
	if pruneFilters.Include("dangling") {
		if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") {
			danglingOnly = false
		} else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") {
			return nil, fmt.Errorf("Invalid filter 'dangling=%s'", pruneFilters.Get("dangling"))
		}
	}

	until, err := getUntilFromPruneFilters(pruneFilters)
	if err != nil {
		return nil, err
	}

	var allImages map[image.ID]*image.Image
	if danglingOnly {
		allImages = daemon.imageStore.Heads()
	} else {
		allImages = daemon.imageStore.Map()
	}
	allContainers := daemon.List()
	imageRefs := map[string]bool{}
	for _, c := range allContainers {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
			imageRefs[c.ID] = true
		}
	}

	// Filter intermediary images and get their unique size
	allLayers := daemon.layerStore.Map()
	topImages := map[image.ID]*image.Image{}
	for id, img := range allImages {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
			dgst := digest.Digest(id)
			if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
				continue
			}
			if !until.IsZero() && img.Created.After(until) {
				continue
			}
			if !matchLabels(pruneFilters, img.Config.Labels) {
				continue
			}
			topImages[id] = img
		}
	}

	canceled := false
deleteImagesLoop:
	for id := range topImages {
		select {
		case <-ctx.Done():
			// we still want to calculate freed size and return the data
			canceled = true
			break deleteImagesLoop
		default:
		}

		dgst := digest.Digest(id)
		hex := dgst.Hex()
		if _, ok := imageRefs[hex]; ok {
			continue
		}

		deletedImages := []types.ImageDeleteResponseItem{}
		refs := daemon.referenceStore.References(dgst)
		if len(refs) > 0 {
			shouldDelete := !danglingOnly
			if !shouldDelete {
				hasTag := false
				for _, ref := range refs {
					if _, ok := ref.(reference.NamedTagged); ok {
						hasTag = true
						break
					}
				}

				// Only delete if it's untagged (i.e. repo:<none>)
				shouldDelete = !hasTag
			}

			if shouldDelete {
				for _, ref := range refs {
					imgDel, err := daemon.ImageDelete(ref.String(), false, true)
					if err != nil {
						logrus.Warnf("could not delete reference %s: %v", ref.String(), err)
						continue
					}
					deletedImages = append(deletedImages, imgDel...)
				}
			}
		} else {
			imgDel, err := daemon.ImageDelete(hex, false, true)
			if err != nil {
				logrus.Warnf("could not delete image %s: %v", hex, err)
				continue
			}
			deletedImages = append(deletedImages, imgDel...)
		}

		rep.ImagesDeleted = append(rep.ImagesDeleted, deletedImages...)
	}

	// Compute how much space was freed
	for _, d := range rep.ImagesDeleted {
		if d.Deleted != "" {
			chid := layer.ChainID(d.Deleted)
			if l, ok := allLayers[chid]; ok {
				diffSize, err := l.DiffSize()
				if err != nil {
					logrus.Warnf("failed to get layer %s size: %v", chid, err)
					continue
				}
				rep.SpaceReclaimed += uint64(diffSize)
			}
		}
	}

	if canceled {
		logrus.Warnf("ImagesPrune operation cancelled: %#v", *rep)
		return nil, ctx.Err()
	}

	return rep, nil
}

// localNetworksPrune removes unused local networks
func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport {
	rep := &types.NetworksPruneReport{}

	until, _ := getUntilFromPruneFilters(pruneFilters)

	// When the function returns true, the walk will stop.
	l := func(nw libnetwork.Network) bool {
		select {
		case <-ctx.Done():
			return true
		default:
		}
		if nw.Info().ConfigOnly() {
			return false
		}
		if !until.IsZero() && nw.Info().Created().After(until) {
			return false
		}
		if !matchLabels(pruneFilters, nw.Info().Labels()) {
			return false
		}
		nwName := nw.Name()
		if runconfig.IsPreDefinedNetwork(nwName) {
			return false
		}
		if len(nw.Endpoints()) > 0 {
			return false
		}
		if err := daemon.DeleteNetwork(nw.ID()); err != nil {
			logrus.Warnf("could not remove local network %s: %v", nwName, err)
			return false
		}
		rep.NetworksDeleted = append(rep.NetworksDeleted, nwName)
		return false
	}
	daemon.netController.WalkNetworks(l)
	return rep
}

// clusterNetworksPrune removes unused cluster networks
func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
	rep := &types.NetworksPruneReport{}

	until, _ := getUntilFromPruneFilters(pruneFilters)

	cluster := daemon.GetCluster()

	if !cluster.IsManager() {
		return rep, nil
	}

	networks, err := cluster.GetNetworks()
	if err != nil {
		return rep, err
	}
	networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`)
	for _, nw := range networks {
		select {
		case <-ctx.Done():
			return rep, ctx.Err()
		default:
			if nw.Ingress {
				// Routing-mesh network removal has to be explicitly invoked by user
				continue
			}
			if !until.IsZero() && nw.Created.After(until) {
				continue
			}
			if !matchLabels(pruneFilters, nw.Labels) {
				continue
			}
			// https://github.com/docker/docker/issues/24186
			// `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
			// So we try to remove it anyway and check the error
			err = cluster.RemoveNetwork(nw.ID)
			if err != nil {
				// we can safely ignore the "network .. is in use" error
				match := networkIsInUse.FindStringSubmatch(err.Error())
				if len(match) != 2 || match[1] != nw.ID {
					logrus.Warnf("could not remove cluster network %s: %v", nw.Name, err)
				}
				continue
			}
			rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name)
		}
	}
	return rep, nil
}

// NetworksPrune removes unused networks
func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
		return nil, errPruneRunning
	}
	defer atomic.StoreInt32(&daemon.pruneRunning, 0)

	// make sure that only accepted filters have been received
	err := pruneFilters.Validate(networksAcceptedFilters)
	if err != nil {
		return nil, err
	}

	if _, err := getUntilFromPruneFilters(pruneFilters); err != nil {
		return nil, err
	}

	rep := &types.NetworksPruneReport{}
	if clusterRep, err := daemon.clusterNetworksPrune(ctx, pruneFilters); err == nil {
		rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...)
	}

	localRep := daemon.localNetworksPrune(ctx, pruneFilters)
	rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...)

	select {
	case <-ctx.Done():
		logrus.Warnf("NetworksPrune operation cancelled: %#v", *rep)
		return nil, ctx.Err()
	default:
	}

	return rep, nil
}

func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) {
	until := time.Time{}
	if !pruneFilters.Include("until") {
		return until, nil
	}
	untilFilters := pruneFilters.Get("until")
	if len(untilFilters) > 1 {
		return until, fmt.Errorf("more than one until filter specified")
	}
	ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now())
	if err != nil {
		return until, err
	}
	seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0)
	if err != nil {
		return until, err
	}
	until = time.Unix(seconds, nanoseconds)
	return until, nil
}

func matchLabels(pruneFilters filters.Args, labels map[string]string) bool {
	if !pruneFilters.MatchKVList("label", labels) {
		return false
	}
	// By default MatchKVList will return true if field (like 'label!') does not exist
	// So we have to add additional Include("label!") check
	if pruneFilters.Include("label!") {
		if pruneFilters.MatchKVList("label!", labels) {
			return false
		}
	}
	return true
}
<commit_msg>prevent image prune panic<commit_after>package daemon

import (
	"fmt"
	"regexp"
	"sync/atomic"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/distribution/reference"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	timetypes "github.com/docker/docker/api/types/time"
	"github.com/docker/docker/image"
	"github.com/docker/docker/layer"
	"github.com/docker/docker/pkg/directory"
	"github.com/docker/docker/runconfig"
	"github.com/docker/docker/volume"
	"github.com/docker/libnetwork"
	digest "github.com/opencontainers/go-digest"
	"golang.org/x/net/context"
)

var (
	// errPruneRunning is returned when a prune request is received while
	// one is in progress
	errPruneRunning = fmt.Errorf("a prune operation is already running")

	containersAcceptedFilters = map[string]bool{
		"label":  true,
		"label!": true,
		"until":  true,
	}
	volumesAcceptedFilters = map[string]bool{
		"label":  true,
		"label!": true,
	}
	imagesAcceptedFilters = map[string]bool{
		"dangling": true,
		"label":    true,
		"label!":   true,
		"until":    true,
	}
	networksAcceptedFilters = map[string]bool{
		"label":  true,
		"label!": true,
		"until":  true,
	}
)

// ContainersPrune removes unused containers
func (daemon *Daemon) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (*types.ContainersPruneReport, error) {
	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
		return nil, errPruneRunning
	}
	defer atomic.StoreInt32(&daemon.pruneRunning, 0)

	rep := &types.ContainersPruneReport{}

	// make sure that only accepted filters have been received
	err := pruneFilters.Validate(containersAcceptedFilters)
	if err != nil {
		return nil, err
	}

	until, err := getUntilFromPruneFilters(pruneFilters)
	if err != nil {
		return nil, err
	}

	allContainers := daemon.List()
	for _, c := range allContainers {
		select {
		case <-ctx.Done():
			logrus.Warnf("ContainersPrune operation cancelled: %#v", *rep)
			return rep, ctx.Err()
		default:
		}

		if !c.IsRunning() {
			if !until.IsZero() && c.Created.After(until) {
				continue
			}
			if !matchLabels(pruneFilters, c.Config.Labels) {
				continue
			}
			cSize, _ := daemon.getSize(c.ID)
			// TODO: sets RmLink to true?
			err := daemon.ContainerRm(c.ID, &types.ContainerRmConfig{})
			if err != nil {
				logrus.Warnf("failed to prune container %s: %v", c.ID, err)
				continue
			}
			if cSize > 0 {
				rep.SpaceReclaimed += uint64(cSize)
			}
			rep.ContainersDeleted = append(rep.ContainersDeleted, c.ID)
		}
	}

	return rep, nil
}

// VolumesPrune removes unused local volumes
func (daemon *Daemon) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (*types.VolumesPruneReport, error) {
	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
		return nil, errPruneRunning
	}
	defer atomic.StoreInt32(&daemon.pruneRunning, 0)

	// make sure that only accepted filters have been received
	err := pruneFilters.Validate(volumesAcceptedFilters)
	if err != nil {
		return nil, err
	}

	rep := &types.VolumesPruneReport{}

	pruneVols := func(v volume.Volume) error {
		select {
		case <-ctx.Done():
			logrus.Warnf("VolumesPrune operation cancelled: %#v", *rep)
			return ctx.Err()
		default:
		}

		name := v.Name()
		refs := daemon.volumes.Refs(v)

		if len(refs) == 0 {
			detailedVolume, ok := v.(volume.DetailedVolume)
			if ok {
				if !matchLabels(pruneFilters, detailedVolume.Labels()) {
					return nil
				}
			}
			vSize, err := directory.Size(v.Path())
			if err != nil {
				logrus.Warnf("could not determine size of volume %s: %v", name, err)
			}
			err = daemon.volumes.Remove(v)
			if err != nil {
				logrus.Warnf("could not remove volume %s: %v", name, err)
				return nil
			}
			rep.SpaceReclaimed += uint64(vSize)
			rep.VolumesDeleted = append(rep.VolumesDeleted, name)
		}

		return nil
	}

	err = daemon.traverseLocalVolumes(pruneVols)

	return rep, err
}

// ImagesPrune removes unused images
func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
		return nil, errPruneRunning
	}
	defer atomic.StoreInt32(&daemon.pruneRunning, 0)

	// make sure that only accepted filters have been received
	err := pruneFilters.Validate(imagesAcceptedFilters)
	if err != nil {
		return nil, err
	}

	rep := &types.ImagesPruneReport{}

	danglingOnly := true
	if pruneFilters.Include("dangling") {
		if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") {
			danglingOnly = false
		} else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") {
			return nil, fmt.Errorf("Invalid filter 'dangling=%s'", pruneFilters.Get("dangling"))
		}
	}

	until, err := getUntilFromPruneFilters(pruneFilters)
	if err != nil {
		return nil, err
	}

	var allImages map[image.ID]*image.Image
	if danglingOnly {
		allImages = daemon.imageStore.Heads()
	} else {
		allImages = daemon.imageStore.Map()
	}
	allContainers := daemon.List()
	imageRefs := map[string]bool{}
	for _, c := range allContainers {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
			imageRefs[c.ID] = true
		}
	}

	// Filter intermediary images and get their unique size
	allLayers := daemon.layerStore.Map()
	topImages := map[image.ID]*image.Image{}
	for id, img := range allImages {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		default:
			dgst := digest.Digest(id)
			if len(daemon.referenceStore.References(dgst)) == 0 && len(daemon.imageStore.Children(id)) != 0 {
				continue
			}
			if !until.IsZero() && img.Created.After(until) {
				continue
			}
			if img.Config != nil && !matchLabels(pruneFilters, img.Config.Labels) {
				continue
			}
			topImages[id] = img
		}
	}

	canceled := false
deleteImagesLoop:
	for id := range topImages {
		select {
		case <-ctx.Done():
			// we still want to calculate freed size and return the data
			canceled = true
			break deleteImagesLoop
		default:
		}

		dgst := digest.Digest(id)
		hex := dgst.Hex()
		if _, ok := imageRefs[hex]; ok {
			continue
		}

		deletedImages := []types.ImageDeleteResponseItem{}
		refs := daemon.referenceStore.References(dgst)
		if len(refs) > 0 {
			shouldDelete := !danglingOnly
			if !shouldDelete {
				hasTag := false
				for _, ref := range refs {
					if _, ok := ref.(reference.NamedTagged); ok {
						hasTag = true
						break
					}
				}

				// Only delete if it's untagged (i.e. repo:<none>)
				shouldDelete = !hasTag
			}

			if shouldDelete {
				for _, ref := range refs {
					imgDel, err := daemon.ImageDelete(ref.String(), false, true)
					if err != nil {
						logrus.Warnf("could not delete reference %s: %v", ref.String(), err)
						continue
					}
					deletedImages = append(deletedImages, imgDel...)
				}
			}
		} else {
			imgDel, err := daemon.ImageDelete(hex, false, true)
			if err != nil {
				logrus.Warnf("could not delete image %s: %v", hex, err)
				continue
			}
			deletedImages = append(deletedImages, imgDel...)
		}

		rep.ImagesDeleted = append(rep.ImagesDeleted, deletedImages...)
	}

	// Compute how much space was freed
	for _, d := range rep.ImagesDeleted {
		if d.Deleted != "" {
			chid := layer.ChainID(d.Deleted)
			if l, ok := allLayers[chid]; ok {
				diffSize, err := l.DiffSize()
				if err != nil {
					logrus.Warnf("failed to get layer %s size: %v", chid, err)
					continue
				}
				rep.SpaceReclaimed += uint64(diffSize)
			}
		}
	}

	if canceled {
		logrus.Warnf("ImagesPrune operation cancelled: %#v", *rep)
		return nil, ctx.Err()
	}

	return rep, nil
}

// localNetworksPrune removes unused local networks
func (daemon *Daemon) localNetworksPrune(ctx context.Context, pruneFilters filters.Args) *types.NetworksPruneReport {
	rep := &types.NetworksPruneReport{}

	until, _ := getUntilFromPruneFilters(pruneFilters)

	// When the function returns true, the walk will stop.
	l := func(nw libnetwork.Network) bool {
		select {
		case <-ctx.Done():
			return true
		default:
		}
		if nw.Info().ConfigOnly() {
			return false
		}
		if !until.IsZero() && nw.Info().Created().After(until) {
			return false
		}
		if !matchLabels(pruneFilters, nw.Info().Labels()) {
			return false
		}
		nwName := nw.Name()
		if runconfig.IsPreDefinedNetwork(nwName) {
			return false
		}
		if len(nw.Endpoints()) > 0 {
			return false
		}
		if err := daemon.DeleteNetwork(nw.ID()); err != nil {
			logrus.Warnf("could not remove local network %s: %v", nwName, err)
			return false
		}
		rep.NetworksDeleted = append(rep.NetworksDeleted, nwName)
		return false
	}
	daemon.netController.WalkNetworks(l)
	return rep
}

// clusterNetworksPrune removes unused cluster networks
func (daemon *Daemon) clusterNetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
	rep := &types.NetworksPruneReport{}

	until, _ := getUntilFromPruneFilters(pruneFilters)

	cluster := daemon.GetCluster()

	if !cluster.IsManager() {
		return rep, nil
	}

	networks, err := cluster.GetNetworks()
	if err != nil {
		return rep, err
	}
	networkIsInUse := regexp.MustCompile(`network ([[:alnum:]]+) is in use`)
	for _, nw := range networks {
		select {
		case <-ctx.Done():
			return rep, ctx.Err()
		default:
			if nw.Ingress {
				// Routing-mesh network removal has to be explicitly invoked by user
				continue
			}
			if !until.IsZero() && nw.Created.After(until) {
				continue
			}
			if !matchLabels(pruneFilters, nw.Labels) {
				continue
			}
			// https://github.com/docker/docker/issues/24186
			// `docker network inspect` unfortunately displays ONLY those containers that are local to that node.
			// So we try to remove it anyway and check the error
			err = cluster.RemoveNetwork(nw.ID)
			if err != nil {
				// we can safely ignore the "network .. is in use" error
				match := networkIsInUse.FindStringSubmatch(err.Error())
				if len(match) != 2 || match[1] != nw.ID {
					logrus.Warnf("could not remove cluster network %s: %v", nw.Name, err)
				}
				continue
			}
			rep.NetworksDeleted = append(rep.NetworksDeleted, nw.Name)
		}
	}
	return rep, nil
}

// NetworksPrune removes unused networks
func (daemon *Daemon) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
	if !atomic.CompareAndSwapInt32(&daemon.pruneRunning, 0, 1) {
		return nil, errPruneRunning
	}
	defer atomic.StoreInt32(&daemon.pruneRunning, 0)

	// make sure that only accepted filters have been received
	err := pruneFilters.Validate(networksAcceptedFilters)
	if err != nil {
		return nil, err
	}

	if _, err := getUntilFromPruneFilters(pruneFilters); err != nil {
		return nil, err
	}

	rep := &types.NetworksPruneReport{}
	if clusterRep, err := daemon.clusterNetworksPrune(ctx, pruneFilters); err == nil {
		rep.NetworksDeleted = append(rep.NetworksDeleted, clusterRep.NetworksDeleted...)
	}

	localRep := daemon.localNetworksPrune(ctx, pruneFilters)
	rep.NetworksDeleted = append(rep.NetworksDeleted, localRep.NetworksDeleted...)

	select {
	case <-ctx.Done():
		logrus.Warnf("NetworksPrune operation cancelled: %#v", *rep)
		return nil, ctx.Err()
	default:
	}

	return rep, nil
}

func getUntilFromPruneFilters(pruneFilters filters.Args) (time.Time, error) {
	until := time.Time{}
	if !pruneFilters.Include("until") {
		return until, nil
	}
	untilFilters := pruneFilters.Get("until")
	if len(untilFilters) > 1 {
		return until, fmt.Errorf("more than one until filter specified")
	}
	ts, err := timetypes.GetTimestamp(untilFilters[0], time.Now())
	if err != nil {
		return until, err
	}
	seconds, nanoseconds, err := timetypes.ParseTimestamps(ts, 0)
	if err != nil {
		return until, err
	}
	until = time.Unix(seconds, nanoseconds)
	return until, nil
}

func matchLabels(pruneFilters filters.Args, labels map[string]string) bool {
	if !pruneFilters.MatchKVList("label", labels) {
		return false
	}
	// By default MatchKVList will return true if field (like 'label!') does not exist
	// So we have to add additional Include("label!") check
	if pruneFilters.Include("label!") {
		if pruneFilters.MatchKVList("label!", labels) {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package dao

import (
	"log"

	models "github.com/abert-on/pettrack-go-api/models"

	"gopkg.in/mgo.v2/bson"

	mgo "gopkg.in/mgo.v2"
)

// PetsDAO represents the database server
type PetsDAO struct {
	Server   string
	Database string
}

var db *mgo.Database

// COLLECTION represents the database collection to use
const (
	COLLECTION = "pets"
)

// Connect establishes a connection to DB
func (p *PetsDAO) Connect() {
	session, err := mgo.Dial(p.Server)
	if err != nil {
		log.Fatal(err)
	}
	db = session.DB(p.Database)
}

// FindAllByUserID gets a list of all pets for a user
func (p *PetsDAO) FindAllByUserID(userID string) ([]models.Pet, error) {
	var pets []models.Pet
	err := db.C(COLLECTION).Find(bson.M{"$eq": bson.M{"userId": userID}}).All(&pets)
	return pets, err
}

// FindByID finds a pet by ID
func (p *PetsDAO) FindByID(id string) (models.Pet, error) {
	var pet models.Pet
	err := db.C(COLLECTION).FindId(bson.ObjectIdHex(id)).One(&pet)
	return pet, err
}

// Insert inserts a pet into the DB
func (p *PetsDAO) Insert(pet models.Pet) error {
	err := db.C(COLLECTION).Insert(&pet)
	return err
}

// Delete deletes an existing pet
func (p *PetsDAO) Delete(pet models.Pet) error {
	err := db.C(COLLECTION).Remove(&pet)
	return err
}

// Update updates an existing pet
func (p *PetsDAO) Update(pet models.Pet) error {
	err := db.C(COLLECTION).UpdateId(pet.ID, &pet)
	return err
}
<commit_msg>Change eq to match<commit_after>package dao

import (
	"log"

	models "github.com/abert-on/pettrack-go-api/models"

	"gopkg.in/mgo.v2/bson"

	mgo "gopkg.in/mgo.v2"
)

// PetsDAO represents the database server
type PetsDAO struct {
	Server   string
	Database string
}

var db *mgo.Database

// COLLECTION represents the database collection to use
const (
	COLLECTION = "pets"
)

// Connect establishes a connection to DB
func (p *PetsDAO) Connect() {
	session, err := mgo.Dial(p.Server)
	if err != nil {
		log.Fatal(err)
	}
	db = session.DB(p.Database)
}

// FindAllByUserID gets a list of all pets for a user
func (p *PetsDAO) FindAllByUserID(userID string) ([]models.Pet, error) {
	var pets []models.Pet
	err := db.C(COLLECTION).Find(bson.M{"$match": bson.M{"userId": userID}}).All(&pets)
	return pets, err
}

// FindByID finds a pet by ID
func (p *PetsDAO) FindByID(id string) (models.Pet, error) {
	var pet models.Pet
	err := db.C(COLLECTION).FindId(bson.ObjectIdHex(id)).One(&pet)
	return pet, err
}

// Insert inserts a pet into the DB
func (p *PetsDAO) Insert(pet models.Pet) error {
	err := db.C(COLLECTION).Insert(&pet)
	return err
}

// Delete deletes an existing pet
func (p *PetsDAO) Delete(pet models.Pet) error {
	err := db.C(COLLECTION).Remove(&pet)
	return err
}

// Update updates an existing pet
func (p *PetsDAO) Update(pet models.Pet) error {
	err := db.C(COLLECTION).UpdateId(pet.ID, &pet)
	return err
}
<|endoftext|>


<commit_before>// Copyright 2016 Mender Software AS
//
//    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 main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"github.com/mendersoftware/deployments/config"
	"github.com/mendersoftware/deployments/integration"
	deploymentsController "github.com/mendersoftware/deployments/resources/deployments/controller"
	"github.com/mendersoftware/deployments/resources/deployments/generator"
	deploymentsModel "github.com/mendersoftware/deployments/resources/deployments/model"
	deploymentsMongo "github.com/mendersoftware/deployments/resources/deployments/mongo"
	deploymentsView "github.com/mendersoftware/deployments/resources/deployments/view"
	imagesController "github.com/mendersoftware/deployments/resources/images/controller"
	imagesModel "github.com/mendersoftware/deployments/resources/images/model"
	imagesMongo "github.com/mendersoftware/deployments/resources/images/mongo"
	"github.com/mendersoftware/deployments/resources/images/s3"
	imagesView "github.com/mendersoftware/deployments/resources/images/view"
	"github.com/mendersoftware/deployments/utils/restutil"
	"github.com/pkg/errors"
	"gopkg.in/mgo.v2"
)

func SetupS3(c config.ConfigReader) (imagesModel.FileStorage, error) {

	bucket := c.GetString(SettingAweS3Bucket)
	region := c.GetString(SettingAwsS3Region)
	if c.IsSet(SettingsAwsAuth) || (c.IsSet(SettingAwsAuthKeyId) && c.IsSet(SettingAwsAuthSecret) && c.IsSet(SettingAwsURI)) {
		return s3.NewSimpleStorageServiceStatic(
			bucket,
			c.GetString(SettingAwsAuthKeyId),
			c.GetString(SettingAwsAuthSecret),
			region,
			c.GetString(SettingAwsAuthToken),
			c.GetString(SettingAwsURI),
		)
	}

	return s3.NewSimpleStorageServiceDefaults(bucket, region)
}

// NewRouter defines all REST API routes.
func NewRouter(c config.ConfigReader) (rest.App, error) {

	dbSession, err := mgo.Dial(c.GetString(SettingMongo))
	if err != nil {
		return nil, err
	}
	dbSession.SetSafe(&mgo.Safe{})

	// Storage Layer
	fileStorage, err := SetupS3(c)
	if err != nil {
		return nil, err
	}
	deploymentsStorage := deploymentsMongo.NewDeploymentsStorage(dbSession)
	deviceDeploymentsStorage := deploymentsMongo.NewDeviceDeploymentsStorage(dbSession)
	deviceDeploymentLogsStorage := deploymentsMongo.NewDeviceDeploymentLogsStorage(dbSession)
	imagesStorage := imagesMongo.NewSoftwareImagesStorage(dbSession)
	if err := imagesStorage.IndexStorage(); err != nil {
		return nil, err
	}

	inventory, err := integration.NewMenderAPI(c.GetString(SettingGateway))
	if err != nil {
		return nil, errors.Wrap(err, "init inventory client")
	}

	// Domain Models
	deploymentModel := deploymentsModel.NewDeploymentModel(deploymentsModel.DeploymentsModelConfig{
		DeploymentsStorage:          deploymentsStorage,
		DeviceDeploymentsStorage:    deviceDeploymentsStorage,
		DeviceDeploymentLogsStorage: deviceDeploymentLogsStorage,
		ImageLinker:                 fileStorage,
		DeviceDeploymentGenerator: generator.NewImageBasedDeviceDeployment(
			imagesStorage,
			generator.NewInventory(inventory),
		),
		ImageContentType: imagesModel.ImageContentType,
	})

	imagesModel := imagesModel.NewImagesModel(fileStorage, deploymentModel, imagesStorage)

	// Controllers
	imagesController := imagesController.NewSoftwareImagesController(imagesModel, new(imagesView.RESTView))
	deploymentsController := deploymentsController.NewDeploymentsController(deploymentModel, new(deploymentsView.DeploymentsView))

	// Routing
	imageRoutes := NewImagesResourceRoutes(imagesController)
	deploymentsRoutes := NewDeploymentsResourceRoutes(deploymentsController)

	routes := append(imageRoutes, deploymentsRoutes...)

	return rest.MakeRouter(restutil.AutogenOptionsRoutes(restutil.NewOptionsHandler, routes...)...)
}

func NewImagesResourceRoutes(controller *imagesController.SoftwareImagesController) []*rest.Route {

	if controller == nil {
		return []*rest.Route{}
	}

	return []*rest.Route{
		rest.Post("/api/0.0.1/images", controller.NewImage),
		rest.Get("/api/0.0.1/images", controller.ListImages),

		rest.Get("/api/0.0.1/images/:id", controller.GetImage),
		rest.Delete("/api/0.0.1/images/:id", controller.DeleteImage),
		rest.Put("/api/0.0.1/images/:id", controller.EditImage),

		rest.Get("/api/0.0.1/images/:id/download", controller.DownloadLink),
	}
}

func NewDeploymentsResourceRoutes(controller *deploymentsController.DeploymentsController) []*rest.Route {

	if controller == nil {
		return []*rest.Route{}
	}

	return []*rest.Route{

		// Deployments
		rest.Post("/api/0.0.1/deployments", controller.PostDeployment),
		rest.Get("/api/0.0.1/deployments", controller.LookupDeployment),
		rest.Get("/api/0.0.1/deployments/:id", controller.GetDeployment),
		rest.Get("/api/0.0.1/deployments/:id/statistics", controller.GetDeploymentStats),
		rest.Put("/api/0.0.1/deployments/:id/status", controller.AbortDeployment),

		// Devices
		rest.Get("/api/0.0.1/device/update", controller.GetDeploymentForDevice),
		rest.Put("/api/0.0.1/device/deployments/:id/status",
			controller.PutDeploymentStatusForDevice),
		rest.Get("/api/0.0.1/deployments/:id/devices",
			controller.GetDeviceStatusesForDeployment),
		rest.Put("/api/0.0.1/device/deployments/:id/log",
			controller.PutDeploymentLogForDevice),
		rest.Get("/api/0.0.1/deployments/:id/devices/:devid/log",
			controller.GetDeploymentLogForDevice),
	}
}
<commit_msg>routing: update routing to mach device API spec<commit_after>// Copyright 2016 Mender Software AS
//
//    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 main

import (
	"github.com/ant0ine/go-json-rest/rest"
	"github.com/mendersoftware/deployments/config"
	"github.com/mendersoftware/deployments/integration"
	deploymentsController "github.com/mendersoftware/deployments/resources/deployments/controller"
	"github.com/mendersoftware/deployments/resources/deployments/generator"
	deploymentsModel "github.com/mendersoftware/deployments/resources/deployments/model"
	deploymentsMongo "github.com/mendersoftware/deployments/resources/deployments/mongo"
	deploymentsView "github.com/mendersoftware/deployments/resources/deployments/view"
	imagesController "github.com/mendersoftware/deployments/resources/images/controller"
	imagesModel "github.com/mendersoftware/deployments/resources/images/model"
	imagesMongo "github.com/mendersoftware/deployments/resources/images/mongo"
	"github.com/mendersoftware/deployments/resources/images/s3"
	imagesView "github.com/mendersoftware/deployments/resources/images/view"
	"github.com/mendersoftware/deployments/utils/restutil"
	"github.com/pkg/errors"
	"gopkg.in/mgo.v2"
)

func SetupS3(c config.ConfigReader) (imagesModel.FileStorage, error) {

	bucket := c.GetString(SettingAweS3Bucket)
	region := c.GetString(SettingAwsS3Region)
	if c.IsSet(SettingsAwsAuth) || (c.IsSet(SettingAwsAuthKeyId) && c.IsSet(SettingAwsAuthSecret) && c.IsSet(SettingAwsURI)) {
		return s3.NewSimpleStorageServiceStatic(
			bucket,
			c.GetString(SettingAwsAuthKeyId),
			c.GetString(SettingAwsAuthSecret),
			region,
			c.GetString(SettingAwsAuthToken),
			c.GetString(SettingAwsURI),
		)
	}

	return s3.NewSimpleStorageServiceDefaults(bucket, region)
}

// NewRouter defines all REST API routes.
func NewRouter(c config.ConfigReader) (rest.App, error) {

	dbSession, err := mgo.Dial(c.GetString(SettingMongo))
	if err != nil {
		return nil, err
	}
	dbSession.SetSafe(&mgo.Safe{})

	// Storage Layer
	fileStorage, err := SetupS3(c)
	if err != nil {
		return nil, err
	}
	deploymentsStorage := deploymentsMongo.NewDeploymentsStorage(dbSession)
	deviceDeploymentsStorage := deploymentsMongo.NewDeviceDeploymentsStorage(dbSession)
	deviceDeploymentLogsStorage := deploymentsMongo.NewDeviceDeploymentLogsStorage(dbSession)
	imagesStorage := imagesMongo.NewSoftwareImagesStorage(dbSession)
	if err := imagesStorage.IndexStorage(); err != nil {
		return nil, err
	}

	inventory, err := integration.NewMenderAPI(c.GetString(SettingGateway))
	if err != nil {
		return nil, errors.Wrap(err, "init inventory client")
	}

	// Domain Models
	deploymentModel := deploymentsModel.NewDeploymentModel(deploymentsModel.DeploymentsModelConfig{
		DeploymentsStorage:          deploymentsStorage,
		DeviceDeploymentsStorage:    deviceDeploymentsStorage,
		DeviceDeploymentLogsStorage: deviceDeploymentLogsStorage,
		ImageLinker:                 fileStorage,
		DeviceDeploymentGenerator: generator.NewImageBasedDeviceDeployment(
			imagesStorage,
			generator.NewInventory(inventory),
		),
		ImageContentType: imagesModel.ImageContentType,
	})

	imagesModel := imagesModel.NewImagesModel(fileStorage, deploymentModel, imagesStorage)

	// Controllers
	imagesController := imagesController.NewSoftwareImagesController(imagesModel, new(imagesView.RESTView))
	deploymentsController := deploymentsController.NewDeploymentsController(deploymentModel, new(deploymentsView.DeploymentsView))

	// Routing
	imageRoutes := NewImagesResourceRoutes(imagesController)
	deploymentsRoutes := NewDeploymentsResourceRoutes(deploymentsController)

	routes := append(imageRoutes, deploymentsRoutes...)

	return rest.MakeRouter(restutil.AutogenOptionsRoutes(restutil.NewOptionsHandler, routes...)...)
}

func NewImagesResourceRoutes(controller *imagesController.SoftwareImagesController) []*rest.Route {

	if controller == nil {
		return []*rest.Route{}
	}

	return []*rest.Route{
		rest.Post("/api/0.0.1/images", controller.NewImage),
		rest.Get("/api/0.0.1/images", controller.ListImages),

		rest.Get("/api/0.0.1/images/:id", controller.GetImage),
		rest.Delete("/api/0.0.1/images/:id", controller.DeleteImage),
		rest.Put("/api/0.0.1/images/:id", controller.EditImage),

		rest.Get("/api/0.0.1/images/:id/download", controller.DownloadLink),
	}
}

func NewDeploymentsResourceRoutes(controller *deploymentsController.DeploymentsController) []*rest.Route {

	if controller == nil {
		return []*rest.Route{}
	}

	return []*rest.Route{

		// Deployments
		rest.Post("/api/0.0.1/deployments", controller.PostDeployment),
		rest.Get("/api/0.0.1/deployments", controller.LookupDeployment),
		rest.Get("/api/0.0.1/deployments/:id", controller.GetDeployment),
		rest.Get("/api/0.0.1/deployments/:id/statistics", controller.GetDeploymentStats),
		rest.Put("/api/0.0.1/deployments/:id/status", controller.AbortDeployment),

		// Devices
		rest.Get("/api/0.0.1/device/deployments/next", controller.GetDeploymentForDevice),
		rest.Put("/api/0.0.1/device/deployments/:id/status",
			controller.PutDeploymentStatusForDevice),
		rest.Get("/api/0.0.1/deployments/:id/devices",
			controller.GetDeviceStatusesForDeployment),
		rest.Put("/api/0.0.1/device/deployments/:id/log",
			controller.PutDeploymentLogForDevice),
		rest.Get("/api/0.0.1/deployments/:id/devices/:devid/log",
			controller.GetDeploymentLogForDevice),
	}
}
<|endoftext|>


<commit_before>package rsa

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha512"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
)

var (
	NotPemEncodeErr   = errors.New("not PEM-encoded")
	UnknownKeyTypeErr = errors.New("Unknown key type error")
	BadPrivateKeyErr  = errors.New("bad private key")
	InvalidPubKeyErr  = errors.New("invalid public key data")
	BadPublicKeyErr   = errors.New("not RSA public key")
)

func decodePrivateKey(key []byte) (*rsa.PrivateKey, error) {
	privateKeyBlock, _ := pem.Decode(key)
	if privateKeyBlock == nil {
		return nil, BadPrivateKeyErr
	}
	if privateKeyBlock.Type != "RSA PRIVATE KEY" {
		return nil, BadPrivateKeyErr
	}

	privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
	if err != nil {
		return nil, err
	}

	return privateKey, err
}

func decodePublicKey(key []byte) (*rsa.PublicKey, error) {
	publicKeyBlock, _ := pem.Decode(key)
	if publicKeyBlock == nil {
		return nil, InvalidPubKeyErr
	}
	if publicKeyBlock.Type != "PUBLIC KEY" {
		return nil, errors.New(fmt.Sprintf("invalid public key type : %s", publicKeyBlock.Type))
	}

	publicKeyInterface, err := x509.ParsePKIXPublicKey(publicKeyBlock.Bytes)
	if err != nil {
		return nil, err
	}

	publicKey, ok := publicKeyInterface.(*rsa.PublicKey)
	if !ok {
		return nil, BadPublicKeyErr
	}

	return publicKey, nil
}

type RSA struct {
	privateKey []byte
	publicKey  []byte
}

func New(pri, pub []byte) *RSA {
	return &RSA{
		privateKey: pri,
		publicKey:  pub,
	}
}

func (r *RSA) Encrypt(p []byte) ([]byte, error) {
	pubkey, err := decodePublicKey(r.publicKey)
	if err != nil {
		return nil, err
	}

	out, err := rsa.EncryptOAEP(sha512.New(), rand.Reader, pubkey, p, []byte(""))
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (r *RSA) Decrypt(c []byte) ([]byte, error) {
	priKey, err := decodePrivateKey(r.privateKey)
	if err != nil {
		return nil, err
	}

	out, err := rsa.DecryptOAEP(sha512.New(), rand.Reader, priKey, c, []byte(""))
	if err != nil {
		return nil, err
	}
	return out, nil
}
<commit_msg>labelを使えるようにする (#9)<commit_after>package rsa

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha512"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
)

var (
	BadPrivateKeyErr = errors.New("bad private key")
	InvalidPubKeyErr = errors.New("invalid public key data")
	BadPublicKeyErr  = errors.New("not RSA public key")
)

func decodePrivateKey(key []byte) (*rsa.PrivateKey, error) {
	privateKeyBlock, _ := pem.Decode(key)
	if privateKeyBlock == nil {
		return nil, BadPrivateKeyErr
	}
	if privateKeyBlock.Type != "RSA PRIVATE KEY" {
		return nil, BadPrivateKeyErr
	}

	privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
	if err != nil {
		return nil, err
	}

	return privateKey, err
}

func decodePublicKey(key []byte) (*rsa.PublicKey, error) {
	publicKeyBlock, _ := pem.Decode(key)
	if publicKeyBlock == nil {
		return nil, InvalidPubKeyErr
	}
	if publicKeyBlock.Type != "PUBLIC KEY" {
		return nil, errors.New(fmt.Sprintf("invalid public key type : %s", publicKeyBlock.Type))
	}

	publicKeyInterface, err := x509.ParsePKIXPublicKey(publicKeyBlock.Bytes)
	if err != nil {
		return nil, err
	}

	publicKey, ok := publicKeyInterface.(*rsa.PublicKey)
	if !ok {
		return nil, BadPublicKeyErr
	}

	return publicKey, nil
}

type RSA struct {
	privateKey []byte
	publicKey  []byte
	label      string
}

func New(pri, pub []byte) *RSA {
	return &RSA{
		privateKey: pri,
		publicKey:  pub,
	}
}

func (r *RSA) SetLabel(l string) *RSA {
	r.label = l
	return r
}

func (r *RSA) Encrypt(p []byte) ([]byte, error) {
	pubkey, err := decodePublicKey(r.publicKey)
	if err != nil {
		return nil, err
	}

	out, err := rsa.EncryptOAEP(sha512.New(), rand.Reader, pubkey, p, []byte(r.label))
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (r *RSA) Decrypt(c []byte) ([]byte, error) {
	priKey, err := decodePrivateKey(r.privateKey)
	if err != nil {
		return nil, err
	}

	out, err := rsa.DecryptOAEP(sha512.New(), rand.Reader, priKey, c, []byte(r.label))
	if err != nil {
		return nil, err
	}
	return out, nil
}
<|endoftext|>


<commit_before>package data

import (
	"time"

	"github.com/asaskevich/govalidator"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type Account struct {
	ID              bson.ObjectId   `bson:"_id"`
	Emails          []AccountEmail  `bson:"emails"`
	Password        AccountPassword `bson:"password"`
	OrganizationIDs []bson.ObjectId `bson:"organization_ids"`

	CreatedAt  time.Time `bson:"created_at"`
	ModifiedAt time.Time `bson:"modified_at"`
}

func GetAccount(id bson.ObjectId) (*Account, error) {
	acc := Account{}

	err := sess.DB("").C(accountC).FindId(id).One(&acc)
	if err == mgo.ErrNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &acc, nil
}

func GetAccountEmail(addr string) (*Account, error) {
	addr, err := govalidator.NormalizeEmail(addr)
	if err != nil {
		return nil, nil
	}
	acc := Account{}
	err = sess.DB("").C(accountC).Find(bson.M{"emails.address_norm": addr}).One(&acc)
	if err == mgo.ErrNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &acc, nil
}

func (a *Account) PrimaryEmail() AccountEmail {
	for _, email := range a.Emails {
		if email.Primary {
			return email
		}
	}
	return AccountEmail{}
}

func (a *Account) Put() error {
	a.ModifiedAt = time.Now()

	if a.ID == "" {
		a.ID = bson.NewObjectId()
		a.CreatedAt = a.ModifiedAt
	}
	_, err := sess.DB("").C(accountC).UpsertId(a.ID, a)
	return err
}
<commit_msg>Implement Organizations method in account<commit_after>package data

import (
	"math"
	"time"

	"github.com/asaskevich/govalidator"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type Account struct {
	ID              bson.ObjectId   `bson:"_id"`
	Emails          []AccountEmail  `bson:"emails"`
	Password        AccountPassword `bson:"password"`
	OrganizationIDs []bson.ObjectId `bson:"organization_ids"`

	CreatedAt  time.Time `bson:"created_at"`
	ModifiedAt time.Time `bson:"modified_at"`
}

func GetAccount(id bson.ObjectId) (*Account, error) {
	acc := Account{}

	err := sess.DB("").C(accountC).FindId(id).One(&acc)
	if err == mgo.ErrNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &acc, nil
}

func GetAccountEmail(addr string) (*Account, error) {
	addr, err := govalidator.NormalizeEmail(addr)
	if err != nil {
		return nil, nil
	}
	acc := Account{}
	err = sess.DB("").C(accountC).Find(bson.M{"emails.address_norm": addr}).One(&acc)
	if err == mgo.ErrNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	return &acc, nil
}

func (a *Account) Organizations() ([]Organization, error) {
	orgs, err := ListOraganizationsOwner(a.ID, 0, math.MaxInt32)
	if err != nil {
		return nil, err
	}
	return orgs, nil
}

func (a *Account) PrimaryEmail() AccountEmail {
	for _, email := range a.Emails {
		if email.Primary {
			return email
		}
	}
	return AccountEmail{}
}

func (a *Account) Put() error {
	a.ModifiedAt = time.Now()

	if a.ID == "" {
		a.ID = bson.NewObjectId()
		a.CreatedAt = a.ModifiedAt
	}
	_, err := sess.DB("").C(accountC).UpsertId(a.ID, a)
	return err
}
<|endoftext|>


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

package gitlab

import (
	"fmt"
	"time"
)

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

// Runner represents a GitLab CI Runner.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
type Runner struct {
	ID          int    `json:"id"`
	Description string `json:"description"`
	Active      bool   `json:"active"`
	IsShared    bool   `json:"is_shared"`
	IPAddress   string `json:"ip_address"`
	Name        string `json:"name"`
	Online      bool   `json:"online"`
	Status      string `json:"status"`
	Token       string `json:"token"`
}

// RunnerDetails represents the GitLab CI runner details.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
type RunnerDetails struct {
	Active       bool       `json:"active"`
	Architecture string     `json:"architecture"`
	Description  string     `json:"description"`
	ID           int        `json:"id"`
	IPAddress    string     `json:"ip_address"`
	IsShared     bool       `json:"is_shared"`
	ContactedAt  *time.Time `json:"contacted_at"`
	Name         string     `json:"name"`
	Online       bool       `json:"online"`
	Status       string     `json:"status"`
	Platform     string     `json:"platform"`
	Projects     []struct {
		ID                int    `json:"id"`
		Name              string `json:"name"`
		NameWithNamespace string `json:"name_with_namespace"`
		Path              string `json:"path"`
		PathWithNamespace string `json:"path_with_namespace"`
	} `json:"projects"`
	Token          string   `json:"token"`
	Revision       string   `json:"revision"`
	TagList        []string `json:"tag_list"`
	Version        string   `json:"version"`
	AccessLevel    string   `json:"access_level"`
	MaximumTimeout int      `json:"maximum_timeout"`
}

// ListRunnersOptions represents the available ListRunners() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-owned-runners
type ListRunnersOptions struct {
	ListOptions
	Scope  *string `url:"scope,omitempty" json:"scope,omitempty"`
	Status *string `url:"status,omitempty" json:"status,omitempty"`
	Type   *string `url:"type,omitempty" json:"type,omitempty"`
}

// ListRunners gets a list of runners accessible by the authenticated user.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-owned-runners
func (s *RunnersService) ListRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	req, err := s.client.NewRequest("GET", "runners", opt, options)
	if err != nil {
		return nil, nil, err
	}

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

	return rs, resp, err
}

// ListAllRunners gets a list of all runners in the GitLab instance. Access is
// restricted to users with admin privileges.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-all-runners
func (s *RunnersService) ListAllRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	req, err := s.client.NewRequest("GET", "runners/all", opt, options)
	if err != nil {
		return nil, nil, err
	}

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

	return rs, resp, err
}

// GetRunnerDetails returns details for given runner.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#get-runner-39-s-details
func (s *RunnersService) GetRunnerDetails(rid interface{}, options ...OptionFunc) (*RunnerDetails, *Response, error) {
	runner, err := parseID(rid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("runners/%s", runner)

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

	var rs *RunnerDetails
	resp, err := s.client.Do(req, &rs)
	if err != nil {
		return nil, resp, err
	}

	return rs, resp, err
}

// UpdateRunnerDetailsOptions represents the available UpdateRunnerDetails() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#update-runner-39-s-details
type UpdateRunnerDetailsOptions struct {
	Description    *string  `url:"description,omitempty" json:"description,omitempty"`
	Active         *bool    `url:"active,omitempty" json:"active,omitempty"`
	TagList        []string `url:"tag_list[],omitempty" json:"tag_list,omitempty"`
	RunUntagged    *bool    `url:"run_untagged,omitempty" json:"run_untagged,omitempty"`
	Locked         *bool    `url:"locked,omitempty" json:"locked,omitempty"`
	AccessLevel    *string  `url:"access_level,omitempty" json:"access_level,omitempty"`
	MaximumTimeout *int     `url:"maximum_timeout,omitempty" json:"maximum_timeout,omitempty"`
}

// UpdateRunnerDetails updates details for a given runner.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#update-runner-39-s-details
func (s *RunnersService) UpdateRunnerDetails(rid interface{}, opt *UpdateRunnerDetailsOptions, options ...OptionFunc) (*RunnerDetails, *Response, error) {
	runner, err := parseID(rid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("runners/%s", runner)

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

	var rs *RunnerDetails
	resp, err := s.client.Do(req, &rs)
	if err != nil {
		return nil, resp, err
	}

	return rs, resp, err
}

// RemoveRunner removes a runner.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#remove-a-runner
func (s *RunnersService) RemoveRunner(rid interface{}, options ...OptionFunc) (*Response, error) {
	runner, err := parseID(rid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("runners/%s", runner)

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

	return s.client.Do(req, nil)
}

// ListRunnerJobsOptions represents the available ListRunnerJobs()
// options. Status can be one of: running, success, failed, canceled.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-runner-39-s-jobs
type ListRunnerJobsOptions struct {
	ListOptions
	Status *string `url:"status,omitempty" json:"status,omitempty"`
}

// ListRunnerJobs gets a list of jobs that are being processed or were processed by specified Runner.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-runner-39-s-jobs
func (s *RunnersService) ListRunnerJobs(rid interface{}, opt *ListRunnerJobsOptions, options ...OptionFunc) ([]*Job, *Response, error) {
	runner, err := parseID(rid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("runners/%s/jobs", runner)

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

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

	return rs, resp, err
}

// ListProjectRunnersOptions represents the available ListProjectRunners()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-project-s-runners
type ListProjectRunnersOptions ListRunnersOptions

// ListProjectRunners gets a list of runners accessible by the authenticated user.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-project-s-runners
func (s *RunnersService) ListProjectRunners(pid interface{}, opt *ListProjectRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/runners", pathEscape(project))

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

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

	return rs, resp, err
}

// EnableProjectRunnerOptions represents the available EnableProjectRunner()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project
type EnableProjectRunnerOptions struct {
	RunnerID int `json:"runner_id"`
}

// EnableProjectRunner enables an available specific runner in the project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project
func (s *RunnersService) EnableProjectRunner(pid interface{}, opt *EnableProjectRunnerOptions, options ...OptionFunc) (*Runner, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/runners", pathEscape(project))

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

	var r *Runner
	resp, err := s.client.Do(req, &r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, err
}

// DisableProjectRunner disables a specific runner from project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#disable-a-runner-from-project
func (s *RunnersService) DisableProjectRunner(pid interface{}, runner int, options ...OptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/runners/%d", pathEscape(project), runner)

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

	return s.client.Do(req, nil)
}

// RegisterNewRunnerOptions represents the available RegisterNewRunner()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#register-a-new-runner
type RegisterNewRunnerOptions struct {
	Token          *string  `url:"token" json:"token"`
	Description    *string  `url:"description,omitempty" json:"description,omitempty"`
	Info           *string  `url:"info,omitempty" json:"info,omitempty"`
	Active         *bool    `url:"active,omitempty" json:"active,omitempty"`
	Locked         *bool    `url:"locked,omitempty" json:"locked,omitempty"`
	RunUntagged    *bool    `url:"run_untagged,omitempty" json:"run_untagged,omitempty"`
	TagList        []string `url:"tag_list[],omitempty" json:"tag_list,omitempty"`
	MaximumTimeout *int     `url:"maximum_timeout,omitempty" json:"maximum_timeout,omitempty"`
}

// RegisterNewRunner registers a new Runner for the instance.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#register-a-new-runner
func (s *RunnersService) RegisterNewRunner(opt *RegisterNewRunnerOptions, options ...OptionFunc) (*Runner, *Response, error) {
	req, err := s.client.NewRequest("POST", "runners", opt, options)
	if err != nil {
		return nil, nil, err
	}

	var r *Runner
	resp, err := s.client.Do(req, &r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, err
}

// DeleteRegisteredRunnerOptions represents the available
// DeleteRegisteredRunner() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#delete-a-registered-runner
type DeleteRegisteredRunnerOptions struct {
	Token *string `url:"token" json:"token"`
}

// DeleteRegisteredRunner registers a new Runner for the instance.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#delete-a-registered-runner
func (s *RunnersService) DeleteRegisteredRunner(opt *DeleteRegisteredRunnerOptions, options ...OptionFunc) (*Response, error) {
	req, err := s.client.NewRequest("DELETE", "runners", opt, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// VerifyRegisteredRunnerOptions represents the available
// VerifyRegisteredRunner() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#verify-authentication-for-a-registered-runner
type VerifyRegisteredRunnerOptions struct {
	Token *string `url:"token" json:"token"`
}

// VerifyRegisteredRunner registers a new Runner for the instance.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#verify-authentication-for-a-registered-runner
func (s *RunnersService) VerifyRegisteredRunner(opt *VerifyRegisteredRunnerOptions, options ...OptionFunc) (*Response, error) {
	req, err := s.client.NewRequest("POST", "runners/verify", opt, options)
	if err != nil {
		return nil, err
	}

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

package gitlab

import (
	"fmt"
	"time"
)

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

// Runner represents a GitLab CI Runner.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
type Runner struct {
	ID          int    `json:"id"`
	Description string `json:"description"`
	Active      bool   `json:"active"`
	IsShared    bool   `json:"is_shared"`
	IPAddress   string `json:"ip_address"`
	Name        string `json:"name"`
	Online      bool   `json:"online"`
	Status      string `json:"status"`
	Token       string `json:"token"`
}

// RunnerDetails represents the GitLab CI runner details.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/runners.html
type RunnerDetails struct {
	Active       bool       `json:"active"`
	Architecture string     `json:"architecture"`
	Description  string     `json:"description"`
	ID           int        `json:"id"`
	IPAddress    string     `json:"ip_address"`
	IsShared     bool       `json:"is_shared"`
	ContactedAt  *time.Time `json:"contacted_at"`
	Name         string     `json:"name"`
	Online       bool       `json:"online"`
	Status       string     `json:"status"`
	Platform     string     `json:"platform"`
	Projects     []struct {
		ID                int    `json:"id"`
		Name              string `json:"name"`
		NameWithNamespace string `json:"name_with_namespace"`
		Path              string `json:"path"`
		PathWithNamespace string `json:"path_with_namespace"`
	} `json:"projects"`
	Token          string   `json:"token"`
	Revision       string   `json:"revision"`
	TagList        []string `json:"tag_list"`
	Version        string   `json:"version"`
	AccessLevel    string   `json:"access_level"`
	MaximumTimeout int      `json:"maximum_timeout"`
}

// TagList is a custom type with specific marshaling characteristics.
type TagList []string

// MarshalJSON implements the json.Marshaler interface.
func (l *TagList) MarshalJSON() ([]byte, error) {
	return json.Marshal(strings.Join(*l, ","))
}

// ListRunnersOptions represents the available ListRunners() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-owned-runners
type ListRunnersOptions struct {
	ListOptions
	Scope   *string `url:"scope,omitempty" json:"scope,omitempty"`
	Status  *string `url:"status,omitempty" json:"status,omitempty"`
	TagList TagList `url:"tag_list,comma,omitempty" json:"type,omitempty"`
	Type    *string `url:"type,omitempty" json:"type,omitempty"`
}

// ListRunners gets a list of runners accessible by the authenticated user.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-owned-runners
func (s *RunnersService) ListRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	req, err := s.client.NewRequest("GET", "runners", opt, options)
	if err != nil {
		return nil, nil, err
	}

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

	return rs, resp, err
}

// ListAllRunners gets a list of all runners in the GitLab instance. Access is
// restricted to users with admin privileges.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-all-runners
func (s *RunnersService) ListAllRunners(opt *ListRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	req, err := s.client.NewRequest("GET", "runners/all", opt, options)
	if err != nil {
		return nil, nil, err
	}

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

	return rs, resp, err
}

// GetRunnerDetails returns details for given runner.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#get-runner-39-s-details
func (s *RunnersService) GetRunnerDetails(rid interface{}, options ...OptionFunc) (*RunnerDetails, *Response, error) {
	runner, err := parseID(rid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("runners/%s", runner)

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

	var rs *RunnerDetails
	resp, err := s.client.Do(req, &rs)
	if err != nil {
		return nil, resp, err
	}

	return rs, resp, err
}

// UpdateRunnerDetailsOptions represents the available UpdateRunnerDetails() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#update-runner-39-s-details
type UpdateRunnerDetailsOptions struct {
	Description    *string  `url:"description,omitempty" json:"description,omitempty"`
	Active         *bool    `url:"active,omitempty" json:"active,omitempty"`
	TagList        []string `url:"tag_list[],omitempty" json:"tag_list,omitempty"`
	RunUntagged    *bool    `url:"run_untagged,omitempty" json:"run_untagged,omitempty"`
	Locked         *bool    `url:"locked,omitempty" json:"locked,omitempty"`
	AccessLevel    *string  `url:"access_level,omitempty" json:"access_level,omitempty"`
	MaximumTimeout *int     `url:"maximum_timeout,omitempty" json:"maximum_timeout,omitempty"`
}

// UpdateRunnerDetails updates details for a given runner.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#update-runner-39-s-details
func (s *RunnersService) UpdateRunnerDetails(rid interface{}, opt *UpdateRunnerDetailsOptions, options ...OptionFunc) (*RunnerDetails, *Response, error) {
	runner, err := parseID(rid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("runners/%s", runner)

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

	var rs *RunnerDetails
	resp, err := s.client.Do(req, &rs)
	if err != nil {
		return nil, resp, err
	}

	return rs, resp, err
}

// RemoveRunner removes a runner.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#remove-a-runner
func (s *RunnersService) RemoveRunner(rid interface{}, options ...OptionFunc) (*Response, error) {
	runner, err := parseID(rid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("runners/%s", runner)

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

	return s.client.Do(req, nil)
}

// ListRunnerJobsOptions represents the available ListRunnerJobs()
// options. Status can be one of: running, success, failed, canceled.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-runner-39-s-jobs
type ListRunnerJobsOptions struct {
	ListOptions
	Status *string `url:"status,omitempty" json:"status,omitempty"`
}

// ListRunnerJobs gets a list of jobs that are being processed or were processed by specified Runner.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-runner-39-s-jobs
func (s *RunnersService) ListRunnerJobs(rid interface{}, opt *ListRunnerJobsOptions, options ...OptionFunc) ([]*Job, *Response, error) {
	runner, err := parseID(rid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("runners/%s/jobs", runner)

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

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

	return rs, resp, err
}

// ListProjectRunnersOptions represents the available ListProjectRunners()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-project-s-runners
type ListProjectRunnersOptions ListRunnersOptions

// ListProjectRunners gets a list of runners accessible by the authenticated user.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#list-project-s-runners
func (s *RunnersService) ListProjectRunners(pid interface{}, opt *ListProjectRunnersOptions, options ...OptionFunc) ([]*Runner, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/runners", pathEscape(project))

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

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

	return rs, resp, err
}

// EnableProjectRunnerOptions represents the available EnableProjectRunner()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project
type EnableProjectRunnerOptions struct {
	RunnerID int `json:"runner_id"`
}

// EnableProjectRunner enables an available specific runner in the project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#enable-a-runner-in-project
func (s *RunnersService) EnableProjectRunner(pid interface{}, opt *EnableProjectRunnerOptions, options ...OptionFunc) (*Runner, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/runners", pathEscape(project))

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

	var r *Runner
	resp, err := s.client.Do(req, &r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, err
}

// DisableProjectRunner disables a specific runner from project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#disable-a-runner-from-project
func (s *RunnersService) DisableProjectRunner(pid interface{}, runner int, options ...OptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/runners/%d", pathEscape(project), runner)

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

	return s.client.Do(req, nil)
}

// RegisterNewRunnerOptions represents the available RegisterNewRunner()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#register-a-new-runner
type RegisterNewRunnerOptions struct {
	Token          *string  `url:"token" json:"token"`
	Description    *string  `url:"description,omitempty" json:"description,omitempty"`
	Info           *string  `url:"info,omitempty" json:"info,omitempty"`
	Active         *bool    `url:"active,omitempty" json:"active,omitempty"`
	Locked         *bool    `url:"locked,omitempty" json:"locked,omitempty"`
	RunUntagged    *bool    `url:"run_untagged,omitempty" json:"run_untagged,omitempty"`
	TagList        []string `url:"tag_list[],omitempty" json:"tag_list,omitempty"`
	MaximumTimeout *int     `url:"maximum_timeout,omitempty" json:"maximum_timeout,omitempty"`
}

// RegisterNewRunner registers a new Runner for the instance.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#register-a-new-runner
func (s *RunnersService) RegisterNewRunner(opt *RegisterNewRunnerOptions, options ...OptionFunc) (*Runner, *Response, error) {
	req, err := s.client.NewRequest("POST", "runners", opt, options)
	if err != nil {
		return nil, nil, err
	}

	var r *Runner
	resp, err := s.client.Do(req, &r)
	if err != nil {
		return nil, resp, err
	}

	return r, resp, err
}

// DeleteRegisteredRunnerOptions represents the available
// DeleteRegisteredRunner() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#delete-a-registered-runner
type DeleteRegisteredRunnerOptions struct {
	Token *string `url:"token" json:"token"`
}

// DeleteRegisteredRunner registers a new Runner for the instance.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#delete-a-registered-runner
func (s *RunnersService) DeleteRegisteredRunner(opt *DeleteRegisteredRunnerOptions, options ...OptionFunc) (*Response, error) {
	req, err := s.client.NewRequest("DELETE", "runners", opt, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}

// VerifyRegisteredRunnerOptions represents the available
// VerifyRegisteredRunner() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#verify-authentication-for-a-registered-runner
type VerifyRegisteredRunnerOptions struct {
	Token *string `url:"token" json:"token"`
}

// VerifyRegisteredRunner registers a new Runner for the instance.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/runners.html#verify-authentication-for-a-registered-runner
func (s *RunnersService) VerifyRegisteredRunner(opt *VerifyRegisteredRunnerOptions, options ...OptionFunc) (*Response, error) {
	req, err := s.client.NewRequest("POST", "runners/verify", opt, options)
	if err != nil {
		return nil, err
	}

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


<commit_before>// Copyright (c) 2014 The SkyDNS Authors. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package main

type Service struct {
	// This *is* the rdata from a SRV record, but with a twist.
	// Host (Target in SRV) must be a domain name, but if it looks like an IP
	// address (4/6), we will treat it like an IP address.

	Priority int
	//	Weight   int // Don't let the API set weights, we will do this automatically.
	Port int
	Host string

	ttl uint32
	key string
}
<commit_msg>Add json tags to service<commit_after>// Copyright (c) 2014 The SkyDNS Authors. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package main

// This *is* the rdata from a SRV record, but with a twist.
// Host (Target in SRV) must be a domain name, but if it looks like an IP
// address (4/6), we will treat it like an IP address.
type Service struct {
	Host     string `json:"host,omitempty"`
	Port     int    `json:"port,omitempty"`
	Priority int    `json:"priority,omitempty"`

	ttl uint32
	key string
}
<|endoftext|>


<commit_before>package luddite

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/http/pprof"
	"os"
	"os/signal"
	"path"
	"strings"
	"syscall"

	log "github.com/SpirentOrion/logrus"
	"github.com/julienschmidt/httprouter"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	DefaultMetricsURIPath  = "/metrics"
	DefaultProfilerURIPath = "/debug/pprof"
)

// Service is an interface that implements a standalone RESTful web service.
type Service interface {
	// AddHandler adds a context-aware middleware handler to the
	// middleware stack. All handlers must be added before Run is
	// called.
	AddHandler(h Handler)

	// AddSingletonResource registers a singleton-style resource
	// (supporting GET and PUT methods only).
	AddSingletonResource(itemPath string, r Resource)

	// AddCollectionResource registers a collection-style resource
	// (supporting GET, POST, PUT, and DELETE methods).
	AddCollectionResource(basePath string, r Resource)

	// Config returns the service's ServiceConfig instance.
	Config() *ServiceConfig

	// Logger returns the service's log.Logger instance.
	Logger() *log.Logger

	// Router returns the service's httprouter.Router instance.
	Router() *httprouter.Router

	// Run is a convenience function that runs the service as an
	// HTTP server. The address is taken from the ServiceConfig
	// passed to NewService.
	Run() error
}

type service struct {
	config        *ServiceConfig
	defaultLogger *log.Logger
	accessLogger  *log.Logger
	router        *httprouter.Router
	handlers      []Handler
	middleware    *middleware
	schema        *SchemaHandler
}

func NewService(config *ServiceConfig) (Service, error) {
	var err error

	// Create the service
	s := &service{
		config: config,
		router: httprouter.New(),
	}

	s.defaultLogger = log.New()
	s.defaultLogger.SetFormatter(&log.JSONFormatter{})
	if config.Log.ServiceLogPath != "" {
		openLogFile(s.defaultLogger, config.Log.ServiceLogPath)
	} else {
		s.defaultLogger.SetOutput(os.Stdout)
	}

	switch strings.ToLower(config.Log.ServiceLogLevel) {
	case "debug":
		s.defaultLogger.SetLevel(log.DebugLevel)
	default:
		fallthrough
	case "info":
		s.defaultLogger.SetLevel(log.InfoLevel)
	case "warn":
		s.defaultLogger.SetLevel(log.WarnLevel)
	case "error":
		s.defaultLogger.SetLevel(log.ErrorLevel)
	}

	s.accessLogger = log.New()
	s.accessLogger.SetFormatter(&log.JSONFormatter{})
	if config.Log.AccessLogPath != "" {
		openLogFile(s.accessLogger, config.Log.AccessLogPath)
	} else {
		s.accessLogger.SetOutput(os.Stdout)
		s.accessLogger.SetLevel(log.DebugLevel)
	}

	s.router.NotFound = http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
		rw.WriteHeader(http.StatusNotFound)
	})

	s.router.MethodNotAllowed = http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
		rw.WriteHeader(http.StatusMethodNotAllowed)
	})

	// Create default middleware handlers
	bottom, err := s.newBottomHandler()
	if err != nil {
		return nil, err
	}

	negotiator, err := s.newNegotiatorHandler()
	if err != nil {
		return nil, err
	}

	version, err := s.newVersionHandler()
	if err != nil {
		return nil, err
	}

	// Build middleware stack
	s.handlers = []Handler{bottom, negotiator, version}
	s.middleware = buildMiddleware(s.handlers)

	// Install default http handlers
	if s.config.Metrics.Enabled {
		s.addMetricsRoute()
	}
	if s.config.Profiler.Enabled {
		s.addProfilerRoutes()
	}
	if config.Schema.Enabled {
		s.addSchemaRoutes()
	}

	// Dump goroutine stacks on demand
	dumpGoroutineStacks(s.defaultLogger)
	return s, nil
}

func (s *service) AddHandler(h Handler) {
	s.handlers = append(s.handlers, h)
	s.middleware = buildMiddleware(s.handlers)
}

func (s *service) AddSingletonResource(basePath string, r Resource) {
	// GET /basePath
	AddGetRoute(s.router, basePath, false, r)

	// PUT /basePath
	AddUpdateRoute(s.router, basePath, false, r)

	// POST /basePath/{action}
	AddActionRoute(s.router, basePath, false, r)
}

func (s *service) AddCollectionResource(basePath string, r Resource) {
	// GET /basePath
	AddListRoute(s.router, basePath, r)

	// GET /basePath/{id}
	AddGetRoute(s.router, basePath, true, r)

	// POST /basePath
	AddCreateRoute(s.router, basePath, r)

	// PUT /basePath/{id}
	AddUpdateRoute(s.router, basePath, true, r)

	// DELETE /basePath
	AddDeleteRoute(s.router, basePath, false, r)

	// DELETE /basePath/{id}
	AddDeleteRoute(s.router, basePath, true, r)

	// POST /basePath/{id}/{action}
	AddActionRoute(s.router, basePath, true, r)
}

func (s *service) Config() *ServiceConfig {
	return s.config
}

func (s *service) Logger() *log.Logger {
	return s.defaultLogger
}

func (s *service) Router() *httprouter.Router {
	return s.router
}

func (s *service) Run() error {
	// Add the router as the final middleware handler
	h, err := s.newRouterHandler()
	if err != nil {
		return err
	}
	s.AddHandler(h)

	var middleware http.Handler = s.middleware
	if s.config.Metrics.Enabled {
		middleware = prometheus.InstrumentHandler("service", middleware)
	}

	// Serve HTTP or HTTPS, depending on config. Use stoppable listener
	// so we can exit gracefully if signaled to do so.
	var stoppableListener net.Listener
	if s.config.Transport.TLS {
		s.defaultLogger.Debugf("HTTPS listening on %s", s.config.Addr)
		stoppableListener, err = NewStoppableTLSListener(s.config.Addr, true, s.config.Transport.CertFilePath, s.config.Transport.KeyFilePath)
	} else {
		s.defaultLogger.Debugf("HTTP listening on %s", s.config.Addr)
		stoppableListener, err = NewStoppableTCPListener(s.config.Addr, true)
	}
	if err != nil {
		return err
	}

	if err = http.Serve(stoppableListener, middleware); err != nil {
		if _, ok := err.(*ListenerStoppedError); !ok {
			return err
		}
	}
	return nil
}

func (s *service) newBottomHandler() (Handler, error) {
	return NewBottom(s, s.defaultLogger, s.accessLogger), nil
}

func (s *service) newNegotiatorHandler() (Handler, error) {
	return NewNegotiator([]string{
		ContentTypeJson,
		ContentTypeCss,
		ContentTypePlain,
		ContentTypeXml,
		ContentTypeHtml,
		ContentTypeOctetStream},
	), nil
}

func (s *service) newVersionHandler() (Handler, error) {
	if s.config.Version.Min < 1 {
		return nil, errors.New("service's minimum API version must be greater than zero")
	}
	if s.config.Version.Max < 1 {
		return nil, errors.New("service's maximum API version must be greater than zero")
	}

	return NewVersion(s.config.Version.Min, s.config.Version.Max), nil
}

func (s *service) newRouterHandler() (Handler, error) {
	// No more middleware handlers: remaining dispatch happens via httprouter
	return WrapHttpHandler(s.router), nil
}

func (s *service) addMetricsRoute() {
	uriPath := s.config.Metrics.UriPath
	if uriPath == "" {
		uriPath = DefaultMetricsURIPath
	}

	h := prometheus.UninstrumentedHandler()
	s.router.GET(uriPath, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { h.ServeHTTP(rw, req) })
}

func (s *service) addProfilerRoutes() {
	uriPath := s.config.Profiler.UriPath
	if uriPath == "" {
		uriPath = DefaultProfilerURIPath
	}

	s.router.GET(path.Join(uriPath, "/"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Index(rw, req) })
	s.router.GET(path.Join(uriPath, "/cmdline"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Cmdline(rw, req) })
	s.router.GET(path.Join(uriPath, "/profile"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Profile(rw, req) })
	s.router.POST(path.Join(uriPath, "/profile"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Profile(rw, req) })
	s.router.GET(path.Join(uriPath, "/symbol"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Symbol(rw, req) })
	s.router.POST(path.Join(uriPath, "/symbol"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Symbol(rw, req) })
	s.router.GET(path.Join(uriPath, "/trace"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Trace(rw, req) })
	s.router.POST(path.Join(uriPath, "/trace"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Trace(rw, req) })
}

func (s *service) addSchemaRoutes() {
	config := s.config

	// Serve the various schemas, e.g. /schema/v1, /schema/v2, etc.
	s.schema = NewSchemaHandler(config.Schema.FilePath)

	s.router.GET(path.Join(config.Schema.UriPath, "/v:version/", "*filepath"), s.schema.ServeHTTP)

	// Temporarily redirect (307) the base schema path to the default schema file, e.g. /schema -> /schema/v2/fileName
	defaultSchemaPath := path.Join(config.Schema.UriPath, fmt.Sprintf("v%d", config.Version.Max), config.Schema.FileName)

	s.router.GET(config.Schema.UriPath, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
		http.Redirect(rw, req, defaultSchemaPath, http.StatusTemporaryRedirect)
	})

	// Temporarily redirect (307) the version schema path to the default schema file, e.g. /schema/v2 -> /schema/v2/fileName
	s.router.GET(path.Join(config.Schema.UriPath, "/v:version/"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
		http.Redirect(rw, req, defaultSchemaPath, http.StatusTemporaryRedirect)
	})

	// Optionally temporarily redirect (307) the root to the base schema path, e.g. / -> /schema
	if config.Schema.RootRedirect {
		s.router.GET("/", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
			http.Redirect(rw, req, config.Schema.UriPath, http.StatusTemporaryRedirect)
		})
	}
}

func openLogFile(logger *log.Logger, logPath string) {
	sigs := make(chan os.Signal, 1)
	logging := make(chan bool, 1)

	go func() {
		var curLog, priorLog *os.File
		for {
			// Open and begin using a new log file
			curLog, _ = os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
			logger.SetOutput(curLog)

			if priorLog == nil {
				// First log, signal the outer goroutine that we're running
				logging <- true
			} else {
				// Follow-on log, close the prior log file
				priorLog.Close()
				priorLog = nil
			}

			// Wait for a SIGHUP
			<-sigs

			// Setup for the next iteration
			priorLog = curLog
		}
	}()

	signal.Notify(sigs, syscall.SIGHUP)
	<-logging
}
<commit_msg>Add image/gif and image/png to content negotiation<commit_after>package luddite

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/http/pprof"
	"os"
	"os/signal"
	"path"
	"strings"
	"syscall"

	log "github.com/SpirentOrion/logrus"
	"github.com/julienschmidt/httprouter"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	DefaultMetricsURIPath  = "/metrics"
	DefaultProfilerURIPath = "/debug/pprof"
)

// Service is an interface that implements a standalone RESTful web service.
type Service interface {
	// AddHandler adds a context-aware middleware handler to the
	// middleware stack. All handlers must be added before Run is
	// called.
	AddHandler(h Handler)

	// AddSingletonResource registers a singleton-style resource
	// (supporting GET and PUT methods only).
	AddSingletonResource(itemPath string, r Resource)

	// AddCollectionResource registers a collection-style resource
	// (supporting GET, POST, PUT, and DELETE methods).
	AddCollectionResource(basePath string, r Resource)

	// Config returns the service's ServiceConfig instance.
	Config() *ServiceConfig

	// Logger returns the service's log.Logger instance.
	Logger() *log.Logger

	// Router returns the service's httprouter.Router instance.
	Router() *httprouter.Router

	// Run is a convenience function that runs the service as an
	// HTTP server. The address is taken from the ServiceConfig
	// passed to NewService.
	Run() error
}

type service struct {
	config        *ServiceConfig
	defaultLogger *log.Logger
	accessLogger  *log.Logger
	router        *httprouter.Router
	handlers      []Handler
	middleware    *middleware
	schema        *SchemaHandler
}

func NewService(config *ServiceConfig) (Service, error) {
	var err error

	// Create the service
	s := &service{
		config: config,
		router: httprouter.New(),
	}

	s.defaultLogger = log.New()
	s.defaultLogger.SetFormatter(&log.JSONFormatter{})
	if config.Log.ServiceLogPath != "" {
		openLogFile(s.defaultLogger, config.Log.ServiceLogPath)
	} else {
		s.defaultLogger.SetOutput(os.Stdout)
	}

	switch strings.ToLower(config.Log.ServiceLogLevel) {
	case "debug":
		s.defaultLogger.SetLevel(log.DebugLevel)
	default:
		fallthrough
	case "info":
		s.defaultLogger.SetLevel(log.InfoLevel)
	case "warn":
		s.defaultLogger.SetLevel(log.WarnLevel)
	case "error":
		s.defaultLogger.SetLevel(log.ErrorLevel)
	}

	s.accessLogger = log.New()
	s.accessLogger.SetFormatter(&log.JSONFormatter{})
	if config.Log.AccessLogPath != "" {
		openLogFile(s.accessLogger, config.Log.AccessLogPath)
	} else {
		s.accessLogger.SetOutput(os.Stdout)
		s.accessLogger.SetLevel(log.DebugLevel)
	}

	s.router.NotFound = http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
		rw.WriteHeader(http.StatusNotFound)
	})

	s.router.MethodNotAllowed = http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
		rw.WriteHeader(http.StatusMethodNotAllowed)
	})

	// Create default middleware handlers
	bottom, err := s.newBottomHandler()
	if err != nil {
		return nil, err
	}

	negotiator, err := s.newNegotiatorHandler()
	if err != nil {
		return nil, err
	}

	version, err := s.newVersionHandler()
	if err != nil {
		return nil, err
	}

	// Build middleware stack
	s.handlers = []Handler{bottom, negotiator, version}
	s.middleware = buildMiddleware(s.handlers)

	// Install default http handlers
	if s.config.Metrics.Enabled {
		s.addMetricsRoute()
	}
	if s.config.Profiler.Enabled {
		s.addProfilerRoutes()
	}
	if config.Schema.Enabled {
		s.addSchemaRoutes()
	}

	// Dump goroutine stacks on demand
	dumpGoroutineStacks(s.defaultLogger)
	return s, nil
}

func (s *service) AddHandler(h Handler) {
	s.handlers = append(s.handlers, h)
	s.middleware = buildMiddleware(s.handlers)
}

func (s *service) AddSingletonResource(basePath string, r Resource) {
	// GET /basePath
	AddGetRoute(s.router, basePath, false, r)

	// PUT /basePath
	AddUpdateRoute(s.router, basePath, false, r)

	// POST /basePath/{action}
	AddActionRoute(s.router, basePath, false, r)
}

func (s *service) AddCollectionResource(basePath string, r Resource) {
	// GET /basePath
	AddListRoute(s.router, basePath, r)

	// GET /basePath/{id}
	AddGetRoute(s.router, basePath, true, r)

	// POST /basePath
	AddCreateRoute(s.router, basePath, r)

	// PUT /basePath/{id}
	AddUpdateRoute(s.router, basePath, true, r)

	// DELETE /basePath
	AddDeleteRoute(s.router, basePath, false, r)

	// DELETE /basePath/{id}
	AddDeleteRoute(s.router, basePath, true, r)

	// POST /basePath/{id}/{action}
	AddActionRoute(s.router, basePath, true, r)
}

func (s *service) Config() *ServiceConfig {
	return s.config
}

func (s *service) Logger() *log.Logger {
	return s.defaultLogger
}

func (s *service) Router() *httprouter.Router {
	return s.router
}

func (s *service) Run() error {
	// Add the router as the final middleware handler
	h, err := s.newRouterHandler()
	if err != nil {
		return err
	}
	s.AddHandler(h)

	var middleware http.Handler = s.middleware
	if s.config.Metrics.Enabled {
		middleware = prometheus.InstrumentHandler("service", middleware)
	}

	// Serve HTTP or HTTPS, depending on config. Use stoppable listener
	// so we can exit gracefully if signaled to do so.
	var stoppableListener net.Listener
	if s.config.Transport.TLS {
		s.defaultLogger.Debugf("HTTPS listening on %s", s.config.Addr)
		stoppableListener, err = NewStoppableTLSListener(s.config.Addr, true, s.config.Transport.CertFilePath, s.config.Transport.KeyFilePath)
	} else {
		s.defaultLogger.Debugf("HTTP listening on %s", s.config.Addr)
		stoppableListener, err = NewStoppableTCPListener(s.config.Addr, true)
	}
	if err != nil {
		return err
	}

	if err = http.Serve(stoppableListener, middleware); err != nil {
		if _, ok := err.(*ListenerStoppedError); !ok {
			return err
		}
	}
	return nil
}

func (s *service) newBottomHandler() (Handler, error) {
	return NewBottom(s, s.defaultLogger, s.accessLogger), nil
}

func (s *service) newNegotiatorHandler() (Handler, error) {
	return NewNegotiator([]string{
		ContentTypeJson,
		ContentTypeCss,
		ContentTypePlain,
		ContentTypeXml,
		ContentTypeHtml,
		ContentTypeGif,
		ContentTypePng,
		ContentTypeOctetStream},
	), nil
}

func (s *service) newVersionHandler() (Handler, error) {
	if s.config.Version.Min < 1 {
		return nil, errors.New("service's minimum API version must be greater than zero")
	}
	if s.config.Version.Max < 1 {
		return nil, errors.New("service's maximum API version must be greater than zero")
	}

	return NewVersion(s.config.Version.Min, s.config.Version.Max), nil
}

func (s *service) newRouterHandler() (Handler, error) {
	// No more middleware handlers: remaining dispatch happens via httprouter
	return WrapHttpHandler(s.router), nil
}

func (s *service) addMetricsRoute() {
	uriPath := s.config.Metrics.UriPath
	if uriPath == "" {
		uriPath = DefaultMetricsURIPath
	}

	h := prometheus.UninstrumentedHandler()
	s.router.GET(uriPath, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { h.ServeHTTP(rw, req) })
}

func (s *service) addProfilerRoutes() {
	uriPath := s.config.Profiler.UriPath
	if uriPath == "" {
		uriPath = DefaultProfilerURIPath
	}

	s.router.GET(path.Join(uriPath, "/"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Index(rw, req) })
	s.router.GET(path.Join(uriPath, "/cmdline"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Cmdline(rw, req) })
	s.router.GET(path.Join(uriPath, "/profile"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Profile(rw, req) })
	s.router.POST(path.Join(uriPath, "/profile"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Profile(rw, req) })
	s.router.GET(path.Join(uriPath, "/symbol"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Symbol(rw, req) })
	s.router.POST(path.Join(uriPath, "/symbol"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Symbol(rw, req) })
	s.router.GET(path.Join(uriPath, "/trace"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Trace(rw, req) })
	s.router.POST(path.Join(uriPath, "/trace"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { pprof.Trace(rw, req) })
}

func (s *service) addSchemaRoutes() {
	config := s.config

	// Serve the various schemas, e.g. /schema/v1, /schema/v2, etc.
	s.schema = NewSchemaHandler(config.Schema.FilePath)

	s.router.GET(path.Join(config.Schema.UriPath, "/v:version/", "*filepath"), s.schema.ServeHTTP)

	// Temporarily redirect (307) the base schema path to the default schema file, e.g. /schema -> /schema/v2/fileName
	defaultSchemaPath := path.Join(config.Schema.UriPath, fmt.Sprintf("v%d", config.Version.Max), config.Schema.FileName)

	s.router.GET(config.Schema.UriPath, func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
		http.Redirect(rw, req, defaultSchemaPath, http.StatusTemporaryRedirect)
	})

	// Temporarily redirect (307) the version schema path to the default schema file, e.g. /schema/v2 -> /schema/v2/fileName
	s.router.GET(path.Join(config.Schema.UriPath, "/v:version/"), func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
		http.Redirect(rw, req, defaultSchemaPath, http.StatusTemporaryRedirect)
	})

	// Optionally temporarily redirect (307) the root to the base schema path, e.g. / -> /schema
	if config.Schema.RootRedirect {
		s.router.GET("/", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
			http.Redirect(rw, req, config.Schema.UriPath, http.StatusTemporaryRedirect)
		})
	}
}

func openLogFile(logger *log.Logger, logPath string) {
	sigs := make(chan os.Signal, 1)
	logging := make(chan bool, 1)

	go func() {
		var curLog, priorLog *os.File
		for {
			// Open and begin using a new log file
			curLog, _ = os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
			logger.SetOutput(curLog)

			if priorLog == nil {
				// First log, signal the outer goroutine that we're running
				logging <- true
			} else {
				// Follow-on log, close the prior log file
				priorLog.Close()
				priorLog = nil
			}

			// Wait for a SIGHUP
			<-sigs

			// Setup for the next iteration
			priorLog = curLog
		}
	}()

	signal.Notify(sigs, syscall.SIGHUP)
	<-logging
}
<|endoftext|>