<commit_before>package kubernetes

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/resource"
	"k8s.io/kubernetes/pkg/api/testapi"
	"k8s.io/kubernetes/pkg/api/unversioned"
	"k8s.io/kubernetes/pkg/client/restclient"
	client "k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/client/unversioned/fake"

	"gitlab.com/gitlab-org/gitlab-ci-multi-runner/common"
	"gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors"
	"gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers"
)

var (
	TRUE           = true
	FALSE          = false
	minikube       = "minikube"
	minikubeConfig *common.KubernetesConfig
)

func TestLimits(t *testing.T) {
	tests := []struct {
		CPU, Memory string
		Expected    api.ResourceList
		Error       bool
	}{
		{
			CPU:    "100m",
			Memory: "100Mi",
			Expected: api.ResourceList{
				api.ResourceLimitsCPU:    resource.MustParse("100m"),
				api.ResourceLimitsMemory: resource.MustParse("100Mi"),
			},
		},
		{
			CPU: "100m",
			Expected: api.ResourceList{
				api.ResourceLimitsCPU: resource.MustParse("100m"),
			},
		},
		{
			Memory: "100Mi",
			Expected: api.ResourceList{
				api.ResourceLimitsMemory: resource.MustParse("100Mi"),
			},
		},
		{
			CPU:      "100j",
			Expected: api.ResourceList{},
			Error:    true,
		},
		{
			Memory:   "100j",
			Expected: api.ResourceList{},
			Error:    true,
		},
		{
			Expected: api.ResourceList{},
		},
	}

	for _, test := range tests {
		res, err := limits(test.CPU, test.Memory)

		if err != nil && !test.Error {
			t.Errorf("got error but expected '%v': %s", test.Expected, err)
			continue
		}

		if !reflect.DeepEqual(res, test.Expected) {
			t.Errorf("got: '%v' but expected: '%v'", res, test.Expected)
			continue
		}
	}
}

func TestBuildContainer(t *testing.T) {
	tests := []struct {
		Name, Image, BuildDir string
		Privileged            bool
		Command               []string
		Environment           []string
		Limits                api.ResourceList

		Expected api.Container
	}{
		{
			Name:        "test",
			Image:       "image",
			BuildDir:    "/test/build",
			Privileged:  true,
			Command:     []string{"test", "command"},
			Environment: nil,
			Limits:      nil,

			Expected: api.Container{
				Name:    "test",
				Image:   "image",
				Command: []string{"test", "command"},
				Env: []api.EnvVar{
					{Name: "CI", Value: "true"}, {Name: "CI_BUILD_REF"}, {Name: "CI_BUILD_BEFORE_SHA"},
					{Name: "CI_BUILD_REF_NAME"}, {Name: "CI_BUILD_ID", Value: "0"}, {Name: "CI_BUILD_REPO"},
					{Name: "CI_BUILD_TOKEN"}, {Name: "CI_PROJECT_ID", Value: "0"}, {Name: "CI_PROJECT_DIR", Value: "/test/build"},
					{Name: "CI_SERVER", Value: "yes"}, {Name: "CI_SERVER_NAME", Value: "GitLab CI"}, {Name: "CI_SERVER_VERSION"},
					{Name: "CI_SERVER_REVISION"}, {Name: "GITLAB_CI", Value: "true"},
				},
				Resources: api.ResourceRequirements{
					Limits: nil,
				},
				VolumeMounts: []api.VolumeMount{
					api.VolumeMount{
						Name:      "repo",
						MountPath: "/test",
					},
					api.VolumeMount{
						Name:      "etc-ssl-certs",
						MountPath: "/etc/ssl/certs",
					},
					api.VolumeMount{
						Name:      "usr-share-ca-certificates",
						MountPath: "/usr/share/ca-certificates",
					},
				},
				SecurityContext: &api.SecurityContext{
					Privileged: &TRUE,
				},
				Stdin: true,
			},
		},
	}

	for _, test := range tests {
		e := executor{
			extraOptions: FakeOptions{
				privileged: test.Privileged,
			},
			AbstractExecutor: executors.AbstractExecutor{
				Build: &common.Build{
					BuildDir: test.BuildDir,
					Runner: &common.RunnerConfig{
						RunnerSettings: common.RunnerSettings{
							Environment: test.Environment,
						},
					},
				},
				ExecutorOptions: executors.ExecutorOptions{
					Shell: common.ShellScriptInfo{
						Build: &common.Build{
							BuildDir: test.BuildDir,
						},
					},
				},
			},
		}
		if bc := e.buildContainer(test.Name, test.Image, test.Limits, test.Command...); !reflect.DeepEqual(bc, test.Expected) {
			t.Errorf("error testing buildContainer. expected '%v', got '%v'", test.Expected, bc)
			continue
		}
	}
}

func TestCleanup(t *testing.T) {
	version := testapi.Default.GroupVersion().Version
	codec := testapi.Default.Codec()

	tests := []struct {
		Pod        *api.Pod
		ClientFunc func(*http.Request) (*http.Response, error)
		Error      bool
	}{
		{
			Pod: &api.Pod{
				ObjectMeta: api.ObjectMeta{
					Name:      "test-pod",
					Namespace: "test-ns",
				},
			},
			ClientFunc: func(req *http.Request) (*http.Response, error) {
				switch p, m := req.URL.Path, req.Method; {
				case m == "DELETE" && p == "/api/"+version+"/namespaces/test-ns/pods/test-pod":
					return &http.Response{StatusCode: 200, Body: FakeReadCloser{
						Reader: strings.NewReader(""),
					}}, nil
				default:
					return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p)
				}
			},
		},
		{
			Pod: &api.Pod{
				ObjectMeta: api.ObjectMeta{
					Name:      "test-pod",
					Namespace: "test-ns",
				},
			},
			ClientFunc: func(req *http.Request) (*http.Response, error) {
				return nil, fmt.Errorf("delete failed")
			},
			Error: true,
		},
	}

	for _, test := range tests {
		c := client.NewOrDie(&restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}})
		fakeClient := fake.RESTClient{
			Codec:  codec,
			Client: fake.CreateHTTPClient(test.ClientFunc),
		}
		c.Client = fakeClient.Client

		ex := executor{
			kubeClient: c,
			pod:        test.Pod,
		}
		errored := false
		buildTrace := FakeBuildTrace{
			testWriter{
				call: func(b []byte) (int, error) {
					if test.Error && !errored {
						if s := string(b); strings.Contains(s, "Error cleaning up") {
							errored = true
						} else {
							t.Errorf("expected failure. got: '%s'", string(b))
						}
					}
					return len(b), nil
				},
			},
		}
		ex.AbstractExecutor.BuildTrace = buildTrace
		ex.AbstractExecutor.BuildLogger = common.NewBuildLogger(buildTrace, logrus.WithFields(logrus.Fields{}))
		ex.Cleanup()
		if test.Error && !errored {
			t.Errorf("expected cleanup to error but it didn't")
		}
	}
}

func TestPrepare(t *testing.T) {
	tests := []struct {
		GlobalConfig *common.Config
		RunnerConfig *common.RunnerConfig
		Build        *common.Build

		Expected *executor
		Error    bool
	}{
		{
			GlobalConfig: &common.Config{},
			RunnerConfig: &common.RunnerConfig{
				RunnerSettings: common.RunnerSettings{
					Kubernetes: &common.KubernetesConfig{
						Host:          "test-server",
						ServiceCPUs:   "0.5",
						ServiceMemory: "200Mi",
						CPUs:          "1.5",
						Memory:        "4Gi",
						Privileged:    true,
					},
				},
			},
			Build: &common.Build{
				GetBuildResponse: common.GetBuildResponse{
					Sha: "1234567890",
					Options: common.BuildOptions{
						"image": "test-image",
					},
					Variables: []common.BuildVariable{
						common.BuildVariable{Key: "privileged", Value: "true"},
					},
				},
				Runner: &common.RunnerConfig{},
			},
			Expected: &executor{
				options: &kubernetesOptions{
					Image: "test-image",
				},
				extraOptions: DefaultOptions{
					BuildVariables: []common.BuildVariable{
						common.BuildVariable{Key: "privileged", Value: "true"},
					},
				},
				serviceLimits: api.ResourceList{
					api.ResourceLimitsCPU:    resource.MustParse("0.5"),
					api.ResourceLimitsMemory: resource.MustParse("200Mi"),
				},
				buildLimits: api.ResourceList{
					api.ResourceLimitsCPU:    resource.MustParse("1.5"),
					api.ResourceLimitsMemory: resource.MustParse("4Gi"),
				},
			},
		},
		{
			GlobalConfig: &common.Config{},
			RunnerConfig: &common.RunnerConfig{
				RunnerSettings: common.RunnerSettings{
					Kubernetes: &common.KubernetesConfig{
						Host:          "test-server",
						ServiceCPUs:   "0.5",
						ServiceMemory: "200Mi",
						CPUs:          "1.5",
						Memory:        "4Gi",
						Privileged:    false,
					},
				},
			},
			Build: &common.Build{
				GetBuildResponse: common.GetBuildResponse{
					Sha: "1234567890",
					Options: common.BuildOptions{
						"image": "test-image",
					},
					Variables: []common.BuildVariable{
						common.BuildVariable{Key: "privileged", Value: "true"},
					},
				},
				Runner: &common.RunnerConfig{},
			},
			Error: true,
		},
	}

	for _, test := range tests {
		e := &executor{
			AbstractExecutor: executors.AbstractExecutor{
				ExecutorOptions: executorOptions,
			},
		}

		err := e.Prepare(test.GlobalConfig, test.RunnerConfig, test.Build)

		if err != nil {
			if !test.Error {
				t.Errorf("Got error. Expected: %v", test.Expected)
			}
			continue
		}

		// Set this to nil so we aren't testing the functionality of the
		// base AbstractExecutor's Prepare method
		e.AbstractExecutor = executors.AbstractExecutor{}

		// TODO: Improve this so we don't have to nil-ify the kubeClient.
		// It currently contains some moving parts that are failing, meaning
		// we'll need to mock _something_
		e.kubeClient = nil
		if !reflect.DeepEqual(e, test.Expected) {
			t.Errorf("Got executor '%+v' but expected '%+v'", e, test.Expected)
			continue
		}
	}
}

func TestKubernetesSuccessRun(t *testing.T) {
	if helpers.SkipIntegrationTests(t, "minikube", "version") {
		return
	}

	buildResponse := common.SuccessfulBuild
	buildResponse.Options = map[string]interface{}{
		"image": "debian",
	}
	build := &common.Build{
		GetBuildResponse: buildResponse,
		Runner: &common.RunnerConfig{
			RunnerSettings: common.RunnerSettings{
				Executor:   "kubernetes",
				Kubernetes: minikubeConfig,
			},
		},
	}

	err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
	assert.NoError(t, err, "Ensure minikube is able to create a local Kubernetes cluster succesfully")
}

func TestMain(m *testing.M) {
	hasMinikube, _ := helpers.ExecuteCommandSucceeded("minikube", "version")
	if !testing.Short() && hasMinikube {
		err := exec.Command("minikube", "start").Run()

		if err != nil {
			fmt.Println("error starting minikube cluster:", err.Error())
			os.Exit(1)
		}

		usr, err := user.Current()

		if err != nil {
			fmt.Println("error looking up current user account:", err.Error())
			os.Exit(1)
		}

		ipBytes, err := exec.Command("minikube", "ip").Output()

		if err != nil {
			fmt.Println("error detecting minikube ip:", err.Error())
			os.Exit(1)
		}

		crtPath := fmt.Sprintf("%s/.minikube/apiserver.crt", usr.HomeDir)
		keyPath := fmt.Sprintf("%s/.minikube/apiserver.key", usr.HomeDir)
		caPath := fmt.Sprintf("%s/.minikube/ca.crt", usr.HomeDir)
		kubeIP := fmt.Sprintf("https://%s:8443", strings.Replace(string(ipBytes), "\n", "", -1))

		minikubeConfig = &common.KubernetesConfig{
			Host:      kubeIP,
			CAFile:    caPath,
			CertFile:  crtPath,
			KeyFile:   keyPath,
			Namespace: "default",
		}

		// wait 3 seconds because `minikube start` exits with status 0 before the apiserver
		// is actually listening
		time.Sleep(time.Second * 3)
	}
	os.Exit(m.Run())
}

type FakeReadCloser struct {
	io.Reader
}

func (f FakeReadCloser) Close() error {
	return nil
}

type FakeBuildTrace struct {
	testWriter
}

func (f FakeBuildTrace) Success()      {}
func (f FakeBuildTrace) Fail(error)    {}
func (f FakeBuildTrace) Notify(func()) {}
func (f FakeBuildTrace) Aborted() chan interface{} {
	return make(chan interface{})
}
func (f FakeBuildTrace) IsStdout() bool {
	return false
}
<commit_msg>Fix tests<commit_after>package kubernetes

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"os/user"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/stretchr/testify/assert"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/resource"
	"k8s.io/kubernetes/pkg/api/testapi"
	"k8s.io/kubernetes/pkg/api/unversioned"
	"k8s.io/kubernetes/pkg/client/restclient"
	client "k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/client/unversioned/fake"

	"gitlab.com/gitlab-org/gitlab-ci-multi-runner/common"
	"gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors"
	"gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers"
)

var (
	TRUE           = true
	FALSE          = false
	minikube       = "minikube"
	minikubeConfig *common.KubernetesConfig
)

func TestLimits(t *testing.T) {
	tests := []struct {
		CPU, Memory string
		Expected    api.ResourceList
		Error       bool
	}{
		{
			CPU:    "100m",
			Memory: "100Mi",
			Expected: api.ResourceList{
				api.ResourceLimitsCPU:    resource.MustParse("100m"),
				api.ResourceLimitsMemory: resource.MustParse("100Mi"),
			},
		},
		{
			CPU: "100m",
			Expected: api.ResourceList{
				api.ResourceLimitsCPU: resource.MustParse("100m"),
			},
		},
		{
			Memory: "100Mi",
			Expected: api.ResourceList{
				api.ResourceLimitsMemory: resource.MustParse("100Mi"),
			},
		},
		{
			CPU:      "100j",
			Expected: api.ResourceList{},
			Error:    true,
		},
		{
			Memory:   "100j",
			Expected: api.ResourceList{},
			Error:    true,
		},
		{
			Expected: api.ResourceList{},
		},
	}

	for _, test := range tests {
		res, err := limits(test.CPU, test.Memory)

		if err != nil && !test.Error {
			t.Errorf("got error but expected '%v': %s", test.Expected, err)
			continue
		}

		if !reflect.DeepEqual(res, test.Expected) {
			t.Errorf("got: '%v' but expected: '%v'", res, test.Expected)
			continue
		}
	}
}

func TestBuildContainer(t *testing.T) {
	tests := []struct {
		Name, Image, BuildDir string
		Privileged            bool
		Command               []string
		Environment           []string
		Limits                api.ResourceList

		Expected api.Container
	}{
		{
			Name:        "test",
			Image:       "image",
			BuildDir:    "/test/build",
			Privileged:  true,
			Command:     []string{"test", "command"},
			Environment: nil,
			Limits:      nil,

			Expected: api.Container{
				Name:    "test",
				Image:   "image",
				Command: []string{"test", "command"},
				Env: []api.EnvVar{
					{Name: "CI", Value: "true"}, {Name: "CI_BUILD_REF"}, {Name: "CI_BUILD_BEFORE_SHA"},
					{Name: "CI_BUILD_REF_NAME"}, {Name: "CI_BUILD_ID", Value: "0"}, {Name: "CI_BUILD_REPO"},
					{Name: "CI_BUILD_TOKEN"}, {Name: "CI_PROJECT_ID", Value: "0"}, {Name: "CI_PROJECT_DIR", Value: "/test/build"},
					{Name: "CI_SERVER", Value: "yes"}, {Name: "CI_SERVER_NAME", Value: "GitLab CI"}, {Name: "CI_SERVER_VERSION"},
					{Name: "CI_SERVER_REVISION"}, {Name: "GITLAB_CI", Value: "true"},
				},
				Resources: api.ResourceRequirements{
					Limits: nil,
				},
				VolumeMounts: []api.VolumeMount{
					api.VolumeMount{
						Name:      "repo",
						MountPath: "/test",
					},
					api.VolumeMount{
						Name:      "etc-ssl-certs",
						MountPath: "/etc/ssl/certs",
					},
					api.VolumeMount{
						Name:      "usr-share-ca-certificates",
						MountPath: "/usr/share/ca-certificates",
					},
				},
				SecurityContext: &api.SecurityContext{
					Privileged: &TRUE,
				},
				Stdin: true,
			},
		},
	}

	for _, test := range tests {
		e := executor{
			AbstractExecutor: executors.AbstractExecutor{
				Build: &common.Build{
					BuildDir: test.BuildDir,
					Runner: &common.RunnerConfig{
						RunnerSettings: common.RunnerSettings{
							Environment: test.Environment,
							Kubernetes: &common.KubernetesConfig{
								Privileged: test.Privileged,
							},
						},
					},
				},
				ExecutorOptions: executors.ExecutorOptions{
					Shell: common.ShellScriptInfo{
						Build: &common.Build{
							BuildDir: test.BuildDir,
						},
					},
				},
			},
		}
		if bc := e.buildContainer(test.Name, test.Image, test.Limits, test.Command...); !reflect.DeepEqual(bc, test.Expected) {
			t.Errorf("error testing buildContainer. expected '%v', got '%v'", test.Expected, bc)
			continue
		}
	}
}

func TestCleanup(t *testing.T) {
	version := testapi.Default.GroupVersion().Version
	codec := testapi.Default.Codec()

	tests := []struct {
		Pod        *api.Pod
		ClientFunc func(*http.Request) (*http.Response, error)
		Error      bool
	}{
		{
			Pod: &api.Pod{
				ObjectMeta: api.ObjectMeta{
					Name:      "test-pod",
					Namespace: "test-ns",
				},
			},
			ClientFunc: func(req *http.Request) (*http.Response, error) {
				switch p, m := req.URL.Path, req.Method; {
				case m == "DELETE" && p == "/api/"+version+"/namespaces/test-ns/pods/test-pod":
					return &http.Response{StatusCode: 200, Body: FakeReadCloser{
						Reader: strings.NewReader(""),
					}}, nil
				default:
					return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p)
				}
			},
		},
		{
			Pod: &api.Pod{
				ObjectMeta: api.ObjectMeta{
					Name:      "test-pod",
					Namespace: "test-ns",
				},
			},
			ClientFunc: func(req *http.Request) (*http.Response, error) {
				return nil, fmt.Errorf("delete failed")
			},
			Error: true,
		},
	}

	for _, test := range tests {
		c := client.NewOrDie(&restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}})
		fakeClient := fake.RESTClient{
			Codec:  codec,
			Client: fake.CreateHTTPClient(test.ClientFunc),
		}
		c.Client = fakeClient.Client

		ex := executor{
			kubeClient: c,
			pod:        test.Pod,
		}
		errored := false
		buildTrace := FakeBuildTrace{
			testWriter{
				call: func(b []byte) (int, error) {
					if test.Error && !errored {
						if s := string(b); strings.Contains(s, "Error cleaning up") {
							errored = true
						} else {
							t.Errorf("expected failure. got: '%s'", string(b))
						}
					}
					return len(b), nil
				},
			},
		}
		ex.AbstractExecutor.BuildTrace = buildTrace
		ex.AbstractExecutor.BuildLogger = common.NewBuildLogger(buildTrace, logrus.WithFields(logrus.Fields{}))
		ex.Cleanup()
		if test.Error && !errored {
			t.Errorf("expected cleanup to error but it didn't")
		}
	}
}

func TestPrepare(t *testing.T) {
	tests := []struct {
		GlobalConfig *common.Config
		RunnerConfig *common.RunnerConfig
		Build        *common.Build

		Expected *executor
		Error    bool
	}{
		{
			GlobalConfig: &common.Config{},
			RunnerConfig: &common.RunnerConfig{
				RunnerSettings: common.RunnerSettings{
					Kubernetes: &common.KubernetesConfig{
						Host:          "test-server",
						ServiceCPUs:   "0.5",
						ServiceMemory: "200Mi",
						CPUs:          "1.5",
						Memory:        "4Gi",
						Privileged:    true,
					},
				},
			},
			Build: &common.Build{
				GetBuildResponse: common.GetBuildResponse{
					Sha: "1234567890",
					Options: common.BuildOptions{
						"image": "test-image",
					},
					Variables: []common.BuildVariable{
						common.BuildVariable{Key: "privileged", Value: "true"},
					},
				},
				Runner: &common.RunnerConfig{},
			},
			Expected: &executor{
				options: &kubernetesOptions{
					Image: "test-image",
				},
				serviceLimits: api.ResourceList{
					api.ResourceLimitsCPU:    resource.MustParse("0.5"),
					api.ResourceLimitsMemory: resource.MustParse("200Mi"),
				},
				buildLimits: api.ResourceList{
					api.ResourceLimitsCPU:    resource.MustParse("1.5"),
					api.ResourceLimitsMemory: resource.MustParse("4Gi"),
				},
			},
		},
		{
			GlobalConfig: &common.Config{},
			RunnerConfig: &common.RunnerConfig{
				RunnerSettings: common.RunnerSettings{
					Kubernetes: &common.KubernetesConfig{
						Host:          "test-server",
						ServiceCPUs:   "0.5",
						ServiceMemory: "200Mi",
						CPUs:          "1.5",
						Memory:        "4Gi",
						Privileged:    false,
					},
				},
			},
			Build: &common.Build{
				GetBuildResponse: common.GetBuildResponse{
					Sha: "1234567890",
					Options: common.BuildOptions{
						"image": "test-image",
					},
				},
				Runner: &common.RunnerConfig{},
			},
			Error: true,
		},
	}

	for _, test := range tests {
		e := &executor{
			AbstractExecutor: executors.AbstractExecutor{
				ExecutorOptions: executorOptions,
			},
		}

		err := e.Prepare(test.GlobalConfig, test.RunnerConfig, test.Build)

		if err != nil {
			if !test.Error {
				t.Errorf("Got error. Expected: %v", test.Expected)
			}
			continue
		}

		// Set this to nil so we aren't testing the functionality of the
		// base AbstractExecutor's Prepare method
		e.AbstractExecutor = executors.AbstractExecutor{}

		// TODO: Improve this so we don't have to nil-ify the kubeClient.
		// It currently contains some moving parts that are failing, meaning
		// we'll need to mock _something_
		e.kubeClient = nil
		if !reflect.DeepEqual(e, test.Expected) {
			t.Errorf("Got executor '%+v' but expected '%+v'", e, test.Expected)
			continue
		}
	}
}

func TestKubernetesSuccessRun(t *testing.T) {
	if helpers.SkipIntegrationTests(t, "minikube", "version") {
		return
	}

	buildResponse := common.SuccessfulBuild
	buildResponse.Options = map[string]interface{}{
		"image": "debian",
	}
	build := &common.Build{
		GetBuildResponse: buildResponse,
		Runner: &common.RunnerConfig{
			RunnerSettings: common.RunnerSettings{
				Executor:   "kubernetes",
				Kubernetes: minikubeConfig,
			},
		},
	}

	err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
	assert.NoError(t, err, "Ensure minikube is able to create a local Kubernetes cluster succesfully")
}

func TestMain(m *testing.M) {
	hasMinikube, _ := helpers.ExecuteCommandSucceeded("minikube", "version")
	if !testing.Short() && hasMinikube {
		err := exec.Command("minikube", "start").Run()

		if err != nil {
			fmt.Println("error starting minikube cluster:", err.Error())
			os.Exit(1)
		}

		usr, err := user.Current()

		if err != nil {
			fmt.Println("error looking up current user account:", err.Error())
			os.Exit(1)
		}

		ipBytes, err := exec.Command("minikube", "ip").Output()

		if err != nil {
			fmt.Println("error detecting minikube ip:", err.Error())
			os.Exit(1)
		}

		crtPath := fmt.Sprintf("%s/.minikube/apiserver.crt", usr.HomeDir)
		keyPath := fmt.Sprintf("%s/.minikube/apiserver.key", usr.HomeDir)
		caPath := fmt.Sprintf("%s/.minikube/ca.crt", usr.HomeDir)
		kubeIP := fmt.Sprintf("https://%s:8443", strings.Replace(string(ipBytes), "\n", "", -1))

		minikubeConfig = &common.KubernetesConfig{
			Host:      kubeIP,
			CAFile:    caPath,
			CertFile:  crtPath,
			KeyFile:   keyPath,
			Namespace: "default",
		}

		// wait 3 seconds because `minikube start` exits with status 0 before the apiserver
		// is actually listening
		time.Sleep(time.Second * 3)
	}
	os.Exit(m.Run())
}

type FakeReadCloser struct {
	io.Reader
}

func (f FakeReadCloser) Close() error {
	return nil
}

type FakeBuildTrace struct {
	testWriter
}

func (f FakeBuildTrace) Success()      {}
func (f FakeBuildTrace) Fail(error)    {}
func (f FakeBuildTrace) Notify(func()) {}
func (f FakeBuildTrace) Aborted() chan interface{} {
	return make(chan interface{})
}
func (f FakeBuildTrace) IsStdout() bool {
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/jwfriese/iossimulator"
)

func main() {
	stdOut := os.Stdout
	stdErr := os.Stderr
	programCall := os.Args
	if len(programCall) < 3 {
		log.Fatal(errors.New("Usage: ./test <runtime> <device type>"))
	}

	args := programCall[1:]
	iosVersion := args[0]
	deviceVersion := args[1]

	testInitReport := fmt.Sprintf("Running Fleet unit tests with iOS version '%s' on  device type '%s'\n", iosVersion, deviceVersion)
	_, err := stdOut.Write([]byte(testInitReport))
	if err != nil {
		log.Fatal(err)
	}

	availabilityErr := iossimulator.IsDeviceAvailable(iosVersion, deviceVersion)
	if availabilityErr != nil {
		log.Fatal(availabilityErr)
	}

	iosVersionNumber := strings.Trim(iosVersion, "iOS ")
	destinationString := fmt.Sprintf("platform=iOS Simulator,OS=%s,name=%s", iosVersionNumber, deviceVersion)
	unitTestCommand := exec.Command("xcodebuild", "test", "-workspace", "Fleet.xcworkspace", "-scheme", "Fleet", "-destination", destinationString)
	xcprettyCommand := exec.Command("xcpretty")
	xcprettyCommand.Stdin, err = unitTestCommand.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	xcprettyCommand.Stdout = stdOut
	xcprettyCommand.Stderr = stdErr
	err = xcprettyCommand.Start()
	if err != nil {
		log.Fatal(err)
	}

	err = unitTestCommand.Run()
	if err != nil {
		log.Fatal(err)
	}

	err = xcprettyCommand.Wait()
	if err != nil {
		log.Fatal(err)
	}

	_, err = stdOut.Write([]byte("Fleet unit tests passed...\n"))
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Directly use 'os' package output variables<commit_after>package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/jwfriese/iossimulator"
)

func main() {
	programCall := os.Args
	if len(programCall) < 3 {
		log.Fatal(errors.New("Usage: ./test <runtime> <device type>"))
	}

	args := programCall[1:]
	iosVersion := args[0]
	deviceVersion := args[1]

	testInitReport := fmt.Sprintf("Running Fleet unit tests with iOS version '%s' on  device type '%s'\n", iosVersion, deviceVersion)
	_, err := os.Stdout.Write([]byte(testInitReport))
	if err != nil {
		log.Fatal(err)
	}

	availabilityErr := iossimulator.IsDeviceAvailable(iosVersion, deviceVersion)
	if availabilityErr != nil {
		log.Fatal(availabilityErr)
	}

	iosVersionNumber := strings.Trim(iosVersion, "iOS ")
	destinationString := fmt.Sprintf("platform=iOS Simulator,OS=%s,name=%s", iosVersionNumber, deviceVersion)
	unitTestCommand := exec.Command("xcodebuild", "test", "-workspace", "Fleet.xcworkspace", "-scheme", "Fleet", "-destination", destinationString)
	xcprettyCommand := exec.Command("xcpretty")
	xcprettyCommand.Stdin, err = unitTestCommand.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	xcprettyCommand.Stdout = os.Stdout
	xcprettyCommand.Stderr = os.Stderr
	err = xcprettyCommand.Start()
	if err != nil {
		log.Fatal(err)
	}

	err = unitTestCommand.Run()
	if err != nil {
		log.Fatal(err)
	}

	err = xcprettyCommand.Wait()
	if err != nil {
		log.Fatal(err)
	}

	_, err = os.Stdout.Write([]byte("Fleet unit tests passed...\n"))
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package casper

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"errors"
	"log"
	"net/http"
	"net/url"
	"sort"
	"strconv"

	"github.com/tcnksm/go-casper/internal/encoding/golombset"
)

const (
	cookieName = "x-go-casper"
)

type Casper struct {
	p uint
	n uint // TODO(tcnksm): need ..?
}

type Options struct {

	// skipPush skips server pushing. This should be only used in testing.
	// Currently, it's kinda hard to receive http push in go http client.
	// Set this and ignore pushing and only test cookie part.
	// This should be removed in future.
	skipPush bool
}

func New(p, n uint) *Casper {
	return &Casper{
		p: p,
		n: n,
	}
}

// Push pushes
//
// TODO(tcnksm): Handle multiple contents
func (c *Casper) Push(w http.ResponseWriter, r *http.Request, content string, opts *Options) error {
	pusher, ok := w.(http.Pusher)
	if !ok {
		// go1.8 or later
		return errors.New("server push is not supported")
	}

	hashs, err := c.readCookie(r)
	if err != nil {
		return err
	}

	// TODO(tcnksm): Enable to configure?
	hashF := func(v []byte) uint {
		h := md5.New()
		h.Write(v)
		b := h.Sum(nil)

		s := hex.EncodeToString(b[12:16])
		i, err := strconv.ParseUint(s, 16, 32)
		if err != nil {
			panic(err)
		}
		return uint(i) % (c.n * c.p)
	}

	// TODO(tcnksm): binary search (or enable to configure?)
	searchF := func(a []uint, h uint) bool {
		for i := 0; i < len(a); i++ {
			if h == a[i] {
				return true
			}

			if h < a[i] {
				return false
			}
		}
		return false
	}

	h := hashF([]byte(content))
	if searchF(hashs, h) {
		log.Println("Already pushded")
		return nil
	}

	if !opts.skipPush {
		if err := pusher.Push(content, nil); err != nil {
			return err
		}
		log.Println("Pushed")
	}

	// Set cookie
	hashs = append(hashs, h)
	sort.Slice(hashs, func(i, j int) bool {
		return hashs[i] < hashs[j]
	})

	var buf bytes.Buffer
	if err := golombset.Encode(&buf, hashs, c.p); err != nil {
		return err
	}

	cookie := &http.Cookie{
		Name:  cookieName,
		Value: url.QueryEscape(buf.String()),
	}
	http.SetCookie(w, cookie)

	return nil
}

// readCookie reads cookie from http request and decode it to hash array.
func (c *Casper) readCookie(r *http.Request) ([]uint, error) {
	cookie, err := r.Cookie(cookieName)
	if err != nil && err != http.ErrNoCookie {
		return nil, err
	}

	if err == http.ErrNoCookie {
		// TODO(tcnksm): Set size
		var hashs []uint
		return hashs, nil
	}

	value, err := url.QueryUnescape(cookie.Value)
	if err != nil {
		return nil, err
	}

	hashs, err := golombset.DecodeAll([]byte(value), c.p)
	if err != nil {
		return nil, err
	}

	return hashs, nil
}
<commit_msg>Refactoring<commit_after>package casper

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"errors"
	"log"
	"net/http"
	"net/url"
	"sort"
	"strconv"

	"github.com/tcnksm/go-casper/internal/encoding/golombset"
)

const (
	cookieName = "x-go-casper"
)

// Casper stores
type Casper struct {
	p uint
	n uint
}

// Options includes casper push options.
type Options struct {
	PushOptions *http.PushOptions

	// force executes server-push without checking cookie value.
	force bool

	// skipPush skips server pushing. This should be only used in testing.
	// Currently, it's kinda hard to receive http push in go http client.
	// Set this and ignore pushing and only test cookie part.
	// This should be removed in future.
	skipPush bool
}

// New initialize casper.
func New(p, n uint) *Casper {
	return &Casper{
		p: p,
		n: n,
	}
}

// Push pushes the given content and set cookie value.
func (c *Casper) Push(w http.ResponseWriter, r *http.Request, content string, opts *Options) error {
	// Pusher is used later in this function but should check
	// it's available or not first to avoid waste calc.
	pusher, ok := w.(http.Pusher)
	if !ok {
		// go1.8 or later
		return errors.New("server push is not supported")
	}

	hashs, err := c.readCookie(r)
	if err != nil {
		return err
	}

	h := c.hash([]byte(content))
	if search(hashs, h) {
		log.Println("Already pushded")
		return nil
	}

	if !opts.skipPush {
		if err := pusher.Push(content, opts.PushOptions); err != nil {
			return err
		}
		log.Println("Pushed")
	}

	hashs = append(hashs, h)
	cookieValue, err := c.generateCookie(hashs)
	if err != nil {
		return err
	}

	cookie := &http.Cookie{
		Name:  cookieName,
		Value: cookieValue,
	}
	http.SetCookie(w, cookie)

	return nil
}

func (c *Casper) hash(p []byte) uint {
	h := md5.New()
	h.Write(p)
	b := h.Sum(nil)

	s := hex.EncodeToString(b[12:16])
	i, err := strconv.ParseUint(s, 16, 32)
	if err != nil {
		panic(err)
	}
	return uint(i) % (c.n * c.p)
}

// generateCookie generates cookie value from the given hash array.
func (c *Casper) generateCookie(hashs []uint) (string, error) {

	sort.Slice(hashs, func(i, j int) bool {
		return hashs[i] < hashs[j]
	})

	var buf bytes.Buffer
	if err := golombset.Encode(&buf, hashs, c.p); err != nil {
		return "", err
	}

	return url.QueryEscape(buf.String()), nil
}

// readCookie reads cookie from http request and decode it to hash array.
func (c *Casper) readCookie(r *http.Request) ([]uint, error) {
	cookie, err := r.Cookie(cookieName)
	if err != nil && err != http.ErrNoCookie {
		return nil, err
	}

	if err == http.ErrNoCookie {
		// TODO(tcnksm): Set size
		var hashs []uint
		return hashs, nil
	}

	value, err := url.QueryUnescape(cookie.Value)
	if err != nil {
		return nil, err
	}

	hashs, err := golombset.DecodeAll([]byte(value), c.p)
	if err != nil {
		return nil, err
	}

	return hashs, nil
}

// TODO(tcnksm): binary search (or enable to configure?)
func search(a []uint, h uint) bool {
	for i := 0; i < len(a); i++ {
		if h == a[i] {
			return true
		}

		if h < a[i] {
			return false
		}
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Opsidian Ltd.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package parsley contains helper functions for parsing and evaluating text input data.
//
// Parsley is a general parser combinator library which can be used to parse context-free, left-recursive languages. It handles indirect as well as direct left-recursion in polynomial time and defines a memoization helper for speeding up parsing time. The language grammar can be easily translated to a set of rules using parsers and combinators.
//
//You can read a general introduction about parser combinators here: https://en.wikipedia.org/wiki/Parser_combinator
//
//For more information about handling left-recursion please check out **Parser Combinators for Ambiguous Left-Recursive Grammars (2007)** by Frost R.A., Hafiz R., and Callaghan P.
//
//Currently the library supports only text processing, but the interfaces are written with binary parsing in mind.
package parsley

import (
	"github.com/opsidian/parsley/ast"
	"github.com/opsidian/parsley/parser"
	"github.com/opsidian/parsley/text"
)

// ParseText parses the given text input and returns with the root node of the AST. It expects a byte array as input, the root parser and whether to ignore whitespaces between terminals. Unless you need full control over whitespaces in your parser set the ignoreWhitespaces to true.
// If there are multiple possible parse trees only the first one is returned.
func ParseText(input []byte, ignoreWhitespaces bool, s parser.Parser) (ast.Node, parser.Error) {
	parser.Stat.Reset()
	r := text.NewReader(input, ignoreWhitespaces)
	parser.Stat.RegisterCall()
	_, resultSet, err := s.Parse(parser.EmptyLeftRecCtx(), r)
	if len(resultSet) == 0 {
		return nil, parser.NewError(err.Pos(), "Failed to parse the input, %s", err.Msg())
	}
	if resultSet[0].Node() == nil {
		return nil, nil
	}
	return resultSet[0].Node(), nil
}

// EvaluateText parses the given text input and evaluates it. It expects a byte array as input, the root parser, the evaluation context and whether to ignore whitespaces between terminals. Unless you need full control over whitespaces in your parser set the ignoreWhitespaces to true.
// If there are multiple possible parse trees only the first one is used for evaluation.
func EvaluateText(input []byte, ignoreWhitespaces bool, s parser.Parser, ctx interface{}) (interface{}, error) {
	node, err := ParseText(input, ignoreWhitespaces, s)
	if err != nil {
		return nil, err
	}
	if node == nil {
		return nil, nil
	}
	return node.Value(ctx)
}
<commit_msg>Fix whitespaces in docs<commit_after>// Copyright (c) 2017 Opsidian Ltd.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package parsley contains helper functions for parsing and evaluating text input data.
//
// Parsley is a general parser combinator library which can be used to parse context-free, left-recursive languages. It handles indirect as well as direct left-recursion in polynomial time and defines a memoization helper for speeding up parsing time. The language grammar can be easily translated to a set of rules using parsers and combinators.
//
// You can read a general introduction about parser combinators here: https://en.wikipedia.org/wiki/Parser_combinator
//
// For more information about handling left-recursion please check out **Parser Combinators for Ambiguous Left-Recursive Grammars (2007)** by Frost R.A., Hafiz R., and Callaghan P.
//
// Currently the library supports only text processing, but the interfaces are written with binary parsing in mind.
package parsley

import (
	"github.com/opsidian/parsley/ast"
	"github.com/opsidian/parsley/parser"
	"github.com/opsidian/parsley/text"
)

// ParseText parses the given text input and returns with the root node of the AST. It expects a byte array as input, the root parser and whether to ignore whitespaces between terminals. Unless you need full control over whitespaces in your parser set the ignoreWhitespaces to true.
// If there are multiple possible parse trees only the first one is returned.
func ParseText(input []byte, ignoreWhitespaces bool, s parser.Parser) (ast.Node, parser.Error) {
	parser.Stat.Reset()
	r := text.NewReader(input, ignoreWhitespaces)
	parser.Stat.RegisterCall()
	_, resultSet, err := s.Parse(parser.EmptyLeftRecCtx(), r)
	if len(resultSet) == 0 {
		return nil, parser.NewError(err.Pos(), "Failed to parse the input, %s", err.Msg())
	}
	if resultSet[0].Node() == nil {
		return nil, nil
	}
	return resultSet[0].Node(), nil
}

// EvaluateText parses the given text input and evaluates it. It expects a byte array as input, the root parser, the evaluation context and whether to ignore whitespaces between terminals. Unless you need full control over whitespaces in your parser set the ignoreWhitespaces to true.
// If there are multiple possible parse trees only the first one is used for evaluation.
func EvaluateText(input []byte, ignoreWhitespaces bool, s parser.Parser, ctx interface{}) (interface{}, error) {
	node, err := ParseText(input, ignoreWhitespaces, s)
	if err != nil {
		return nil, err
	}
	if node == nil {
		return nil, nil
	}
	return node.Value(ctx)
}
<|endoftext|>


<commit_before>// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package gorilla/pat is a request router and dispatcher with a pat-like
interface. It is an alternative to gorilla/mux that showcases how it can
be used as a base for different API flavors. Package pat is documented at:

	http://gopkgdoc.appspot.com/pkg/github.com/bmizerany/pat

Let's start registering a couple of URL paths and handlers:

	func main() {
		r := pat.New()
		r.Get("/", HomeHandler)
		r.Get("/products", ProductsHandler)
		r.Get("/articles", ArticlesHandler)
		http.Handle("/", r)
	}

Here we register three routes mapping URL paths to handlers. This is
equivalent to how http.HandleFunc() works: if an incoming GET request matches
one of the paths, the corresponding handler is called passing
(http.ResponseWriter, *http.Request) as parameters.

Note: gorilla/pat matches path prefixes, so you must register the most
specific paths first.

Note: differently from pat, these methods accept a handler function, and not an
http.Handler. We think this is shorter and more convenient. To set an
http.Handler, use the Add() method.

Paths can have variables. They are defined using the format {name} or
{name:pattern}. If a regular expression pattern is not defined, the matched
variable will be anything until the next slash. For example:

	r := pat.New()
	r.Get("/articles/{category}/{id:[0-9]+}", ArticleHandler)
	r.Get("/articles/{category}/", ArticlesCategoryHandler)
	r.Get("/products/{key}", ProductHandler)

The names are used to create a map of route variables which are stored in the
URL query, prefixed by a colon:

	category := req.URL.Query().Get(":category")

As in the gorilla/mux package, other matchers can be added to the registered
routes and URLs can be reversed as well. To build a URL for a route, first
add a name to it:

	r.Get("/products/{key}", ProductHandler).Name("product")

Then you can get it using the name and generate a URL:

	url, err := r.GetRoute("product").URL("key", "transmogrifier")

...and the result will be a url.URL with the following path:

	"/products/transmogrifier"

Check the mux documentation for more details about URL building and extra
matchers:

	http://gorilla-web.appspot.com/pkg/gorilla/mux/
*/
package pat
<commit_msg>pat: inverted route order because of path prefixes.<commit_after>// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package gorilla/pat is a request router and dispatcher with a pat-like
interface. It is an alternative to gorilla/mux that showcases how it can
be used as a base for different API flavors. Package pat is documented at:

	http://gopkgdoc.appspot.com/pkg/github.com/bmizerany/pat

Let's start registering a couple of URL paths and handlers:

	func main() {
		r := pat.New()
		r.Get("/products", ProductsHandler)
		r.Get("/articles", ArticlesHandler)
		r.Get("/", HomeHandler)
		http.Handle("/", r)
	}

Here we register three routes mapping URL paths to handlers. This is
equivalent to how http.HandleFunc() works: if an incoming GET request matches
one of the paths, the corresponding handler is called passing
(http.ResponseWriter, *http.Request) as parameters.

Note: gorilla/pat matches path prefixes, so you must register the most
specific paths first.

Note: differently from pat, these methods accept a handler function, and not an
http.Handler. We think this is shorter and more convenient. To set an
http.Handler, use the Add() method.

Paths can have variables. They are defined using the format {name} or
{name:pattern}. If a regular expression pattern is not defined, the matched
variable will be anything until the next slash. For example:

	r := pat.New()
	r.Get("/articles/{category}/{id:[0-9]+}", ArticleHandler)
	r.Get("/articles/{category}/", ArticlesCategoryHandler)
	r.Get("/products/{key}", ProductHandler)

The names are used to create a map of route variables which are stored in the
URL query, prefixed by a colon:

	category := req.URL.Query().Get(":category")

As in the gorilla/mux package, other matchers can be added to the registered
routes and URLs can be reversed as well. To build a URL for a route, first
add a name to it:

	r.Get("/products/{key}", ProductHandler).Name("product")

Then you can get it using the name and generate a URL:

	url, err := r.GetRoute("product").URL("key", "transmogrifier")

...and the result will be a url.URL with the following path:

	"/products/transmogrifier"

Check the mux documentation for more details about URL building and extra
matchers:

	http://gorilla-web.appspot.com/pkg/gorilla/mux/
*/
package pat
<|endoftext|>


<commit_before>//
// @project geniusrabbit.com 2016
// @author Dmitry Ponomarev <demdxx@gmail.com> 2016
//

package notificationcenter

import (
	"io"
	"sync"
)

var (
	loggers         = map[string]Logger{}
	subscribers     = map[string]Subscriber{}
	closeEvent      = make(chan bool, 1000)
	closeEventCount int
)

// LoggerByName interface
func LoggerByName(name string) Logger {
	l, _ := loggers[name]
	return l
}

// SubscriberByName interface
func SubscriberByName(name string) Subscriber {
	s, _ := subscribers[name]
	return s
}

// Register logger/subscriber
func Register(name string, elem ...interface{}) error {
	for _, el := range elem {
		var registered bool

		if logger, ok := el.(Logger); ok {
			if _, ok := loggers[name]; ok {
				return ErrInterfaceAlreadySubscribed
			}
			loggers[name] = logger
			registered = true
		}

		if subscriber, ok := el.(Subscriber); ok {
			if _, ok := subscribers[name]; ok {
				return ErrInterfaceAlreadySubscribed
			}
			subscribers[name] = subscriber
			registered = true
		}

		if !registered {
			return ErrInvalidObject
		}
	}
	return nil
}

// Unregister logger/subscriber object
func Unregister(elem ...interface{}) error {
	for _, el := range elem {
		for k, i := range loggers {
			if i == el {
				delete(loggers, k)
			}
		}
		for k, i := range subscribers {
			if i == el {
				delete(subscribers, k)
			}
		}
	}
	return nil
}

// UnregisterAllByName logger/subscriber
func UnregisterAllByName(name string, _loggers, _subscribers bool) {
	if i, ok := loggers[name]; ok && _loggers {
		if cl, ok := i.(io.Closer); ok {
			cl.Close()
		}
		delete(loggers, name)
	}
	if i, ok := subscribers[name]; ok && _subscribers {
		if cl, ok := i.(io.Closer); ok {
			cl.Close()
		}
		delete(subscribers, name)
	}
}

// Send message
func Send(name string, msg ...interface{}) error {
	if l, ok := loggers[name]; ok {
		return l.Send(msg...)
	}
	return ErrInvalidObject
}

// Listen subscribers
func Listen() (err error) {
	if nil == subscribers {
		return nil
	}

	var w sync.WaitGroup

	for _, sub := range subscribers {
		w.Add(1)
		go func() {
			err = sub.Listen()
			w.Done()
		}()
	}

	w.Wait()
	return
}

// Close notification center
func Close() {
	for _, i := range loggers {
		if cl, ok := i.(io.Closer); ok {
			cl.Close()
		}
	}

	for _, i := range subscribers {
		if cl, ok := i.(io.Closer); ok {
			cl.Close()
		}
	}

	for i := 0; i < closeEventCount; i++ {
		closeEvent <- true
	}
}

// OnClose event
func OnClose() <-chan bool {
	closeEventCount++
	return (<-chan bool)(closeEvent)
}
<commit_msg>Add global Subscribe/Unsubscribe functions<commit_after>//
// @project geniusrabbit.com 2016 – 2017
// @author Dmitry Ponomarev <demdxx@gmail.com> 2016 – 2017
//

package notificationcenter

import (
	"fmt"
	"io"
	"sync"
)

var (
	loggers         = map[string]Logger{}
	subscribers     = map[string]Subscriber{}
	closeEvent      = make(chan bool, 1000)
	closeEventCount int
)

// LoggerByName interface
func LoggerByName(name string) Logger {
	l, _ := loggers[name]
	return l
}

// SubscriberByName interface
func SubscriberByName(name string) Subscriber {
	s, _ := subscribers[name]
	return s
}

// Register logger/subscriber
func Register(name string, elem ...interface{}) error {
	for _, el := range elem {
		var registered bool

		if logger, ok := el.(Logger); ok {
			if _, ok := loggers[name]; ok {
				return ErrInterfaceAlreadySubscribed
			}
			loggers[name] = logger
			registered = true
		}

		if subscriber, ok := el.(Subscriber); ok {
			if _, ok := subscribers[name]; ok {
				return ErrInterfaceAlreadySubscribed
			}
			subscribers[name] = subscriber
			registered = true
		}

		if !registered {
			return ErrInvalidObject
		}
	}
	return nil
}

// Unregister logger/subscriber object
func Unregister(elem ...interface{}) error {
	for _, el := range elem {
		for k, i := range loggers {
			if i == el {
				delete(loggers, k)
			}
		}
		for k, i := range subscribers {
			if i == el {
				delete(subscribers, k)
			}
		}
	}
	return nil
}

// UnregisterAllByName logger/subscriber
func UnregisterAllByName(name string, _loggers, _subscribers bool) {
	if i, ok := loggers[name]; ok && _loggers {
		if cl, ok := i.(io.Closer); ok {
			cl.Close()
		}
		delete(loggers, name)
	}
	if i, ok := subscribers[name]; ok && _subscribers {
		if cl, ok := i.(io.Closer); ok {
			cl.Close()
		}
		delete(subscribers, name)
	}
}

// Send message
func Send(name string, msg ...interface{}) error {
	if l, ok := loggers[name]; ok {
		return l.Send(msg...)
	}
	return ErrInvalidObject
}

// Subscribe new handler
func Subscribe(name string, h Handler) error {
	if sub, _ := subscribers[name]; nil != sub {
		return sub.Subscribe(h)
	}
	return fmt.Errorf("Undefined subscriber with name: %s", name)
}

// Unsubscribe this handler by ptr
func Unsubscribe(name string, h Handler) error {
	if sub, _ := subscribers[name]; nil != sub {
		return sub.Unsubscribe(h)
	}
	return fmt.Errorf("Undefined subscriber with name: %s", name)
}

// Listen subscribers
func Listen() (err error) {
	if nil == subscribers {
		return nil
	}

	var w sync.WaitGroup

	for _, sub := range subscribers {
		w.Add(1)
		go func() {
			err = sub.Listen()
			w.Done()
		}()
	}

	w.Wait()
	return
}

// Close notification center
func Close() {
	for _, i := range loggers {
		if cl, ok := i.(io.Closer); ok {
			cl.Close()
		}
	}

	for _, i := range subscribers {
		if cl, ok := i.(io.Closer); ok {
			cl.Close()
		}
	}

	for i := 0; i < closeEventCount; i++ {
		closeEvent <- true
	}
}

// OnClose event
func OnClose() <-chan bool {
	closeEventCount++
	return (<-chan bool)(closeEvent)
}
<|endoftext|>


<commit_before>package paymentmodel

import (
	"errors"

	"github.com/koding/bongo"
)

func (p Plan) GetId() int64 {
	return p.Id
}

func (Plan) TableName() string {
	return "api.payment_plan"
}

//----------------------------------------------------------
// Crud methods
//----------------------------------------------------------

func (p *Plan) One(q *bongo.Query) error {
	return bongo.B.One(p, p, q)
}

func (p *Plan) Create() error {
	return bongo.B.Create(p)
}

var NameNotSet = errors.New("name not set")

func (p *Plan) ByName() (bool, error) {
	if p.Name == "" {
		return false, NameNotSet
	}

	selector := map[string]interface{}{"name": p.Name}

	err := p.One(bongo.NewQS(selector))
	if err == bongo.RecordNotFound {
		return false, nil
	}

	return true, nil
}
<commit_msg>payment: ignore duplicate plan errors when creating<commit_after>package paymentmodel

import (
	"errors"

	"github.com/koding/bongo"
)

func (p Plan) GetId() int64 {
	return p.Id
}

func (Plan) TableName() string {
	return "api.payment_plan"
}

//----------------------------------------------------------
// Crud methods
//----------------------------------------------------------

func (p *Plan) One(q *bongo.Query) error {
	return bongo.B.One(p, p, q)
}

var ErrDuplicatePlan = errors.New(
	`pq: duplicate key value violates unique constraint "payment_plan_name"`,
)

func (p *Plan) Create() error {
	err := bongo.B.Create(p)
	if err != nil && err.Error() != ErrDuplicatePlan.Error() {
		return err
	}

	return nil
}

var NameNotSet = errors.New("name not set")

func (p *Plan) ByName() (bool, error) {
	if p.Name == "" {
		return false, NameNotSet
	}

	selector := map[string]interface{}{"name": p.Name}

	err := p.One(bongo.NewQS(selector))
	if err == bongo.RecordNotFound {
		return false, nil
	}

	return true, nil
}
<|endoftext|>


<commit_before>package handler

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"time"

	"koding/tools/utils"
	"socialapi/config"
	"socialapi/models"
	"socialapi/workers/common/metrics"
	"socialapi/workers/common/response"
	"strconv"
	"strings"

	"github.com/juju/ratelimit"
	kmetrics "github.com/koding/metrics"

	tigertonic "github.com/rcrowley/go-tigertonic"

	gometrics "github.com/rcrowley/go-metrics"
)

const (
	PostRequest   = "POST"
	GetRequest    = "GET"
	DeleteRequest = "DELETE"
)

var (
	// TODO allowed origins must be configurable
	cors     = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
	conf     *config.Config
	trackers *metrics.Trackers
)

type Request struct {
	Handler        interface{}
	Endpoint       string
	Type           string
	Name           string
	CollectMetrics bool
	Metrics        *kmetrics.Metrics
	// used for external requests
	Params    map[string]string
	Cookie    string
	Cookies   []*http.Cookie
	Body      interface{}
	Headers   map[string]string
	Ratelimit func(*http.Request) *ratelimit.Bucket
}

// todo add prooper logging
func getAccount(r *http.Request) *models.Account {
	cookie, err := r.Cookie("clientId")
	if err != nil {
		return models.NewAccount()
	}

	// if cookie doenst exists return empty account
	if cookie.Value == "" {
		return models.NewAccount()
	}

	session, err := models.Cache.Session.ById(cookie.Value)
	if err != nil {
		return models.NewAccount()
	}

	// if session doesnt have username, return empty account
	if session.Username == "" {
		return models.NewAccount()
	}

	acc := models.NewAccount()
	// err is ignored intentionally
	acc.ByNick(session.Username)

	return acc
}

const timedOutMsg = `{"description":"request timed out","error":"koding.RequestTimedoutError"}`
const timeoutDuration = time.Second * 30

func Wrapper(r Request) http.Handler {
	handler := r.Handler

	var hHandler http.Handler

	// count the statuses of the requests
	hHandler = buildHandlerWithStatusCount(handler, r)

	hHandler = buildHandlerWithTimeTracking(hHandler, r)

	// every request should return under 30 secs, if not return 503 service
	// unavailable
	hHandler = http.TimeoutHandler(hHandler, timeoutDuration, timedOutMsg)

	// set rate limiting if the handler has one
	if r.Ratelimit != nil {
		hHandler = BuildHandlerWithRateLimit(hHandler, r.Ratelimit)
	}
	// create the final handler
	return cors.Build(hHandler)
}

// count the statuses of the requests
func buildHandlerWithStatusCount(handler interface{}, r Request) http.Handler {
	return CountedByStatus(
		tigertonic.Marshaled(handler), r.Name, r.CollectMetrics,
	)
}

// add request time tracking
func buildHandlerWithTimeTracking(handler http.Handler, r Request) http.Handler {
	var registry gometrics.Registry

	if r.Metrics != nil {
		registry = r.Metrics.Registry
	}

	return tigertonic.Timed(
		handler,
		r.Name,
		registry,
	)
}

func BuildHandlerWithContext(handler http.Handler) http.Handler {
	// add context
	return tigertonic.If(
		func(r *http.Request) (http.Header, error) {
			// this is an example
			// set group name to context
			//

			context := &models.Context{
				GroupName: "koding",
				Client: &models.Client{
					Account: getAccount(r),
					IP:      net.ParseIP(utils.GetIpAddress(r)),
				},
			}

			*(tigertonic.Context(r).(*models.Context)) = *context
			return nil, nil
		},
		handler,
	)
}

func BuildHandlerWithRateLimit(handler http.Handler, limiter func(*http.Request) *ratelimit.Bucket) http.Handler {
	// add context
	return tigertonic.If(
		func(r *http.Request) (http.Header, error) {
			// get the limiter
			bucket := limiter(r)

			h := http.Header{}
			h.Add("X-RateLimit-Limit", strconv.FormatInt(int64(bucket.Rate()*60), 10))

			// check if any 		throttling is enabled and then check token's available.
			// Tokens are filled per frequency of the initial bucket, so every request
			// is going to take one token from the bucket. If many requests come in (in
			// span time larger than the bucket's frequency), there will be no token's
			// available more so it will return a zero.
			timeToWait := bucket.Take(1)
			if timeToWait > time.Duration(0) {
				h.Add("Retry-After", strconv.FormatInt(int64(timeToWait.Seconds()), 10))
				return h, &response.LimitRateExceededError{}
			}

			return h, nil
		},
		handler,
	)
}

//----------------------------------------------------------
// CounterByStatus
//----------------------------------------------------------

type CounterByStatus struct {
	name           string
	handler        http.Handler
	collectMetrics bool
}

func (c *CounterByStatus) ServeHTTP(w0 http.ResponseWriter, r *http.Request) {
	w := tigertonic.NewTeeHeaderResponseWriter(w0)
	c.handler.ServeHTTP(w, r)

	if w.StatusCode <= 300 {
		c.track()
	}
}

func (c *CounterByStatus) track() {
	// don't log if analytics are disabled for that endpoint
	if !c.collectMetrics {
		return
	}

	// set `conf` and `trackers` if either is not set
	if conf == nil || trackers == nil {
		conf = config.MustGet()

		// don't log if analytics are disabled globally
		if !conf.Mixpanel.Enabled {
			return
		}

		trackers = metrics.InitTrackers(
			metrics.NewMixpanelTracker(conf.Mixpanel.Token),
		)
	}

	trackers.Track(c.name)
}

func CountedByStatus(handler http.Handler, name string, collectMetrics bool) *CounterByStatus {
	return &CounterByStatus{
		name:           name,
		handler:        handler,
		collectMetrics: collectMetrics,
	}
}

//
func MakeRequest(request *Request) (*http.Response, error) {
	if request.Cookie != "" {
		request.Cookies = parseCookiesToArray(request.Cookie)
	}

	request.Endpoint = prepareQueryString(request.Endpoint, request.Params)

	client := new(http.Client)
	hostname := config.MustGet().CustomDomain.Local
	endpoint := fmt.Sprintf("%s/%s", hostname, request.Endpoint)

	var byteData io.Reader
	if request.Body != nil {
		body, err := json.Marshal(request.Body)
		if err != nil {
			return nil, err
		}
		byteData = bytes.NewReader(body)
	}

	req, err := http.NewRequest(request.Type, endpoint, byteData)
	req = prepareHeaders(req, request.Headers)

	if err != nil {
		return nil, err
	}

	// Add cookies
	for _, cookie := range request.Cookies {
		req.AddCookie(cookie)
	}

	return client.Do(req)
}

func prepareQueryString(endpoint string, params map[string]string) string {
	if len(params) == 0 {
		return endpoint
	}

	fullPath := fmt.Sprintf("%s?", endpoint)

	for key, value := range params {
		fullPath = fmt.Sprintf("%s%s=%s&", fullPath, key, value)
	}

	return fullPath[0 : len(fullPath)-1]
}

func prepareHeaders(req *http.Request, headers map[string]string) *http.Request {
	newReq := new(http.Request)
	*newReq = *req
	for k, v := range headers {
		newReq.Header.Set(k, v)
	}

	return newReq
}

func parseCookiesToArray(cookie string) []*http.Cookie {
	pairs := strings.Split(cookie, "; ")
	cookies := make([]*http.Cookie, 0)

	if len(pairs) == 0 {
		return cookies
	}

	for _, val := range pairs {
		cp := strings.Split(val, "=")
		if len(cp) != 2 {
			continue
		}

		c := new(http.Cookie)
		c.Name = cp[0]
		c.Value = cp[1]

		cookies = append(cookies, c)
	}

	return cookies
}

func ParseCookiesToMap(cookie string) map[string]*http.Cookie {
	pairs := strings.Split(cookie, "; ")
	cookies := make(map[string]*http.Cookie, 0)

	if len(pairs) == 0 {
		return cookies
	}

	for _, val := range pairs {
		cp := strings.Split(val, "=")
		if len(cp) != 2 {
			continue
		}

		c := new(http.Cookie)
		c.Name = cp[0]
		c.Value = cp[1]

		cookies[cp[0]] = c
	}

	return cookies
}
<commit_msg>socialapi: handler is changed about geoip<commit_after>package handler

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"time"

	"koding/tools/utils"
	"socialapi/config"
	"socialapi/models"
	"socialapi/workers/common/metrics"
	"socialapi/workers/common/response"
	"strconv"
	"strings"

	"github.com/koding/runner"
	"github.com/koding/bongo"
	"github.com/juju/ratelimit"
	kmetrics "github.com/koding/metrics"

	tigertonic "github.com/rcrowley/go-tigertonic"

	gometrics "github.com/rcrowley/go-metrics"
)

const (
	PostRequest   = "POST"
	GetRequest    = "GET"
	DeleteRequest = "DELETE"
)

var (
	// TODO allowed origins must be configurable
	cors     = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
	conf     *config.Config
	trackers *metrics.Trackers
)

type Request struct {
	Handler        interface{}
	Endpoint       string
	Type           string
	Name           string
	CollectMetrics bool
	Metrics        *kmetrics.Metrics
	// used for external requests
	Params    map[string]string
	Cookie    string
	Cookies   []*http.Cookie
	Body      interface{}
	Headers   map[string]string
	Ratelimit func(*http.Request) *ratelimit.Bucket
}

// todo add prooper logging
func getAccount(r *http.Request) *models.Account {
	cookie, err := r.Cookie("clientId")
	if err != nil {
		return models.NewAccount()
	}

	// if cookie doenst exists return empty account
	if cookie.Value == "" {
		return models.NewAccount()
	}

	session, err := models.Cache.Session.ById(cookie.Value)
	if err != nil {
		return models.NewAccount()
	}

	// if session doesnt have username, return empty account
	if session.Username == "" {
		return models.NewAccount()
	}

	acc := models.NewAccount()
	// err is ignored intentionally
	err = acc.ByNick(session.Username)
	if err != nil && err != bongo.RecordNotFound {
	    runner.MustGetLogger().Error("Err while getting account: %s, err :%s", session.Username, err.Error())
	}

	return acc
}

const timedOutMsg = `{"description":"request timed out","error":"koding.RequestTimedoutError"}`
const timeoutDuration = time.Second * 30

func Wrapper(r Request) http.Handler {
	handler := r.Handler

	var hHandler http.Handler

	// count the statuses of the requests
	hHandler = buildHandlerWithStatusCount(handler, r)

	hHandler = buildHandlerWithTimeTracking(hHandler, r)

	// every request should return under 30 secs, if not return 503 service
	// unavailable
	hHandler = http.TimeoutHandler(hHandler, timeoutDuration, timedOutMsg)

	// set rate limiting if the handler has one
	if r.Ratelimit != nil {
		hHandler = BuildHandlerWithRateLimit(hHandler, r.Ratelimit)
	}
	// create the final handler
	return cors.Build(hHandler)
}

// count the statuses of the requests
func buildHandlerWithStatusCount(handler interface{}, r Request) http.Handler {
	return CountedByStatus(
		tigertonic.Marshaled(handler), r.Name, r.CollectMetrics,
	)
}

// add request time tracking
func buildHandlerWithTimeTracking(handler http.Handler, r Request) http.Handler {
	var registry gometrics.Registry

	if r.Metrics != nil {
		registry = r.Metrics.Registry
	}

	return tigertonic.Timed(
		handler,
		r.Name,
		registry,
	)
}

func BuildHandlerWithContext(handler http.Handler) http.Handler {
	// add context
	return tigertonic.If(
		func(r *http.Request) (http.Header, error) {
			// this is an example
			// set group name to context
			//

			context := &models.Context{
				GroupName: "koding",
				Client: &models.Client{
					Account: getAccount(r),
					IP:      net.ParseIP(utils.GetIpAddress(r)),
				},
			}

			*(tigertonic.Context(r).(*models.Context)) = *context
			return nil, nil
		},
		handler,
	)
}

func BuildHandlerWithRateLimit(handler http.Handler, limiter func(*http.Request) *ratelimit.Bucket) http.Handler {
	// add context
	return tigertonic.If(
		func(r *http.Request) (http.Header, error) {
			// get the limiter
			bucket := limiter(r)

			h := http.Header{}
			h.Add("X-RateLimit-Limit", strconv.FormatInt(int64(bucket.Rate()*60), 10))

			// check if any 		throttling is enabled and then check token's available.
			// Tokens are filled per frequency of the initial bucket, so every request
			// is going to take one token from the bucket. If many requests come in (in
			// span time larger than the bucket's frequency), there will be no token's
			// available more so it will return a zero.
			timeToWait := bucket.Take(1)
			if timeToWait > time.Duration(0) {
				h.Add("Retry-After", strconv.FormatInt(int64(timeToWait.Seconds()), 10))
				return h, &response.LimitRateExceededError{}
			}

			return h, nil
		},
		handler,
	)
}

//----------------------------------------------------------
// CounterByStatus
//----------------------------------------------------------

type CounterByStatus struct {
	name           string
	handler        http.Handler
	collectMetrics bool
}

func (c *CounterByStatus) ServeHTTP(w0 http.ResponseWriter, r *http.Request) {
	w := tigertonic.NewTeeHeaderResponseWriter(w0)
	c.handler.ServeHTTP(w, r)

	if w.StatusCode <= 300 {
		c.track()
	}
}

func (c *CounterByStatus) track() {
	// don't log if analytics are disabled for that endpoint
	if !c.collectMetrics {
		return
	}

	// set `conf` and `trackers` if either is not set
	if conf == nil || trackers == nil {
		conf = config.MustGet()

		// don't log if analytics are disabled globally
		if !conf.Mixpanel.Enabled {
			return
		}

		trackers = metrics.InitTrackers(
			metrics.NewMixpanelTracker(conf.Mixpanel.Token),
		)
	}

	trackers.Track(c.name)
}

func CountedByStatus(handler http.Handler, name string, collectMetrics bool) *CounterByStatus {
	return &CounterByStatus{
		name:           name,
		handler:        handler,
		collectMetrics: collectMetrics,
	}
}

//
func MakeRequest(request *Request) (*http.Response, error) {
	if request.Cookie != "" {
		request.Cookies = parseCookiesToArray(request.Cookie)
	}

	request.Endpoint = prepareQueryString(request.Endpoint, request.Params)

	client := new(http.Client)
	hostname := config.MustGet().CustomDomain.Local
	endpoint := fmt.Sprintf("%s/%s", hostname, request.Endpoint)

	var byteData io.Reader
	if request.Body != nil {
		body, err := json.Marshal(request.Body)
		if err != nil {
			return nil, err
		}
		byteData = bytes.NewReader(body)
	}

	req, err := http.NewRequest(request.Type, endpoint, byteData)
	req = prepareHeaders(req, request.Headers)

	if err != nil {
		return nil, err
	}

	// Add cookies
	for _, cookie := range request.Cookies {
		req.AddCookie(cookie)
	}

	return client.Do(req)
}

func prepareQueryString(endpoint string, params map[string]string) string {
	if len(params) == 0 {
		return endpoint
	}

	fullPath := fmt.Sprintf("%s?", endpoint)

	for key, value := range params {
		fullPath = fmt.Sprintf("%s%s=%s&", fullPath, key, value)
	}

	return fullPath[0 : len(fullPath)-1]
}

func prepareHeaders(req *http.Request, headers map[string]string) *http.Request {
	newReq := new(http.Request)
	*newReq = *req
	for k, v := range headers {
		newReq.Header.Set(k, v)
	}

	return newReq
}

func parseCookiesToArray(cookie string) []*http.Cookie {
	pairs := strings.Split(cookie, "; ")
	cookies := make([]*http.Cookie, 0)

	if len(pairs) == 0 {
		return cookies
	}

	for _, val := range pairs {
		cp := strings.Split(val, "=")
		if len(cp) != 2 {
			continue
		}

		c := new(http.Cookie)
		c.Name = cp[0]
		c.Value = cp[1]

		cookies = append(cookies, c)
	}

	return cookies
}

func ParseCookiesToMap(cookie string) map[string]*http.Cookie {
	pairs := strings.Split(cookie, "; ")
	cookies := make(map[string]*http.Cookie, 0)

	if len(pairs) == 0 {
		return cookies
	}

	for _, val := range pairs {
		cp := strings.Split(val, "=")
		if len(cp) != 2 {
			continue
		}

		c := new(http.Cookie)
		c.Name = cp[0]
		c.Value = cp[1]

		cookies[cp[0]] = c
	}

	return cookies
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"

	"veyron.io/veyron/veyron/profiles"
	"veyron.io/veyron/veyron2"
	"veyron.io/veyron/veyron2/ipc"
	"veyron.io/veyron/veyron2/naming"
	"veyron.io/veyron/veyron2/security"

	"test_service"
)

type testServiceDispatcher struct {
	cache           interface{}
	errorThrower    interface{}
	cancelCollector interface{}
}

func (sd *testServiceDispatcher) Lookup(suffix, method string) (interface{}, security.Authorizer, error) {
	if strings.HasPrefix(suffix, "cache") {
		return ipc.ReflectInvoker(sd.cache), nil, nil
	}

	if strings.HasPrefix(suffix, "errorThrower") {
		return ipc.ReflectInvoker(sd.errorThrower), nil, nil
	}

	if strings.HasPrefix(suffix, "cancel") {
		return ipc.ReflectInvoker(sd.cancelCollector), nil, nil
	}

	return ipc.ReflectInvoker(sd.cache), nil, nil
}

func StartServer(r veyron2.Runtime) (ipc.Server, naming.Endpoint, error) {
	// Create a new server instance.
	s, err := r.NewServer()
	if err != nil {
		return nil, nil, fmt.Errorf("failure creating server: %v", err)
	}

	disp := &testServiceDispatcher{
		cache:           test_service.NewServerCache(NewCached()),
		errorThrower:    test_service.NewServerErrorThrower(NewErrorThrower()),
		cancelCollector: test_service.NewServerCancelCollector(NewCancelCollector()),
	}

	// Create an endpoint and begin listening.
	endpoint, err := s.Listen(profiles.LocalListenSpec)
	if err != nil {
		return nil, nil, fmt.Errorf("error listening to service: %v", err)
	}

	// Publish the services. This will register them in the mount table and
	// maintain the registration until StopServing is called.
	if err := s.Serve("test_service", disp); err != nil {
		return nil, nil, fmt.Errorf("error publishing service '%s': %v", err)
	}

	return s, endpoint, nil
}
<commit_msg>veyron2/ipc: 3 of n. 'Invoker and Signature' rationalisation.<commit_after>package main

import (
	"fmt"
	"strings"

	"veyron.io/veyron/veyron/profiles"
	"veyron.io/veyron/veyron2"
	"veyron.io/veyron/veyron2/ipc"
	"veyron.io/veyron/veyron2/naming"
	"veyron.io/veyron/veyron2/security"

	"test_service"
)

type testServiceDispatcher struct {
	cache           interface{}
	errorThrower    interface{}
	cancelCollector interface{}
}

func (sd *testServiceDispatcher) Lookup(suffix, method string) (interface{}, security.Authorizer, error) {
	if strings.HasPrefix(suffix, "cache") {
		return ipc.ReflectInvoker(sd.cache), nil, nil
	}

	if strings.HasPrefix(suffix, "errorThrower") {
		return ipc.ReflectInvoker(sd.errorThrower), nil, nil
	}

	if strings.HasPrefix(suffix, "cancel") {
		return ipc.ReflectInvoker(sd.cancelCollector), nil, nil
	}

	return ipc.ReflectInvoker(sd.cache), nil, nil
}

func StartServer(r veyron2.Runtime) (ipc.Server, naming.Endpoint, error) {
	// Create a new server instance.
	s, err := r.NewServer()
	if err != nil {
		return nil, nil, fmt.Errorf("failure creating server: %v", err)
	}

	disp := &testServiceDispatcher{
		cache:           test_service.NewServerCache(NewCached()),
		errorThrower:    test_service.NewServerErrorThrower(NewErrorThrower()),
		cancelCollector: test_service.NewServerCancelCollector(NewCancelCollector()),
	}

	// Create an endpoint and begin listening.
	endpoint, err := s.Listen(profiles.LocalListenSpec)
	if err != nil {
		return nil, nil, fmt.Errorf("error listening to service: %v", err)
	}

	// Publish the services. This will register them in the mount table and
	// maintain the registration until StopServing is called.
	if err := s.ServeDispatcher("test_service", disp); err != nil {
		return nil, nil, fmt.Errorf("error publishing service '%s': %v", err)
	}

	return s, endpoint, nil
}
<|endoftext|>


<commit_before>package common

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/ChrisTrenkamp/goxpath"
	"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
)

// Parallels9Driver is a base type for Parallels builders.
type Parallels9Driver struct {
	// This is the path to the "prlctl" application.
	PrlctlPath string

	// This is the path to the "prlsrvctl" application.
	PrlsrvctlPath string

	// The path to the parallels_dhcp_leases file
	dhcpLeaseFile string
}

// Import creates a clone of the source VM and reassigns the MAC address if needed.
func (d *Parallels9Driver) Import(name, srcPath, dstDir string, reassignMAC bool) error {
	err := d.Prlctl("register", srcPath, "--preserve-uuid")
	if err != nil {
		return err
	}

	srcID, err := getVMID(srcPath)
	if err != nil {
		return err
	}

	srcMAC := "auto"
	if !reassignMAC {
		srcMAC, err = getFirtsMACAddress(srcPath)
		if err != nil {
			return err
		}
	}

	err = d.Prlctl("clone", srcID, "--name", name, "--dst", dstDir)
	if err != nil {
		return err
	}

	err = d.Prlctl("unregister", srcID)
	if err != nil {
		return err
	}

	err = d.Prlctl("set", name, "--device-set", "net0", "--mac", srcMAC)
	if err != nil {
		return err
	}
	return nil
}

func getVMID(path string) (string, error) {
	return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Identification/VmUuid")
}

func getFirtsMACAddress(path string) (string, error) {
	return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Hardware/NetworkAdapter[@id='0']/MAC")
}

func getConfigValueFromXpath(path, xpath string) (string, error) {
	file, err := os.Open(path + "/config.pvs")
	if err != nil {
		return "", err
	}

	doc, err := xmltree.ParseXML(file)
	if err != nil {
		return "", err
	}

	xpExec := goxpath.MustParse(xpath)
	node, err := xpExec.Exec(doc)
	if err != nil {
		return "", err
	}

	return node.String(), nil
}

// Finds an application bundle by identifier (for "darwin" platform only)
func getAppPath(bundleID string) (string, error) {
	var stdout bytes.Buffer

	cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleID)
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		return "", err
	}

	pathOutput := strings.TrimSpace(stdout.String())
	if pathOutput == "" {
		if fi, err := os.Stat("/Applications/Parallels Desktop.app"); err == nil {
			if fi.IsDir() {
				return "/Applications/Parallels Desktop.app", nil
			}
		}

		return "", fmt.Errorf(
			"Could not detect Parallels Desktop! Make sure it is properly installed.")
	}

	return pathOutput, nil
}

// CompactDisk performs the compation of the specified virtual disk image.
func (d *Parallels9Driver) CompactDisk(diskPath string) error {
	prlDiskToolPath, err := exec.LookPath("prl_disk_tool")
	if err != nil {
		return err
	}

	// Analyze the disk content and remove unused blocks
	command := []string{
		"compact",
		"--hdd", diskPath,
	}
	if err := exec.Command(prlDiskToolPath, command...).Run(); err != nil {
		return err
	}

	// Remove null blocks
	command = []string{
		"compact", "--buildmap",
		"--hdd", diskPath,
	}
	if err := exec.Command(prlDiskToolPath, command...).Run(); err != nil {
		return err
	}

	return nil
}

// DeviceAddCDROM adds a virtual CDROM device and attaches the specified image.
func (d *Parallels9Driver) DeviceAddCDROM(name string, image string) (string, error) {
	command := []string{
		"set", name,
		"--device-add", "cdrom",
		"--image", image,
	}

	out, err := exec.Command(d.PrlctlPath, command...).Output()
	if err != nil {
		return "", err
	}

	deviceRe := regexp.MustCompile(`\s+(cdrom\d+)\s+`)
	matches := deviceRe.FindStringSubmatch(string(out))
	if matches == nil {
		return "", fmt.Errorf(
			"Could not determine cdrom device name in the output:\n%s", string(out))
	}

	deviceName := matches[1]
	return deviceName, nil
}

// DiskPath returns a full path to the first virtual disk drive.
func (d *Parallels9Driver) DiskPath(name string) (string, error) {
	out, err := exec.Command(d.PrlctlPath, "list", "-i", name).Output()
	if err != nil {
		return "", err
	}

	HDDRe := regexp.MustCompile("hdd0.* image='(.*)' type=*")
	matches := HDDRe.FindStringSubmatch(string(out))
	if matches == nil {
		return "", fmt.Errorf(
			"Could not determine hdd image path in the output:\n%s", string(out))
	}

	HDDPath := matches[1]
	return HDDPath, nil
}

// IsRunning determines whether the VM is running or not.
func (d *Parallels9Driver) IsRunning(name string) (bool, error) {
	var stdout bytes.Buffer

	cmd := exec.Command(d.PrlctlPath, "list", name, "--no-header", "--output", "status")
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		return false, err
	}

	log.Printf("Checking VM state: %s\n", strings.TrimSpace(stdout.String()))

	for _, line := range strings.Split(stdout.String(), "\n") {
		if line == "running" {
			return true, nil
		}

		if line == "suspended" {
			return true, nil
		}
		if line == "paused" {
			return true, nil
		}
		if line == "stopping" {
			return true, nil
		}
	}

	return false, nil
}

// Stop forcibly stops the VM.
func (d *Parallels9Driver) Stop(name string) error {
	if err := d.Prlctl("stop", name); err != nil {
		return err
	}

	// We sleep here for a little bit to let the session "unlock"
	time.Sleep(2 * time.Second)

	return nil
}

// Prlctl executes the specified "prlctl" command.
func (d *Parallels9Driver) Prlctl(args ...string) error {
	var stdout, stderr bytes.Buffer

	log.Printf("Executing prlctl: %#v", args)
	cmd := exec.Command(d.PrlctlPath, args...)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf("prlctl error: %s", stderrString)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	return err
}

// Verify raises an error if the builder could not be used on that host machine.
func (d *Parallels9Driver) Verify() error {
	return nil
}

// Version returns the version of Parallels Desktop installed on that host.
func (d *Parallels9Driver) Version() (string, error) {
	out, err := exec.Command(d.PrlctlPath, "--version").Output()
	if err != nil {
		return "", err
	}

	versionRe := regexp.MustCompile(`prlctl version (\d+\.\d+.\d+)`)
	matches := versionRe.FindStringSubmatch(string(out))
	if matches == nil {
		return "", fmt.Errorf(
			"Could not find Parallels Desktop version in output:\n%s", string(out))
	}

	version := matches[1]
	log.Printf("Parallels Desktop version: %s", version)
	return version, nil
}

// SendKeyScanCodes sends the specified scancodes as key events to the VM.
// It is performed using "Prltype" script (refer to "prltype.go").
func (d *Parallels9Driver) SendKeyScanCodes(vmName string, codes ...string) error {
	var stdout, stderr bytes.Buffer

	if codes == nil || len(codes) == 0 {
		log.Printf("No scan codes to send")
		return nil
	}

	f, err := ioutil.TempFile("", "prltype")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())

	script := []byte(Prltype)
	_, err = f.Write(script)
	if err != nil {
		return err
	}

	args := prepend(vmName, codes)
	args = prepend(f.Name(), args)
	cmd := exec.Command("/usr/bin/python", args...)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err = cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf("prltype error: %s", stderrString)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	return err
}

func prepend(head string, tail []string) []string {
	tmp := make([]string, len(tail)+1)
	for i := 0; i < len(tail); i++ {
		tmp[i+1] = tail[i]
	}
	tmp[0] = head
	return tmp
}

// SetDefaultConfiguration applies pre-defined default settings to the VM config.
func (d *Parallels9Driver) SetDefaultConfiguration(vmName string) error {
	commands := make([][]string, 7)
	commands[0] = []string{"set", vmName, "--cpus", "1"}
	commands[1] = []string{"set", vmName, "--memsize", "512"}
	commands[2] = []string{"set", vmName, "--startup-view", "same"}
	commands[3] = []string{"set", vmName, "--on-shutdown", "close"}
	commands[4] = []string{"set", vmName, "--on-window-close", "keep-running"}
	commands[5] = []string{"set", vmName, "--auto-share-camera", "off"}
	commands[6] = []string{"set", vmName, "--smart-guard", "off"}

	for _, command := range commands {
		err := d.Prlctl(command...)
		if err != nil {
			return err
		}
	}
	return nil
}

// MAC returns the MAC address of the VM's first network interface.
func (d *Parallels9Driver) MAC(vmName string) (string, error) {
	var stdout bytes.Buffer

	cmd := exec.Command(d.PrlctlPath, "list", "-i", vmName)
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		log.Printf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
		return "", err
	}

	stdoutString := strings.TrimSpace(stdout.String())
	re := regexp.MustCompile("net0.* mac=([0-9A-F]{12}) card=.*")
	macMatch := re.FindAllStringSubmatch(stdoutString, 1)

	if len(macMatch) != 1 {
		return "", fmt.Errorf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
	}

	mac := macMatch[0][1]
	log.Printf("Found MAC address for NIC: net0 - %s\n", mac)
	return mac, nil
}

// IPAddress finds the IP address of a VM connected that uses DHCP by its MAC address
//
// Parses the file /Library/Preferences/Parallels/parallels_dhcp_leases
// file contain a list of DHCP leases given by Parallels Desktop
// Example line:
// 10.211.55.181="1418921112,1800,001c42f593fb,ff42f593fb000100011c25b9ff001c42f593fb"
// IP Address   ="Lease expiry, Lease time, MAC, MAC or DUID"
func (d *Parallels9Driver) IPAddress(mac string) (string, error) {

	if len(mac) != 12 {
		return "", fmt.Errorf("Not a valid MAC address: %s. It should be exactly 12 digits.", mac)
	}

	leases, err := ioutil.ReadFile(d.dhcpLeaseFile)
	if err != nil {
		return "", err
	}

	re := regexp.MustCompile("(.*)=\"(.*),(.*)," + strings.ToLower(mac) + ",.*\"")
	mostRecentIP := ""
	mostRecentLease := uint64(0)
	for _, l := range re.FindAllStringSubmatch(string(leases), -1) {
		ip := l[1]
		expiry, _ := strconv.ParseUint(l[2], 10, 64)
		leaseTime, _ := strconv.ParseUint(l[3], 10, 32)
		log.Printf("Found lease: %s for MAC: %s, expiring at %d, leased for %d s.\n", ip, mac, expiry, leaseTime)
		if mostRecentLease <= expiry-leaseTime {
			mostRecentIP = ip
			mostRecentLease = expiry - leaseTime
		}
	}

	if len(mostRecentIP) == 0 {
		return "", fmt.Errorf("IP lease not found for MAC address %s in: %s\n", mac, d.dhcpLeaseFile)
	}

	log.Printf("Found IP lease: %s for MAC address %s\n", mostRecentIP, mac)
	return mostRecentIP, nil
}

// ToolsISOPath returns a full path to the Parallels Tools ISO for the specified guest
// OS type. The following OS types are supported: "win", "lin", "mac", "other".
func (d *Parallels9Driver) ToolsISOPath(k string) (string, error) {
	appPath, err := getAppPath("com.parallels.desktop.console")
	if err != nil {
		return "", err
	}

	toolsPath := filepath.Join(appPath, "Contents", "Resources", "Tools", "prl-tools-"+k+".iso")
	log.Printf("Parallels Tools path: '%s'", toolsPath)
	return toolsPath, nil
}
<commit_msg>spelling: compaction<commit_after>package common

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/ChrisTrenkamp/goxpath"
	"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
)

// Parallels9Driver is a base type for Parallels builders.
type Parallels9Driver struct {
	// This is the path to the "prlctl" application.
	PrlctlPath string

	// This is the path to the "prlsrvctl" application.
	PrlsrvctlPath string

	// The path to the parallels_dhcp_leases file
	dhcpLeaseFile string
}

// Import creates a clone of the source VM and reassigns the MAC address if needed.
func (d *Parallels9Driver) Import(name, srcPath, dstDir string, reassignMAC bool) error {
	err := d.Prlctl("register", srcPath, "--preserve-uuid")
	if err != nil {
		return err
	}

	srcID, err := getVMID(srcPath)
	if err != nil {
		return err
	}

	srcMAC := "auto"
	if !reassignMAC {
		srcMAC, err = getFirtsMACAddress(srcPath)
		if err != nil {
			return err
		}
	}

	err = d.Prlctl("clone", srcID, "--name", name, "--dst", dstDir)
	if err != nil {
		return err
	}

	err = d.Prlctl("unregister", srcID)
	if err != nil {
		return err
	}

	err = d.Prlctl("set", name, "--device-set", "net0", "--mac", srcMAC)
	if err != nil {
		return err
	}
	return nil
}

func getVMID(path string) (string, error) {
	return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Identification/VmUuid")
}

func getFirtsMACAddress(path string) (string, error) {
	return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Hardware/NetworkAdapter[@id='0']/MAC")
}

func getConfigValueFromXpath(path, xpath string) (string, error) {
	file, err := os.Open(path + "/config.pvs")
	if err != nil {
		return "", err
	}

	doc, err := xmltree.ParseXML(file)
	if err != nil {
		return "", err
	}

	xpExec := goxpath.MustParse(xpath)
	node, err := xpExec.Exec(doc)
	if err != nil {
		return "", err
	}

	return node.String(), nil
}

// Finds an application bundle by identifier (for "darwin" platform only)
func getAppPath(bundleID string) (string, error) {
	var stdout bytes.Buffer

	cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleID)
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		return "", err
	}

	pathOutput := strings.TrimSpace(stdout.String())
	if pathOutput == "" {
		if fi, err := os.Stat("/Applications/Parallels Desktop.app"); err == nil {
			if fi.IsDir() {
				return "/Applications/Parallels Desktop.app", nil
			}
		}

		return "", fmt.Errorf(
			"Could not detect Parallels Desktop! Make sure it is properly installed.")
	}

	return pathOutput, nil
}

// CompactDisk performs the compaction of the specified virtual disk image.
func (d *Parallels9Driver) CompactDisk(diskPath string) error {
	prlDiskToolPath, err := exec.LookPath("prl_disk_tool")
	if err != nil {
		return err
	}

	// Analyze the disk content and remove unused blocks
	command := []string{
		"compact",
		"--hdd", diskPath,
	}
	if err := exec.Command(prlDiskToolPath, command...).Run(); err != nil {
		return err
	}

	// Remove null blocks
	command = []string{
		"compact", "--buildmap",
		"--hdd", diskPath,
	}
	if err := exec.Command(prlDiskToolPath, command...).Run(); err != nil {
		return err
	}

	return nil
}

// DeviceAddCDROM adds a virtual CDROM device and attaches the specified image.
func (d *Parallels9Driver) DeviceAddCDROM(name string, image string) (string, error) {
	command := []string{
		"set", name,
		"--device-add", "cdrom",
		"--image", image,
	}

	out, err := exec.Command(d.PrlctlPath, command...).Output()
	if err != nil {
		return "", err
	}

	deviceRe := regexp.MustCompile(`\s+(cdrom\d+)\s+`)
	matches := deviceRe.FindStringSubmatch(string(out))
	if matches == nil {
		return "", fmt.Errorf(
			"Could not determine cdrom device name in the output:\n%s", string(out))
	}

	deviceName := matches[1]
	return deviceName, nil
}

// DiskPath returns a full path to the first virtual disk drive.
func (d *Parallels9Driver) DiskPath(name string) (string, error) {
	out, err := exec.Command(d.PrlctlPath, "list", "-i", name).Output()
	if err != nil {
		return "", err
	}

	HDDRe := regexp.MustCompile("hdd0.* image='(.*)' type=*")
	matches := HDDRe.FindStringSubmatch(string(out))
	if matches == nil {
		return "", fmt.Errorf(
			"Could not determine hdd image path in the output:\n%s", string(out))
	}

	HDDPath := matches[1]
	return HDDPath, nil
}

// IsRunning determines whether the VM is running or not.
func (d *Parallels9Driver) IsRunning(name string) (bool, error) {
	var stdout bytes.Buffer

	cmd := exec.Command(d.PrlctlPath, "list", name, "--no-header", "--output", "status")
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		return false, err
	}

	log.Printf("Checking VM state: %s\n", strings.TrimSpace(stdout.String()))

	for _, line := range strings.Split(stdout.String(), "\n") {
		if line == "running" {
			return true, nil
		}

		if line == "suspended" {
			return true, nil
		}
		if line == "paused" {
			return true, nil
		}
		if line == "stopping" {
			return true, nil
		}
	}

	return false, nil
}

// Stop forcibly stops the VM.
func (d *Parallels9Driver) Stop(name string) error {
	if err := d.Prlctl("stop", name); err != nil {
		return err
	}

	// We sleep here for a little bit to let the session "unlock"
	time.Sleep(2 * time.Second)

	return nil
}

// Prlctl executes the specified "prlctl" command.
func (d *Parallels9Driver) Prlctl(args ...string) error {
	var stdout, stderr bytes.Buffer

	log.Printf("Executing prlctl: %#v", args)
	cmd := exec.Command(d.PrlctlPath, args...)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf("prlctl error: %s", stderrString)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	return err
}

// Verify raises an error if the builder could not be used on that host machine.
func (d *Parallels9Driver) Verify() error {
	return nil
}

// Version returns the version of Parallels Desktop installed on that host.
func (d *Parallels9Driver) Version() (string, error) {
	out, err := exec.Command(d.PrlctlPath, "--version").Output()
	if err != nil {
		return "", err
	}

	versionRe := regexp.MustCompile(`prlctl version (\d+\.\d+.\d+)`)
	matches := versionRe.FindStringSubmatch(string(out))
	if matches == nil {
		return "", fmt.Errorf(
			"Could not find Parallels Desktop version in output:\n%s", string(out))
	}

	version := matches[1]
	log.Printf("Parallels Desktop version: %s", version)
	return version, nil
}

// SendKeyScanCodes sends the specified scancodes as key events to the VM.
// It is performed using "Prltype" script (refer to "prltype.go").
func (d *Parallels9Driver) SendKeyScanCodes(vmName string, codes ...string) error {
	var stdout, stderr bytes.Buffer

	if codes == nil || len(codes) == 0 {
		log.Printf("No scan codes to send")
		return nil
	}

	f, err := ioutil.TempFile("", "prltype")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())

	script := []byte(Prltype)
	_, err = f.Write(script)
	if err != nil {
		return err
	}

	args := prepend(vmName, codes)
	args = prepend(f.Name(), args)
	cmd := exec.Command("/usr/bin/python", args...)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err = cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf("prltype error: %s", stderrString)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	return err
}

func prepend(head string, tail []string) []string {
	tmp := make([]string, len(tail)+1)
	for i := 0; i < len(tail); i++ {
		tmp[i+1] = tail[i]
	}
	tmp[0] = head
	return tmp
}

// SetDefaultConfiguration applies pre-defined default settings to the VM config.
func (d *Parallels9Driver) SetDefaultConfiguration(vmName string) error {
	commands := make([][]string, 7)
	commands[0] = []string{"set", vmName, "--cpus", "1"}
	commands[1] = []string{"set", vmName, "--memsize", "512"}
	commands[2] = []string{"set", vmName, "--startup-view", "same"}
	commands[3] = []string{"set", vmName, "--on-shutdown", "close"}
	commands[4] = []string{"set", vmName, "--on-window-close", "keep-running"}
	commands[5] = []string{"set", vmName, "--auto-share-camera", "off"}
	commands[6] = []string{"set", vmName, "--smart-guard", "off"}

	for _, command := range commands {
		err := d.Prlctl(command...)
		if err != nil {
			return err
		}
	}
	return nil
}

// MAC returns the MAC address of the VM's first network interface.
func (d *Parallels9Driver) MAC(vmName string) (string, error) {
	var stdout bytes.Buffer

	cmd := exec.Command(d.PrlctlPath, "list", "-i", vmName)
	cmd.Stdout = &stdout
	if err := cmd.Run(); err != nil {
		log.Printf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
		return "", err
	}

	stdoutString := strings.TrimSpace(stdout.String())
	re := regexp.MustCompile("net0.* mac=([0-9A-F]{12}) card=.*")
	macMatch := re.FindAllStringSubmatch(stdoutString, 1)

	if len(macMatch) != 1 {
		return "", fmt.Errorf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
	}

	mac := macMatch[0][1]
	log.Printf("Found MAC address for NIC: net0 - %s\n", mac)
	return mac, nil
}

// IPAddress finds the IP address of a VM connected that uses DHCP by its MAC address
//
// Parses the file /Library/Preferences/Parallels/parallels_dhcp_leases
// file contain a list of DHCP leases given by Parallels Desktop
// Example line:
// 10.211.55.181="1418921112,1800,001c42f593fb,ff42f593fb000100011c25b9ff001c42f593fb"
// IP Address   ="Lease expiry, Lease time, MAC, MAC or DUID"
func (d *Parallels9Driver) IPAddress(mac string) (string, error) {

	if len(mac) != 12 {
		return "", fmt.Errorf("Not a valid MAC address: %s. It should be exactly 12 digits.", mac)
	}

	leases, err := ioutil.ReadFile(d.dhcpLeaseFile)
	if err != nil {
		return "", err
	}

	re := regexp.MustCompile("(.*)=\"(.*),(.*)," + strings.ToLower(mac) + ",.*\"")
	mostRecentIP := ""
	mostRecentLease := uint64(0)
	for _, l := range re.FindAllStringSubmatch(string(leases), -1) {
		ip := l[1]
		expiry, _ := strconv.ParseUint(l[2], 10, 64)
		leaseTime, _ := strconv.ParseUint(l[3], 10, 32)
		log.Printf("Found lease: %s for MAC: %s, expiring at %d, leased for %d s.\n", ip, mac, expiry, leaseTime)
		if mostRecentLease <= expiry-leaseTime {
			mostRecentIP = ip
			mostRecentLease = expiry - leaseTime
		}
	}

	if len(mostRecentIP) == 0 {
		return "", fmt.Errorf("IP lease not found for MAC address %s in: %s\n", mac, d.dhcpLeaseFile)
	}

	log.Printf("Found IP lease: %s for MAC address %s\n", mostRecentIP, mac)
	return mostRecentIP, nil
}

// ToolsISOPath returns a full path to the Parallels Tools ISO for the specified guest
// OS type. The following OS types are supported: "win", "lin", "mac", "other".
func (d *Parallels9Driver) ToolsISOPath(k string) (string, error) {
	appPath, err := getAppPath("com.parallels.desktop.console")
	if err != nil {
		return "", err
	}

	toolsPath := filepath.Join(appPath, "Contents", "Resources", "Tools", "prl-tools-"+k+".iso")
	log.Printf("Parallels Tools path: '%s'", toolsPath)
	return toolsPath, nil
}
<|endoftext|>


<commit_before>package terminfo

import (
	"errors"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/nhooyr/terminfo/caps"
)

// Terminfo describes a terminal's capabilities.
type Terminfo struct {
	Names      []string
	Bools      [caps.BoolCount]bool
	Numbers    [caps.NumberCount]int16
	Strings    [caps.StringCount]string
	ExtBools   map[string]bool
	ExtNumbers map[string]int16
	ExtStrings map[string]string
}

// Terminfo cache.
var (
	db      = make(map[string]*Terminfo)
	dbMutex = new(sync.RWMutex)
)

// LoadEnv calls Load with the name as $TERM.
func LoadEnv() (*Terminfo, error) {
	return Load(os.Getenv("TERM"))
}

// Returned when no name is provided to Load.
var ErrEmptyTerm = errors.New("terminfo: empty term name")

// Load follows the behavior described in terminfo(5) to find correct the terminfo file
// using the name, reads the file and then returns a Terminfo struct that describes the file.
func Load(name string) (ti *Terminfo, err error) {
	if name == "" {
		return nil, ErrEmptyTerm
	}
	dbMutex.RLock()
	ti, ok := db[name]
	dbMutex.RUnlock()
	if ok {
		return
	}
	if terminfo := os.Getenv("TERMINFO"); terminfo != "" {
		return openDir(terminfo, name)
	}
	if home := os.Getenv("HOME"); home != "" {
		ti, err = openDir(home+"/.terminfo", name)
		if err == nil {
			return
		}
	}
	if dirs := os.Getenv("TERMINFO_DIRS"); dirs != "" {
		for _, dir := range strings.Split(dirs, ":") {
			if dir == "" {
				dir = "/usr/share/terminfo"
			}
			ti, err = openDir(dir, name)
			if err == nil {
				return
			}
		}
	}
	return openDir("/usr/share/terminfo", name)
}

// openDir reads the Terminfo file specified by the dir and name.
func openDir(dir, name string) (*Terminfo, error) {
	// Try typical *nix path.
	b, err := ioutil.ReadFile(dir + "/" + name[0:1] + "/" + name)
	if err != nil {
		// Fallback to the darwin specific path.
		b, err = ioutil.ReadFile(dir + "/" + strconv.FormatUint(uint64(name[0]), 16) + "/" + name)
		if err != nil {
			return nil, err
		}
	}
	r := &decoder{buf: b}
	if err = r.unmarshal(); err != nil {
		return nil, err
	}
	// Cache the Terminfo struct.
	dbMutex.Lock()
	for i := range r.ti.Names {
		db[r.ti.Names[i]] = r.ti
	}
	dbMutex.Unlock()
	return r.ti, nil
}

// Color takes a foreground and background color and returns string
// that sets them for this terminal.
func (ti *Terminfo) Color(fg, bg int) (rv string) {
	maxColors := int(ti.Numbers[caps.MaxColors])
	// Map bright colors to lower versions if color table only holds 8.
	if maxColors == 8 {
		if fg > 7 && fg < 16 {
			fg -= 8
		}
		if bg > 7 && bg < 16 {
			bg -= 8
		}
	}
	if maxColors > fg && fg >= 0 {
		rv += ti.Parm(caps.SetAForeground, fg)
	}
	if maxColors > bg && bg >= 0 {
		rv += ti.Parm(caps.SetABackground, bg)
	}
	return
}

// Parm is a thin wrapper around the function Parm.
func (ti *Terminfo) Parm(i int, p ...interface{}) string {
	return Parm(ti.Strings[i], p...)
}

// TODO undestand this
func (ti *Terminfo) TPuts(w io.Writer, s string, baud int) {
	for {
		start := strings.Index(s, "$<")
		if start == -1 {
			// Most strings don't need padding, which is good news!
			io.WriteString(w, s)
			return
		}
		io.WriteString(w, s[:start])
		s = s[start+2:]
		end := strings.Index(s, ">")
		if end == -1 {
			// Unterminated... just emit bytes unadulterated.
			io.WriteString(w, "$<"+s)
			return
		}
		val := s[:end]
		s = s[end+1:]
		padus := 0
		unit := 1000
		dot := false
	LOOP:
		for i := range val {
			switch val[i] {
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
				padus *= 10
				padus += int(val[i] - '0')
				if dot {
					unit *= 10
				}
			case '.':
				if !dot {
					dot = true
				} else {
					break LOOP
				}
			default:
				break LOOP
			}
		}
		cnt := int(((baud / 8) * padus) / unit)
		for cnt > 0 {
			io.WriteString(w, ti.Strings[caps.PadChar])
			cnt--
		}
	}
}
<commit_msg>ti.Goto, rename tput and comment<commit_after>package terminfo

import (
	"errors"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/nhooyr/terminfo/caps"
)

// Terminfo describes a terminal's capabilities.
type Terminfo struct {
	Names      []string
	Bools      [caps.BoolCount]bool
	Numbers    [caps.NumberCount]int16
	Strings    [caps.StringCount]string
	ExtBools   map[string]bool
	ExtNumbers map[string]int16
	ExtStrings map[string]string
}

// Terminfo cache.
var (
	db      = make(map[string]*Terminfo)
	dbMutex = new(sync.RWMutex)
)

// LoadEnv calls Load with the name as $TERM.
func LoadEnv() (*Terminfo, error) {
	return Load(os.Getenv("TERM"))
}

// Returned when no name is provided to Load.
var ErrEmptyTerm = errors.New("terminfo: empty term name")

// Load follows the behavior described in terminfo(5) to find correct the terminfo file
// using the name, reads the file and then returns a Terminfo struct that describes the file.
func Load(name string) (ti *Terminfo, err error) {
	if name == "" {
		return nil, ErrEmptyTerm
	}
	dbMutex.RLock()
	ti, ok := db[name]
	dbMutex.RUnlock()
	if ok {
		return
	}
	if terminfo := os.Getenv("TERMINFO"); terminfo != "" {
		return openDir(terminfo, name)
	}
	if home := os.Getenv("HOME"); home != "" {
		ti, err = openDir(home+"/.terminfo", name)
		if err == nil {
			return
		}
	}
	if dirs := os.Getenv("TERMINFO_DIRS"); dirs != "" {
		for _, dir := range strings.Split(dirs, ":") {
			if dir == "" {
				dir = "/usr/share/terminfo"
			}
			ti, err = openDir(dir, name)
			if err == nil {
				return
			}
		}
	}
	return openDir("/usr/share/terminfo", name)
}

// openDir reads the Terminfo file specified by the dir and name.
func openDir(dir, name string) (*Terminfo, error) {
	// Try typical *nix path.
	b, err := ioutil.ReadFile(dir + "/" + name[0:1] + "/" + name)
	if err != nil {
		// Fallback to the darwin specific path.
		b, err = ioutil.ReadFile(dir + "/" + strconv.FormatUint(uint64(name[0]), 16) + "/" + name)
		if err != nil {
			return nil, err
		}
	}
	r := &decoder{buf: b}
	if err = r.unmarshal(); err != nil {
		return nil, err
	}
	// Cache the Terminfo struct.
	dbMutex.Lock()
	for i := range r.ti.Names {
		db[r.ti.Names[i]] = r.ti
	}
	dbMutex.Unlock()
	return r.ti, nil
}

// Color takes a foreground and background color and returns string
// that sets them for this terminal.
func (ti *Terminfo) Color(fg, bg int) (rv string) {
	maxColors := int(ti.Numbers[caps.MaxColors])
	// Map bright colors to lower versions if color table only holds 8.
	if maxColors == 8 {
		if fg > 7 && fg < 16 {
			fg -= 8
		}
		if bg > 7 && bg < 16 {
			bg -= 8
		}
	}
	if maxColors > fg && fg >= 0 {
		rv += ti.Parm(caps.SetAForeground, fg)
	}
	if maxColors > bg && bg >= 0 {
		rv += ti.Parm(caps.SetABackground, bg)
	}
	return
}

// Parm is a thin wrapper around the function Parm.
func (ti *Terminfo) Parm(i int, p ...interface{}) string {
	return Parm(ti.Strings[i], p...)
}

// Puts emits the string to the writer, but expands inline padding
// indications (of the form $<[delay]> where [delay] is msec) to
// a suitable number of padding characters (usually null bytes) based
// upon the supplied baud.  At high baud rates, more padding characters
// will be inserted.  All Terminfo based strings should be emitted using
// this function.
// TODO undestand this
func (ti *Terminfo) Puts(w io.Writer, s string, baud int) {
	for {
		start := strings.Index(s, "$<")
		if start == -1 {
			// Most strings don't need padding, which is good news!
			io.WriteString(w, s)
			return
		}
		io.WriteString(w, s[:start])
		s = s[start+2:]
		end := strings.Index(s, ">")
		if end == -1 {
			// Unterminated... just emit bytes unadulterated.
			io.WriteString(w, "$<"+s)
			return
		}
		val := s[:end]
		s = s[end+1:]
		padus := 0
		unit := 1000
		dot := false
	LOOP:
		for i := range val {
			switch val[i] {
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
				padus *= 10
				padus += int(val[i] - '0')
				if dot {
					unit *= 10
				}
			case '.':
				if !dot {
					dot = true
				} else {
					break LOOP
				}
			default:
				break LOOP
			}
		}
		cnt := int(((baud / 8) * padus) / unit)
		for cnt > 0 {
			io.WriteString(w, ti.Strings[caps.PadChar])
			cnt--
		}
	}
}

// Goto returns a string suitable for addressing the cursor at the given
// row and column. The origin 0, 0 is in the upper left corner of the screen.
func (ti *Terminfo) Goto(row, col int) string {
	return ti.Parm(caps.CursorAddress, row, col)
}
<|endoftext|>