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

// Handler for Docker containers.
package docker

import (
	"fmt"
	"math"
	"path"
	"strings"
	"time"

	"github.com/docker/libcontainer/cgroups"
	cgroup_fs "github.com/docker/libcontainer/cgroups/fs"
	libcontainerConfigs "github.com/docker/libcontainer/configs"
	"github.com/fsouza/go-dockerclient"
	"github.com/google/cadvisor/container"
	containerLibcontainer "github.com/google/cadvisor/container/libcontainer"
	"github.com/google/cadvisor/fs"
	info "github.com/google/cadvisor/info/v1"
	"github.com/google/cadvisor/utils"
)

// Path to aufs dir where all the files exist.
// aufs/layers is ignored here since it does not hold a lot of data.
// aufs/mnt contains the mount points used to compose the rootfs. Hence it is also ignored.
var pathToAufsDir = "aufs/diff"

type dockerContainerHandler struct {
	client             *docker.Client
	name               string
	id                 string
	aliases            []string
	machineInfoFactory info.MachineInfoFactory

	// Path to the libcontainer config file.
	libcontainerConfigPath string

	// Path to the libcontainer state file.
	libcontainerStatePath string

	// TODO(vmarmol): Remove when we depend on a newer Docker.
	// Path to the libcontainer pid file.
	libcontainerPidPath string

	// Absolute path to the cgroup hierarchies of this container.
	// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
	cgroupPaths map[string]string

	// Manager of this container's cgroups.
	cgroupManager cgroups.Manager

	usesAufsDriver bool
	fsInfo         fs.FsInfo
	storageDirs    []string

	// Time at which this container was created.
	creationTime time.Time

	// Metadata labels associated with the container.
	labels map[string]string
}

func newDockerContainerHandler(
	client *docker.Client,
	name string,
	machineInfoFactory info.MachineInfoFactory,
	fsInfo fs.FsInfo,
	usesAufsDriver bool,
	cgroupSubsystems *containerLibcontainer.CgroupSubsystems,
) (container.ContainerHandler, error) {
	// Create the cgroup paths.
	cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints))
	for key, val := range cgroupSubsystems.MountPoints {
		cgroupPaths[key] = path.Join(val, name)
	}

	// Generate the equivalent cgroup manager for this container.
	cgroupManager := &cgroup_fs.Manager{
		Cgroups: &libcontainerConfigs.Cgroup{
			Name: name,
		},
		Paths: cgroupPaths,
	}

	id := ContainerNameToDockerId(name)
	handler := &dockerContainerHandler{
		id:                 id,
		client:             client,
		name:               name,
		machineInfoFactory: machineInfoFactory,
		cgroupPaths:        cgroupPaths,
		cgroupManager:      cgroupManager,
		usesAufsDriver:     usesAufsDriver,
		fsInfo:             fsInfo,
	}
	handler.storageDirs = append(handler.storageDirs, path.Join(*dockerRootDir, pathToAufsDir, id))

	// We assume that if Inspect fails then the container is not known to docker.
	ctnr, err := client.InspectContainer(id)
	if err != nil {
		return nil, fmt.Errorf("failed to inspect container %q: %v", id, err)
	}
	handler.creationTime = ctnr.Created

	// Add the name and bare ID as aliases of the container.
	handler.aliases = append(handler.aliases, strings.TrimPrefix(ctnr.Name, "/"))
	handler.aliases = append(handler.aliases, id)
	handler.labels = ctnr.Config.Labels

	return handler, nil
}

func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) {
	return info.ContainerReference{
		Name:      self.name,
		Aliases:   self.aliases,
		Namespace: DockerNamespace,
	}, nil
}

func (self *dockerContainerHandler) readLibcontainerConfig() (*libcontainerConfigs.Config, error) {
	config, err := containerLibcontainer.ReadConfig(*dockerRootDir, *dockerRunDir, self.id)
	if err != nil {
		return nil, fmt.Errorf("failed to read libcontainer config: %v", err)
	}

	// Replace cgroup parent and name with our own since we may be running in a different context.
	if config.Cgroups == nil {
		config.Cgroups = new(libcontainerConfigs.Cgroup)
	}
	config.Cgroups.Name = self.name
	config.Cgroups.Parent = "/"

	return config, nil
}

func libcontainerConfigToContainerSpec(config *libcontainerConfigs.Config, mi *info.MachineInfo) info.ContainerSpec {
	var spec info.ContainerSpec
	spec.HasMemory = true
	spec.Memory.Limit = math.MaxUint64
	spec.Memory.SwapLimit = math.MaxUint64
	if config.Cgroups.Memory > 0 {
		spec.Memory.Limit = uint64(config.Cgroups.Memory)
	}
	if config.Cgroups.MemorySwap > 0 {
		spec.Memory.SwapLimit = uint64(config.Cgroups.MemorySwap)
	}

	// Get CPU info
	spec.HasCpu = true
	spec.Cpu.Limit = 1024
	if config.Cgroups.CpuShares != 0 {
		spec.Cpu.Limit = uint64(config.Cgroups.CpuShares)
	}
	spec.Cpu.Mask = utils.FixCpuMask(config.Cgroups.CpusetCpus, mi.NumCores)

	spec.HasNetwork = len(config.Networks) > 0
	spec.HasDiskIo = true

	return spec
}

func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) {
	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return info.ContainerSpec{}, err
	}
	libcontainerConfig, err := self.readLibcontainerConfig()
	if err != nil {
		return info.ContainerSpec{}, err
	}

	spec := libcontainerConfigToContainerSpec(libcontainerConfig, mi)
	spec.CreationTime = self.creationTime
	if self.usesAufsDriver {
		spec.HasFilesystem = true
	}
	spec.Labels = self.labels

	return spec, err
}

func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error {
	// No support for non-aufs storage drivers.
	if !self.usesAufsDriver {
		return nil
	}

	// As of now we assume that all the storage dirs are on the same device.
	// The first storage dir will be that of the image layers.
	deviceInfo, err := self.fsInfo.GetDirFsDevice(self.storageDirs[0])
	if err != nil {
		return err
	}

	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return err
	}
	var limit uint64 = 0
	// Docker does not impose any filesystem limits for containers. So use capacity as limit.
	for _, fs := range mi.Filesystems {
		if fs.Device == deviceInfo.Device {
			limit = fs.Capacity
			break
		}
	}

	fsStat := info.FsStats{Device: deviceInfo.Device, Limit: limit}

	var usage uint64 = 0
	for _, dir := range self.storageDirs {
		// TODO(Vishh): Add support for external mounts.
		dirUsage, err := self.fsInfo.GetDirUsage(dir)
		if err != nil {
			return err
		}
		usage += dirUsage
	}
	fsStat.Usage = usage
	stats.Filesystem = append(stats.Filesystem, fsStat)

	return nil
}

// TODO(vmarmol): Get from libcontainer API instead of cgroup manager when we don't have to support older Dockers.
func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error) {
	config, err := self.readLibcontainerConfig()
	if err != nil {
		return nil, err
	}

	var networkInterfaces []string
	if len(config.Networks) > 0 {
		// ContainerStats only reports stat for one network device.
		// TODO(vmarmol): Handle multiple physical network devices.
		for _, n := range config.Networks {
			// Take the first non-loopback.
			if n.Type != "loopback" {
				networkInterfaces = []string{n.HostInterfaceName}
				break
			}
		}
	}
	stats, err := containerLibcontainer.GetStats(self.cgroupManager, networkInterfaces)
	if err != nil {
		return stats, err
	}

	// TODO(rjnagal): Remove the conversion when network stats are read from libcontainer.
	convertInterfaceStats(&stats.Network.InterfaceStats)
	for i := range stats.Network.Interfaces {
		convertInterfaceStats(&stats.Network.Interfaces[i])
	}

	// Get filesystem stats.
	err = self.getFsStats(stats)
	if err != nil {
		return stats, err
	}

	return stats, nil
}

func convertInterfaceStats(stats *info.InterfaceStats) {
	net := *stats

	// Ingress for host veth is from the container.
	// Hence tx_bytes stat on the host veth is actually number of bytes received by the container.
	stats.RxBytes = net.TxBytes
	stats.RxPackets = net.TxPackets
	stats.RxErrors = net.TxErrors
	stats.RxDropped = net.TxDropped
	stats.TxBytes = net.RxBytes
	stats.TxPackets = net.RxPackets
	stats.TxErrors = net.RxErrors
	stats.TxDropped = net.RxDropped
}

func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
	if self.name != "/docker" {
		return []info.ContainerReference{}, nil
	}
	opt := docker.ListContainersOptions{
		All: true,
	}
	containers, err := self.client.ListContainers(opt)
	if err != nil {
		return nil, err
	}

	ret := make([]info.ContainerReference, 0, len(containers)+1)
	for _, c := range containers {
		if !strings.HasPrefix(c.Status, "Up ") {
			continue
		}

		ref := info.ContainerReference{
			Name:      FullContainerName(c.ID),
			Aliases:   append(c.Names, c.ID),
			Namespace: DockerNamespace,
		}
		ret = append(ret, ref)
	}

	return ret, nil
}

func (self *dockerContainerHandler) GetCgroupPath(resource string) (string, error) {
	path, ok := self.cgroupPaths[resource]
	if !ok {
		return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, self.name)
	}
	return path, nil
}

func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
	// TODO(vmarmol): Implement.
	return nil, nil
}

func (self *dockerContainerHandler) GetContainerLabels() map[string]string {
	return self.labels
}

func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
	return containerLibcontainer.GetProcesses(self.cgroupManager)
}

func (self *dockerContainerHandler) WatchSubcontainers(events chan container.SubcontainerEvent) error {
	return fmt.Errorf("watch is unimplemented in the Docker container driver")
}

func (self *dockerContainerHandler) StopWatchingSubcontainers() error {
	// No-op for Docker driver.
	return nil
}

func (self *dockerContainerHandler) Exists() bool {
	return containerLibcontainer.Exists(*dockerRootDir, *dockerRunDir, self.id)
}

func DockerInfo() (map[string]string, error) {
	client, err := docker.NewClient(*ArgDockerEndpoint)
	if err != nil {
		return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
	}
	info, err := client.Info()
	if err != nil {
		return nil, err
	}
	return info.Map(), nil
}

func DockerImages() ([]docker.APIImages, error) {
	client, err := docker.NewClient(*ArgDockerEndpoint)
	if err != nil {
		return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
	}
	images, err := client.ListImages(docker.ListImagesOptions{All: false})
	if err != nil {
		return nil, err
	}
	return images, nil
}
<commit_msg>Fix network info for docker containers running with --net=host.<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Handler for Docker containers.
package docker

import (
	"fmt"
	"math"
	"path"
	"strings"
	"time"

	"github.com/docker/libcontainer/cgroups"
	cgroup_fs "github.com/docker/libcontainer/cgroups/fs"
	libcontainerConfigs "github.com/docker/libcontainer/configs"
	"github.com/fsouza/go-dockerclient"
	"github.com/google/cadvisor/container"
	containerLibcontainer "github.com/google/cadvisor/container/libcontainer"
	"github.com/google/cadvisor/fs"
	info "github.com/google/cadvisor/info/v1"
	"github.com/google/cadvisor/utils"
)

// Path to aufs dir where all the files exist.
// aufs/layers is ignored here since it does not hold a lot of data.
// aufs/mnt contains the mount points used to compose the rootfs. Hence it is also ignored.
var pathToAufsDir = "aufs/diff"

type dockerContainerHandler struct {
	client             *docker.Client
	name               string
	id                 string
	aliases            []string
	machineInfoFactory info.MachineInfoFactory

	// Path to the libcontainer config file.
	libcontainerConfigPath string

	// Path to the libcontainer state file.
	libcontainerStatePath string

	// TODO(vmarmol): Remove when we depend on a newer Docker.
	// Path to the libcontainer pid file.
	libcontainerPidPath string

	// Absolute path to the cgroup hierarchies of this container.
	// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
	cgroupPaths map[string]string

	// Manager of this container's cgroups.
	cgroupManager cgroups.Manager

	usesAufsDriver bool
	fsInfo         fs.FsInfo
	storageDirs    []string

	// Time at which this container was created.
	creationTime time.Time

	// Metadata labels associated with the container.
	labels map[string]string
}

func newDockerContainerHandler(
	client *docker.Client,
	name string,
	machineInfoFactory info.MachineInfoFactory,
	fsInfo fs.FsInfo,
	usesAufsDriver bool,
	cgroupSubsystems *containerLibcontainer.CgroupSubsystems,
) (container.ContainerHandler, error) {
	// Create the cgroup paths.
	cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints))
	for key, val := range cgroupSubsystems.MountPoints {
		cgroupPaths[key] = path.Join(val, name)
	}

	// Generate the equivalent cgroup manager for this container.
	cgroupManager := &cgroup_fs.Manager{
		Cgroups: &libcontainerConfigs.Cgroup{
			Name: name,
		},
		Paths: cgroupPaths,
	}

	id := ContainerNameToDockerId(name)
	handler := &dockerContainerHandler{
		id:                 id,
		client:             client,
		name:               name,
		machineInfoFactory: machineInfoFactory,
		cgroupPaths:        cgroupPaths,
		cgroupManager:      cgroupManager,
		usesAufsDriver:     usesAufsDriver,
		fsInfo:             fsInfo,
	}
	handler.storageDirs = append(handler.storageDirs, path.Join(*dockerRootDir, pathToAufsDir, id))

	// We assume that if Inspect fails then the container is not known to docker.
	ctnr, err := client.InspectContainer(id)
	if err != nil {
		return nil, fmt.Errorf("failed to inspect container %q: %v", id, err)
	}
	handler.creationTime = ctnr.Created

	// Add the name and bare ID as aliases of the container.
	handler.aliases = append(handler.aliases, strings.TrimPrefix(ctnr.Name, "/"))
	handler.aliases = append(handler.aliases, id)
	handler.labels = ctnr.Config.Labels

	return handler, nil
}

func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) {
	return info.ContainerReference{
		Name:      self.name,
		Aliases:   self.aliases,
		Namespace: DockerNamespace,
	}, nil
}

func (self *dockerContainerHandler) readLibcontainerConfig() (*libcontainerConfigs.Config, error) {
	config, err := containerLibcontainer.ReadConfig(*dockerRootDir, *dockerRunDir, self.id)
	if err != nil {
		return nil, fmt.Errorf("failed to read libcontainer config: %v", err)
	}

	// Replace cgroup parent and name with our own since we may be running in a different context.
	if config.Cgroups == nil {
		config.Cgroups = new(libcontainerConfigs.Cgroup)
	}
	config.Cgroups.Name = self.name
	config.Cgroups.Parent = "/"

	return config, nil
}

func libcontainerConfigToContainerSpec(config *libcontainerConfigs.Config, mi *info.MachineInfo) info.ContainerSpec {
	var spec info.ContainerSpec
	spec.HasMemory = true
	spec.Memory.Limit = math.MaxUint64
	spec.Memory.SwapLimit = math.MaxUint64
	if config.Cgroups.Memory > 0 {
		spec.Memory.Limit = uint64(config.Cgroups.Memory)
	}
	if config.Cgroups.MemorySwap > 0 {
		spec.Memory.SwapLimit = uint64(config.Cgroups.MemorySwap)
	}

	// Get CPU info
	spec.HasCpu = true
	spec.Cpu.Limit = 1024
	if config.Cgroups.CpuShares != 0 {
		spec.Cpu.Limit = uint64(config.Cgroups.CpuShares)
	}
	spec.Cpu.Mask = utils.FixCpuMask(config.Cgroups.CpusetCpus, mi.NumCores)

	// Docker reports a loop device for containers with --net=host. Ignore
	// those too.
	networkCount := 0
	for _, n := range config.Networks {
		if n.Type != "loopback" {
			networkCount += 1
		}
	}

	spec.HasNetwork = networkCount > 0
	spec.HasDiskIo = true

	return spec
}

func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) {
	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return info.ContainerSpec{}, err
	}
	libcontainerConfig, err := self.readLibcontainerConfig()
	if err != nil {
		return info.ContainerSpec{}, err
	}

	spec := libcontainerConfigToContainerSpec(libcontainerConfig, mi)
	spec.CreationTime = self.creationTime
	if self.usesAufsDriver {
		spec.HasFilesystem = true
	}
	spec.Labels = self.labels

	return spec, err
}

func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error {
	// No support for non-aufs storage drivers.
	if !self.usesAufsDriver {
		return nil
	}

	// As of now we assume that all the storage dirs are on the same device.
	// The first storage dir will be that of the image layers.
	deviceInfo, err := self.fsInfo.GetDirFsDevice(self.storageDirs[0])
	if err != nil {
		return err
	}

	mi, err := self.machineInfoFactory.GetMachineInfo()
	if err != nil {
		return err
	}
	var limit uint64 = 0
	// Docker does not impose any filesystem limits for containers. So use capacity as limit.
	for _, fs := range mi.Filesystems {
		if fs.Device == deviceInfo.Device {
			limit = fs.Capacity
			break
		}
	}

	fsStat := info.FsStats{Device: deviceInfo.Device, Limit: limit}

	var usage uint64 = 0
	for _, dir := range self.storageDirs {
		// TODO(Vishh): Add support for external mounts.
		dirUsage, err := self.fsInfo.GetDirUsage(dir)
		if err != nil {
			return err
		}
		usage += dirUsage
	}
	fsStat.Usage = usage
	stats.Filesystem = append(stats.Filesystem, fsStat)

	return nil
}

// TODO(vmarmol): Get from libcontainer API instead of cgroup manager when we don't have to support older Dockers.
func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error) {
	config, err := self.readLibcontainerConfig()
	if err != nil {
		return nil, err
	}

	var networkInterfaces []string
	if len(config.Networks) > 0 {
		// ContainerStats only reports stat for one network device.
		// TODO(vmarmol): Handle multiple physical network devices.
		for _, n := range config.Networks {
			// Take the first non-loopback.
			if n.Type != "loopback" {
				networkInterfaces = []string{n.HostInterfaceName}
				break
			}
		}
	}
	stats, err := containerLibcontainer.GetStats(self.cgroupManager, networkInterfaces)
	if err != nil {
		return stats, err
	}

	// TODO(rjnagal): Remove the conversion when network stats are read from libcontainer.
	convertInterfaceStats(&stats.Network.InterfaceStats)
	for i := range stats.Network.Interfaces {
		convertInterfaceStats(&stats.Network.Interfaces[i])
	}

	// Get filesystem stats.
	err = self.getFsStats(stats)
	if err != nil {
		return stats, err
	}

	return stats, nil
}

func convertInterfaceStats(stats *info.InterfaceStats) {
	net := *stats

	// Ingress for host veth is from the container.
	// Hence tx_bytes stat on the host veth is actually number of bytes received by the container.
	stats.RxBytes = net.TxBytes
	stats.RxPackets = net.TxPackets
	stats.RxErrors = net.TxErrors
	stats.RxDropped = net.TxDropped
	stats.TxBytes = net.RxBytes
	stats.TxPackets = net.RxPackets
	stats.TxErrors = net.RxErrors
	stats.TxDropped = net.RxDropped
}

func (self *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
	if self.name != "/docker" {
		return []info.ContainerReference{}, nil
	}
	opt := docker.ListContainersOptions{
		All: true,
	}
	containers, err := self.client.ListContainers(opt)
	if err != nil {
		return nil, err
	}

	ret := make([]info.ContainerReference, 0, len(containers)+1)
	for _, c := range containers {
		if !strings.HasPrefix(c.Status, "Up ") {
			continue
		}

		ref := info.ContainerReference{
			Name:      FullContainerName(c.ID),
			Aliases:   append(c.Names, c.ID),
			Namespace: DockerNamespace,
		}
		ret = append(ret, ref)
	}

	return ret, nil
}

func (self *dockerContainerHandler) GetCgroupPath(resource string) (string, error) {
	path, ok := self.cgroupPaths[resource]
	if !ok {
		return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, self.name)
	}
	return path, nil
}

func (self *dockerContainerHandler) ListThreads(listType container.ListType) ([]int, error) {
	// TODO(vmarmol): Implement.
	return nil, nil
}

func (self *dockerContainerHandler) GetContainerLabels() map[string]string {
	return self.labels
}

func (self *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
	return containerLibcontainer.GetProcesses(self.cgroupManager)
}

func (self *dockerContainerHandler) WatchSubcontainers(events chan container.SubcontainerEvent) error {
	return fmt.Errorf("watch is unimplemented in the Docker container driver")
}

func (self *dockerContainerHandler) StopWatchingSubcontainers() error {
	// No-op for Docker driver.
	return nil
}

func (self *dockerContainerHandler) Exists() bool {
	return containerLibcontainer.Exists(*dockerRootDir, *dockerRunDir, self.id)
}

func DockerInfo() (map[string]string, error) {
	client, err := docker.NewClient(*ArgDockerEndpoint)
	if err != nil {
		return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
	}
	info, err := client.Info()
	if err != nil {
		return nil, err
	}
	return info.Map(), nil
}

func DockerImages() ([]docker.APIImages, error) {
	client, err := docker.NewClient(*ArgDockerEndpoint)
	if err != nil {
		return nil, fmt.Errorf("unable to communicate with docker daemon: %v", err)
	}
	images, err := client.ListImages(docker.ListImagesOptions{All: false})
	if err != nil {
		return nil, err
	}
	return images, nil
}
<|endoftext|>


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

package exporterparser

import (
	"bytes"
	"context"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"testing"

	commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
	zipkinmodel "github.com/openzipkin/zipkin-go/model"

	"github.com/census-instrumentation/opencensus-service/exporter"
	"github.com/census-instrumentation/opencensus-service/internal/config/viperutils"
	"github.com/census-instrumentation/opencensus-service/internal/testutils"
	"github.com/census-instrumentation/opencensus-service/receiver/zipkin"
)

func TestZipkinEndpointFromNode(t *testing.T) {
	type args struct {
		node         *commonpb.Node
		serviceName  string
		endpointType zipkinDirection
	}
	tests := []struct {
		name    string
		args    args
		want    *zipkinmodel.Endpoint
		wantErr bool
	}{
		{
			name:    "Nil Node",
			args:    args{node: nil, serviceName: "", endpointType: isLocalEndpoint},
			want:    nil,
			wantErr: false,
		},
		{
			name:    "Only svc name",
			args:    args{node: &commonpb.Node{}, serviceName: "test", endpointType: isLocalEndpoint},
			want:    &zipkinmodel.Endpoint{ServiceName: "test"},
			wantErr: false,
		},
		{
			name: "Only ipv4",
			args: args{
				node: &commonpb.Node{
					Attributes: map[string]string{"ipv4": "1.2.3.4"},
				},
				serviceName:  "",
				endpointType: isLocalEndpoint,
			},
			want:    &zipkinmodel.Endpoint{IPv4: net.ParseIP("1.2.3.4")},
			wantErr: false,
		},
		{
			name: "Only ipv6 remote",
			args: args{
				node: &commonpb.Node{
					Attributes: map[string]string{"zipkin.remoteEndpoint.ipv6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334"},
				},
				serviceName:  "",
				endpointType: isRemoteEndpoint,
			},
			want:    &zipkinmodel.Endpoint{IPv6: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
			wantErr: false,
		},
		{
			name: "Only port",
			args: args{
				node: &commonpb.Node{
					Attributes: map[string]string{"port": "42"},
				},
				serviceName:  "",
				endpointType: isLocalEndpoint,
			},
			want:    &zipkinmodel.Endpoint{Port: 42},
			wantErr: false,
		},
		{
			name: "Service name, ipv4, and port",
			args: args{
				node: &commonpb.Node{
					Attributes: map[string]string{"ipv4": "4.3.2.1", "port": "2"},
				},
				serviceName:  "test-svc",
				endpointType: isLocalEndpoint,
			},
			want:    &zipkinmodel.Endpoint{ServiceName: "test-svc", IPv4: net.ParseIP("4.3.2.1"), Port: 2},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := zipkinEndpointFromNode(tt.args.node, tt.args.serviceName, tt.args.endpointType)
			if (err != nil) != tt.wantErr {
				t.Errorf("zipkinEndpointFromNode() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("zipkinEndpointFromNode() = %v, want %v", got, tt.want)
			}
		})
	}
}

// This function tests that Zipkin spans that are received then processed roundtrip
// back to almost the same JSON with differences:
// a) Go's net.IP.String intentional shortens 0s with "::" but also converts to hex values
//    so
//          "7::0.128.128.127"
//    becomes
//          "7::80:807f"
//
// The rest of the fields should match up exactly
func TestZipkinExportersFromViper_roundtripJSON(t *testing.T) {
	responseReady := make(chan bool)
	buf := new(bytes.Buffer)
	cst := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		io.Copy(buf, r.Body)
		r.Body.Close()
		responseReady <- true
	}))
	defer cst.Close()

	config := `
zipkin:
  upload_period: 1ms
  endpoint: ` + cst.URL
	v, _ := viperutils.ViperFromYAMLBytes([]byte(config))
	tes, _, doneFns, err := ZipkinExportersFromViper(v)
	if len(tes) == 0 || err != nil {
		t.Fatalf("Failed to parse out exporters: %v", err)
	}
	defer func() {
		for _, fn := range doneFns {
			fn()
		}
	}()

	if g, w := len(tes), 1; g != w {
		t.Errorf("Number of trace exporters: Got %d Want %d", g, w)
	}

	// Run the Zipkin receiver to "receive spans upload from a client application"
	zi, err := zipkinreceiver.New(":0")
	if err != nil {
		t.Fatalf("Failed to create a new Zipkin receiver: %v", err)
	}

	zexp := exporter.MultiTraceExporters(tes...)
	if err := zi.StartTraceReception(context.Background(), zexp); err != nil {
		t.Fatalf("Failed to start trace reception: %v", err)
	}
	defer zi.StopTraceReception(context.Background())

	// Let the receiver receive "uploaded Zipkin spans from a Java client application"
	req, _ := http.NewRequest("POST", "https://tld.org/", strings.NewReader(zipkinSpansJSONJavaLibrary))
	responseWriter := httptest.NewRecorder()
	zi.ServeHTTP(responseWriter, req)
	// Wait for the server to write the response.
	<-responseReady

	// We expect back the exact JSON that was received
	want := testutils.GenerateNormalizedJSON(`
[{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385","parentId": "86154a4ba6e91385","id": "4d1e00c0db9010db",
  "kind": "CLIENT","name": "get",
  "timestamp": 1472470996199000,"duration": 207000,
  "localEndpoint": {"serviceName": "frontend","ipv6": "7::80:807f"},
  "remoteEndpoint": {"serviceName": "backend","ipv4": "192.168.99.101","port": 9000},
  "annotations": [
    {"timestamp": 1472470996238000,"value": "foo"},
    {"timestamp": 1472470996403000,"value": "bar"}
  ],
  "tags": {"http.path": "/api","clnt/finagle.version": "6.45.0"}
},
{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385","parentId": "86154a4ba6e91386","id": "4d1e00c0db9010db",
  "kind": "SERVER","name": "put",
  "timestamp": 1472470996199000,"duration": 207000,
  "localEndpoint": {"serviceName": "frontend","ipv6": "7::80:807f"},
  "remoteEndpoint": {"serviceName": "frontend", "ipv4": "192.168.99.101","port": 9000},
  "annotations": [
    {"timestamp": 1472470996238000,"value": "foo"},
    {"timestamp": 1472470996403000,"value": "bar"}
  ],
  "tags": {"http.path": "/api","clnt/finagle.version": "6.45.0"}
},
{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385",
  "parentId": "86154a4ba6e91386",
  "id": "4d1e00c0db9010db",
  "kind": "SERVER",
  "name": "put",
  "timestamp": 1472470996199000,
  "duration": 207000
}]`)

	// Finally we need to inspect the output
	gotBytes, _ := ioutil.ReadAll(buf)
	got := testutils.GenerateNormalizedJSON(string(gotBytes))
	if got != want {
		t.Errorf("RoundTrip result do not match:\nGot\n %s\n\nWant\n: %s\n", got, want)
	}
}

const zipkinSpansJSONJavaLibrary = `
[{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385",
  "parentId": "86154a4ba6e91385",
  "id": "4d1e00c0db9010db",
  "kind": "CLIENT",
  "name": "get",
  "timestamp": 1472470996199000,
  "duration": 207000,
  "localEndpoint": {
    "serviceName": "frontend",
    "ipv6": "7::0.128.128.127"
  },
  "remoteEndpoint": {
    "serviceName": "backend",
    "ipv4": "192.168.99.101",
    "port": 9000
  },
  "annotations": [
    {
      "timestamp": 1472470996238000,
      "value": "foo"
    },
    {
      "timestamp": 1472470996403000,
      "value": "bar"
    }
  ],
  "tags": {
    "http.path": "/api",
    "clnt/finagle.version": "6.45.0"
  }
},
{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385",
  "parentId": "86154a4ba6e91386",
  "id": "4d1e00c0db9010db",
  "kind": "SERVER",
  "name": "put",
  "timestamp": 1472470996199000,
  "duration": 207000,
  "localEndpoint": {
    "serviceName": "frontend",
    "ipv6": "7::0.128.128.127"
  },
  "remoteEndpoint": {
    "serviceName": "frontend",
    "ipv4": "192.168.99.101",
    "port": 9000
  },
  "annotations": [
    {
      "timestamp": 1472470996238000,
      "value": "foo"
    },
    {
      "timestamp": 1472470996403000,
      "value": "bar"
    }
  ],
  "tags": {
    "http.path": "/api",
    "clnt/finagle.version": "6.45.0"
  }
},
{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385",
  "parentId": "86154a4ba6e91386",
  "id": "4d1e00c0db9010db",
  "kind": "SERVER",
  "name": "put",
  "timestamp": 1472470996199000,
  "duration": 207000
}]
`
<commit_msg>Make TestZipkinExportersFromYAML_roundtripJSON reliable (#391)<commit_after>// Copyright 2018, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package exporterparser

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"testing"

	commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
	zipkinmodel "github.com/openzipkin/zipkin-go/model"
	zipkinreporter "github.com/openzipkin/zipkin-go/reporter"

	"github.com/census-instrumentation/opencensus-service/exporter"
	"github.com/census-instrumentation/opencensus-service/internal/config/viperutils"
	"github.com/census-instrumentation/opencensus-service/internal/testutils"
	"github.com/census-instrumentation/opencensus-service/receiver/zipkin"
)

func TestZipkinEndpointFromNode(t *testing.T) {
	type args struct {
		node         *commonpb.Node
		serviceName  string
		endpointType zipkinDirection
	}
	tests := []struct {
		name    string
		args    args
		want    *zipkinmodel.Endpoint
		wantErr bool
	}{
		{
			name:    "Nil Node",
			args:    args{node: nil, serviceName: "", endpointType: isLocalEndpoint},
			want:    nil,
			wantErr: false,
		},
		{
			name:    "Only svc name",
			args:    args{node: &commonpb.Node{}, serviceName: "test", endpointType: isLocalEndpoint},
			want:    &zipkinmodel.Endpoint{ServiceName: "test"},
			wantErr: false,
		},
		{
			name: "Only ipv4",
			args: args{
				node: &commonpb.Node{
					Attributes: map[string]string{"ipv4": "1.2.3.4"},
				},
				serviceName:  "",
				endpointType: isLocalEndpoint,
			},
			want:    &zipkinmodel.Endpoint{IPv4: net.ParseIP("1.2.3.4")},
			wantErr: false,
		},
		{
			name: "Only ipv6 remote",
			args: args{
				node: &commonpb.Node{
					Attributes: map[string]string{"zipkin.remoteEndpoint.ipv6": "2001:0db8:85a3:0000:0000:8a2e:0370:7334"},
				},
				serviceName:  "",
				endpointType: isRemoteEndpoint,
			},
			want:    &zipkinmodel.Endpoint{IPv6: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
			wantErr: false,
		},
		{
			name: "Only port",
			args: args{
				node: &commonpb.Node{
					Attributes: map[string]string{"port": "42"},
				},
				serviceName:  "",
				endpointType: isLocalEndpoint,
			},
			want:    &zipkinmodel.Endpoint{Port: 42},
			wantErr: false,
		},
		{
			name: "Service name, ipv4, and port",
			args: args{
				node: &commonpb.Node{
					Attributes: map[string]string{"ipv4": "4.3.2.1", "port": "2"},
				},
				serviceName:  "test-svc",
				endpointType: isLocalEndpoint,
			},
			want:    &zipkinmodel.Endpoint{ServiceName: "test-svc", IPv4: net.ParseIP("4.3.2.1"), Port: 2},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := zipkinEndpointFromNode(tt.args.node, tt.args.serviceName, tt.args.endpointType)
			if (err != nil) != tt.wantErr {
				t.Errorf("zipkinEndpointFromNode() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("zipkinEndpointFromNode() = %v, want %v", got, tt.want)
			}
		})
	}
}

// This function tests that Zipkin spans that are received then processed roundtrip
// back to almost the same JSON with differences:
// a) Go's net.IP.String intentional shortens 0s with "::" but also converts to hex values
//    so
//          "7::0.128.128.127"
//    becomes
//          "7::80:807f"
//
// The rest of the fields should match up exactly
func TestZipkinExportersFromViper_roundtripJSON(t *testing.T) {
	buf := new(bytes.Buffer)
	cst := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		io.Copy(buf, r.Body)
		r.Body.Close()
	}))
	defer cst.Close()

	config := `
zipkin:
  upload_period: 1ms
  endpoint: ` + cst.URL
	v, _ := viperutils.ViperFromYAMLBytes([]byte(config))
	tes, _, doneFns, err := ZipkinExportersFromViper(v)
	if len(tes) == 0 || err != nil {
		t.Fatalf("Failed to parse out exporters: %v", err)
	}
	defer func() {
		for _, fn := range doneFns {
			fn()
		}
	}()

	if g, w := len(tes), 1; g != w {
		t.Errorf("Number of trace exporters: Got %d Want %d", g, w)
	}

	// The test requires the spans from zipkinSpansJSONJavaLibrary to be sent in a single batch, use
	// a mock to ensure that this happens as intended.
	mzr := newMockZipkinReporter(cst.URL)
	tes[0].(*zipkinExporter).reporter = mzr

	// Run the Zipkin receiver to "receive spans upload from a client application"
	zi, err := zipkinreceiver.New(":0")
	if err != nil {
		t.Fatalf("Failed to create a new Zipkin receiver: %v", err)
	}

	zexp := exporter.MultiTraceExporters(tes...)
	if err := zi.StartTraceReception(context.Background(), zexp); err != nil {
		t.Fatalf("Failed to start trace reception: %v", err)
	}
	defer zi.StopTraceReception(context.Background())

	// Let the receiver receive "uploaded Zipkin spans from a Java client application"
	req, _ := http.NewRequest("POST", "https://tld.org/", strings.NewReader(zipkinSpansJSONJavaLibrary))
	responseWriter := httptest.NewRecorder()
	zi.ServeHTTP(responseWriter, req)

	// Use the mock zipkin reporter to ensure all expected spans in a single batch. Since Flush waits for
	// server response there is no need for further synchronization.
	if err := mzr.Flush(); err != nil {
		t.Fatalf("Failed to flush zipkin reporter: %v", err)
	}

	// We expect back the exact JSON that was received
	want := testutils.GenerateNormalizedJSON(`
[{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385","parentId": "86154a4ba6e91385","id": "4d1e00c0db9010db",
  "kind": "CLIENT","name": "get",
  "timestamp": 1472470996199000,"duration": 207000,
  "localEndpoint": {"serviceName": "frontend","ipv6": "7::80:807f"},
  "remoteEndpoint": {"serviceName": "backend","ipv4": "192.168.99.101","port": 9000},
  "annotations": [
    {"timestamp": 1472470996238000,"value": "foo"},
    {"timestamp": 1472470996403000,"value": "bar"}
  ],
  "tags": {"http.path": "/api","clnt/finagle.version": "6.45.0"}
},
{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385","parentId": "86154a4ba6e91386","id": "4d1e00c0db9010db",
  "kind": "SERVER","name": "put",
  "timestamp": 1472470996199000,"duration": 207000,
  "localEndpoint": {"serviceName": "frontend","ipv6": "7::80:807f"},
  "remoteEndpoint": {"serviceName": "frontend", "ipv4": "192.168.99.101","port": 9000},
  "annotations": [
    {"timestamp": 1472470996238000,"value": "foo"},
    {"timestamp": 1472470996403000,"value": "bar"}
  ],
  "tags": {"http.path": "/api","clnt/finagle.version": "6.45.0"}
},
{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385",
  "parentId": "86154a4ba6e91386",
  "id": "4d1e00c0db9010db",
  "kind": "SERVER",
  "name": "put",
  "timestamp": 1472470996199000,
  "duration": 207000
}]`)

	// Finally we need to inspect the output
	gotBytes, _ := ioutil.ReadAll(buf)
	got := testutils.GenerateNormalizedJSON(string(gotBytes))
	if got != want {
		t.Errorf("RoundTrip result do not match:\nGot\n %s\n\nWant\n: %s\n", got, want)
	}
}

type mockZipkinReporter struct {
	url    string
	client *http.Client
	batch  []*zipkinmodel.SpanModel
}

var _ (zipkinreporter.Reporter) = (*mockZipkinReporter)(nil)

func (r *mockZipkinReporter) Send(span zipkinmodel.SpanModel) {
	r.batch = append(r.batch, &span)
}
func (r *mockZipkinReporter) Close() error {
	return nil
}

func newMockZipkinReporter(url string) *mockZipkinReporter {
	return &mockZipkinReporter{
		url:    url,
		client: &http.Client{},
	}
}

func (r *mockZipkinReporter) Flush() error {
	sendBatch := r.batch
	r.batch = nil

	if len(sendBatch) == 0 {
		return nil
	}

	body, err := json.Marshal(sendBatch)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", r.url, bytes.NewReader(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := r.client.Do(req)
	if err != nil {
		return err
	}
	_ = resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		return fmt.Errorf("http request failed with status code %d", resp.StatusCode)
	}

	return nil
}

const zipkinSpansJSONJavaLibrary = `
[{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385",
  "parentId": "86154a4ba6e91385",
  "id": "4d1e00c0db9010db",
  "kind": "CLIENT",
  "name": "get",
  "timestamp": 1472470996199000,
  "duration": 207000,
  "localEndpoint": {
    "serviceName": "frontend",
    "ipv6": "7::0.128.128.127"
  },
  "remoteEndpoint": {
    "serviceName": "backend",
    "ipv4": "192.168.99.101",
    "port": 9000
  },
  "annotations": [
    {
      "timestamp": 1472470996238000,
      "value": "foo"
    },
    {
      "timestamp": 1472470996403000,
      "value": "bar"
    }
  ],
  "tags": {
    "http.path": "/api",
    "clnt/finagle.version": "6.45.0"
  }
},
{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385",
  "parentId": "86154a4ba6e91386",
  "id": "4d1e00c0db9010db",
  "kind": "SERVER",
  "name": "put",
  "timestamp": 1472470996199000,
  "duration": 207000,
  "localEndpoint": {
    "serviceName": "frontend",
    "ipv6": "7::0.128.128.127"
  },
  "remoteEndpoint": {
    "serviceName": "frontend",
    "ipv4": "192.168.99.101",
    "port": 9000
  },
  "annotations": [
    {
      "timestamp": 1472470996238000,
      "value": "foo"
    },
    {
      "timestamp": 1472470996403000,
      "value": "bar"
    }
  ],
  "tags": {
    "http.path": "/api",
    "clnt/finagle.version": "6.45.0"
  }
},
{
  "traceId": "4d1e00c0db9010db86154a4ba6e91385",
  "parentId": "86154a4ba6e91386",
  "id": "4d1e00c0db9010db",
  "kind": "SERVER",
  "name": "put",
  "timestamp": 1472470996199000,
  "duration": 207000
}]
`
<|endoftext|>


<commit_before>package transform

import "github.com/drone/drone/yaml"

const clone = "clone"

// Clone transforms the Yaml to include a clone step.
func Clone(c *yaml.Config, plugin string) error {
	for _, p := range c.Pipeline {
		if p.Name == clone {
			return nil
		}
	}
	if plugin == "" {
		plugin = "git"
	}

	s := &yaml.Container{
		Image: plugin,
		Name:  clone,
	}

	c.Pipeline = append([]*yaml.Container{s}, c.Pipeline...)
	return nil
}
<commit_msg>default git if not set<commit_after>package transform

import "github.com/drone/drone/yaml"

const clone = "clone"

// Clone transforms the Yaml to include a clone step.
func Clone(c *yaml.Config, plugin string) error {
	if plugin == "" {
		plugin = "git"
	}

	for _, p := range c.Pipeline {
		if p.Name == clone {
			if p.Image == "" {
				p.Image = plugin
			}
			return nil
		}
	}

	s := &yaml.Container{
		Image: plugin,
		Name:  clone,
	}

	c.Pipeline = append([]*yaml.Container{s}, c.Pipeline...)
	return nil
}
<|endoftext|>


<commit_before>package checks

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

func CheckMasterApis(urls string) error {
	log.Println("Checking master apis. At least one has to be up")

	urlArr := strings.Split(urls, ",")

	oneApiOk := false
	var msg string
	for _, u := range urlArr {
		if err := checkHttp(u); err == nil {
			oneApiOk = true
		} else {
			msg += u + " is not reachable. "
		}
	}

	if oneApiOk {
		return nil
	} else {
		return errors.New(msg)
	}
}

func CheckOcGetNodes() error {
	log.Println("Checking oc get nodes output")

	out, err := runOcGetNodes()
	if err != nil {
		return err
	}

	if strings.Contains(out, "NotReady") {
		// Wait a few seconds and see if still NotReady
		// to avoid wrong alerts
		time.Sleep(10 * time.Second)

		out2, err := runOcGetNodes()
		if err != nil {
			return err
		}
		if strings.Contains(out2, "NotReady") {
			time.Sleep(10 * time.Second)

			out3, err := runOcGetNodes()
			if err != nil {
				return err
			}
			if strings.Contains(out3, "NotReady") {
				return errors.New("Some node is not ready! 'oc get nodes' output contained NotReady. Output: " + out3)
			}
		}
	}

	return nil
}

func runOcGetNodes() (string, error) {
	out, err := exec.Command("bash", "-c", "oc get nodes --show-labels | grep -v monitoring=false | grep -v SchedulingDisabled").Output()
	if err != nil {
		msg := "Could not parse oc get nodes output: " + err.Error()
		log.Println(msg)
		return "", errors.New(msg)
	}
	return string(out), nil
}

func CheckDnsNslookupOnKubernetes() error {
	log.Println("Checking nslookup to kubernetes ip")

	cmd := exec.Command("nslookup", daemonDNSEndpoint+".", kubernetesIP)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		msg := "DNS resolution via nslookup & kubernetes failed." + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	stdOut := out.String()

	if strings.Contains(stdOut, "Server") && strings.Count(stdOut, "Address") >= 2 && strings.Contains(stdOut, "Name") {
		return nil
	} else {
		return errors.New("Problem with dns to kubernetes. nsLookup had wrong output")
	}
}

func CheckDnsServiceNode() error {
	log.Println("Checking dns to a openshift service")

	ips := getIpsForName(daemonDNSServiceA)

	if ips == nil {
		return errors.New("Failed to lookup ip on node (dnsmasq) for name " + daemonDNSServiceA)
	} else {
		return nil
	}
}

func CheckDnsInPod() error {
	log.Println("Checking dns to a openshift service inside a pod")

	ips := getIpsForName(daemonDNSPod)

	if ips == nil {
		return errors.New("Failed to lookup ip in pod for name " + daemonDNSPod)
	} else {
		return nil
	}
}

func CheckPodHttpAtoB() error {
	log.Println("Checking if http connection does not work if network not joined")

	// This should fail as we do not have access to this project
	if err := checkHttp("http://" + daemonDNSServiceB + ":8090/hello"); err == nil {
		errors.New("Pod A could access pod b. This should not be allowed!")
	}

	return nil
}

func CheckPodHttpAtoC(slow bool) error {
	log.Println("Checking if http connection does work with joined network")

	if err := checkHttp("http://" + daemonDNSServiceC + ":8090/" + getEndpoint(slow)); err != nil {
		return errors.New("Pod A could access pod C. This should not work. Route/Router problem?")
	}

	return nil
}

func CheckHttpService(slow bool) error {
	errA := checkHttp("http://" + daemonDNSServiceA + ":8090/" + getEndpoint(slow))
	errB := checkHttp("http://" + daemonDNSServiceB + ":8090/" + getEndpoint(slow))
	errC := checkHttp("http://" + daemonDNSServiceC + ":8090/" + getEndpoint(slow))

	if errA != nil || errB != nil || errC != nil {
		msg := "Could not reach one of the services (a/b/c)"
		log.Println(msg)
		return errors.New(msg)
	}

	return nil
}

func CheckHttpHaProxy(publicUrl string, slow bool) error {
	log.Println("Checking http via HA-Proxy")

	if err := checkHttp(publicUrl + ":80/" + getEndpoint(slow)); err != nil {
		return errors.New("Could not access pods via haproxy. Route/Router problem?")
	}

	return nil
}

func CheckRegistryHealth(ip string) error {
	log.Println("Checking registry health")

	if err := checkHttp("http://" + ip + ":5000/healthz"); err != nil {
		time.Sleep(10 * time.Second)

		if err2 := checkHttp("http://" + ip + ":5000/healthz"); err2 != nil {
			return fmt.Errorf("Registry health check failed. %v", err2.Error())
		}
	}

	return nil
}

func CheckHawcularHealth(ip string) error {
	log.Println("Checking metrics health")

	if err := checkHttp("https://" + ip + ":443"); err != nil {
		return errors.New("Hawcular health check failed")
	}

	return nil
}

func CheckRouterHealth(ip string) error {
	log.Println("Checking router health", ip)

	if err := checkHttp("http://" + ip + ":1936/healthz"); err != nil {
		time.Sleep(10 * time.Second)

		if err2 := checkHttp("http://" + ip + ":5000/healthz"); err2 != nil {
			return fmt.Errorf("Router health check failed for %v, %v", ip, err2.Error())
		}
	}

	return nil
}

func CheckLoggingRestartsCount() error {
	log.Println("Checking log-container restart count")

	out, err := exec.Command("bash", "-c", "oc get pods -n logging -o wide | tr -s ' ' | cut -d ' ' -f 4").Output()
	if err != nil {
		msg := "Could not parse logging container restart count: " + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	isOk := true
	var msg string
	for _, l := range strings.Split(string(out), "\n") {
		if !strings.HasPrefix(l, "RESTARTS") && len(strings.TrimSpace(l)) > 0 {
			cnt, _ := strconv.Atoi(l)
			if cnt > 2 {
				msg = "A logging-container has restart count bigger than 2 - " + strconv.Itoa(cnt)
				isOk = false
			}
		}
	}

	if !isOk {
		return errors.New(msg)
	} else {
		return nil
	}
}

func CheckRouterRestartCount() error {
	log.Println("Checking router restart count")

	out, err := exec.Command("bash", "-c", "oc get po -n default | grep router | grep -v deploy | tr -s ' ' | cut -d ' ' -f 4").Output()
	if err != nil {
		msg := "Could not parse router restart count: " + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	isOk := true
	var msg string
	for _, l := range strings.Split(string(out), "\n") {
		if !strings.HasPrefix(l, "RESTARTS") && len(strings.TrimSpace(l)) > 0 {
			cnt, _ := strconv.Atoi(l)
			if cnt > 5 {
				msg = "A Router has restart count bigger than 5 - " + strconv.Itoa(cnt)
				isOk = false
			}
		}
	}

	if isOk {
		return nil
	} else {
		return errors.New(msg)
	}
}

func CheckEtcdHealth(etcdIps string, etcdCertPath string) error {
	log.Println("Checking etcd health")

	var msg string
	isOk := true

	if len(etcdCertPath) > 0 {
		// Check etcd with custom certs path
		isOk = checkEtcdHealthWithCertPath(&msg, etcdCertPath, etcdIps)

		if !isOk {
			log.Println("etcd health check with custom cert path failed, trying with default")

			// Check etcd with default certs path
			isOk = checkEtcdHealthWithCertPath(&msg, "/etc/etcd/", etcdIps)
		}
	} else {
		// Check etcd with default certs path
		isOk = checkEtcdHealthWithCertPath(&msg, "/etc/etcd/", etcdIps)
	}

	if !isOk {
		return errors.New(msg)
	} else {
		return nil
	}
}

func checkEtcdHealthWithCertPath(msg *string, certPath string, etcdIps string) bool {
	cmd := exec.Command("etcdctl", "--peers", etcdIps, "--ca-file", certPath+"ca.crt",
		"--key-file", certPath+"peer.key", "--cert-file", certPath+"peer.crt", "cluster-health")

	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		log.Println("error while running etcd health check", err)
		*msg = "etcd health check failed: " + err.Error()
		return false
	}

	stdOut := out.String()
	if strings.Contains(stdOut, "unhealthy") || strings.Contains(stdOut, "unreachable") {
		*msg += "Etcd health check was 'cluster unhealthy'"
		return false
	}

	return true
}

func CheckLimitsAndQuotas(allowedWithout int) error {
	log.Println("Checking limits & quotas")

	// Count projects
	projectCount, err := exec.Command("bash", "-c", "oc get projects  | wc -l").Output()
	if err != nil {
		msg := "Could not parse project count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Count limits
	limitCount, err := exec.Command("bash", "-c", "oc get limits --all-namespaces | wc -l").Output()
	if err != nil {
		msg := "Could not parse limit count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Count quotas
	quotaCount, err := exec.Command("bash", "-c", "oc get quota --all-namespaces | wc -l").Output()
	if err != nil {
		msg := "Could not parse quota count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Parse them
	pCount, err := strconv.Atoi(strings.TrimSpace(string(projectCount)))
	lCount, _ := strconv.Atoi(strings.TrimSpace(string(limitCount)))
	qCount, _ := strconv.Atoi(strings.TrimSpace(string(quotaCount)))

	log.Println("Parsed values (projects,limits,quotas)", pCount, lCount, qCount)

	if pCount-allowedWithout != lCount {
		return errors.New("There are some projects without limits")
	}
	if pCount-allowedWithout != qCount {
		return errors.New("There are some projects without quotas")
	}

	return nil
}
<commit_msg>Check capacity when node notready before alerting<commit_after>package checks

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"time"
)

func CheckMasterApis(urls string) error {
	log.Println("Checking master apis. At least one has to be up")

	urlArr := strings.Split(urls, ",")

	oneApiOk := false
	var msg string
	for _, u := range urlArr {
		if err := checkHttp(u); err == nil {
			oneApiOk = true
		} else {
			msg += u + " is not reachable. "
		}
	}

	if oneApiOk {
		return nil
	} else {
		return errors.New(msg)
	}
}

func CheckOcGetNodes() error {
	log.Println("Checking oc get nodes output")

	availablePodHardLimit, err := getAvailablePodHardLimit()
	if err != nil {
		return err
	}

	var notReadyCount int
	var out string
	for i := 0; i < 5; i++ {
		out, err := runOcGetNodes()
		if err != nil {
			return err
		}
		notReadyCount = nodesNotReady(out)
		if notReadyCount*100 < availablePodHardLimit {
			return nil
		}
		// wait a few seconds and then check again
		time.Sleep(10 * time.Second)
	}
	return fmt.Errorf("%v nodes are not ready! AvailablePodHardLimit: %v (notReady*100>=hardLimit) Output: %v", notReadyCount, availablePodHardLimit, out)
}

func getAvailablePodHardLimit() (int, error) {
	totalPods, err := getTotalPods()
	if err != nil {
		return 0, err
	}
	totalCapacity, err := getTotalPodCapacity()
	if err != nil {
		return 0, err
	}
	return totalCapacity - totalPods, nil
}

func nodesNotReady(output string) int {
	r := regexp.MustCompile("NotReady")
	matches := r.FindAllStringIndex(output, -1)
	return len(matches)
}

func getTotalPods() (int, error) {
	out, err := exec.Command("bash", "-c", "oc get pods --all-namespaces | grep -v Error | grep -v Completed | wc -l").Output()
	if err != nil {
		return 0, errors.New("Could not parse oc get pods output: " + err.Error())
	}
	i, err := strconv.Atoi(string(out))
	if err != nil {
		return 0, errors.New("Could not parse oc get pods output: " + err.Error())
	}
	return i, nil
}

func getTotalPodCapacity() (int, error) {
	out, err := exec.Command("bash", "-c", "oc describe nodes -l purpose=workingnode | grep Capacity -A4 | grep pods | awk '{ print $2 }' | paste -sd+ | bc").Output()
	if err != nil {
		return 0, errors.New("Could not parse oc describe nodes output: " + err.Error())
	}
	i, err := strconv.Atoi(string(out))
	if err != nil {
		return 0, errors.New("Could not parse oc describe nodes output: " + err.Error())
	}
	return i, nil
}

func runOcGetNodes() (string, error) {
	out, err := exec.Command("bash", "-c", "oc get nodes --show-labels | grep -v monitoring=false | grep -v SchedulingDisabled").Output()
	if err != nil {
		msg := "Could not parse oc get nodes output: " + err.Error()
		log.Println(msg)
		return "", errors.New(msg)
	}
	return string(out), nil
}

func CheckDnsNslookupOnKubernetes() error {
	log.Println("Checking nslookup to kubernetes ip")

	cmd := exec.Command("nslookup", daemonDNSEndpoint+".", kubernetesIP)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		msg := "DNS resolution via nslookup & kubernetes failed." + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	stdOut := out.String()

	if strings.Contains(stdOut, "Server") && strings.Count(stdOut, "Address") >= 2 && strings.Contains(stdOut, "Name") {
		return nil
	} else {
		return errors.New("Problem with dns to kubernetes. nsLookup had wrong output")
	}
}

func CheckDnsServiceNode() error {
	log.Println("Checking dns to a openshift service")

	ips := getIpsForName(daemonDNSServiceA)

	if ips == nil {
		return errors.New("Failed to lookup ip on node (dnsmasq) for name " + daemonDNSServiceA)
	} else {
		return nil
	}
}

func CheckDnsInPod() error {
	log.Println("Checking dns to a openshift service inside a pod")

	ips := getIpsForName(daemonDNSPod)

	if ips == nil {
		return errors.New("Failed to lookup ip in pod for name " + daemonDNSPod)
	} else {
		return nil
	}
}

func CheckPodHttpAtoB() error {
	log.Println("Checking if http connection does not work if network not joined")

	// This should fail as we do not have access to this project
	if err := checkHttp("http://" + daemonDNSServiceB + ":8090/hello"); err == nil {
		errors.New("Pod A could access pod b. This should not be allowed!")
	}

	return nil
}

func CheckPodHttpAtoC(slow bool) error {
	log.Println("Checking if http connection does work with joined network")

	if err := checkHttp("http://" + daemonDNSServiceC + ":8090/" + getEndpoint(slow)); err != nil {
		return errors.New("Pod A could access pod C. This should not work. Route/Router problem?")
	}

	return nil
}

func CheckHttpService(slow bool) error {
	errA := checkHttp("http://" + daemonDNSServiceA + ":8090/" + getEndpoint(slow))
	errB := checkHttp("http://" + daemonDNSServiceB + ":8090/" + getEndpoint(slow))
	errC := checkHttp("http://" + daemonDNSServiceC + ":8090/" + getEndpoint(slow))

	if errA != nil || errB != nil || errC != nil {
		msg := "Could not reach one of the services (a/b/c)"
		log.Println(msg)
		return errors.New(msg)
	}

	return nil
}

func CheckHttpHaProxy(publicUrl string, slow bool) error {
	log.Println("Checking http via HA-Proxy")

	if err := checkHttp(publicUrl + ":80/" + getEndpoint(slow)); err != nil {
		return errors.New("Could not access pods via haproxy. Route/Router problem?")
	}

	return nil
}

func CheckRegistryHealth(ip string) error {
	log.Println("Checking registry health")

	if err := checkHttp("http://" + ip + ":5000/healthz"); err != nil {
		time.Sleep(10 * time.Second)

		if err2 := checkHttp("http://" + ip + ":5000/healthz"); err2 != nil {
			return fmt.Errorf("Registry health check failed. %v", err2.Error())
		}
	}

	return nil
}

func CheckHawcularHealth(ip string) error {
	log.Println("Checking metrics health")

	if err := checkHttp("https://" + ip + ":443"); err != nil {
		return errors.New("Hawcular health check failed")
	}

	return nil
}

func CheckRouterHealth(ip string) error {
	log.Println("Checking router health", ip)

	if err := checkHttp("http://" + ip + ":1936/healthz"); err != nil {
		time.Sleep(10 * time.Second)

		if err2 := checkHttp("http://" + ip + ":5000/healthz"); err2 != nil {
			return fmt.Errorf("Router health check failed for %v, %v", ip, err2.Error())
		}
	}

	return nil
}

func CheckLoggingRestartsCount() error {
	log.Println("Checking log-container restart count")

	out, err := exec.Command("bash", "-c", "oc get pods -n logging -o wide | tr -s ' ' | cut -d ' ' -f 4").Output()
	if err != nil {
		msg := "Could not parse logging container restart count: " + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	isOk := true
	var msg string
	for _, l := range strings.Split(string(out), "\n") {
		if !strings.HasPrefix(l, "RESTARTS") && len(strings.TrimSpace(l)) > 0 {
			cnt, _ := strconv.Atoi(l)
			if cnt > 2 {
				msg = "A logging-container has restart count bigger than 2 - " + strconv.Itoa(cnt)
				isOk = false
			}
		}
	}

	if !isOk {
		return errors.New(msg)
	} else {
		return nil
	}
}

func CheckRouterRestartCount() error {
	log.Println("Checking router restart count")

	out, err := exec.Command("bash", "-c", "oc get po -n default | grep router | grep -v deploy | tr -s ' ' | cut -d ' ' -f 4").Output()
	if err != nil {
		msg := "Could not parse router restart count: " + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	isOk := true
	var msg string
	for _, l := range strings.Split(string(out), "\n") {
		if !strings.HasPrefix(l, "RESTARTS") && len(strings.TrimSpace(l)) > 0 {
			cnt, _ := strconv.Atoi(l)
			if cnt > 5 {
				msg = "A Router has restart count bigger than 5 - " + strconv.Itoa(cnt)
				isOk = false
			}
		}
	}

	if isOk {
		return nil
	} else {
		return errors.New(msg)
	}
}

func CheckEtcdHealth(etcdIps string, etcdCertPath string) error {
	log.Println("Checking etcd health")

	var msg string
	isOk := true

	if len(etcdCertPath) > 0 {
		// Check etcd with custom certs path
		isOk = checkEtcdHealthWithCertPath(&msg, etcdCertPath, etcdIps)

		if !isOk {
			log.Println("etcd health check with custom cert path failed, trying with default")

			// Check etcd with default certs path
			isOk = checkEtcdHealthWithCertPath(&msg, "/etc/etcd/", etcdIps)
		}
	} else {
		// Check etcd with default certs path
		isOk = checkEtcdHealthWithCertPath(&msg, "/etc/etcd/", etcdIps)
	}

	if !isOk {
		return errors.New(msg)
	} else {
		return nil
	}
}

func checkEtcdHealthWithCertPath(msg *string, certPath string, etcdIps string) bool {
	cmd := exec.Command("etcdctl", "--peers", etcdIps, "--ca-file", certPath+"ca.crt",
		"--key-file", certPath+"peer.key", "--cert-file", certPath+"peer.crt", "cluster-health")

	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		log.Println("error while running etcd health check", err)
		*msg = "etcd health check failed: " + err.Error()
		return false
	}

	stdOut := out.String()
	if strings.Contains(stdOut, "unhealthy") || strings.Contains(stdOut, "unreachable") {
		*msg += "Etcd health check was 'cluster unhealthy'"
		return false
	}

	return true
}

func CheckLimitsAndQuotas(allowedWithout int) error {
	log.Println("Checking limits & quotas")

	// Count projects
	projectCount, err := exec.Command("bash", "-c", "oc get projects  | wc -l").Output()
	if err != nil {
		msg := "Could not parse project count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Count limits
	limitCount, err := exec.Command("bash", "-c", "oc get limits --all-namespaces | wc -l").Output()
	if err != nil {
		msg := "Could not parse limit count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Count quotas
	quotaCount, err := exec.Command("bash", "-c", "oc get quota --all-namespaces | wc -l").Output()
	if err != nil {
		msg := "Could not parse quota count" + err.Error()
		log.Println(msg)
		return errors.New(msg)
	}

	// Parse them
	pCount, err := strconv.Atoi(strings.TrimSpace(string(projectCount)))
	lCount, _ := strconv.Atoi(strings.TrimSpace(string(limitCount)))
	qCount, _ := strconv.Atoi(strings.TrimSpace(string(quotaCount)))

	log.Println("Parsed values (projects,limits,quotas)", pCount, lCount, qCount)

	if pCount-allowedWithout != lCount {
		return errors.New("There are some projects without limits")
	}
	if pCount-allowedWithout != qCount {
		return errors.New("There are some projects without quotas")
	}

	return nil
}
<|endoftext|>


<commit_before>package example

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	_ "net/http/pprof"
	"testing"
	"time"

	"github.com/jjeffcaii/engine.io"
)

var server eio.Engine

func init() {
	flag.Parse()
	server = eio.NewEngineBuilder().Build()
	http.HandleFunc("/conns", func(writer http.ResponseWriter, request *http.Request) {
		writer.WriteHeader(http.StatusOK)
		writer.Write([]byte(fmt.Sprintf("totals: %d", server.CountClients())))
	})
}

func TestNothing(t *testing.T) {
	server.OnConnect(func(socket eio.Socket) {
		log.Println("========> socket connect:", socket.ID())
		socket.OnMessage(func(data []byte) {
			// do nothing.
			log.Println("===> got message:", string(data))
		})
		socket.OnClose(func(reason string) {
			log.Println("========> socket closed:", socket.ID())
		})
	})
	http.HandleFunc(eio.DefaultPath, server.Router())
	log.Fatalln(http.ListenAndServe(":3000", nil))
}

func TestEcho(t *testing.T) {
	server.OnConnect(func(socket eio.Socket) {
		socket.OnMessage(func(data []byte) {
			socket.Send(fmt.Sprintf("ECHO: %s", data))
		})
		socket.OnUpgrade(func() {
			log.Println("socket", socket.ID(), "upgrade success")
		})
	})
	log.Fatalln(server.Listen(":3000"))
}

func TestEchoAndBrd(t *testing.T) {
	tick := time.NewTicker(5 * time.Second)
	kill := make(chan uint8)
	go func() {
		for {
			select {
			case <-tick.C:
				for _, it := range server.GetClients() {
					it.Send(fmt.Sprintf("AUTO_BRD: %s", time.Now().Format(time.RFC3339)))
				}
				break
			case <-kill:
				tick.Stop()
				return
			}
		}
	}()

	defer func() {
		close(kill)
		server.Close()
	}()
	server.OnConnect(func(socket eio.Socket) {
		//log.Println("========> socket connect:", socket.ID())
		socket.OnMessage(func(data []byte) {
			socket.Send(fmt.Sprintf("ECHO1: %s", data))
			socket.Send(fmt.Sprintf("ECHO2: %s", data))
		})
		socket.OnClose(func(reason string) {
			//log.Println("========> socket closed:", socket.ID())
		})
	})
	log.Fatalln(server.Listen(":3000"))
}
<commit_msg>add simple test page<commit_after>package example

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	_ "net/http/pprof"
	"testing"

	"github.com/jjeffcaii/engine.io"
)

var server eio.Engine

func init() {
	flag.Parse()
	server = eio.NewEngineBuilder().Build()
	http.HandleFunc("/conns", func(writer http.ResponseWriter, request *http.Request) {
		writer.WriteHeader(http.StatusOK)
		writer.Write([]byte(fmt.Sprintf("totals: %d", server.CountClients())))
	})
	var indexHtml = `

        polling

        jsonp

        websocket

        auto

	`
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("Content-Type", "text/html; charset=UTF-8")
		writer.Write([]byte(indexHtml))
	})
}

func TestEchoServer(t *testing.T) {
	server.OnConnect(func(socket eio.Socket) {
		socket.OnMessage(func(data []byte) {
			socket.Send(fmt.Sprintf("[ECHO] %s", data))
		})
	})
	log.Fatalln(server.Listen(":3000"))
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/md5"
	"fmt"
	"net"
	"regexp"
	"strings"
	"sync"
	"syscall"
	"time"
	"net/http"
	"io/ioutil"
	"encoding/base64"
	"errors"

	"golang.org/x/crypto/ssh"
)

const (
	maxNameLength        = 32
	historyLength        = 20
	systemMessageFormat  = "\033[1;90m"
	privateMessageFormat = "\033[1m"
	highlightFormat      = Bold + "\033[48;5;11m\033[38;5;16m"
	beep                 = "\007"
)

var (
	reStripText = regexp.MustCompile("[^0-9A-Za-z_.-]")
)

// Clients is a map of clients
type Clients map[string]*Client

// Server holds all the fields used by a server
type Server struct {
	sshConfig *ssh.ServerConfig
	done      chan struct{}
	clients   Clients
	count     int
	history   *History
	motd      string
	whitelist map[string]struct{}   // fingerprint lookup
	admins    map[string]struct{}   // fingerprint lookup
	bannedPK  map[string]*time.Time // fingerprint lookup
	started   time.Time
	sync.Mutex
}

// NewServer constructs a new server
func NewServer(privateKey []byte) (*Server, error) {
	signer, err := ssh.ParsePrivateKey(privateKey)
	if err != nil {
		return nil, err
	}

	server := Server{
		done:      make(chan struct{}),
		clients:   Clients{},
		count:     0,
		history:   NewHistory(historyLength),
		motd:      "",
		whitelist: map[string]struct{}{},
		admins:    map[string]struct{}{},
		bannedPK:  map[string]*time.Time{},
		started:   time.Now(),
	}

	config := ssh.ServerConfig{
		NoClientAuth: false,
		// Auth-related things should be constant-time to avoid timing attacks.
		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
			fingerprint := Fingerprint(key)
			if server.IsBanned(fingerprint) {
				return nil, fmt.Errorf("Banned.")
			}
			if !server.IsWhitelisted(fingerprint) {
				return nil, fmt.Errorf("Not Whitelisted.")
			}
			perm := &ssh.Permissions{Extensions: map[string]string{"fingerprint": fingerprint}}
			return perm, nil
		},
	}
	config.AddHostKey(signer)

	server.sshConfig = &config

	return &server, nil
}

// Len returns the number of clients
func (s *Server) Len() int {
	return len(s.clients)
}

// SysMsg broadcasts the given message to everyone
func (s *Server) SysMsg(msg string, args ...interface{}) {
	s.Broadcast(ContinuousFormat(systemMessageFormat, " * "+fmt.Sprintf(msg, args...)), nil)
}

// Broadcast broadcasts the given message to everyone except for the given client
func (s *Server) Broadcast(msg string, except *Client) {
	logger.Debugf("Broadcast to %d: %s", s.Len(), msg)
	s.history.Add(msg)

	for _, client := range s.clients {
		if except != nil && client == except {
			continue
		}

		if strings.Contains(msg, client.Name) {
			// Turn message red if client's name is mentioned, and send BEL if they have enabled beeping
			personalMsg := strings.Replace(msg, client.Name, highlightFormat+client.Name+Reset, -1)
			if client.beepMe {
				personalMsg += beep
			}
			client.Send(personalMsg)
		} else {
			client.Send(msg)
		}
	}
}

// Privmsg sends a message to a particular nick, if it exists
func (s *Server) Privmsg(nick, message string, sender *Client) error {
	// Get the recipient
	target, ok := s.clients[strings.ToLower(nick)]
	if !ok {
		return fmt.Errorf("no client with that nick")
	}
	// Send the message
	target.Msg <- fmt.Sprintf(beep+"[PM from %v] %s%v%s", sender.ColoredName(), privateMessageFormat, message, Reset)
	logger.Debugf("PM from %v to %v: %v", sender.Name, nick, message)
	return nil
}

// SetMotd sets the Message of the Day (MOTD)
func (s *Server) SetMotd(motd string) {
	s.Lock()
	s.motd = motd
	s.Unlock()
}

// MotdUnicast sends the MOTD as a SysMsg
func (s *Server) MotdUnicast(client *Client) {
	if s.motd == "" {
		return
	}
	client.SysMsg(s.motd)
}

// MotdBroadcast broadcasts the MOTD
func (s *Server) MotdBroadcast(client *Client) {
	if s.motd == "" {
		return
	}
	s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * New MOTD set by %s.", client.ColoredName())), client)
	s.Broadcast(s.motd, client)
}

// Add adds the client to the list of clients
func (s *Server) Add(client *Client) {
	go func() {
		s.MotdUnicast(client)
		client.SendLines(s.history.Get(10))
	}()

	s.Lock()
	s.count++

	newName, err := s.proposeName(client.Name)
	if err != nil {
		client.SysMsg("Your name '%s' is not available, renamed to '%s'. Use /nick <name> to change it.", client.Name, ColorString(client.Color, newName))
	}

	client.Rename(newName)
	s.clients[strings.ToLower(client.Name)] = client
	num := len(s.clients)
	s.Unlock()

	s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * %s joined. (Total connected: %d)", client.Name, num)), client)
}

// Remove removes the given client from the list of clients
func (s *Server) Remove(client *Client) {
	s.Lock()
	delete(s.clients, strings.ToLower(client.Name))
	s.Unlock()

	s.SysMsg("%s left.", client.Name)
}

func (s *Server) proposeName(name string) (string, error) {
	// Assumes caller holds lock.
	var err error
	name = reStripText.ReplaceAllString(name, "")

	if len(name) > maxNameLength {
		name = name[:maxNameLength]
	} else if len(name) == 0 {
		name = fmt.Sprintf("Guest%d", s.count)
	}

	_, collision := s.clients[strings.ToLower(name)]
	if collision {
		err = fmt.Errorf("%s is not available", name)
		name = fmt.Sprintf("Guest%d", s.count)
	}

	return name, err
}

// Rename renames the given client (user)
func (s *Server) Rename(client *Client, newName string) {
	s.Lock()
	var oldName string
	if strings.ToLower(newName) == strings.ToLower(client.Name) {
		oldName = client.Name
		client.Rename(newName)
	} else {
		newName, err := s.proposeName(newName)
		if err != nil {
			client.SysMsg("%s", err)
			s.Unlock()
			return
		}

		// TODO: Use a channel/goroutine for adding clients, rather than locks?
		delete(s.clients, strings.ToLower(client.Name))
		oldName = client.Name
		client.Rename(newName)
		s.clients[strings.ToLower(client.Name)] = client
		s.Unlock()
	}
	s.SysMsg("%s is now known as %s.", ColorString(client.Color, oldName), ColorString(client.Color, newName))
}

// List lists the clients with the given prefix
func (s *Server) List(prefix *string) []string {
	r := []string{}

	for name := range s.clients {
		if prefix != nil && !strings.HasPrefix(name, *prefix) {
			continue
		}
		r = append(r, name)
	}

	return r
}

// Who returns the client with a given name
func (s *Server) Who(name string) *Client {
	return s.clients[strings.ToLower(name)]
}

// Op adds the given fingerprint to the list of admins
func (s *Server) Op(fingerprint string) {
	logger.Infof("Adding admin: %s", fingerprint)
	s.Lock()
	s.admins[fingerprint] = struct{}{}
	s.Unlock()
}

// Whitelist adds the given fingerprint to the whitelist
func (s *Server) Whitelist(fingerprint string) error {
	if strings.HasPrefix(fingerprint, "github.com/") {
		logger.Infof("Adding github account %s to whitelist", fingerprint)

		keys, err := getGithubKey(fingerprint)
		if err != nil {
			return err
		}
		if len(keys) == 0 {
			return errors.New(fmt.Sprintf("No github user %s", fingerprint))
		}
		for _, key := range keys {
			fingerprint = Fingerprint(key)
			logger.Infof("Adding whitelist: %s", fingerprint)
			s.Lock()
			s.whitelist[fingerprint] = struct{}{}
			s.Unlock()
		}
	} else {
		logger.Infof("Adding whitelist: %s", fingerprint)
		s.Lock()
		s.whitelist[fingerprint] = struct{}{}
		s.Unlock()
	}
	return nil
}

var r *regexp.Regexp = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`)
func getGithubKey(url string) ([]ssh.PublicKey, error) {
	resp, err := http.Get("http://" + url + ".keys")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	body_str := string(body)
	keys := r.FindAllStringSubmatch(body_str, -1)
	pubs := make([]ssh.PublicKey, 0, 3)
	for _, key := range keys {
		if(len(key) < 2) {
			continue
		}

		body_decoded, err := base64.StdEncoding.DecodeString(key[1])
		if err != nil {
			return nil, err
		}

		pub, err := ssh.ParsePublicKey(body_decoded)
		if err != nil {
			return nil, err
		}

		pubs = append(pubs, pub)
	}
	return pubs, nil
}

// Uptime returns the time since the server was started
func (s *Server) Uptime() string {
	return time.Now().Sub(s.started).String()
}

// IsOp checks if the given client is Op
func (s *Server) IsOp(client *Client) bool {
	_, r := s.admins[client.Fingerprint()]
	return r
}

// IsWhitelisted checks if the given fingerprint is whitelisted
func (s *Server) IsWhitelisted(fingerprint string) bool {
	/* if no whitelist, anyone is welcome */
	if len(s.whitelist) == 0 {
		return true
	}

	/* otherwise, check for whitelist presence */
	_, r := s.whitelist[fingerprint]
	return r
}

// IsBanned checks if the given fingerprint is banned
func (s *Server) IsBanned(fingerprint string) bool {
	ban, hasBan := s.bannedPK[fingerprint]
	if !hasBan {
		return false
	}
	if ban == nil {
		return true
	}
	if ban.Before(time.Now()) {
		s.Unban(fingerprint)
		return false
	}
	return true
}

// Ban bans a fingerprint for the given duration
func (s *Server) Ban(fingerprint string, duration *time.Duration) {
	var until *time.Time
	s.Lock()
	if duration != nil {
		when := time.Now().Add(*duration)
		until = &when
	}
	s.bannedPK[fingerprint] = until
	s.Unlock()
}

// Unban unbans a banned fingerprint
func (s *Server) Unban(fingerprint string) {
	s.Lock()
	delete(s.bannedPK, fingerprint)
	s.Unlock()
}

// Start starts the server
func (s *Server) Start(laddr string) error {
	// Once a ServerConfig has been configured, connections can be
	// accepted.
	socket, err := net.Listen("tcp", laddr)
	if err != nil {
		return err
	}

	logger.Infof("Listening on %s", laddr)

	go func() {
		defer socket.Close()
		for {
			conn, err := socket.Accept()

			if err != nil {
				logger.Errorf("Failed to accept connection: %v", err)
				if err == syscall.EINVAL {
					// TODO: Handle shutdown more gracefully?
					return
				}
			}

			// Goroutineify to resume accepting sockets early.
			go func() {
				// From a standard TCP connection to an encrypted SSH connection
				sshConn, channels, requests, err := ssh.NewServerConn(conn, s.sshConfig)
				if err != nil {
					logger.Errorf("Failed to handshake: %v", err)
					return
				}

				version := reStripText.ReplaceAllString(string(sshConn.ClientVersion()), "")
				if len(version) > 100 {
					version = "Evil Jerk with a superlong string"
				}
				logger.Infof("Connection #%d from: %s, %s, %s", s.count+1, sshConn.RemoteAddr(), sshConn.User(), version)

				go ssh.DiscardRequests(requests)

				client := NewClient(s, sshConn)
				go client.handleChannels(channels)
			}()
		}
	}()

	go func() {
		<-s.done
		socket.Close()
	}()

	return nil
}

// AutoCompleteFunction handles auto completion of nicks
func (s *Server) AutoCompleteFunction(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
	if key == 9 {
		shortLine := strings.Split(line[:pos], " ")
		partialNick := shortLine[len(shortLine)-1]

		nicks := s.List(&partialNick)
		if len(nicks) > 0 {
			nick := nicks[len(nicks)-1]
			posPartialNick := pos - len(partialNick)
			if len(shortLine) < 2 {
				nick += ": "
			} else {
				nick += " "
			}
			newLine = strings.Replace(line[posPartialNick:],
				partialNick, nick, 1)
			newLine = line[:posPartialNick] + newLine
			newPos = pos + (len(nick) - len(partialNick))
			ok = true
		}
	} else {
		ok = false
	}
	return
}

// Stop stops the server
func (s *Server) Stop() {
	for _, client := range s.clients {
		client.Conn.Close()
	}

	close(s.done)
}

// Fingerprint returns the fingerprint based on a public key
func Fingerprint(k ssh.PublicKey) string {
	hash := md5.Sum(k.Marshal())
	r := fmt.Sprintf("% x", hash)
	return strings.Replace(r, " ", ":", -1)
}
<commit_msg>Fixed issues reported by golint.<commit_after>package main

import (
	"crypto/md5"
	"fmt"
	"net"
	"regexp"
	"strings"
	"sync"
	"syscall"
	"time"
	"net/http"
	"io/ioutil"
	"encoding/base64"

	"golang.org/x/crypto/ssh"
)

const (
	maxNameLength        = 32
	historyLength        = 20
	systemMessageFormat  = "\033[1;90m"
	privateMessageFormat = "\033[1m"
	highlightFormat      = Bold + "\033[48;5;11m\033[38;5;16m"
	beep                 = "\007"
)

var (
	reStripText = regexp.MustCompile("[^0-9A-Za-z_.-]")
)

// Clients is a map of clients
type Clients map[string]*Client

// Server holds all the fields used by a server
type Server struct {
	sshConfig *ssh.ServerConfig
	done      chan struct{}
	clients   Clients
	count     int
	history   *History
	motd      string
	whitelist map[string]struct{}   // fingerprint lookup
	admins    map[string]struct{}   // fingerprint lookup
	bannedPK  map[string]*time.Time // fingerprint lookup
	started   time.Time
	sync.Mutex
}

// NewServer constructs a new server
func NewServer(privateKey []byte) (*Server, error) {
	signer, err := ssh.ParsePrivateKey(privateKey)
	if err != nil {
		return nil, err
	}

	server := Server{
		done:      make(chan struct{}),
		clients:   Clients{},
		count:     0,
		history:   NewHistory(historyLength),
		motd:      "",
		whitelist: map[string]struct{}{},
		admins:    map[string]struct{}{},
		bannedPK:  map[string]*time.Time{},
		started:   time.Now(),
	}

	config := ssh.ServerConfig{
		NoClientAuth: false,
		// Auth-related things should be constant-time to avoid timing attacks.
		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
			fingerprint := Fingerprint(key)
			if server.IsBanned(fingerprint) {
				return nil, fmt.Errorf("Banned.")
			}
			if !server.IsWhitelisted(fingerprint) {
				return nil, fmt.Errorf("Not Whitelisted.")
			}
			perm := &ssh.Permissions{Extensions: map[string]string{"fingerprint": fingerprint}}
			return perm, nil
		},
	}
	config.AddHostKey(signer)

	server.sshConfig = &config

	return &server, nil
}

// Len returns the number of clients
func (s *Server) Len() int {
	return len(s.clients)
}

// SysMsg broadcasts the given message to everyone
func (s *Server) SysMsg(msg string, args ...interface{}) {
	s.Broadcast(ContinuousFormat(systemMessageFormat, " * "+fmt.Sprintf(msg, args...)), nil)
}

// Broadcast broadcasts the given message to everyone except for the given client
func (s *Server) Broadcast(msg string, except *Client) {
	logger.Debugf("Broadcast to %d: %s", s.Len(), msg)
	s.history.Add(msg)

	for _, client := range s.clients {
		if except != nil && client == except {
			continue
		}

		if strings.Contains(msg, client.Name) {
			// Turn message red if client's name is mentioned, and send BEL if they have enabled beeping
			personalMsg := strings.Replace(msg, client.Name, highlightFormat+client.Name+Reset, -1)
			if client.beepMe {
				personalMsg += beep
			}
			client.Send(personalMsg)
		} else {
			client.Send(msg)
		}
	}
}

// Privmsg sends a message to a particular nick, if it exists
func (s *Server) Privmsg(nick, message string, sender *Client) error {
	// Get the recipient
	target, ok := s.clients[strings.ToLower(nick)]
	if !ok {
		return fmt.Errorf("no client with that nick")
	}
	// Send the message
	target.Msg <- fmt.Sprintf(beep+"[PM from %v] %s%v%s", sender.ColoredName(), privateMessageFormat, message, Reset)
	logger.Debugf("PM from %v to %v: %v", sender.Name, nick, message)
	return nil
}

// SetMotd sets the Message of the Day (MOTD)
func (s *Server) SetMotd(motd string) {
	s.Lock()
	s.motd = motd
	s.Unlock()
}

// MotdUnicast sends the MOTD as a SysMsg
func (s *Server) MotdUnicast(client *Client) {
	if s.motd == "" {
		return
	}
	client.SysMsg(s.motd)
}

// MotdBroadcast broadcasts the MOTD
func (s *Server) MotdBroadcast(client *Client) {
	if s.motd == "" {
		return
	}
	s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * New MOTD set by %s.", client.ColoredName())), client)
	s.Broadcast(s.motd, client)
}

// Add adds the client to the list of clients
func (s *Server) Add(client *Client) {
	go func() {
		s.MotdUnicast(client)
		client.SendLines(s.history.Get(10))
	}()

	s.Lock()
	s.count++

	newName, err := s.proposeName(client.Name)
	if err != nil {
		client.SysMsg("Your name '%s' is not available, renamed to '%s'. Use /nick <name> to change it.", client.Name, ColorString(client.Color, newName))
	}

	client.Rename(newName)
	s.clients[strings.ToLower(client.Name)] = client
	num := len(s.clients)
	s.Unlock()

	s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * %s joined. (Total connected: %d)", client.Name, num)), client)
}

// Remove removes the given client from the list of clients
func (s *Server) Remove(client *Client) {
	s.Lock()
	delete(s.clients, strings.ToLower(client.Name))
	s.Unlock()

	s.SysMsg("%s left.", client.Name)
}

func (s *Server) proposeName(name string) (string, error) {
	// Assumes caller holds lock.
	var err error
	name = reStripText.ReplaceAllString(name, "")

	if len(name) > maxNameLength {
		name = name[:maxNameLength]
	} else if len(name) == 0 {
		name = fmt.Sprintf("Guest%d", s.count)
	}

	_, collision := s.clients[strings.ToLower(name)]
	if collision {
		err = fmt.Errorf("%s is not available", name)
		name = fmt.Sprintf("Guest%d", s.count)
	}

	return name, err
}

// Rename renames the given client (user)
func (s *Server) Rename(client *Client, newName string) {
	s.Lock()
	var oldName string
	if strings.ToLower(newName) == strings.ToLower(client.Name) {
		oldName = client.Name
		client.Rename(newName)
	} else {
		newName, err := s.proposeName(newName)
		if err != nil {
			client.SysMsg("%s", err)
			s.Unlock()
			return
		}

		// TODO: Use a channel/goroutine for adding clients, rather than locks?
		delete(s.clients, strings.ToLower(client.Name))
		oldName = client.Name
		client.Rename(newName)
		s.clients[strings.ToLower(client.Name)] = client
		s.Unlock()
	}
	s.SysMsg("%s is now known as %s.", ColorString(client.Color, oldName), ColorString(client.Color, newName))
}

// List lists the clients with the given prefix
func (s *Server) List(prefix *string) []string {
	r := []string{}

	for name := range s.clients {
		if prefix != nil && !strings.HasPrefix(name, *prefix) {
			continue
		}
		r = append(r, name)
	}

	return r
}

// Who returns the client with a given name
func (s *Server) Who(name string) *Client {
	return s.clients[strings.ToLower(name)]
}

// Op adds the given fingerprint to the list of admins
func (s *Server) Op(fingerprint string) {
	logger.Infof("Adding admin: %s", fingerprint)
	s.Lock()
	s.admins[fingerprint] = struct{}{}
	s.Unlock()
}

// Whitelist adds the given fingerprint to the whitelist
func (s *Server) Whitelist(fingerprint string) error {
	if strings.HasPrefix(fingerprint, "github.com/") {
		logger.Infof("Adding github account %s to whitelist", fingerprint)

		keys, err := getGithubKey(fingerprint)
		if err != nil {
			return err
		}
		if len(keys) == 0 {
			return fmt.Errorf("No github user %s", fingerprint)
		}
		for _, key := range keys {
			fingerprint = Fingerprint(key)
			logger.Infof("Adding whitelist: %s", fingerprint)
			s.Lock()
			s.whitelist[fingerprint] = struct{}{}
			s.Unlock()
		}
	} else {
		logger.Infof("Adding whitelist: %s", fingerprint)
		s.Lock()
		s.whitelist[fingerprint] = struct{}{}
		s.Unlock()
	}
	return nil
}

var r = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`)
func getGithubKey(url string) ([]ssh.PublicKey, error) {
	resp, err := http.Get("http://" + url + ".keys")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	bodyStr := string(body)
	keys := r.FindAllStringSubmatch(bodyStr, -1)
	pubs := make([]ssh.PublicKey, 0, 3)
	for _, key := range keys {
		if(len(key) < 2) {
			continue
		}

		bodyDecoded, err := base64.StdEncoding.DecodeString(key[1])
		if err != nil {
			return nil, err
		}

		pub, err := ssh.ParsePublicKey(bodyDecoded)
		if err != nil {
			return nil, err
		}

		pubs = append(pubs, pub)
	}
	return pubs, nil
}

// Uptime returns the time since the server was started
func (s *Server) Uptime() string {
	return time.Now().Sub(s.started).String()
}

// IsOp checks if the given client is Op
func (s *Server) IsOp(client *Client) bool {
	_, r := s.admins[client.Fingerprint()]
	return r
}

// IsWhitelisted checks if the given fingerprint is whitelisted
func (s *Server) IsWhitelisted(fingerprint string) bool {
	/* if no whitelist, anyone is welcome */
	if len(s.whitelist) == 0 {
		return true
	}

	/* otherwise, check for whitelist presence */
	_, r := s.whitelist[fingerprint]
	return r
}

// IsBanned checks if the given fingerprint is banned
func (s *Server) IsBanned(fingerprint string) bool {
	ban, hasBan := s.bannedPK[fingerprint]
	if !hasBan {
		return false
	}
	if ban == nil {
		return true
	}
	if ban.Before(time.Now()) {
		s.Unban(fingerprint)
		return false
	}
	return true
}

// Ban bans a fingerprint for the given duration
func (s *Server) Ban(fingerprint string, duration *time.Duration) {
	var until *time.Time
	s.Lock()
	if duration != nil {
		when := time.Now().Add(*duration)
		until = &when
	}
	s.bannedPK[fingerprint] = until
	s.Unlock()
}

// Unban unbans a banned fingerprint
func (s *Server) Unban(fingerprint string) {
	s.Lock()
	delete(s.bannedPK, fingerprint)
	s.Unlock()
}

// Start starts the server
func (s *Server) Start(laddr string) error {
	// Once a ServerConfig has been configured, connections can be
	// accepted.
	socket, err := net.Listen("tcp", laddr)
	if err != nil {
		return err
	}

	logger.Infof("Listening on %s", laddr)

	go func() {
		defer socket.Close()
		for {
			conn, err := socket.Accept()

			if err != nil {
				logger.Errorf("Failed to accept connection: %v", err)
				if err == syscall.EINVAL {
					// TODO: Handle shutdown more gracefully?
					return
				}
			}

			// Goroutineify to resume accepting sockets early.
			go func() {
				// From a standard TCP connection to an encrypted SSH connection
				sshConn, channels, requests, err := ssh.NewServerConn(conn, s.sshConfig)
				if err != nil {
					logger.Errorf("Failed to handshake: %v", err)
					return
				}

				version := reStripText.ReplaceAllString(string(sshConn.ClientVersion()), "")
				if len(version) > 100 {
					version = "Evil Jerk with a superlong string"
				}
				logger.Infof("Connection #%d from: %s, %s, %s", s.count+1, sshConn.RemoteAddr(), sshConn.User(), version)

				go ssh.DiscardRequests(requests)

				client := NewClient(s, sshConn)
				go client.handleChannels(channels)
			}()
		}
	}()

	go func() {
		<-s.done
		socket.Close()
	}()

	return nil
}

// AutoCompleteFunction handles auto completion of nicks
func (s *Server) AutoCompleteFunction(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
	if key == 9 {
		shortLine := strings.Split(line[:pos], " ")
		partialNick := shortLine[len(shortLine)-1]

		nicks := s.List(&partialNick)
		if len(nicks) > 0 {
			nick := nicks[len(nicks)-1]
			posPartialNick := pos - len(partialNick)
			if len(shortLine) < 2 {
				nick += ": "
			} else {
				nick += " "
			}
			newLine = strings.Replace(line[posPartialNick:],
				partialNick, nick, 1)
			newLine = line[:posPartialNick] + newLine
			newPos = pos + (len(nick) - len(partialNick))
			ok = true
		}
	} else {
		ok = false
	}
	return
}

// Stop stops the server
func (s *Server) Stop() {
	for _, client := range s.clients {
		client.Conn.Close()
	}

	close(s.done)
}

// Fingerprint returns the fingerprint based on a public key
func Fingerprint(k ssh.PublicKey) string {
	hash := md5.Sum(k.Marshal())
	r := fmt.Sprintf("% x", hash)
	return strings.Replace(r, " ", ":", -1)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/csv"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
)

func main() {
	// log, err := syslog.New(syslog.LOG_ERR, "godir")
	// if err != nil {
	// 	panic("Could connect to syslog")
	// }
	// log.Info("Starting godir")
	address := flag.String("address", "127.0.0.1", "The address to listen on")
	path := flag.String("path", "", "Path to the csv of redirects")
	port := flag.String("port", "8080", "The port to listen on")
	// watch := flag.Bool("watch", false, "Watch for CSV file changes")
	flag.Parse()
	if *path == "" {
		log.Fatalln("You must supply a mapping file")
	}
	fmt.Println("Starting godir...")
	rules := make(map[string]string)
	f, err := os.Open(*path)
	defer f.Close()
	if err != nil {
		panic("Cant read file")
	}

	reader := csv.NewReader(f)

	for {
		row, err := reader.Read()
		// stop at end of file
		if err == io.EOF {
			break
		}
		// ignore comments
		if strings.HasPrefix(row[0], "#") {
			continue
		}
		// build the rules map
		rules[row[0]] = row[1]
		// ignore empty lines
		if len(row) == 0 {
			break
		}
	}
	http.HandleFunc("/", handler(redirectHandler, rules))
	log.Fatal(http.ListenAndServe(*address+":"+*port, nil))
	// reqpath := strings.Trim(req.URL.Path, "/")
}

func redirectHandler(w http.ResponseWriter, r *http.Request, rules map[string]string) {
	fmt.Println(rules)
}

func handler(fn func(http.ResponseWriter, *http.Request, map[string]string), rules map[string]string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		target := rules[r.URL.Path]
		fmt.Println("target", target)
		if target == "" {
			http.NotFound(w, r)
			return
		}
		http.Redirect(w, r, target, http.StatusMovedPermanently)
	}

	// fmt.Println(rules)
	// fmt.Fprintf(w, "Hi there, I love %s!\n", r.URL.Path)
}
<commit_msg>use JSON instead of CSV<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	// "io"
	"io/ioutil"
	"log"
	"net/http"
	// "os"
	"strings"
)

var rules map[string]map[string]string

func main() {
	// log, err := syslog.New(syslog.LOG_ERR, "godir")
	// if err != nil {
	// 	panic("Could connect to syslog")
	// }
	// log.Info("Starting godir")
	address := flag.String("address", "127.0.0.1", "The address to listen on")
	path := flag.String("path", "", "Path to the csv of redirects")
	port := flag.String("port", "8080", "The port to listen on")
	// watch := flag.Bool("watch", false, "Watch for CSV file changes")
	flag.Parse()
	if *path == "" {
		log.Fatalln("You must supply a mapping file")
	}
	fmt.Println("Starting godir...")
	// rules := make(map[string]string)
	f, err := ioutil.ReadFile(*path)
	// defer f.Close()
	if err != nil {
		log.Panicln("Cant read file", err)
	}

	err = json.Unmarshal(f, &rules)
	if err != nil {
		log.Panicln("Can't read file", err)
	}

	http.HandleFunc("/", handler(redirectHandler, rules))
	log.Fatal(http.ListenAndServe(*address+":"+*port, nil))
	// reqpath := strings.Trim(req.URL.Path, "/")
}

func redirectHandler(w http.ResponseWriter, r *http.Request, rules map[string]map[string]string) {
	fmt.Println(rules)
}

func handler(fn func(http.ResponseWriter, *http.Request, map[string]map[string]string), rules map[string]map[string]string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		target := rules[strings.Split(r.Host, ":")[0]][r.URL.Path]
		fmt.Println("target", target)
		if target == "" {
			http.NotFound(w, r)
			return
		}
		http.Redirect(w, r, target, http.StatusMovedPermanently)
	}

	// fmt.Println(rules)
	// fmt.Fprintf(w, "Hi there, I love %s!\n", r.URL.Path)
}
<|endoftext|>


<commit_before>package main

import (
  "github.com/go-martini/martini"
)

func main() {
  m := martini.Classic()
  m.Get("/", func() string {
    return "Hello world!"
  })
  m.Run()
}
<commit_msg>Preliminary GET endpoint<commit_after>package main

import (
    "net/http"

    "github.com/go-martini/martini"
    // "github.com/user/gorse-cache-redis"
    // "github.com/user/gorse-handler-auth"
    // "github.com/user/gorse-storage-kafka"
    // "github.com/user/gorse-storage-redis"
)

func main() {
    m := martini.Classic()

    m.Handlers()

    m.Get("/**", func(r *http.Request, params martini.Params) string {
        topic := params["_1"]
        marker, marker_present := r.URL.Query()["marker"]

        // Get message from storage
        // if marker_present {
        //     storage.Get(topic, marker)
        // } else {
        //     storage.Get(topic)
        // }

        tempReturn := "Getting messages on " + topic
        if marker_present {
            tempReturn += " with marker of " + marker[0]
        }
        return tempReturn + "\r\n"
    })

    m.Run()
}
<|endoftext|>