<commit_before>package main

import (
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/Azure/azure-docker-extension/pkg/driver"
	"github.com/Azure/azure-docker-extension/pkg/executil"
	"github.com/Azure/azure-docker-extension/pkg/util"
	"github.com/Azure/azure-docker-extension/pkg/vmextension"

	yaml "github.com/cloudfoundry-incubator/candiedyaml"
)

const (
	composeBin         = "docker-compose"
	composeTimeoutSecs = 600

	composeYml     = "docker-compose.yml"
	composeYmlDir  = "/etc/docker/compose"
	composeProject = "compose" // prefix for compose-created containers

	dockerCfgDir  = "/etc/docker"
	dockerCaCert  = "ca.pem"
	dockerSrvCert = "cert.pem"
	dockerSrvKey  = "key.pem"
)

func enable(he vmextension.HandlerEnvironment, d driver.DistroDriver) error {
	settings, err := parseSettings(he.HandlerEnvironment.ConfigFolder)
	if err != nil {
		return err
	}

	dockerUrl, composeUrl := getDockerUrls(settings.AzureEnv)

	// Install docker daemon
	log.Printf("++ install docker")
	if _, err := exec.LookPath("docker"); err == nil {
		log.Printf("docker already installed. not re-installing")
	} else {
		// TODO(ahmetb) Temporary retry logic around installation for serialization
		// problem in Azure VM Scale Sets. In case of scale-up event, the new VM with
		// multiple extensions (such as Linux Diagnostics and Docker Extension) will install
		// the extensions in parallel and that will result in non-deterministic
		// acquisition of dpkg lock (apt-get install) and thus causing one of the
		// extensions to fail.
		//
		// Adding this temporary retry logic just for Linux Diagnostics extension
		// assuming it will take at most 5 minutes to be done with apt-get lock.
		//
		// This retry logic should be removed once the issue is fixed on the resource
		// provider layer.

		var (
			nRetries      = 6
			retryInterval = time.Minute * 1
		)

		for nRetries > 0 {
			if err := d.InstallDocker(dockerUrl); err != nil {
				nRetries--
				if nRetries == 0 {
					return err
				}
				log.Printf("install failed. remaining attempts=%d. error=%v", nRetries, err)
				log.Printf("sleeping %s", retryInterval)
				time.Sleep(retryInterval)
			} else {
				break
			}
		}
	}
	log.Printf("-- install docker")

	// Install docker-compose
	log.Printf("++ install docker-compose")
	if err := installCompose(composeBinPath(d), composeUrl); err != nil {
		return fmt.Errorf("error installing docker-compose: %v", err)
	}
	log.Printf("-- install docker-compose")

	// Add user to 'docker' group to user docker as non-root
	u, err := util.GetAzureUser()
	if err != nil {
		return fmt.Errorf("failed to get provisioned user: %v", err)
	}
	log.Printf("++ add user to docker group")
	if out, err := executil.Exec("usermod", "-aG", "docker", u); err != nil {
		log.Printf("%s", string(out))
		return err
	}
	log.Printf("-- add user to docker group")

	// Install docker remote access certs
	log.Printf("++ setup docker certs")
	if err := installDockerCerts(*settings, dockerCfgDir); err != nil {
		return fmt.Errorf("error installing docker certs: %v", err)
	}
	log.Printf("-- setup docker certs")

	// Update dockeropts
	log.Printf("++ update dockeropts")
	restartNeeded, err := updateDockerOpts(d, getArgs(*settings, d))
	if err != nil {
		return fmt.Errorf("failed to update dockeropts: %v", err)
	}
	log.Printf("restart needed: %v", restartNeeded)
	log.Printf("-- update dockeropts")

	// Restart docker
	log.Printf("++ restart docker")
	if !restartNeeded {
		log.Printf("no restart needed. issuing only a start command.")
		_ = d.StartDocker() // ignore error as it already may be running due to multiple calls to enable
	} else {
		log.Printf("restarting docker-engine")
		if err := d.RestartDocker(); err != nil {
			return err
		}
	}
	time.Sleep(3 * time.Second) // wait for instance to come up
	log.Printf("-- restart docker")

	// Login Docker registry server
	log.Printf("++ login docker registry")
	if err := loginRegistry(settings.Login); err != nil {
		return err
	}
	log.Printf("-- login docker registry")

	// Compose Up
	log.Printf("++ compose up")
	if err := composeUp(d, settings.ComposeJson, settings.ComposeEnv, settings.ComposeProtectedEnv); err != nil {
		return fmt.Errorf("'docker-compose up' failed: %v. Check logs at %s.", err, filepath.Join(he.HandlerEnvironment.LogFolder, LogFilename))
	}
	log.Printf("-- compose up")
	return nil
}

// installCompose download docker-compose and saves to the specified path if it
// is not already installed.
func installCompose(path string, composeUrl string) error {
	// Check if already installed at path.
	if ok, err := util.PathExists(path); err != nil {
		return err
	} else if ok {
		log.Printf("docker-compose is already installed at %s", path)
		return nil
	}

	// Create dir if not exists
	dir := filepath.Dir(path)
	ok, err := util.PathExists(dir)
	if err != nil {
		return err
	} else if !ok {
		if err := os.MkdirAll(dir, 755); err != nil {
			return err
		}
	}

	log.Printf("Downloading compose from %s", composeUrl)
	resp, err := http.Get(composeUrl)
	if err != nil {
		return fmt.Errorf("error downloading docker-compose: %v", err)
	}
	if resp.StatusCode/100 != 2 {
		return fmt.Errorf("response status code from %s: %s", composeUrl, resp.Status)
	}
	defer resp.Body.Close()

	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0777)
	if err != nil {
		return fmt.Errorf("error creating %s: %v", path, err)
	}

	defer f.Close()
	if _, err := io.Copy(f, resp.Body); err != nil {
		return fmt.Errorf("failed to save response body to %s: %v", path, err)
	}
	return nil
}

// loginRegistry calls the `docker login` command to authenticate the engine to the
// specified registry with given credentials.
func loginRegistry(s dockerLoginSettings) error {
	if !s.HasLoginInfo() {
		log.Println("registry login not specificied")
		return nil
	}
	opts := []string{
		"login",
		"--email=" + s.Email,
		"--username=" + s.Username,
		"--password=" + s.Password,
	}
	if s.Server != "" {
		opts = append(opts, s.Server)
	}
	_, err := executil.Exec("docker", opts...)
	if err != nil {
		return errors.New("'docker login' failed")
	}
	return nil
}

// composeBinPath returns the path docker-compose binary should be installed at
// on the host operating system.
func composeBinPath(d driver.DistroDriver) string {
	return filepath.Join(d.DockerComposeDir(), composeBin)
}

// composeUp converts given json to yaml, saves to a file on the host and
// uses `docker-compose up -d` to create the containers.
func composeUp(d driver.DistroDriver, json map[string]interface{}, publicEnv, protectedEnv map[string]string) error {
	if len(json) == 0 {
		log.Println("docker-compose config not specified, noop")
		return nil
	}

	// Convert json to yaml
	yaml, err := yaml.Marshal(json)
	if err != nil {
		return fmt.Errorf("error converting to compose.yml: %v", err)
	}

	if err := os.MkdirAll(composeYmlDir, 0777); err != nil {
		return fmt.Errorf("failed creating %s: %v", composeYmlDir, err)
	}
	log.Printf("Using compose yaml:>>>>>\n%s\n<<<<<", string(yaml))
	ymlPath := filepath.Join(composeYmlDir, composeYml)
	if err := ioutil.WriteFile(ymlPath, yaml, 0666); err != nil {
		return fmt.Errorf("error writing %s: %v", ymlPath, err)
	}

	if publicEnv == nil {
		publicEnv = make(map[string]string)
	}

	// set timeout for docker-compose -> docker-engine interactions.
	// When downloading large images, docker-compose intermittently times out
	// (gh#docker/compose/issues/2186) (gh#Azure/azure-docker-extension/issues/87).
	if _, ok := publicEnv["COMPOSE_HTTP_TIMEOUT"]; !ok {
		publicEnv["COMPOSE_HTTP_TIMEOUT"] = fmt.Sprintf("%d", composeTimeoutSecs)
	}

	// provide a consistent default project name for docker-compose. this is to prevent
	// inconsistencies that may occur when we change where docker-compose.yml lives.
	if _, ok := publicEnv["COMPOSE_PROJECT_NAME"]; !ok {
		publicEnv["COMPOSE_PROJECT_NAME"] = composeProject
	}

	// set public environment variables to be used in docker-compose
	for k, v := range publicEnv {
		log.Printf("Setting docker-compose environment variable %q=%q.", k, v)
		os.Setenv(k, v)
		defer os.Unsetenv(k)
	}

	// set protected environment variables to be used in docker-compose
	for k, v := range protectedEnv {
		log.Printf("Setting protected docker-compose environment variable %q.", k)
		os.Setenv(k, v)
		defer os.Unsetenv(k)
	}

	return executil.ExecPipeToFds(executil.Fds{Out: ioutil.Discard}, composeBinPath(d), "-f", ymlPath, "up", "-d")
}

// installDockerCerts saves the configured certs to the specified dir
// if and only if the certs are not already placed there. If no certs
// are provided  or some certs already exist, nothing is written.
func installDockerCerts(s DockerHandlerSettings, dstDir string) error {
	m := []struct {
		src string
		dst string
	}{
		{s.Certs.CABase64, filepath.Join(dstDir, dockerCaCert)},
		{s.Certs.ServerCertBase64, filepath.Join(dstDir, dockerSrvCert)},
		{s.Certs.ServerKeyBase64, filepath.Join(dstDir, dockerSrvKey)},
	}

	// Check if certs are provided
	for _, v := range m {
		if len(v.src) == 0 {
			log.Printf("Docker certificate %s is not provided in the extension settings, skipping docker certs installation", v.dst)
			return nil
		}
	}

	// Check the target directory, if not create
	if ok, err := util.PathExists(dstDir); err != nil {
		return fmt.Errorf("error checking cert dir: %v", err)
	} else if !ok {
		if err := os.MkdirAll(dstDir, 0755); err != nil {
			return err
		}
	}

	// Write the certs
	for _, v := range m {
		// Decode base64
		in := strings.TrimSpace(v.src)
		f, err := base64.StdEncoding.DecodeString(in)
		if err != nil {
			// Fallback to original file input
			f = []byte(in)
		}

		if err := ioutil.WriteFile(v.dst, f, 0600); err != nil {
			return fmt.Errorf("error writing certificate: %v", err)
		}
	}
	return nil
}

func updateDockerOpts(dd driver.DistroDriver, args string) (bool, error) {
	log.Printf("Updating daemon args to: %s", args)
	restartNeeded, err := dd.UpdateDockerArgs(args)
	if err != nil {
		return false, fmt.Errorf("error updating DOCKER_OPTS: %v", err)
	}
	return restartNeeded, nil
}

// getArgs provides set of arguments that should be used in updating Docker
// daemon options based on the distro.
func getArgs(s DockerHandlerSettings, dd driver.DistroDriver) string {
	args := dd.BaseOpts()

	if s.Certs.HasDockerCerts() {
		tls := []string{"--tlsverify",
			fmt.Sprintf("--tlscacert=%s", filepath.Join(dockerCfgDir, dockerCaCert)),
			fmt.Sprintf("--tlscert=%s", filepath.Join(dockerCfgDir, dockerSrvCert)),
			fmt.Sprintf("--tlskey=%s", filepath.Join(dockerCfgDir, dockerSrvKey)),
		}
		args = append(args, tls...)
	}

	if s.Docker.Port != "" {
		args = append(args, fmt.Sprintf("-H=0.0.0.0:%s", s.Docker.Port))
	}

	if len(s.Docker.Options) > 0 {
		args = append(args, s.Docker.Options...)
	}

	return strings.Join(args, " ")
}

func getDockerUrls(env string) (string, string) {
	urlMap := map[string]map[string]string{
		"china": map[string]string{
			// This script is the same as https://get.docker.com, except
			// 1. apt_url and yum_url points to the mirror in China
			// 2. fix an issue in detecting distro versions
			"docker" : "http://mirror.azure.cn/repo/install-docker-engine.sh",
			"compose": "http://mirror.azure.cn/docker-toolbox/linux/compose/1.6.2/docker-compose-Linux-x86_64",
		},
		"global": map[string]string{
			"docker" : "https://get.docker.com/",
			"compose": "https://github.com/docker/compose/releases/download/1.6.2/docker-compose-Linux-x86_64",
		},
	}
	if value, ok := urlMap[strings.ToLower(env)]; ok {
		return value["docker"], value["compose"]
	}
	return urlMap["global"]["docker"], urlMap["global"]["docker"]
}
<commit_msg>Update comments<commit_after>package main

import (
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/Azure/azure-docker-extension/pkg/driver"
	"github.com/Azure/azure-docker-extension/pkg/executil"
	"github.com/Azure/azure-docker-extension/pkg/util"
	"github.com/Azure/azure-docker-extension/pkg/vmextension"

	yaml "github.com/cloudfoundry-incubator/candiedyaml"
)

const (
	composeBin         = "docker-compose"
	composeTimeoutSecs = 600

	composeYml     = "docker-compose.yml"
	composeYmlDir  = "/etc/docker/compose"
	composeProject = "compose" // prefix for compose-created containers

	dockerCfgDir  = "/etc/docker"
	dockerCaCert  = "ca.pem"
	dockerSrvCert = "cert.pem"
	dockerSrvKey  = "key.pem"
)

func enable(he vmextension.HandlerEnvironment, d driver.DistroDriver) error {
	settings, err := parseSettings(he.HandlerEnvironment.ConfigFolder)
	if err != nil {
		return err
	}

	dockerUrl, composeUrl := getDockerUrls(settings.AzureEnv)

	// Install docker daemon
	log.Printf("++ install docker")
	if _, err := exec.LookPath("docker"); err == nil {
		log.Printf("docker already installed. not re-installing")
	} else {
		// TODO(ahmetb) Temporary retry logic around installation for serialization
		// problem in Azure VM Scale Sets. In case of scale-up event, the new VM with
		// multiple extensions (such as Linux Diagnostics and Docker Extension) will install
		// the extensions in parallel and that will result in non-deterministic
		// acquisition of dpkg lock (apt-get install) and thus causing one of the
		// extensions to fail.
		//
		// Adding this temporary retry logic just for Linux Diagnostics extension
		// assuming it will take at most 5 minutes to be done with apt-get lock.
		//
		// This retry logic should be removed once the issue is fixed on the resource
		// provider layer.

		var (
			nRetries      = 6
			retryInterval = time.Minute * 1
		)

		for nRetries > 0 {
			if err := d.InstallDocker(dockerUrl); err != nil {
				nRetries--
				if nRetries == 0 {
					return err
				}
				log.Printf("install failed. remaining attempts=%d. error=%v", nRetries, err)
				log.Printf("sleeping %s", retryInterval)
				time.Sleep(retryInterval)
			} else {
				break
			}
		}
	}
	log.Printf("-- install docker")

	// Install docker-compose
	log.Printf("++ install docker-compose")
	if err := installCompose(composeBinPath(d), composeUrl); err != nil {
		return fmt.Errorf("error installing docker-compose: %v", err)
	}
	log.Printf("-- install docker-compose")

	// Add user to 'docker' group to user docker as non-root
	u, err := util.GetAzureUser()
	if err != nil {
		return fmt.Errorf("failed to get provisioned user: %v", err)
	}
	log.Printf("++ add user to docker group")
	if out, err := executil.Exec("usermod", "-aG", "docker", u); err != nil {
		log.Printf("%s", string(out))
		return err
	}
	log.Printf("-- add user to docker group")

	// Install docker remote access certs
	log.Printf("++ setup docker certs")
	if err := installDockerCerts(*settings, dockerCfgDir); err != nil {
		return fmt.Errorf("error installing docker certs: %v", err)
	}
	log.Printf("-- setup docker certs")

	// Update dockeropts
	log.Printf("++ update dockeropts")
	restartNeeded, err := updateDockerOpts(d, getArgs(*settings, d))
	if err != nil {
		return fmt.Errorf("failed to update dockeropts: %v", err)
	}
	log.Printf("restart needed: %v", restartNeeded)
	log.Printf("-- update dockeropts")

	// Restart docker
	log.Printf("++ restart docker")
	if !restartNeeded {
		log.Printf("no restart needed. issuing only a start command.")
		_ = d.StartDocker() // ignore error as it already may be running due to multiple calls to enable
	} else {
		log.Printf("restarting docker-engine")
		if err := d.RestartDocker(); err != nil {
			return err
		}
	}
	time.Sleep(3 * time.Second) // wait for instance to come up
	log.Printf("-- restart docker")

	// Login Docker registry server
	log.Printf("++ login docker registry")
	if err := loginRegistry(settings.Login); err != nil {
		return err
	}
	log.Printf("-- login docker registry")

	// Compose Up
	log.Printf("++ compose up")
	if err := composeUp(d, settings.ComposeJson, settings.ComposeEnv, settings.ComposeProtectedEnv); err != nil {
		return fmt.Errorf("'docker-compose up' failed: %v. Check logs at %s.", err, filepath.Join(he.HandlerEnvironment.LogFolder, LogFilename))
	}
	log.Printf("-- compose up")
	return nil
}

// installCompose download docker-compose and saves to the specified path if it
// is not already installed.
func installCompose(path string, composeUrl string) error {
	// Check if already installed at path.
	if ok, err := util.PathExists(path); err != nil {
		return err
	} else if ok {
		log.Printf("docker-compose is already installed at %s", path)
		return nil
	}

	// Create dir if not exists
	dir := filepath.Dir(path)
	ok, err := util.PathExists(dir)
	if err != nil {
		return err
	} else if !ok {
		if err := os.MkdirAll(dir, 755); err != nil {
			return err
		}
	}

	log.Printf("Downloading compose from %s", composeUrl)
	resp, err := http.Get(composeUrl)
	if err != nil {
		return fmt.Errorf("error downloading docker-compose: %v", err)
	}
	if resp.StatusCode/100 != 2 {
		return fmt.Errorf("response status code from %s: %s", composeUrl, resp.Status)
	}
	defer resp.Body.Close()

	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0777)
	if err != nil {
		return fmt.Errorf("error creating %s: %v", path, err)
	}

	defer f.Close()
	if _, err := io.Copy(f, resp.Body); err != nil {
		return fmt.Errorf("failed to save response body to %s: %v", path, err)
	}
	return nil
}

// loginRegistry calls the `docker login` command to authenticate the engine to the
// specified registry with given credentials.
func loginRegistry(s dockerLoginSettings) error {
	if !s.HasLoginInfo() {
		log.Println("registry login not specificied")
		return nil
	}
	opts := []string{
		"login",
		"--email=" + s.Email,
		"--username=" + s.Username,
		"--password=" + s.Password,
	}
	if s.Server != "" {
		opts = append(opts, s.Server)
	}
	_, err := executil.Exec("docker", opts...)
	if err != nil {
		return errors.New("'docker login' failed")
	}
	return nil
}

// composeBinPath returns the path docker-compose binary should be installed at
// on the host operating system.
func composeBinPath(d driver.DistroDriver) string {
	return filepath.Join(d.DockerComposeDir(), composeBin)
}

// composeUp converts given json to yaml, saves to a file on the host and
// uses `docker-compose up -d` to create the containers.
func composeUp(d driver.DistroDriver, json map[string]interface{}, publicEnv, protectedEnv map[string]string) error {
	if len(json) == 0 {
		log.Println("docker-compose config not specified, noop")
		return nil
	}

	// Convert json to yaml
	yaml, err := yaml.Marshal(json)
	if err != nil {
		return fmt.Errorf("error converting to compose.yml: %v", err)
	}

	if err := os.MkdirAll(composeYmlDir, 0777); err != nil {
		return fmt.Errorf("failed creating %s: %v", composeYmlDir, err)
	}
	log.Printf("Using compose yaml:>>>>>\n%s\n<<<<<", string(yaml))
	ymlPath := filepath.Join(composeYmlDir, composeYml)
	if err := ioutil.WriteFile(ymlPath, yaml, 0666); err != nil {
		return fmt.Errorf("error writing %s: %v", ymlPath, err)
	}

	if publicEnv == nil {
		publicEnv = make(map[string]string)
	}

	// set timeout for docker-compose -> docker-engine interactions.
	// When downloading large images, docker-compose intermittently times out
	// (gh#docker/compose/issues/2186) (gh#Azure/azure-docker-extension/issues/87).
	if _, ok := publicEnv["COMPOSE_HTTP_TIMEOUT"]; !ok {
		publicEnv["COMPOSE_HTTP_TIMEOUT"] = fmt.Sprintf("%d", composeTimeoutSecs)
	}

	// provide a consistent default project name for docker-compose. this is to prevent
	// inconsistencies that may occur when we change where docker-compose.yml lives.
	if _, ok := publicEnv["COMPOSE_PROJECT_NAME"]; !ok {
		publicEnv["COMPOSE_PROJECT_NAME"] = composeProject
	}

	// set public environment variables to be used in docker-compose
	for k, v := range publicEnv {
		log.Printf("Setting docker-compose environment variable %q=%q.", k, v)
		os.Setenv(k, v)
		defer os.Unsetenv(k)
	}

	// set protected environment variables to be used in docker-compose
	for k, v := range protectedEnv {
		log.Printf("Setting protected docker-compose environment variable %q.", k)
		os.Setenv(k, v)
		defer os.Unsetenv(k)
	}

	return executil.ExecPipeToFds(executil.Fds{Out: ioutil.Discard}, composeBinPath(d), "-f", ymlPath, "up", "-d")
}

// installDockerCerts saves the configured certs to the specified dir
// if and only if the certs are not already placed there. If no certs
// are provided  or some certs already exist, nothing is written.
func installDockerCerts(s DockerHandlerSettings, dstDir string) error {
	m := []struct {
		src string
		dst string
	}{
		{s.Certs.CABase64, filepath.Join(dstDir, dockerCaCert)},
		{s.Certs.ServerCertBase64, filepath.Join(dstDir, dockerSrvCert)},
		{s.Certs.ServerKeyBase64, filepath.Join(dstDir, dockerSrvKey)},
	}

	// Check if certs are provided
	for _, v := range m {
		if len(v.src) == 0 {
			log.Printf("Docker certificate %s is not provided in the extension settings, skipping docker certs installation", v.dst)
			return nil
		}
	}

	// Check the target directory, if not create
	if ok, err := util.PathExists(dstDir); err != nil {
		return fmt.Errorf("error checking cert dir: %v", err)
	} else if !ok {
		if err := os.MkdirAll(dstDir, 0755); err != nil {
			return err
		}
	}

	// Write the certs
	for _, v := range m {
		// Decode base64
		in := strings.TrimSpace(v.src)
		f, err := base64.StdEncoding.DecodeString(in)
		if err != nil {
			// Fallback to original file input
			f = []byte(in)
		}

		if err := ioutil.WriteFile(v.dst, f, 0600); err != nil {
			return fmt.Errorf("error writing certificate: %v", err)
		}
	}
	return nil
}

func updateDockerOpts(dd driver.DistroDriver, args string) (bool, error) {
	log.Printf("Updating daemon args to: %s", args)
	restartNeeded, err := dd.UpdateDockerArgs(args)
	if err != nil {
		return false, fmt.Errorf("error updating DOCKER_OPTS: %v", err)
	}
	return restartNeeded, nil
}

// getArgs provides set of arguments that should be used in updating Docker
// daemon options based on the distro.
func getArgs(s DockerHandlerSettings, dd driver.DistroDriver) string {
	args := dd.BaseOpts()

	if s.Certs.HasDockerCerts() {
		tls := []string{"--tlsverify",
			fmt.Sprintf("--tlscacert=%s", filepath.Join(dockerCfgDir, dockerCaCert)),
			fmt.Sprintf("--tlscert=%s", filepath.Join(dockerCfgDir, dockerSrvCert)),
			fmt.Sprintf("--tlskey=%s", filepath.Join(dockerCfgDir, dockerSrvKey)),
		}
		args = append(args, tls...)
	}

	if s.Docker.Port != "" {
		args = append(args, fmt.Sprintf("-H=0.0.0.0:%s", s.Docker.Port))
	}

	if len(s.Docker.Options) > 0 {
		args = append(args, s.Docker.Options...)
	}

	return strings.Join(args, " ")
}

func getDockerUrls(env string) (string, string) {
	urlMap := map[string]map[string]string{
		"china": map[string]string{
			// This script is synced with https://get.docker.com daily, with the change that apt_url and yum_url point to the mirror in China
			"docker" : "http://mirror.azure.cn/repo/install-docker-engine.sh",
			"compose": "http://mirror.azure.cn/docker-toolbox/linux/compose/1.6.2/docker-compose-Linux-x86_64",
		},
		"global": map[string]string{
			"docker" : "https://get.docker.com/",
			"compose": "https://github.com/docker/compose/releases/download/1.6.2/docker-compose-Linux-x86_64",
		},
	}
	if value, ok := urlMap[strings.ToLower(env)]; ok {
		return value["docker"], value["compose"]
	}
	return urlMap["global"]["docker"], urlMap["global"]["docker"]
}
<|endoftext|>


<commit_before>//go:build linux && cgo
// +build linux,cgo

package drivers

import (
	"fmt"
	"os"
	"unsafe"

	"github.com/pkg/errors"

	// Used by cgo
	_ "github.com/lxc/lxd/lxd/include"
)

/*
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#define _FILE_OFFSET_BITS 64
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/loop.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "../../include/macro.h"
#include "../../include/memory_utils.h"

#define LXD_MAXPATH 4096
#define LXD_NUMSTRLEN64 21
#define LXD_MAX_LOOP_PATHLEN (2 * sizeof("loop/")) + LXD_NUMSTRLEN64 + sizeof("backing_file") + 1

// If a loop file is already associated with a loop device, find it.
// This looks at "/sys/block" to avoid having to parse all of "/dev". Also, this
// allows to retrieve the full name of the backing file even if
// strlen(backing file) > LO_NAME_SIZE.
static int find_associated_loop_device(const char *loop_file,
				       char *loop_dev_name)
{
	__do_closedir DIR *dir = NULL;
	char looppath[LXD_MAX_LOOP_PATHLEN];
	char buf[LXD_MAXPATH];
	struct dirent *dp;

	dir = opendir("/sys/block");
	if (!dir)
		return -1;

	while ((dp = readdir(dir))) {
		__do_close int loop_path_fd = -EBADF;
		int ret;
		size_t totlen;
		struct stat fstatbuf;
		int dfd = -1;

		if (!dp)
			break;

		if (strncmp(dp->d_name, "loop", 4))
			continue;

		dfd = dirfd(dir);
		if (dfd < 0)
			continue;

		ret = snprintf(looppath, sizeof(looppath), "%s/loop/backing_file", dp->d_name);
		if (ret < 0 || (size_t)ret >= sizeof(looppath))
			continue;

		ret = fstatat(dfd, looppath, &fstatbuf, 0);
		if (ret < 0)
			continue;

		loop_path_fd = openat(dfd, looppath, O_RDONLY | O_CLOEXEC, 0);
		if (ret < 0)
			continue;

		// Clear buffer.
		memset(buf, 0, sizeof(buf));
		ret = read(loop_path_fd, buf, sizeof(buf));
		if (ret < 0)
			continue;

		totlen = strlen(buf);

		// Trim newlines.
		while ((totlen > 0) && (buf[totlen - 1] == '\n'))
			buf[--totlen] = '\0';

		if (strcmp(buf, loop_file))
			continue;

		// Create path to loop device.
		ret = snprintf(loop_dev_name, LO_NAME_SIZE, "/dev/%s",
			       dp->d_name);
		if (ret < 0 || ret >= LO_NAME_SIZE)
			continue;

		// Open fd to loop device.
		return open(loop_dev_name, O_RDWR);
	}

	return -1;
}

static int get_unused_loop_dev_legacy(char *loop_name)
{
	__do_closedir DIR *dir = NULL;
	struct dirent *dp;
	struct loop_info64 lo64;

	dir = opendir("/dev");
	if (!dir)
		return -1;

	while ((dp = readdir(dir))) {
		__do_close int dfd = -EBADF, fd = -EBADF;
		int ret;

		if (!dp)
			break;

		if (strncmp(dp->d_name, "loop", 4) != 0)
			continue;

		dfd = dirfd(dir);
		if (dfd < 0)
			continue;

		fd = openat(dfd, dp->d_name, O_RDWR);
		if (fd < 0)
			continue;

		ret = ioctl(fd, LOOP_GET_STATUS64, &lo64);
		if (ret < 0)
			if (ioctl(fd, LOOP_GET_STATUS64, &lo64) == 0 || errno != ENXIO)
				continue;

		ret = snprintf(loop_name, LO_NAME_SIZE, "/dev/%s", dp->d_name);
		if (ret < 0 || ret >= LO_NAME_SIZE)
			continue;

		return move_fd(fd);
	}

	return -1;
}

static int get_unused_loop_dev(char *name_loop)
{
	__do_close int fd_ctl = -EBADF;
	int loop_nr, ret;

	fd_ctl = open("/dev/loop-control", O_RDWR | O_CLOEXEC);
	if (fd_ctl < 0)
		return -ENODEV;

	loop_nr = ioctl(fd_ctl, LOOP_CTL_GET_FREE);
	if (loop_nr < 0)
		return -1;

	ret = snprintf(name_loop, LO_NAME_SIZE, "/dev/loop%d", loop_nr);
	if (ret < 0 || ret >= LO_NAME_SIZE)
		return -1;

	return open(name_loop, O_RDWR | O_CLOEXEC);
}

static int prepare_loop_dev(const char *source, char *loop_dev, int flags)
{
	__do_close int fd_img = -EBADF, fd_loop = -EBADF;
	int ret;
	struct loop_info64 lo64;

	fd_loop = get_unused_loop_dev(loop_dev);
	if (fd_loop < 0) {
		if (fd_loop == -ENODEV)
			fd_loop = get_unused_loop_dev_legacy(loop_dev);
		else
			return -1;
	}

	fd_img = open(source, O_RDWR | O_CLOEXEC);
	if (fd_img < 0)
		return -1;

	ret = ioctl(fd_loop, LOOP_SET_FD, fd_img);
	if (ret < 0)
		return -1;

	memset(&lo64, 0, sizeof(lo64));
	lo64.lo_flags = flags;

	ret = ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
	if (ret < 0)
		return -1;

	return move_fd(fd_loop);
}

static inline int prepare_loop_dev_retry(const char *source, char *loop_dev, int flags)
{
	int ret;
	unsigned int idx = 0;

	do {
		ret = prepare_loop_dev(source, loop_dev, flags);
		idx++;
	} while (ret < 0 && errno == EBUSY && idx < 30);

	return ret;
}

// Note that this does not guarantee to clear the loop device in time so that
// find_associated_loop_device() will not report that there still is a
// configured device (udev and so on...). So don't call
// find_associated_loop_device() after having called
// set_autoclear_loop_device().
int set_autoclear_loop_device(int fd_loop)
{
	struct loop_info64 lo64;

	memset(&lo64, 0, sizeof(lo64));
	lo64.lo_flags = LO_FLAGS_AUTOCLEAR;
	errno = 0;
	return ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
}

// Directly release the loop device
int free_loop_device(int fd_loop)
{
	return ioctl(fd_loop, LOOP_CLR_FD);
}

// Unset the LO_FLAGS_AUTOCLEAR flag on the given loop device file descriptor.
int unset_autoclear_loop_device(int fd_loop)
{
	int ret;
	struct loop_info64 lo64;

	errno = 0;
	ret = ioctl(fd_loop, LOOP_GET_STATUS64, &lo64);
	if (ret < 0)
		return -1;

	if ((lo64.lo_flags & LO_FLAGS_AUTOCLEAR) == 0)
		return 0;

	lo64.lo_flags &= ~LO_FLAGS_AUTOCLEAR;
	errno = 0;
	return ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
}
*/
import "C"

// LoFlagsAutoclear determines whether the loop device will autodestruct on last
// close.
const LoFlagsAutoclear int = C.LO_FLAGS_AUTOCLEAR

// PrepareLoopDev detects and sets up a loop device for source. It returns an
// open file descriptor to the free loop device and the path of the free loop
// device. It's the callers responsibility to close the open file descriptor.
func PrepareLoopDev(source string, flags int) (*os.File, error) {
	cLoopDev := C.malloc(C.size_t(C.LO_NAME_SIZE))
	if cLoopDev == nil {
		return nil, fmt.Errorf("Failed to allocate memory in C")
	}
	defer C.free(cLoopDev)

	cSource := C.CString(source)
	defer C.free(unsafe.Pointer(cSource))
	loopFd, _ := C.find_associated_loop_device(cSource, (*C.char)(cLoopDev))
	if loopFd >= 0 {
		return os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev))), nil
	}

	loopFd, err := C.prepare_loop_dev_retry(cSource, (*C.char)(cLoopDev), C.int(flags))
	if loopFd < 0 {
		if err != nil {
			return nil, errors.Wrapf(err, "Failed to prepare loop device for %q", source)
		}

		return nil, fmt.Errorf("Failed to prepare loop device for %q", source)
	}

	return os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev))), nil
}

// releaseLoopDev releases the loop dev assigned to the provided file.
func releaseLoopDev(source string) error {
	cLoopDev := C.malloc(C.size_t(C.LO_NAME_SIZE))
	if cLoopDev == nil {
		return fmt.Errorf("Failed to allocate memory in C")
	}
	defer C.free(cLoopDev)

	cSource := C.CString(source)
	loopFd, err := C.find_associated_loop_device(cSource, (*C.char)(cLoopDev))
	if err != nil {
		return err
	}

	// Prepare a Go file and defer close on the loop device.
	fd := os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev)))
	defer fd.Close()

	if loopFd >= 0 {
		_, err := C.free_loop_device(C.int(loopFd))
		if err != nil {
			return err
		}
	}

	return nil
}

// SetAutoclearOnLoopDev enables autodestruction of the provided loopback device.
func SetAutoclearOnLoopDev(loopFd int) error {
	ret, err := C.set_autoclear_loop_device(C.int(loopFd))
	if ret < 0 {
		if err != nil {
			return err
		}
		return fmt.Errorf("Failed to set LO_FLAGS_AUTOCLEAR")
	}

	return nil
}

// UnsetAutoclearOnLoopDev disables autodestruction of the provided loopback device.
func UnsetAutoclearOnLoopDev(loopFd int) error {
	ret, err := C.unset_autoclear_loop_device(C.int(loopFd))
	if ret < 0 {
		if err != nil {
			return err
		}
		return fmt.Errorf("Failed to unset LO_FLAGS_AUTOCLEAR")
	}

	return nil
}
<commit_msg>lxd/storage: Use O_CLOEXEC<commit_after>//go:build linux && cgo
// +build linux,cgo

package drivers

import (
	"fmt"
	"os"
	"unsafe"

	"github.com/pkg/errors"

	// Used by cgo
	_ "github.com/lxc/lxd/lxd/include"
)

/*
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#define _FILE_OFFSET_BITS 64
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/loop.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "../../include/macro.h"
#include "../../include/memory_utils.h"

#define LXD_MAXPATH 4096
#define LXD_NUMSTRLEN64 21
#define LXD_MAX_LOOP_PATHLEN (2 * sizeof("loop/")) + LXD_NUMSTRLEN64 + sizeof("backing_file") + 1

// If a loop file is already associated with a loop device, find it.
// This looks at "/sys/block" to avoid having to parse all of "/dev". Also, this
// allows to retrieve the full name of the backing file even if
// strlen(backing file) > LO_NAME_SIZE.
static int find_associated_loop_device(const char *loop_file,
				       char *loop_dev_name)
{
	__do_closedir DIR *dir = NULL;
	char looppath[LXD_MAX_LOOP_PATHLEN];
	char buf[LXD_MAXPATH];
	struct dirent *dp;

	dir = opendir("/sys/block");
	if (!dir)
		return -1;

	while ((dp = readdir(dir))) {
		__do_close int loop_path_fd = -EBADF;
		int ret;
		size_t totlen;
		struct stat fstatbuf;
		int dfd = -1;

		if (!dp)
			break;

		if (strncmp(dp->d_name, "loop", 4))
			continue;

		dfd = dirfd(dir);
		if (dfd < 0)
			continue;

		ret = snprintf(looppath, sizeof(looppath), "%s/loop/backing_file", dp->d_name);
		if (ret < 0 || (size_t)ret >= sizeof(looppath))
			continue;

		ret = fstatat(dfd, looppath, &fstatbuf, 0);
		if (ret < 0)
			continue;

		loop_path_fd = openat(dfd, looppath, O_RDONLY | O_CLOEXEC, 0);
		if (ret < 0)
			continue;

		// Clear buffer.
		memset(buf, 0, sizeof(buf));
		ret = read(loop_path_fd, buf, sizeof(buf));
		if (ret < 0)
			continue;

		totlen = strlen(buf);

		// Trim newlines.
		while ((totlen > 0) && (buf[totlen - 1] == '\n'))
			buf[--totlen] = '\0';

		if (strcmp(buf, loop_file))
			continue;

		// Create path to loop device.
		ret = snprintf(loop_dev_name, LO_NAME_SIZE, "/dev/%s",
			       dp->d_name);
		if (ret < 0 || ret >= LO_NAME_SIZE)
			continue;

		// Open fd to loop device.
		return open(loop_dev_name, O_RDWR | O_CLOEXEC);
	}

	return -1;
}

static int get_unused_loop_dev_legacy(char *loop_name)
{
	__do_closedir DIR *dir = NULL;
	struct dirent *dp;
	struct loop_info64 lo64;

	dir = opendir("/dev");
	if (!dir)
		return -1;

	while ((dp = readdir(dir))) {
		__do_close int dfd = -EBADF, fd = -EBADF;
		int ret;

		if (!dp)
			break;

		if (strncmp(dp->d_name, "loop", 4) != 0)
			continue;

		dfd = dirfd(dir);
		if (dfd < 0)
			continue;

		fd = openat(dfd, dp->d_name, O_RDWR);
		if (fd < 0)
			continue;

		ret = ioctl(fd, LOOP_GET_STATUS64, &lo64);
		if (ret < 0)
			if (ioctl(fd, LOOP_GET_STATUS64, &lo64) == 0 || errno != ENXIO)
				continue;

		ret = snprintf(loop_name, LO_NAME_SIZE, "/dev/%s", dp->d_name);
		if (ret < 0 || ret >= LO_NAME_SIZE)
			continue;

		return move_fd(fd);
	}

	return -1;
}

static int get_unused_loop_dev(char *name_loop)
{
	__do_close int fd_ctl = -EBADF;
	int loop_nr, ret;

	fd_ctl = open("/dev/loop-control", O_RDWR | O_CLOEXEC);
	if (fd_ctl < 0)
		return -ENODEV;

	loop_nr = ioctl(fd_ctl, LOOP_CTL_GET_FREE);
	if (loop_nr < 0)
		return -1;

	ret = snprintf(name_loop, LO_NAME_SIZE, "/dev/loop%d", loop_nr);
	if (ret < 0 || ret >= LO_NAME_SIZE)
		return -1;

	return open(name_loop, O_RDWR | O_CLOEXEC);
}

static int prepare_loop_dev(const char *source, char *loop_dev, int flags)
{
	__do_close int fd_img = -EBADF, fd_loop = -EBADF;
	int ret;
	struct loop_info64 lo64;

	fd_loop = get_unused_loop_dev(loop_dev);
	if (fd_loop < 0) {
		if (fd_loop == -ENODEV)
			fd_loop = get_unused_loop_dev_legacy(loop_dev);
		else
			return -1;
	}

	fd_img = open(source, O_RDWR | O_CLOEXEC);
	if (fd_img < 0)
		return -1;

	ret = ioctl(fd_loop, LOOP_SET_FD, fd_img);
	if (ret < 0)
		return -1;

	memset(&lo64, 0, sizeof(lo64));
	lo64.lo_flags = flags;

	ret = ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
	if (ret < 0)
		return -1;

	return move_fd(fd_loop);
}

static inline int prepare_loop_dev_retry(const char *source, char *loop_dev, int flags)
{
	int ret;
	unsigned int idx = 0;

	do {
		ret = prepare_loop_dev(source, loop_dev, flags);
		idx++;
	} while (ret < 0 && errno == EBUSY && idx < 30);

	return ret;
}

// Note that this does not guarantee to clear the loop device in time so that
// find_associated_loop_device() will not report that there still is a
// configured device (udev and so on...). So don't call
// find_associated_loop_device() after having called
// set_autoclear_loop_device().
int set_autoclear_loop_device(int fd_loop)
{
	struct loop_info64 lo64;

	memset(&lo64, 0, sizeof(lo64));
	lo64.lo_flags = LO_FLAGS_AUTOCLEAR;
	errno = 0;
	return ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
}

// Directly release the loop device
int free_loop_device(int fd_loop)
{
	return ioctl(fd_loop, LOOP_CLR_FD);
}

// Unset the LO_FLAGS_AUTOCLEAR flag on the given loop device file descriptor.
int unset_autoclear_loop_device(int fd_loop)
{
	int ret;
	struct loop_info64 lo64;

	errno = 0;
	ret = ioctl(fd_loop, LOOP_GET_STATUS64, &lo64);
	if (ret < 0)
		return -1;

	if ((lo64.lo_flags & LO_FLAGS_AUTOCLEAR) == 0)
		return 0;

	lo64.lo_flags &= ~LO_FLAGS_AUTOCLEAR;
	errno = 0;
	return ioctl(fd_loop, LOOP_SET_STATUS64, &lo64);
}
*/
import "C"

// LoFlagsAutoclear determines whether the loop device will autodestruct on last
// close.
const LoFlagsAutoclear int = C.LO_FLAGS_AUTOCLEAR

// PrepareLoopDev detects and sets up a loop device for source. It returns an
// open file descriptor to the free loop device and the path of the free loop
// device. It's the callers responsibility to close the open file descriptor.
func PrepareLoopDev(source string, flags int) (*os.File, error) {
	cLoopDev := C.malloc(C.size_t(C.LO_NAME_SIZE))
	if cLoopDev == nil {
		return nil, fmt.Errorf("Failed to allocate memory in C")
	}
	defer C.free(cLoopDev)

	cSource := C.CString(source)
	defer C.free(unsafe.Pointer(cSource))
	loopFd, _ := C.find_associated_loop_device(cSource, (*C.char)(cLoopDev))
	if loopFd >= 0 {
		return os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev))), nil
	}

	loopFd, err := C.prepare_loop_dev_retry(cSource, (*C.char)(cLoopDev), C.int(flags))
	if loopFd < 0 {
		if err != nil {
			return nil, errors.Wrapf(err, "Failed to prepare loop device for %q", source)
		}

		return nil, fmt.Errorf("Failed to prepare loop device for %q", source)
	}

	return os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev))), nil
}

// releaseLoopDev releases the loop dev assigned to the provided file.
func releaseLoopDev(source string) error {
	cLoopDev := C.malloc(C.size_t(C.LO_NAME_SIZE))
	if cLoopDev == nil {
		return fmt.Errorf("Failed to allocate memory in C")
	}
	defer C.free(cLoopDev)

	cSource := C.CString(source)
	loopFd, err := C.find_associated_loop_device(cSource, (*C.char)(cLoopDev))
	if err != nil {
		return err
	}

	// Prepare a Go file and defer close on the loop device.
	fd := os.NewFile(uintptr(loopFd), C.GoString((*C.char)(cLoopDev)))
	defer fd.Close()

	if loopFd >= 0 {
		_, err := C.free_loop_device(C.int(loopFd))
		if err != nil {
			return err
		}
	}

	return nil
}

// SetAutoclearOnLoopDev enables autodestruction of the provided loopback device.
func SetAutoclearOnLoopDev(loopFd int) error {
	ret, err := C.set_autoclear_loop_device(C.int(loopFd))
	if ret < 0 {
		if err != nil {
			return err
		}
		return fmt.Errorf("Failed to set LO_FLAGS_AUTOCLEAR")
	}

	return nil
}

// UnsetAutoclearOnLoopDev disables autodestruction of the provided loopback device.
func UnsetAutoclearOnLoopDev(loopFd int) error {
	ret, err := C.unset_autoclear_loop_device(C.int(loopFd))
	if ret < 0 {
		if err != nil {
			return err
		}
		return fmt.Errorf("Failed to unset LO_FLAGS_AUTOCLEAR")
	}

	return nil
}
<|endoftext|>


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

package sql

import (
	"reflect"
	"github.com/ligato/cn-infra/utils/safeclose"
)

// SliceIt reads everything from the ValIterator & stores it to pointerToASlice
// It closes the iterator (since nothing left in the iterator)
func SliceIt(pointerToASlice interface{}, it ValIterator) error {
	/* TODO defer func() {
		if exp := recover(); exp != nil && it != nil {
			logger.Error(exp)
			exp = safeclose.Close(it)
			if exp != nil {
				logger.Error(exp)
			}
		}
	}()*/

	sl := reflect.ValueOf(pointerToASlice)
	if sl.Kind() == reflect.Ptr {
		sl = sl.Elem()
	} else {
		panic("must be pointer")
	}

	if sl.Kind() != reflect.Slice {
		panic("must be slice")
	}

	sliceType := sl.Type()

	sliceElemType := sliceType.Elem()
	sliceElemPtr := sliceElemType.Kind() == reflect.Ptr
	if sliceElemPtr {
		sliceElemType = sliceElemType.Elem()
	}
	for i := 0; i < 5; i++ {
		row := reflect.New(sliceElemType)
		if stop := it.GetNext(row.Interface()); stop {
			break
		}

		if sliceElemPtr {
			sl.Set(reflect.Append(sl, row))
		} else {
			sl.Set(reflect.Append(sl, row.Elem()))
		}
	}

	return safeclose.Close(it)
}
<commit_msg>ODPM-419 removed safety for loop upper bound from slice_utils.go<commit_after>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sql

import (
	"reflect"
	"github.com/ligato/cn-infra/utils/safeclose"
)

// SliceIt reads everything from the ValIterator & stores it to pointerToASlice
// It closes the iterator (since nothing left in the iterator)
func SliceIt(pointerToASlice interface{}, it ValIterator) error {
	/* TODO defer func() {
		if exp := recover(); exp != nil && it != nil {
			logger.Error(exp)
			exp = safeclose.Close(it)
			if exp != nil {
				logger.Error(exp)
			}
		}
	}()*/

	sl := reflect.ValueOf(pointerToASlice)
	if sl.Kind() == reflect.Ptr {
		sl = sl.Elem()
	} else {
		panic("must be pointer")
	}

	if sl.Kind() != reflect.Slice {
		panic("must be slice")
	}

	sliceType := sl.Type()

	sliceElemType := sliceType.Elem()
	sliceElemPtr := sliceElemType.Kind() == reflect.Ptr
	if sliceElemPtr {
		sliceElemType = sliceElemType.Elem()
	}
	for {
		row := reflect.New(sliceElemType)
		if stop := it.GetNext(row.Interface()); stop {
			break
		}

		if sliceElemPtr {
			sl.Set(reflect.Append(sl, row))
		} else {
			sl.Set(reflect.Append(sl, row.Elem()))
		}
	}

	return safeclose.Close(it)
}
<|endoftext|>


<commit_before>package orm

import (
	"fmt"
	"gnd.la/orm/driver"
	"gnd.la/orm/query"
)

type Query struct {
	orm       *Orm
	model     *joinModel
	methods   []*driver.Methods
	jtype     JoinType
	q         query.Q
	limit     int
	offset    int
	sortField string
	sortDir   int
	depth     int
	err       error
}

func (q *Query) ensureTable(f string) error {
	if q.model == nil {
		fmt.Errorf("no table selected, set one with Table() before calling %s()", f)
	}
	return nil
}

// Table sets the table for the query. If the table was
// previously set, it's overridden. Rather than using
// strings to select tables, a Table object (which is
// returned from Register) is used. This way is not
// possible to mistype a table name, which avoids lots
// of errors.
func (q *Query) Table(t *Table) *Query {
	q.model = t.model
	return q
}

func (q *Query) Join(jt JoinType) *Query {
	q.jtype = jt
	return q
}

// Filter adds another condition to the query. In other
// words, it ANDs the previous condition with the one passed in.
func (q *Query) Filter(qu query.Q) *Query {
	if qu != nil {
		if q.q == nil {
			q.q = qu
		} else {
			switch x := q.q.(type) {
			case *query.And:
				x.Conditions = append(x.Conditions, qu)
			default:
				q.q = And(q.q, qu)
			}
		}
	}
	return q
}

// Limit sets the maximum number of results
// for the query.
func (q *Query) Limit(limit int) *Query {
	q.limit = limit
	return q
}

// Offset sets the offset for the query.
func (q *Query) Offset(offset int) *Query {
	q.offset = offset
	return q
}

// Sort sets the field and direction used for sorting
// this query.
func (q *Query) Sort(field string, dir Sort) *Query {
	q.sortField = field
	q.sortDir = int(dir)
	return q
}

// One fetches the first result for this query. If there
// are no results, it returns ErrNotFound.
func (q *Query) One(out ...interface{}) error {
	iter := q.iter(1)
	if iter.Next(out...) {
		// Must close the iter manually, because we're not
		// reaching the end.
		iter.Close()
		return nil
	}
	if err := iter.Err(); err != nil {
		return err
	}
	return ErrNotFound
}

// Exists returns wheter a result with the specified query
// exists.
func (q *Query) Exists() (bool, error) {
	if err := q.ensureTable("Exists"); err != nil {
		return false, err
	}
	q.orm.numQueries++
	return q.orm.driver.Exists(q.model, q.q)
}

// Iter returns an Iter object which lets you
// iterate over the results produced by the
// query.
func (q *Query) Iter() *Iter {
	return q.iter(q.limit)
}

// Count returns the number of results for the query. Note that
// you have to set the table manually before calling Count().
func (q *Query) Count() (uint64, error) {
	if err := q.ensureTable("Count"); err != nil {
		return 0, err
	}
	q.orm.numQueries++
	return q.orm.driver.Count(q.model, q.q, q.limit, q.offset)
}

// MustCount works like Count, but panics if there's an error.
func (q *Query) MustCount() uint64 {
	c, err := q.Count()
	if err != nil {
		panic(err)
	}
	return c
}

func (q *Query) SetDepth(depth int) {
	q.depth = depth
}

// Clone returns a copy of the query.
func (q *Query) Clone() *Query {
	return &Query{
		orm:       q.orm,
		model:     q.model,
		q:         q.q,
		limit:     q.limit,
		offset:    q.offset,
		sortField: q.sortField,
		sortDir:   q.sortDir,
		depth:     q.depth,
		err:       q.err,
	}
}

func (q *Query) iter(limit int) *Iter {
	return &Iter{
		q:     q,
		limit: limit,
		err:   q.err,
	}
}
<commit_msg>Document Join() method<commit_after>package orm

import (
	"fmt"
	"gnd.la/orm/driver"
	"gnd.la/orm/query"
)

type Query struct {
	orm       *Orm
	model     *joinModel
	methods   []*driver.Methods
	jtype     JoinType
	q         query.Q
	limit     int
	offset    int
	sortField string
	sortDir   int
	depth     int
	err       error
}

func (q *Query) ensureTable(f string) error {
	if q.model == nil {
		fmt.Errorf("no table selected, set one with Table() before calling %s()", f)
	}
	return nil
}

// Table sets the table for the query. If the table was
// previously set, it's overridden. Rather than using
// strings to select tables, a Table object (which is
// returned from Register) is used. This way is not
// possible to mistype a table name, which avoids lots
// of errors.
func (q *Query) Table(t *Table) *Query {
	q.model = t.model
	return q
}

// Join sets the default join type for this query. If not
// specifed, an INNER JOIN is performed. Note that not all
// drivers support RIGHT joins (e.g. sqlite).
func (q *Query) Join(jt JoinType) *Query {
	q.jtype = jt
	return q
}

// Filter adds another condition to the query. In other
// words, it ANDs the previous condition with the one passed in.
func (q *Query) Filter(qu query.Q) *Query {
	if qu != nil {
		if q.q == nil {
			q.q = qu
		} else {
			switch x := q.q.(type) {
			case *query.And:
				x.Conditions = append(x.Conditions, qu)
			default:
				q.q = And(q.q, qu)
			}
		}
	}
	return q
}

// Limit sets the maximum number of results
// for the query.
func (q *Query) Limit(limit int) *Query {
	q.limit = limit
	return q
}

// Offset sets the offset for the query.
func (q *Query) Offset(offset int) *Query {
	q.offset = offset
	return q
}

// Sort sets the field and direction used for sorting
// this query.
func (q *Query) Sort(field string, dir Sort) *Query {
	q.sortField = field
	q.sortDir = int(dir)
	return q
}

// One fetches the first result for this query. If there
// are no results, it returns ErrNotFound.
func (q *Query) One(out ...interface{}) error {
	iter := q.iter(1)
	if iter.Next(out...) {
		// Must close the iter manually, because we're not
		// reaching the end.
		iter.Close()
		return nil
	}
	if err := iter.Err(); err != nil {
		return err
	}
	return ErrNotFound
}

// Exists returns wheter a result with the specified query
// exists.
func (q *Query) Exists() (bool, error) {
	if err := q.ensureTable("Exists"); err != nil {
		return false, err
	}
	q.orm.numQueries++
	return q.orm.driver.Exists(q.model, q.q)
}

// Iter returns an Iter object which lets you
// iterate over the results produced by the
// query.
func (q *Query) Iter() *Iter {
	return q.iter(q.limit)
}

// Count returns the number of results for the query. Note that
// you have to set the table manually before calling Count().
func (q *Query) Count() (uint64, error) {
	if err := q.ensureTable("Count"); err != nil {
		return 0, err
	}
	q.orm.numQueries++
	return q.orm.driver.Count(q.model, q.q, q.limit, q.offset)
}

// MustCount works like Count, but panics if there's an error.
func (q *Query) MustCount() uint64 {
	c, err := q.Count()
	if err != nil {
		panic(err)
	}
	return c
}

func (q *Query) SetDepth(depth int) {
	q.depth = depth
}

// Clone returns a copy of the query.
func (q *Query) Clone() *Query {
	return &Query{
		orm:       q.orm,
		model:     q.model,
		q:         q.q,
		limit:     q.limit,
		offset:    q.offset,
		sortField: q.sortField,
		sortDir:   q.sortDir,
		depth:     q.depth,
		err:       q.err,
	}
}

func (q *Query) iter(limit int) *Iter {
	return &Iter{
		q:     q,
		limit: limit,
		err:   q.err,
	}
}
<|endoftext|>


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

// gce_test is an integration test meant to verify the Cloud SQL Proxy works as
// expected on a Google Compute Engine VM. It provisions a GCE VM, loads a
// newly-compiled proxy client onto that VM, and then does some connectivity tests.
//
// If the VM specified by -vm_name doesn't exist already a new VM is created.
// If a VM does already exist, its 'sshKeys' metadata value is set to a newly
// generated key.
//
// Required flags:
//    -db_name, -project
//
// Example invocation:
//     go test -v -run TestConnectionLimit -args -project=my-project -db_name=my-project:the-region:sql-name
package tests

import (
	"bytes"
	"fmt"
	"testing"
	"time"
	"log"
)

// TestConnectionLimit provisions a new GCE VM and verifies that the proxy works on it.
// It uses application default credentials.
func TestConnectionLimit(t *testing.T) {
	err, ssh := setupGCEProxy(t, []string{"-max_connections", "5"})

	cmd := fmt.Sprintf(`mysql -uroot -S cloudsql/%s -e "SELECT 1; SELECT SLEEP(120);"`, *databaseName)

	// Use less than the sshd MaxStartups configuration (defaults to 10)
	for i := 0; i < 5; i++ {
		go func() {
			log.Print("Starting blocking mysql command")
			var sout, serr bytes.Buffer
			if err = sshRun(ssh, cmd, nil, &sout, &serr); err != nil {
				t.Fatalf("Error running mysql: %v\n\nstandard out:\n%s\nstandard err:\n%s", err, &sout, &serr)
			}
			t.Logf("Blocking command output %s", &sout)
		}()
	}

	time.Sleep(time.Second * 5)
	var sout, serr bytes.Buffer
	log.Print("Test connection refusal")
	cmd = fmt.Sprintf(`mysql -uroot -S cloudsql/%s -e "SELECT 1;"`, *databaseName)
	if err = sshRun(ssh, cmd, nil, &sout, &serr); err == nil {
		t.Fatalf("Mysql connection should have been refused:\n\nstandard out:\n%s\nstandard err:\n%s", &sout, &serr)
	}
	t.Logf("Test command output %s", &sout)
}
<commit_msg>Use t.Errorf() instead of t.Fatalf() in child goroutines to continue execution<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// gce_test is an integration test meant to verify the Cloud SQL Proxy works as
// expected on a Google Compute Engine VM. It provisions a GCE VM, loads a
// newly-compiled proxy client onto that VM, and then does some connectivity tests.
//
// If the VM specified by -vm_name doesn't exist already a new VM is created.
// If a VM does already exist, its 'sshKeys' metadata value is set to a newly
// generated key.
//
// Required flags:
//    -db_name, -project
//
// Example invocation:
//     go test -v -run TestConnectionLimit -args -project=my-project -db_name=my-project:the-region:sql-name
package tests

import (
	"bytes"
	"fmt"
	"testing"
	"time"
	"log"
)

// TestConnectionLimit provisions a new GCE VM and verifies that the proxy works on it.
// It uses application default credentials.
func TestConnectionLimit(t *testing.T) {
	err, ssh := setupGCEProxy(t, []string{"-max_connections", "5"})

	cmd := fmt.Sprintf(`mysql -uroot -S cloudsql/%s -e "SELECT 1; SELECT SLEEP(120);"`, *databaseName)

	// Use less than the sshd MaxStartups configuration (defaults to 10)
	for i := 0; i < 5; i++ {
		go func() {
			log.Print("Starting blocking mysql command")
			var sout, serr bytes.Buffer
			if err = sshRun(ssh, cmd, nil, &sout, &serr); err != nil {
				t.Errorf("Error running mysql: %v\n\nstandard out:\n%s\nstandard err:\n%s", err, &sout, &serr)
			}
			t.Logf("Blocking command output %s", &sout)
		}()
	}

	time.Sleep(time.Second * 5)
	var sout, serr bytes.Buffer
	log.Print("Test connection refusal")
	cmd = fmt.Sprintf(`mysql -uroot -S cloudsql/%s -e "SELECT 1;"`, *databaseName)
	if err = sshRun(ssh, cmd, nil, &sout, &serr); err == nil {
		t.Fatalf("Mysql connection should have been refused:\n\nstandard out:\n%s\nstandard err:\n%s", &sout, &serr)
	}
	t.Logf("Test command output %s", &sout)
}
<|endoftext|>


<commit_before>package bxmpp

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/jpillora/backoff"
	"github.com/matterbridge/go-xmpp"
	"github.com/rs/xid"
)

type Bxmpp struct {
	*bridge.Config

	startTime time.Time
	xc        *xmpp.Client
	xmppMap   map[string]string
	connected bool
	sync.RWMutex

	avatarAvailability map[string]bool
	avatarMap          map[string]string
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Bxmpp{
		Config:             cfg,
		xmppMap:            make(map[string]string),
		avatarAvailability: make(map[string]bool),
		avatarMap:          make(map[string]string),
	}
}

func (b *Bxmpp) Connect() error {
	b.Log.Infof("Connecting %s", b.GetString("Server"))
	if err := b.createXMPP(); err != nil {
		b.Log.Debugf("%#v", err)
		return err
	}

	b.Log.Info("Connection succeeded")
	go b.manageConnection()
	return nil
}

func (b *Bxmpp) Disconnect() error {
	return nil
}

func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
	if channel.Options.Key != "" {
		b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
		b.xc.JoinProtectedMUC(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"), channel.Options.Key, xmpp.NoHistory, 0, nil)
	} else {
		b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"))
	}
	return nil
}

func (b *Bxmpp) Send(msg config.Message) (string, error) {
	// should be fixed by using a cache instead of dropping
	if !b.Connected() {
		return "", fmt.Errorf("bridge %s not connected, dropping message %#v to bridge", b.Account, msg)
	}
	// ignore delete messages
	if msg.Event == config.EventMsgDelete {
		return "", nil
	}

	b.Log.Debugf("=> Receiving %#v", msg)

	if msg.Event == config.EventAvatarDownload {
		return b.cacheAvatar(&msg), nil
	}

	// Make a action /me of the message, prepend the username with it.
	// https://xmpp.org/extensions/xep-0245.html
	if msg.Event == config.EventUserAction {
		msg.Username = "/me " + msg.Username
	}

	// Upload a file (in XMPP case send the upload URL because XMPP has no native upload support).
	var err error
	if msg.Extra != nil {
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
			b.Log.Debugf("=> Sending attachement message %#v", rmsg)
			if b.GetString("WebhookURL") != "" {
				err = b.postSlackCompatibleWebhook(msg)
			} else {
				_, err = b.xc.Send(xmpp.Chat{
					Type:   "groupchat",
					Remote: rmsg.Channel + "@" + b.GetString("Muc"),
					Text:   rmsg.Username + rmsg.Text,
				})
			}

			if err != nil {
				b.Log.WithError(err).Error("Unable to send message with share URL.")
			}
		}
		if len(msg.Extra["file"]) > 0 {
			return "", b.handleUploadFile(&msg)
		}
	}

	if b.GetString("WebhookURL") != "" {
		b.Log.Debugf("Sending message using Webhook")
		err := b.postSlackCompatibleWebhook(msg)
		if err != nil {
			b.Log.Errorf("Failed to send message using webhook: %s", err)
			return "", err
		}

		return "", nil
	}

	// Post normal message.
	var msgReplaceID string
	msgID := xid.New().String()
	if msg.ID != "" {
		msgID = msg.ID
		msgReplaceID = msg.ID
	}
	b.Log.Debugf("=> Sending message %#v", msg)
	if _, err := b.xc.Send(xmpp.Chat{
		Type:      "groupchat",
		Remote:    msg.Channel + "@" + b.GetString("Muc"),
		Text:      msg.Username + msg.Text,
		ID:        msgID,
		ReplaceID: msgReplaceID,
	}); err != nil {
		return "", err
	}
	return msgID, nil
}

func (b *Bxmpp) postSlackCompatibleWebhook(msg config.Message) error {
	type XMPPWebhook struct {
		Username string `json:"username"`
		Text     string `json:"text"`
	}
	webhookBody, err := json.Marshal(XMPPWebhook{
		Username: msg.Username,
		Text:     msg.Text,
	})
	if err != nil {
		b.Log.Errorf("Failed to marshal webhook: %s", err)
		return err
	}

	resp, err := http.Post(b.GetString("WebhookURL")+"/"+url.QueryEscape(msg.Channel), "application/json", bytes.NewReader(webhookBody))
	if err != nil {
		b.Log.Errorf("Failed to POST webhook: %s", err)
		return err
	}

	resp.Body.Close()
	return nil
}

func (b *Bxmpp) createXMPP() error {
	var serverName string
	switch {
	case !b.GetBool("Anonymous"):
		if !strings.Contains(b.GetString("Jid"), "@") {
			return fmt.Errorf("the Jid %s doesn't contain an @", b.GetString("Jid"))
		}
		serverName = strings.Split(b.GetString("Jid"), "@")[1]
	case !strings.Contains(b.GetString("Server"), ":"):
		serverName = strings.Split(b.GetString("Server"), ":")[0]
	default:
		serverName = b.GetString("Server")
	}

	tc := &tls.Config{
		ServerName:         serverName,
		InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
	}

	xmpp.DebugWriter = b.Log.Writer()

	options := xmpp.Options{
		Host:                         b.GetString("Server"),
		User:                         b.GetString("Jid"),
		Password:                     b.GetString("Password"),
		NoTLS:                        true,
		StartTLS:                     !b.GetBool("NoTLS"),
		TLSConfig:                    tc,
		Debug:                        b.GetBool("debug"),
		Session:                      true,
		Status:                       "",
		StatusMessage:                "",
		Resource:                     "",
		InsecureAllowUnencryptedAuth: b.GetBool("NoTLS"),
	}
	var err error
	b.xc, err = options.NewClient()
	return err
}

func (b *Bxmpp) manageConnection() {
	b.setConnected(true)
	initial := true
	bf := &backoff.Backoff{
		Min:    time.Second,
		Max:    5 * time.Minute,
		Jitter: true,
	}

	// Main connection loop. Each iteration corresponds to a successful
	// connection attempt and the subsequent handling of the connection.
	for {
		if initial {
			initial = false
		} else {
			b.Remote <- config.Message{
				Username: "system",
				Text:     "rejoin",
				Channel:  "",
				Account:  b.Account,
				Event:    config.EventRejoinChannels,
			}
		}

		if err := b.handleXMPP(); err != nil {
			b.Log.WithError(err).Error("Disconnected.")
			b.setConnected(false)
		}

		// Reconnection loop using an exponential back-off strategy. We
		// only break out of the loop if we have successfully reconnected.
		for {
			d := bf.Duration()
			b.Log.Infof("Reconnecting in %s.", d)
			time.Sleep(d)

			b.Log.Infof("Reconnecting now.")
			if err := b.createXMPP(); err == nil {
				b.setConnected(true)
				bf.Reset()
				break
			}
			b.Log.Warn("Failed to reconnect.")
		}
	}
}

func (b *Bxmpp) xmppKeepAlive() chan bool {
	done := make(chan bool)
	go func() {
		ticker := time.NewTicker(90 * time.Second)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				b.Log.Debugf("PING")
				if err := b.xc.PingC2S("", ""); err != nil {
					b.Log.Debugf("PING failed %#v", err)
				}
			case <-done:
				return
			}
		}
	}()
	return done
}

func (b *Bxmpp) handleXMPP() error {
	b.startTime = time.Now()

	done := b.xmppKeepAlive()
	defer close(done)

	for {
		m, err := b.xc.Recv()
		if err != nil {
			return err
		}

		switch v := m.(type) {
		case xmpp.Chat:
			if v.Type == "groupchat" {
				b.Log.Debugf("== Receiving %#v", v)

				// Skip invalid messages.
				if b.skipMessage(v) {
					continue
				}

				var event string
				if strings.Contains(v.Text, "has set the subject to:") {
					event = config.EventTopicChange
				}

				available, sok := b.avatarAvailability[v.Remote]
				avatar := ""
				if !sok {
					b.Log.Debugf("Requesting avatar data")
					b.avatarAvailability[v.Remote] = false
					b.xc.AvatarRequestData(v.Remote)
				} else if available {
					avatar = getAvatar(b.avatarMap, v.Remote, b.General)
				}

				msgID := v.ID
				if v.ReplaceID != "" {
					msgID = v.ReplaceID
				}
				rmsg := config.Message{
					Username: b.parseNick(v.Remote),
					Text:     v.Text,
					Channel:  b.parseChannel(v.Remote),
					Account:  b.Account,
					Avatar:   avatar,
					UserID:   v.Remote,
					ID:       msgID,
					Event:    event,
				}

				// Check if we have an action event.
				var ok bool
				rmsg.Text, ok = b.replaceAction(rmsg.Text)
				if ok {
					rmsg.Event = config.EventUserAction
				}

				b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
				b.Log.Debugf("<= Message is %#v", rmsg)
				b.Remote <- rmsg
			}
		case xmpp.AvatarData:
			b.handleDownloadAvatar(v)
			b.avatarAvailability[v.From] = true
			b.Log.Debugf("Avatar for %s is now available", v.From)
		case xmpp.Presence:
			// Do nothing.
		}
	}
}

func (b *Bxmpp) replaceAction(text string) (string, bool) {
	if strings.HasPrefix(text, "/me ") {
		return strings.Replace(text, "/me ", "", -1), true
	}
	return text, false
}

// handleUploadFile handles native upload of files
func (b *Bxmpp) handleUploadFile(msg *config.Message) error {
	var urlDesc string

	for _, file := range msg.Extra["file"] {
		fileInfo := file.(config.FileInfo)
		if fileInfo.Comment != "" {
			msg.Text += fileInfo.Comment + ": "
		}
		if fileInfo.URL != "" {
			msg.Text = fileInfo.URL
			if fileInfo.Comment != "" {
				msg.Text = fileInfo.Comment + ": " + fileInfo.URL
				urlDesc = fileInfo.Comment
			}
		}
		if _, err := b.xc.Send(xmpp.Chat{
			Type:   "groupchat",
			Remote: msg.Channel + "@" + b.GetString("Muc"),
			Text:   msg.Username + msg.Text,
		}); err != nil {
			return err
		}

		if fileInfo.URL != "" {
			if _, err := b.xc.SendOOB(xmpp.Chat{
				Type:    "groupchat",
				Remote:  msg.Channel + "@" + b.GetString("Muc"),
				Ooburl:  fileInfo.URL,
				Oobdesc: urlDesc,
			}); err != nil {
				b.Log.WithError(err).Warn("Failed to send share URL.")
			}
		}
	}
	return nil
}

func (b *Bxmpp) parseNick(remote string) string {
	s := strings.Split(remote, "@")
	if len(s) > 1 {
		s = strings.Split(s[1], "/")
		if len(s) == 2 {
			return s[1] // nick
		}
	}
	return ""
}

func (b *Bxmpp) parseChannel(remote string) string {
	s := strings.Split(remote, "@")
	if len(s) >= 2 {
		return s[0] // channel
	}
	return ""
}

// skipMessage skips messages that need to be skipped
func (b *Bxmpp) skipMessage(message xmpp.Chat) bool {
	// skip messages from ourselves
	if b.parseNick(message.Remote) == b.GetString("Nick") {
		return true
	}

	// skip empty messages
	if message.Text == "" {
		return true
	}

	// skip subject messages
	if strings.Contains(message.Text, "</subject>") {
		return true
	}

	// do not show subjects on connect #732
	if strings.Contains(message.Text, "has set the subject to:") && time.Since(b.startTime) < time.Second*5 {
		return true
	}

	// Ignore messages posted by our webhook
	if b.GetString("WebhookURL") != "" && strings.Contains(message.ID, "webhookbot") {
		return true
	}

	// skip delayed messages
	return !message.Stamp.IsZero() && time.Since(message.Stamp).Minutes() > 5
}

func (b *Bxmpp) setConnected(state bool) {
	b.Lock()
	b.connected = state
	defer b.Unlock()
}

func (b *Bxmpp) Connected() bool {
	b.RLock()
	defer b.RUnlock()
	return b.connected
}
<commit_msg>Use a new msgID when replacing messages (xmpp). Fixes #1584 (#1623)<commit_after>package bxmpp

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/jpillora/backoff"
	"github.com/matterbridge/go-xmpp"
	"github.com/rs/xid"
)

type Bxmpp struct {
	*bridge.Config

	startTime time.Time
	xc        *xmpp.Client
	xmppMap   map[string]string
	connected bool
	sync.RWMutex

	avatarAvailability map[string]bool
	avatarMap          map[string]string
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Bxmpp{
		Config:             cfg,
		xmppMap:            make(map[string]string),
		avatarAvailability: make(map[string]bool),
		avatarMap:          make(map[string]string),
	}
}

func (b *Bxmpp) Connect() error {
	b.Log.Infof("Connecting %s", b.GetString("Server"))
	if err := b.createXMPP(); err != nil {
		b.Log.Debugf("%#v", err)
		return err
	}

	b.Log.Info("Connection succeeded")
	go b.manageConnection()
	return nil
}

func (b *Bxmpp) Disconnect() error {
	return nil
}

func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
	if channel.Options.Key != "" {
		b.Log.Debugf("using key %s for channel %s", channel.Options.Key, channel.Name)
		b.xc.JoinProtectedMUC(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"), channel.Options.Key, xmpp.NoHistory, 0, nil)
	} else {
		b.xc.JoinMUCNoHistory(channel.Name+"@"+b.GetString("Muc"), b.GetString("Nick"))
	}
	return nil
}

func (b *Bxmpp) Send(msg config.Message) (string, error) {
	// should be fixed by using a cache instead of dropping
	if !b.Connected() {
		return "", fmt.Errorf("bridge %s not connected, dropping message %#v to bridge", b.Account, msg)
	}
	// ignore delete messages
	if msg.Event == config.EventMsgDelete {
		return "", nil
	}

	b.Log.Debugf("=> Receiving %#v", msg)

	if msg.Event == config.EventAvatarDownload {
		return b.cacheAvatar(&msg), nil
	}

	// Make a action /me of the message, prepend the username with it.
	// https://xmpp.org/extensions/xep-0245.html
	if msg.Event == config.EventUserAction {
		msg.Username = "/me " + msg.Username
	}

	// Upload a file (in XMPP case send the upload URL because XMPP has no native upload support).
	var err error
	if msg.Extra != nil {
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
			b.Log.Debugf("=> Sending attachement message %#v", rmsg)
			if b.GetString("WebhookURL") != "" {
				err = b.postSlackCompatibleWebhook(msg)
			} else {
				_, err = b.xc.Send(xmpp.Chat{
					Type:   "groupchat",
					Remote: rmsg.Channel + "@" + b.GetString("Muc"),
					Text:   rmsg.Username + rmsg.Text,
				})
			}

			if err != nil {
				b.Log.WithError(err).Error("Unable to send message with share URL.")
			}
		}
		if len(msg.Extra["file"]) > 0 {
			return "", b.handleUploadFile(&msg)
		}
	}

	if b.GetString("WebhookURL") != "" {
		b.Log.Debugf("Sending message using Webhook")
		err := b.postSlackCompatibleWebhook(msg)
		if err != nil {
			b.Log.Errorf("Failed to send message using webhook: %s", err)
			return "", err
		}

		return "", nil
	}

	// Post normal message.
	var msgReplaceID string
	msgID := xid.New().String()
	if msg.ID != "" {
		msgReplaceID = msg.ID
	}
	b.Log.Debugf("=> Sending message %#v", msg)
	if _, err := b.xc.Send(xmpp.Chat{
		Type:      "groupchat",
		Remote:    msg.Channel + "@" + b.GetString("Muc"),
		Text:      msg.Username + msg.Text,
		ID:        msgID,
		ReplaceID: msgReplaceID,
	}); err != nil {
		return "", err
	}
	return msgID, nil
}

func (b *Bxmpp) postSlackCompatibleWebhook(msg config.Message) error {
	type XMPPWebhook struct {
		Username string `json:"username"`
		Text     string `json:"text"`
	}
	webhookBody, err := json.Marshal(XMPPWebhook{
		Username: msg.Username,
		Text:     msg.Text,
	})
	if err != nil {
		b.Log.Errorf("Failed to marshal webhook: %s", err)
		return err
	}

	resp, err := http.Post(b.GetString("WebhookURL")+"/"+url.QueryEscape(msg.Channel), "application/json", bytes.NewReader(webhookBody))
	if err != nil {
		b.Log.Errorf("Failed to POST webhook: %s", err)
		return err
	}

	resp.Body.Close()
	return nil
}

func (b *Bxmpp) createXMPP() error {
	var serverName string
	switch {
	case !b.GetBool("Anonymous"):
		if !strings.Contains(b.GetString("Jid"), "@") {
			return fmt.Errorf("the Jid %s doesn't contain an @", b.GetString("Jid"))
		}
		serverName = strings.Split(b.GetString("Jid"), "@")[1]
	case !strings.Contains(b.GetString("Server"), ":"):
		serverName = strings.Split(b.GetString("Server"), ":")[0]
	default:
		serverName = b.GetString("Server")
	}

	tc := &tls.Config{
		ServerName:         serverName,
		InsecureSkipVerify: b.GetBool("SkipTLSVerify"), // nolint: gosec
	}

	xmpp.DebugWriter = b.Log.Writer()

	options := xmpp.Options{
		Host:                         b.GetString("Server"),
		User:                         b.GetString("Jid"),
		Password:                     b.GetString("Password"),
		NoTLS:                        true,
		StartTLS:                     !b.GetBool("NoTLS"),
		TLSConfig:                    tc,
		Debug:                        b.GetBool("debug"),
		Session:                      true,
		Status:                       "",
		StatusMessage:                "",
		Resource:                     "",
		InsecureAllowUnencryptedAuth: b.GetBool("NoTLS"),
	}
	var err error
	b.xc, err = options.NewClient()
	return err
}

func (b *Bxmpp) manageConnection() {
	b.setConnected(true)
	initial := true
	bf := &backoff.Backoff{
		Min:    time.Second,
		Max:    5 * time.Minute,
		Jitter: true,
	}

	// Main connection loop. Each iteration corresponds to a successful
	// connection attempt and the subsequent handling of the connection.
	for {
		if initial {
			initial = false
		} else {
			b.Remote <- config.Message{
				Username: "system",
				Text:     "rejoin",
				Channel:  "",
				Account:  b.Account,
				Event:    config.EventRejoinChannels,
			}
		}

		if err := b.handleXMPP(); err != nil {
			b.Log.WithError(err).Error("Disconnected.")
			b.setConnected(false)
		}

		// Reconnection loop using an exponential back-off strategy. We
		// only break out of the loop if we have successfully reconnected.
		for {
			d := bf.Duration()
			b.Log.Infof("Reconnecting in %s.", d)
			time.Sleep(d)

			b.Log.Infof("Reconnecting now.")
			if err := b.createXMPP(); err == nil {
				b.setConnected(true)
				bf.Reset()
				break
			}
			b.Log.Warn("Failed to reconnect.")
		}
	}
}

func (b *Bxmpp) xmppKeepAlive() chan bool {
	done := make(chan bool)
	go func() {
		ticker := time.NewTicker(90 * time.Second)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				b.Log.Debugf("PING")
				if err := b.xc.PingC2S("", ""); err != nil {
					b.Log.Debugf("PING failed %#v", err)
				}
			case <-done:
				return
			}
		}
	}()
	return done
}

func (b *Bxmpp) handleXMPP() error {
	b.startTime = time.Now()

	done := b.xmppKeepAlive()
	defer close(done)

	for {
		m, err := b.xc.Recv()
		if err != nil {
			return err
		}

		switch v := m.(type) {
		case xmpp.Chat:
			if v.Type == "groupchat" {
				b.Log.Debugf("== Receiving %#v", v)

				// Skip invalid messages.
				if b.skipMessage(v) {
					continue
				}

				var event string
				if strings.Contains(v.Text, "has set the subject to:") {
					event = config.EventTopicChange
				}

				available, sok := b.avatarAvailability[v.Remote]
				avatar := ""
				if !sok {
					b.Log.Debugf("Requesting avatar data")
					b.avatarAvailability[v.Remote] = false
					b.xc.AvatarRequestData(v.Remote)
				} else if available {
					avatar = getAvatar(b.avatarMap, v.Remote, b.General)
				}

				msgID := v.ID
				if v.ReplaceID != "" {
					msgID = v.ReplaceID
				}
				rmsg := config.Message{
					Username: b.parseNick(v.Remote),
					Text:     v.Text,
					Channel:  b.parseChannel(v.Remote),
					Account:  b.Account,
					Avatar:   avatar,
					UserID:   v.Remote,
					ID:       msgID,
					Event:    event,
				}

				// Check if we have an action event.
				var ok bool
				rmsg.Text, ok = b.replaceAction(rmsg.Text)
				if ok {
					rmsg.Event = config.EventUserAction
				}

				b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
				b.Log.Debugf("<= Message is %#v", rmsg)
				b.Remote <- rmsg
			}
		case xmpp.AvatarData:
			b.handleDownloadAvatar(v)
			b.avatarAvailability[v.From] = true
			b.Log.Debugf("Avatar for %s is now available", v.From)
		case xmpp.Presence:
			// Do nothing.
		}
	}
}

func (b *Bxmpp) replaceAction(text string) (string, bool) {
	if strings.HasPrefix(text, "/me ") {
		return strings.Replace(text, "/me ", "", -1), true
	}
	return text, false
}

// handleUploadFile handles native upload of files
func (b *Bxmpp) handleUploadFile(msg *config.Message) error {
	var urlDesc string

	for _, file := range msg.Extra["file"] {
		fileInfo := file.(config.FileInfo)
		if fileInfo.Comment != "" {
			msg.Text += fileInfo.Comment + ": "
		}
		if fileInfo.URL != "" {
			msg.Text = fileInfo.URL
			if fileInfo.Comment != "" {
				msg.Text = fileInfo.Comment + ": " + fileInfo.URL
				urlDesc = fileInfo.Comment
			}
		}
		if _, err := b.xc.Send(xmpp.Chat{
			Type:   "groupchat",
			Remote: msg.Channel + "@" + b.GetString("Muc"),
			Text:   msg.Username + msg.Text,
		}); err != nil {
			return err
		}

		if fileInfo.URL != "" {
			if _, err := b.xc.SendOOB(xmpp.Chat{
				Type:    "groupchat",
				Remote:  msg.Channel + "@" + b.GetString("Muc"),
				Ooburl:  fileInfo.URL,
				Oobdesc: urlDesc,
			}); err != nil {
				b.Log.WithError(err).Warn("Failed to send share URL.")
			}
		}
	}
	return nil
}

func (b *Bxmpp) parseNick(remote string) string {
	s := strings.Split(remote, "@")
	if len(s) > 1 {
		s = strings.Split(s[1], "/")
		if len(s) == 2 {
			return s[1] // nick
		}
	}
	return ""
}

func (b *Bxmpp) parseChannel(remote string) string {
	s := strings.Split(remote, "@")
	if len(s) >= 2 {
		return s[0] // channel
	}
	return ""
}

// skipMessage skips messages that need to be skipped
func (b *Bxmpp) skipMessage(message xmpp.Chat) bool {
	// skip messages from ourselves
	if b.parseNick(message.Remote) == b.GetString("Nick") {
		return true
	}

	// skip empty messages
	if message.Text == "" {
		return true
	}

	// skip subject messages
	if strings.Contains(message.Text, "</subject>") {
		return true
	}

	// do not show subjects on connect #732
	if strings.Contains(message.Text, "has set the subject to:") && time.Since(b.startTime) < time.Second*5 {
		return true
	}

	// Ignore messages posted by our webhook
	if b.GetString("WebhookURL") != "" && strings.Contains(message.ID, "webhookbot") {
		return true
	}

	// skip delayed messages
	return !message.Stamp.IsZero() && time.Since(message.Stamp).Minutes() > 5
}

func (b *Bxmpp) setConnected(state bool) {
	b.Lock()
	b.connected = state
	defer b.Unlock()
}

func (b *Bxmpp) Connected() bool {
	b.RLock()
	defer b.RUnlock()
	return b.connected
}
<|endoftext|>


<commit_before>// +build linux

package dokodemo

import (
	"syscall"

	"github.com/v2ray/v2ray-core/common/log"
	v2net "github.com/v2ray/v2ray-core/common/net"
	"github.com/v2ray/v2ray-core/transport/internet"
	"github.com/v2ray/v2ray-core/transport/internet/tcp"
)

const SO_ORIGINAL_DST = 80

func GetOriginalDestination(conn internet.Connection) v2net.Destination {
	tcpConn, ok := conn.(internet.SysFd)
	if !ok {
		log.Info("Dokodemo: Failed to get sys fd.")
		return nil
	}
	fd, err := tcpConn.SysFd()
	if err != nil {
		log.Info("Dokodemo: Failed to get original destination: ", err)
		return nil
	}

	addr, err := syscall.GetsockoptIPv6Mreq(fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST)
	if err != nil {
		log.Info("Dokodemo: Failed to call getsockopt: ", err)
		return nil
	}
	ip := v2net.IPAddress(addr.Multiaddr[4:8])
	port := uint16(addr.Multiaddr[2])<<8 + uint16(addr.Multiaddr[3])
	return v2net.TCPDestination(ip, v2net.Port(port))
}
<commit_msg>remove unused import<commit_after>// +build linux

package dokodemo

import (
	"syscall"

	"github.com/v2ray/v2ray-core/common/log"
	v2net "github.com/v2ray/v2ray-core/common/net"
	"github.com/v2ray/v2ray-core/transport/internet"
)

const SO_ORIGINAL_DST = 80

func GetOriginalDestination(conn internet.Connection) v2net.Destination {
	tcpConn, ok := conn.(internet.SysFd)
	if !ok {
		log.Info("Dokodemo: Failed to get sys fd.")
		return nil
	}
	fd, err := tcpConn.SysFd()
	if err != nil {
		log.Info("Dokodemo: Failed to get original destination: ", err)
		return nil
	}

	addr, err := syscall.GetsockoptIPv6Mreq(fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST)
	if err != nil {
		log.Info("Dokodemo: Failed to call getsockopt: ", err)
		return nil
	}
	ip := v2net.IPAddress(addr.Multiaddr[4:8])
	port := uint16(addr.Multiaddr[2])<<8 + uint16(addr.Multiaddr[3])
	return v2net.TCPDestination(ip, v2net.Port(port))
}
<|endoftext|>


<commit_before>package cmd

import (
	"bytes"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/gobuffalo/buffalo/meta"
	"github.com/gobuffalo/envy"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"github.com/gobuffalo/pop"
	"github.com/spf13/cobra"
)

const vendorPattern = "/vendor/"

var vendorRegex = regexp.MustCompile(vendorPattern)

func init() {
	decorate("test", testCmd)
	RootCmd.AddCommand(testCmd)
}

var testCmd = &cobra.Command{
	Use:                "test",
	Short:              "Runs the tests for your Buffalo app",
	DisableFlagParsing: true,
	RunE: func(c *cobra.Command, args []string) error {
		os.Setenv("GO_ENV", "test")
		if _, err := os.Stat("database.yml"); err == nil {
			// there's a database
			test, err := pop.Connect("test")
			if err != nil {
				return errors.WithStack(err)
			}

			// drop the test db:
			test.Dialect.DropDB()

			// create the test db:
			err = test.Dialect.CreateDB()
			if err != nil {
				return errors.WithStack(err)
			}

			if schema := findSchema(); schema != nil {
				err = test.Dialect.LoadSchema(schema)
				if err != nil {
					return errors.WithStack(err)
				}
			}
		}
		return testRunner(args)
	},
}

func findSchema() io.Reader {
	if f, err := os.Open(filepath.Join("migrations", "schema.sql")); err == nil {
		return f
	}
	if dev, err := pop.Connect("development"); err == nil {
		schema := &bytes.Buffer{}
		if err = dev.Dialect.DumpSchema(schema); err == nil {
			return schema
		}
	}

	if test, err := pop.Connect("test"); err == nil {
		if err := test.MigrateUp("./migrations"); err == nil {
			if f, err := os.Open(filepath.Join("migrations", "schema.sql")); err == nil {
				return f
			}
		}
	}
	return nil
}

func testRunner(args []string) error {
	var mFlag bool
	cargs := []string{}
	pargs := []string{}
	var larg string
	for i, a := range args {
		switch a {
		case "-run":
			cargs = append(cargs, "-run", args[i+1])
		case "-m":
			mFlag = true
			cargs = append(cargs, "-testify.m", args[i+1])
		case "-v":
			cargs = append(cargs, "-v")
		default:
			if larg != "-run" && larg != "-m" {
				pargs = append(pargs, a)
			}
		}
		larg = a
	}

	cmd := newTestCmd(cargs)
	if mFlag {
		return mFlagRunner(cargs, pargs)
	}

	pkgs, err := testPackages(pargs)
	if err != nil {
		return errors.WithStack(err)
	}
	cmd.Args = append(cmd.Args, pkgs...)
	logrus.Info(strings.Join(cmd.Args, " "))
	return cmd.Run()
}

func mFlagRunner(args []string, pargs []string) error {
	app := meta.New(".")
	pwd, _ := os.Getwd()
	defer os.Chdir(pwd)

	pkgs, err := testPackages(pargs)
	if err != nil {
		return errors.WithStack(err)
	}
	var errs bool
	for _, p := range pkgs {
		os.Chdir(pwd)
		if p == app.PackagePkg {
			continue
		}
		cmd := newTestCmd(args)
		p = strings.TrimPrefix(p, app.PackagePkg+string(filepath.Separator))
		logrus.Info(strings.Join(cmd.Args, " "))
		os.Chdir(p)
		if err := cmd.Run(); err != nil {
			errs = true
		}
	}
	if errs {
		return errors.New("errors running tests")
	}
	return nil
}

func testPackages(givenArgs []string) ([]string, error) {
	// If there are args, then assume these are the packages to test.
	//
	// Instead of always returning all packages from 'go list ./...', just
	// return the given packages in this case
	if len(givenArgs) > 0 {
		return givenArgs, nil
	}
	args := []string{}
	out, err := exec.Command(envy.Get("GO_BIN", "go"), "list", "./...").Output()
	if err != nil {
		return args, err
	}
	pkgs := bytes.Split(bytes.TrimSpace(out), []byte("\n"))
	for _, p := range pkgs {
		if !vendorRegex.Match(p) {
			args = append(args, string(p))
		}
	}
	return args, nil
}

func newTestCmd(args []string) *exec.Cmd {
	cargs := []string{"test", "-p", "1"}
	cargs = append(cargs, args...)
	cmd := exec.Command(envy.Get("GO_BIN", "go"), cargs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}
<commit_msg>buffalo test needs to support sqlite<commit_after>package cmd

import (
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/gobuffalo/buffalo/meta"
	"github.com/gobuffalo/envy"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"github.com/gobuffalo/pop"
	"github.com/spf13/cobra"
)

const vendorPattern = "/vendor/"

var vendorRegex = regexp.MustCompile(vendorPattern)

func init() {
	decorate("test", testCmd)
	RootCmd.AddCommand(testCmd)
}

var testCmd = &cobra.Command{
	Use:                "test",
	Short:              "Runs the tests for your Buffalo app",
	DisableFlagParsing: true,
	RunE: func(c *cobra.Command, args []string) error {
		os.Setenv("GO_ENV", "test")
		if _, err := os.Stat("database.yml"); err == nil {
			// there's a database
			test, err := pop.Connect("test")
			if err != nil {
				return errors.WithStack(err)
			}

			// drop the test db:
			test.Dialect.DropDB()

			// create the test db:
			err = test.Dialect.CreateDB()
			if err != nil {
				return errors.WithStack(err)
			}

			if schema := findSchema(); schema != nil {
				err = test.Dialect.LoadSchema(schema)
				if err != nil {
					return errors.WithStack(err)
				}
			}
		}
		return testRunner(args)
	},
}

func findSchema() io.Reader {
	if f, err := os.Open(filepath.Join("migrations", "schema.sql")); err == nil {
		return f
	}
	if dev, err := pop.Connect("development"); err == nil {
		schema := &bytes.Buffer{}
		if err = dev.Dialect.DumpSchema(schema); err == nil {
			return schema
		}
	}

	if test, err := pop.Connect("test"); err == nil {
		if err := test.MigrateUp("./migrations"); err == nil {
			if f, err := os.Open(filepath.Join("migrations", "schema.sql")); err == nil {
				return f
			}
		}
	}
	return nil
}

func testRunner(args []string) error {
	var mFlag bool
	cargs := []string{}
	pargs := []string{}
	var larg string
	for i, a := range args {
		switch a {
		case "-run":
			cargs = append(cargs, "-run", args[i+1])
		case "-m":
			mFlag = true
			cargs = append(cargs, "-testify.m", args[i+1])
		case "-v":
			cargs = append(cargs, "-v")
		default:
			if larg != "-run" && larg != "-m" {
				pargs = append(pargs, a)
			}
		}
		larg = a
	}

	cmd := newTestCmd(cargs)
	if mFlag {
		return mFlagRunner(cargs, pargs)
	}

	pkgs, err := testPackages(pargs)
	if err != nil {
		return errors.WithStack(err)
	}
	cmd.Args = append(cmd.Args, pkgs...)
	logrus.Info(strings.Join(cmd.Args, " "))
	return cmd.Run()
}

func mFlagRunner(args []string, pargs []string) error {
	app := meta.New(".")
	pwd, _ := os.Getwd()
	defer os.Chdir(pwd)

	pkgs, err := testPackages(pargs)
	if err != nil {
		return errors.WithStack(err)
	}
	var errs bool
	for _, p := range pkgs {
		os.Chdir(pwd)
		if p == app.PackagePkg {
			continue
		}
		cmd := newTestCmd(args)
		p = strings.TrimPrefix(p, app.PackagePkg+string(filepath.Separator))
		logrus.Info(strings.Join(cmd.Args, " "))
		os.Chdir(p)
		if err := cmd.Run(); err != nil {
			errs = true
		}
	}
	if errs {
		return errors.New("errors running tests")
	}
	return nil
}

func testPackages(givenArgs []string) ([]string, error) {
	// If there are args, then assume these are the packages to test.
	//
	// Instead of always returning all packages from 'go list ./...', just
	// return the given packages in this case
	if len(givenArgs) > 0 {
		return givenArgs, nil
	}
	args := []string{}
	out, err := exec.Command(envy.Get("GO_BIN", "go"), "list", "./...").Output()
	if err != nil {
		return args, err
	}
	pkgs := bytes.Split(bytes.TrimSpace(out), []byte("\n"))
	for _, p := range pkgs {
		if !vendorRegex.Match(p) {
			args = append(args, string(p))
		}
	}
	return args, nil
}

func newTestCmd(args []string) *exec.Cmd {
	cargs := []string{"test", "-p", "1"}
	if b, err := ioutil.ReadFile("database.yml"); err == nil {
		if bytes.Contains(b, []byte("sqlite")) {
			cargs = append(cargs, "-tags", "sqlite")
		}
	}
	cargs = append(cargs, args...)
	cmd := exec.Command(envy.Get("GO_BIN", "go"), cargs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}
<|endoftext|>


<commit_before>package kf

import (
	"encoding/json"
	"fmt"

	"github.com/silenceper/wechat/v2/util"
)

const (
	// 获取会话状态
	serviceStateGetAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/get?access_token=%s"
	// 变更会话状态
	serviceStateTransAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/trans?access_token=%s"
)

// ServiceStateGetOptions 获取会话状态请求参数
type ServiceStateGetOptions struct {
	OpenKFID       string `json:"open_kfid"`       // 客服帐号ID
	ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
}

// ServiceStateGetSchema 获取会话状态响应内容
type ServiceStateGetSchema struct {
	util.CommonError
	ServiceState  int    `json:"service_state"`  // 当前的会话状态，状态定义参考概述中的表格
	ServiceUserID string `json:"service_userid"` // 接待人员的userid，仅当state=3时有效
}

// ServiceStateGet 获取会话状态
// 0	未处理	新会话接入。可选择：1.直接用API自动回复消息。2.放进待接入池等待接待人员接待。3.指定接待人员进行接待
// 1	由智能助手接待	可使用API回复消息。可选择转入待接入池或者指定接待人员处理。
// 2	待接入池排队中	在待接入池中排队等待接待人员接入。可选择转为指定人员接待
// 3	由人工接待	人工接待中。可选择结束会话
// 4	已结束	会话已经结束或未开始。不允许变更会话状态，等待用户发起咨询
// 注：一个微信用户向一个客服帐号发起咨询后，在48h内，或主动结束会话前（包括接待人员手动结束，或企业通过API结束会话），都算是一次会话
func (r *Client) ServiceStateGet(options ServiceStateGetOptions) (info ServiceStateGetSchema, err error) {
	var (
		accessToken string
		data        []byte
	)
	accessToken, err = r.ctx.GetAccessToken()
	if err != nil {
		return
	}
	data, err = util.PostJSON(fmt.Sprintf(serviceStateGetAddr, accessToken), options)
	if err != nil {
		return
	}
	if err = json.Unmarshal(data, &info); err != nil {
		return
	}
	if info.ErrCode != 0 {
		return info, NewSDKErr(info.ErrCode, info.ErrMsg)
	}
	return info, nil
}

// ServiceStateTransOptions 变更会话状态请求参数
type ServiceStateTransOptions struct {
	OpenKFID       string `json:"open_kfid"`       // 客服帐号ID
	ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
	ServiceState   int    `json:"service_state"`   // 变更的目标状态，状态定义和所允许的变更可参考概述中的流程图和表格
	ServicerUserID string `json:"servicer_userid"` // 接待人员的userid，当state=3时要求必填，接待人员须处于“正在接待”中
}

// ServiceStateTrans 变更会话状态
func (r *Client) ServiceStateTrans(options ServiceStateTransOptions) (info util.CommonError, err error) {
	var (
		accessToken string
		data        []byte
	)
	accessToken, err = r.ctx.GetAccessToken()
	if err != nil {
		return
	}
	data, err = util.PostJSON(fmt.Sprintf(serviceStateTransAddr, accessToken), options)
	if err != nil {
		return
	}
	if err = json.Unmarshal(data, &info); err != nil {
		return
	}
	if info.ErrCode != 0 {
		return info, NewSDKErr(info.ErrCode, info.ErrMsg)
	}
	return info, nil
}
<commit_msg>feat:微信客服支持通过接口转接人工会话 (#492)<commit_after>package kf

import (
	"encoding/json"
	"fmt"

	"github.com/silenceper/wechat/v2/util"
)

const (
	// 获取会话状态
	serviceStateGetAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/get?access_token=%s"
	// 变更会话状态
	serviceStateTransAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/trans?access_token=%s"
)

// ServiceStateGetOptions 获取会话状态请求参数
type ServiceStateGetOptions struct {
	OpenKFID       string `json:"open_kfid"`       // 客服帐号ID
	ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
}

// ServiceStateGetSchema 获取会话状态响应内容
type ServiceStateGetSchema struct {
	util.CommonError
	ServiceState  int    `json:"service_state"`  // 当前的会话状态，状态定义参考概述中的表格
	ServiceUserID string `json:"service_userid"` // 接待人员的userid，仅当state=3时有效
}

// ServiceStateGet 获取会话状态
// 0	未处理	新会话接入（客户发信咨询）。可选择：1.直接用API自动回复消息。2.放进待接入池等待接待人员接待。3.指定接待人员（接待人员须处于“正在接待”中，下同）进行接待
// 1	由智能助手接待		可使用API回复消息。可选择转入待接入池或者指定接待人员处理
// 2	待接入池排队中		在待接入池中排队等待接待人员接入。可选择转为指定人员接待
// 3	由人工接待	人工接待中。可选择转接给其他接待人员处理或者结束会话
// 4	已结束	会话已经结束或未开始。不允许变更会话状态，客户重新发信咨询后会话状态变为“未处理”
// 注：一个微信用户向一个客服帐号发起咨询后，在48h内，或主动结束会话前（包括接待人员手动结束，或企业通过API结束会话），都算是一次会话
func (r *Client) ServiceStateGet(options ServiceStateGetOptions) (info ServiceStateGetSchema, err error) {
	var (
		accessToken string
		data        []byte
	)
	accessToken, err = r.ctx.GetAccessToken()
	if err != nil {
		return
	}
	data, err = util.PostJSON(fmt.Sprintf(serviceStateGetAddr, accessToken), options)
	if err != nil {
		return
	}
	if err = json.Unmarshal(data, &info); err != nil {
		return
	}
	if info.ErrCode != 0 {
		return info, NewSDKErr(info.ErrCode, info.ErrMsg)
	}
	return info, nil
}

// ServiceStateTransOptions 变更会话状态请求参数
type ServiceStateTransOptions struct {
	OpenKFID       string `json:"open_kfid"`       // 客服帐号ID
	ExternalUserID string `json:"external_userid"` // 微信客户的external_userid
	ServiceState   int    `json:"service_state"`   // 变更的目标状态，状态定义和所允许的变更可参考概述中的流程图和表格
	ServicerUserID string `json:"servicer_userid"` // 接待人员的userid，当state=3时要求必填，接待人员须处于“正在接待”中
}

// ServiceStateTrans 变更会话状态
func (r *Client) ServiceStateTrans(options ServiceStateTransOptions) (info util.CommonError, err error) {
	var (
		accessToken string
		data        []byte
	)
	accessToken, err = r.ctx.GetAccessToken()
	if err != nil {
		return
	}
	data, err = util.PostJSON(fmt.Sprintf(serviceStateTransAddr, accessToken), options)
	if err != nil {
		return
	}
	if err = json.Unmarshal(data, &info); err != nil {
		return
	}
	if info.ErrCode != 0 {
		return info, NewSDKErr(info.ErrCode, info.ErrMsg)
	}
	return info, nil
}
<|endoftext|>