<commit_before>// Copyright (c) 2015 SUSE LLC. 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.

package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/SUSE/dockerclient"
	"github.com/docker/docker/pkg/tlsconfig"
)

// dockerError encapsulates a dockerclient.WaitResult that has an exit status
// different than 0. This is done this way because, for some commands, zypper
// might set an exit code different than 0, even if there was no error. For
// example, the patch-check command can set the exit code 100, to determine
// that there are patches to be installed. In this case, the caller can decide
// what to do depending on the error returned by zypper. Therefore, the caller
// of functions such as `startContainer` should only care about this type if
// the command being implemented has this kind of behavior.
type dockerError struct {
	dockerclient.WaitResult
}

func (de dockerError) Error() string {
	return fmt.Sprintf("Command exited with status %d", de.ExitCode)
}

// The DockerClient interface lists all the functions that we use from Docker clients. Take
// a look at http://godoc.org/github.com/samalba/dockerclient if you want to
// read the documentation for each function.
type DockerClient interface {
	ListImages(all bool, filter string, filters *dockerclient.ListFilter) ([]*dockerclient.Image, error)
	ListContainers(all bool, size bool, filters string) ([]dockerclient.Container, error)

	CreateContainer(config *dockerclient.ContainerConfig, name string) (string, error)
	StartContainer(id string, config *dockerclient.HostConfig) error
	RemoveContainer(id string, force, volume bool) error
	KillContainer(id, signal string) error
	ResizeContainer(id string, isExec bool, width, height int) error
	Commit(id string, c *dockerclient.ContainerConfig, repo, tag, comment, author string) (string, error)

	Wait(id string) <-chan dockerclient.WaitResult

	ContainerLogs(id string, options *dockerclient.LogOptions) (io.ReadCloser, error)
}

// This global variable holds the instance to the docker client.
var dockerClient DockerClient

const (
	// The path in which the Docker client is listening to.
	dockerSocket = "unix:///var/run/docker.sock"

	// The timeout in which the container is allowed to run a command as given
	// to the `startContainer` function.
	containerTimeout = 2 * time.Second

	// tls-related files, code taken from docker/common.go
	defaultTrustKeyFile = "key.json"
	defaultCaFile       = "ca.pem"
	defaultKeyFile      = "key.pem"
	defaultCertFile     = "cert.pem"
)

// Use this function to safely retrieve the singleton instance of the Docker
// client. In order to guarantee such safety, the instance has to be
// initialized when no goroutines are being executed concurrently (e.g. the
// `init` or the `main` function).
func getDockerClient() DockerClient {
	if dockerClient != nil {
		return dockerClient
	}

	// We can safely discard the error. The connection will be started
	// successfully because internally `NewDockerClientTimeout` will handle the
	// connection as a dial for the http package. Therefore, it won't fail even
	// if the given URL does not exist. This is ok, since this possible error
	// will appear later on with subsequent commands.
	//
	// The only time it will return an error will be if the given URL has a bad
	// format, which won't happen.

	daemonUrl := dockerSocket
	if os.Getenv("DOCKER_HOST") != "" {
		daemonUrl = os.Getenv("DOCKER_HOST")
	}

	tlsConfig, err := setupTlsConfig()
	if err != nil {
		panic(err)
	}

	dockerClient, _ = dockerclient.NewDockerClientTimeout(daemonUrl, tlsConfig,
		containerTimeout)
	return dockerClient
}

func setupTlsConfig() (*tls.Config, error) {
	if os.Getenv("DOCKER_TLS_VERIFY") == "" {
		return &tls.Config{}, nil
	}
	dockerCertPath := os.Getenv("DOCKER_CERT_PATH")

	var tlsOptions tlsconfig.Options
	tlsOptions.CAFile = filepath.Join(dockerCertPath, defaultCaFile)
	tlsOptions.CertFile = filepath.Join(dockerCertPath, defaultCertFile)
	tlsOptions.KeyFile = filepath.Join(dockerCertPath, defaultKeyFile)
	return tlsconfig.Client(tlsOptions)
}

// humanizeCommandError tries to print an explicit and useful message for a
// failing command inside of a docker container (based on the given image).
func humanizeCommandError(cmd, image string, err error) {
	var reason string

	switch err.(type) {
	case dockerError:
		de := err.(dockerError)
		if de.ExitCode == 127 {
			reason = "command not found"
		} else {
			reason = err.Error()
		}
	default:
		reason = err.Error()
	}

	msg := "Could not execute command '%s' successfully in image '%s': %v.\n"
	log.Printf(msg, cmd, image, reason)
}

// Looks for the specified command inside of a Docker image.
// The given image string is just the ID of said image.
// It returns true if the command was successful, false otherwise.
func checkCommandInImage(img, cmd string) bool {
	containerId, err := startContainer(img, []string{cmd}, false, false)

	defer removeContainer(containerId)

	if err != nil {
		humanizeCommandError(cmd, img, err)
		return false
	}
	return true
}

// runStreamedCommand is a convenient wrapper of the `runCommandInContainer`
// for functions that just need to run a command on streaming without the
// burden of removing the resulting container, etc.
//
// The image has to be provided, otherwise this function will exit with 1 as
// the status code and it will both log and print that no image was provided.
// The given command will be executed as "zypper ref && zypper <command>".
//
// If getError is set to false, then this function will always return nil.
// Otherwise, it will return the error as given by the `runCommandInContainer`
// function.
func runStreamedCommand(img, cmd string, getError bool) error {
	if img == "" {
		logAndFatalf("Error: no image name specified.\n")
		return nil
	}

	cmd = formatZypperCommand("ref", cmd)
	id, err := runCommandInContainer(img, []string{cmd}, true)
	removeContainer(id)

	if getError {
		return err
	}
	if err != nil {
		log.Printf("Error: %s\n", err)
		exitWithCode(1)
	}
	return nil
}

// Run the given command in a container based on the given image. The given
// image string is just the ID of said image.
// The STDOUT and STDERR of the container can be streamed to the host's STDOUT
// by setting the `streaming` parameter to true.
// It returns the ID of the container spawned from the image.
// Note well: the container is NOT deleted when the given command terminates.
func runCommandInContainer(img string, cmd []string, streaming bool) (string, error) {
	return startContainer(img, cmd, streaming, true)
}

// Get the host config to be used for starting containers.
func getHostConfig() *dockerclient.HostConfig {
	if currentContext == nil {
		return &dockerclient.HostConfig{}
	}
	return &dockerclient.HostConfig{ExtraHosts: currentContext.GlobalStringSlice("add-host")}
}

// Start a container from the specified image and then runs the given command
// inside of it. The given image string is just the ID of said image.
// The STDOUT and STDERR of the container can be streamed to the host's STDOUT
// by setting the `streaming` parameter to true.
// When `wait` is set to true the function will wait untill the container exits,
// otherwise it will timeout raising an error.
// Note well: the container is NOT deleted when the given command terminates.
// This is again up to the caller.
//
// It returns the ID of the container spawned from the image. The error
// returned can be of type dockerError. This only happens when the container
// has run normally (no signals, no timeout), but the exit code is not 0. Read
// the documentation on the `dockerError` command on why we do this.
func startContainer(img string, cmd []string, streaming, wait bool) (string, error) {
	id, err := createContainer(img, cmd)
	if err != nil {
		log.Println(err)
		return "", err
	}

	client := getDockerClient()
	if err = client.StartContainer(id, getHostConfig()); err != nil {
		// Silently fail, since it might be "zypper" not existing and we don't
		// want to add noise to the log.
		return id, err
	}
	resizeTty(id)

	sc := make(chan bool)

	if streaming {
		// setup logging
		rc, err := dockerClient.ContainerLogs(id, &dockerclient.LogOptions{
			Follow: true, // required to keep streaming
			Stdout: true,
			Stderr: true,
		})
		if err != nil {
			log.Println(err)
			return id, err
		}
		defer func() {
			if err := rc.Close(); err != nil {
				log.Print(err)
			}
		}()
		go func() {
			if _, err := io.Copy(os.Stdout, rc); err != nil {
				log.Print(err)
			}
			sc <- true
		}()
	}

	timeout := make(chan int)
	go func() {
		if !wait {
			time.Sleep(containerTimeout)
			timeout <- 1
		}
	}()

	select {
	case res := <-client.Wait(id):
		if streaming {
			<-sc
		}
		if res.Error != nil {
			return id, res.Error
		} else if res.ExitCode != 0 {
			return id, dockerError{res}
		}
	case <-timeout:
		return id, fmt.Errorf("Timed out when waiting for a container.\n")
	case <-killChannel:
		if err := client.KillContainer(id, "KILL"); err != nil {
			fmt.Println("Error while killing running container:", err)
		} else {
			removeContainer(id)
		}
		exitWithCode(1)
	}

	return id, nil
}

// Creates a container based on the given image. The given image string is just
// the ID of said image. The command specified is set as the entry point of the
// container.
// It returns the ID of the spawned container when successful, nil otherwise.
// The error is set accordingly when it's not possible to create the container.
// Note well: the container is not running at this time, it must be started via
// the `startContainer` function.
func createContainer(img string, cmd []string) (string, error) {
	client := getDockerClient()

	// First of all we create a container in which we will run the command.
	config := &dockerclient.ContainerConfig{
		Image:        img,
		Cmd:          cmd,
		Entrypoint:   []string{"/bin/sh", "-c"},
		AttachStdout: true,
		AttachStderr: true,
		// required to avoid garbage when cmd overwrites the terminal
		// like "zypper ref" does
		Tty: true,
	}
	id, err := client.CreateContainer(config, "")
	if err != nil {
		return "", err
	}
	return id, nil
}

// Safely remove the given container. It will deal with the error by logging
// it.
func removeContainer(id string) {
	client := getDockerClient()
	if err := client.RemoveContainer(id, true, true); err != nil {
		log.Println(err)
	}
}

// Create a Docker image from the container specified by containerId and
// returns the image ID of the created one.
// repo is the name of the reposity, hence it includes also the namespace (eg: suse/sle11sp3)
// tag is the version of the image (eg: 1.0.0)
func commitContainerToImage(containerId, repo, tag, comment, author string) (string, error) {
	client := getDockerClient()
	imageId, err := client.Commit(containerId, &dockerclient.ContainerConfig{}, repo, tag, comment, author)
	return imageId, err
}

// Spawns a container from the specified image, runs the specified command inside
// of it and commits the results to a new image.
// The name of the new image is specified via target_repo and target_tag.
// The container is always deleted.
// If something goes wrong an error message is returned.
// Returns the ID of the new image on success.
func runCommandAndCommitToImage(img, target_repo, target_tag, cmd, comment, author string) (string, error) {
	containerId, err := runCommandInContainer(img, []string{cmd}, true)
	if err != nil {
		return "", err
	}

	imageId, err := commitContainerToImage(containerId, target_repo, target_tag, comment, author)

	// always remove the container
	removeContainer(containerId)

	return imageId, err
}

// Looks for the specified running container and makes sure it's running either
// SUSE or openSUSE.
func checkContainerRunning(id string) (*dockerclient.Container, error) {
	client := getDockerClient()
	var container *dockerclient.Container

	containers, err := client.ListContainers(false, false, "")
	if err != nil {
		return nil, fmt.Errorf("Error while fetching running containers: %v", err)
	}

	for _, c := range containers {
		if id == c.Id {
			container = &c
			break
		}
		// look also for the short version of the container ID
		if len(id) >= 12 && strings.Index(c.Id, id) == 0 {
			container = &c
			break
		}
		// for some reason the daemon has all the names prefixed by "/"
		if arrayIncludeString(c.Names, "/"+id) {
			container = &c
			break
		}
	}

	if container == nil {
		return nil, fmt.Errorf("Cannot find running container: %s", id)
	}

	cache := getCacheFile()
	if !cache.isSUSE(container.Image) {
		return nil, fmt.Errorf(
			"The container %s is based on the Docker image %s which is not a SUSE system",
			id, container.Image)
	}

	return container, nil
}
<commit_msg>Increased the timeout for containers<commit_after>// Copyright (c) 2015 SUSE LLC. 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.

package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/SUSE/dockerclient"
	"github.com/docker/docker/pkg/tlsconfig"
)

// dockerError encapsulates a dockerclient.WaitResult that has an exit status
// different than 0. This is done this way because, for some commands, zypper
// might set an exit code different than 0, even if there was no error. For
// example, the patch-check command can set the exit code 100, to determine
// that there are patches to be installed. In this case, the caller can decide
// what to do depending on the error returned by zypper. Therefore, the caller
// of functions such as `startContainer` should only care about this type if
// the command being implemented has this kind of behavior.
type dockerError struct {
	dockerclient.WaitResult
}

func (de dockerError) Error() string {
	return fmt.Sprintf("Command exited with status %d", de.ExitCode)
}

// The DockerClient interface lists all the functions that we use from Docker clients. Take
// a look at http://godoc.org/github.com/samalba/dockerclient if you want to
// read the documentation for each function.
type DockerClient interface {
	ListImages(all bool, filter string, filters *dockerclient.ListFilter) ([]*dockerclient.Image, error)
	ListContainers(all bool, size bool, filters string) ([]dockerclient.Container, error)

	CreateContainer(config *dockerclient.ContainerConfig, name string) (string, error)
	StartContainer(id string, config *dockerclient.HostConfig) error
	RemoveContainer(id string, force, volume bool) error
	KillContainer(id, signal string) error
	ResizeContainer(id string, isExec bool, width, height int) error
	Commit(id string, c *dockerclient.ContainerConfig, repo, tag, comment, author string) (string, error)

	Wait(id string) <-chan dockerclient.WaitResult

	ContainerLogs(id string, options *dockerclient.LogOptions) (io.ReadCloser, error)
}

// This global variable holds the instance to the docker client.
var dockerClient DockerClient

const (
	// The path in which the Docker client is listening to.
	dockerSocket = "unix:///var/run/docker.sock"

	// The timeout in which the container is allowed to run a command as given
	// to the `startContainer` function.
	containerTimeout = 15 * time.Second

	// tls-related files, code taken from docker/common.go
	defaultTrustKeyFile = "key.json"
	defaultCaFile       = "ca.pem"
	defaultKeyFile      = "key.pem"
	defaultCertFile     = "cert.pem"
)

// Use this function to safely retrieve the singleton instance of the Docker
// client. In order to guarantee such safety, the instance has to be
// initialized when no goroutines are being executed concurrently (e.g. the
// `init` or the `main` function).
func getDockerClient() DockerClient {
	if dockerClient != nil {
		return dockerClient
	}

	// We can safely discard the error. The connection will be started
	// successfully because internally `NewDockerClientTimeout` will handle the
	// connection as a dial for the http package. Therefore, it won't fail even
	// if the given URL does not exist. This is ok, since this possible error
	// will appear later on with subsequent commands.
	//
	// The only time it will return an error will be if the given URL has a bad
	// format, which won't happen.

	daemonUrl := dockerSocket
	if os.Getenv("DOCKER_HOST") != "" {
		daemonUrl = os.Getenv("DOCKER_HOST")
	}

	tlsConfig, err := setupTlsConfig()
	if err != nil {
		panic(err)
	}

	dockerClient, _ = dockerclient.NewDockerClientTimeout(daemonUrl, tlsConfig,
		containerTimeout)
	return dockerClient
}

func setupTlsConfig() (*tls.Config, error) {
	if os.Getenv("DOCKER_TLS_VERIFY") == "" {
		return &tls.Config{}, nil
	}
	dockerCertPath := os.Getenv("DOCKER_CERT_PATH")

	var tlsOptions tlsconfig.Options
	tlsOptions.CAFile = filepath.Join(dockerCertPath, defaultCaFile)
	tlsOptions.CertFile = filepath.Join(dockerCertPath, defaultCertFile)
	tlsOptions.KeyFile = filepath.Join(dockerCertPath, defaultKeyFile)
	return tlsconfig.Client(tlsOptions)
}

// humanizeCommandError tries to print an explicit and useful message for a
// failing command inside of a docker container (based on the given image).
func humanizeCommandError(cmd, image string, err error) {
	var reason string

	switch err.(type) {
	case dockerError:
		de := err.(dockerError)
		if de.ExitCode == 127 {
			reason = "command not found"
		} else {
			reason = err.Error()
		}
	default:
		reason = err.Error()
	}

	msg := "Could not execute command '%s' successfully in image '%s': %v.\n"
	log.Printf(msg, cmd, image, reason)
}

// Looks for the specified command inside of a Docker image.
// The given image string is just the ID of said image.
// It returns true if the command was successful, false otherwise.
func checkCommandInImage(img, cmd string) bool {
	containerId, err := startContainer(img, []string{cmd}, false, false)

	defer removeContainer(containerId)

	if err != nil {
		humanizeCommandError(cmd, img, err)
		return false
	}
	return true
}

// runStreamedCommand is a convenient wrapper of the `runCommandInContainer`
// for functions that just need to run a command on streaming without the
// burden of removing the resulting container, etc.
//
// The image has to be provided, otherwise this function will exit with 1 as
// the status code and it will both log and print that no image was provided.
// The given command will be executed as "zypper ref && zypper <command>".
//
// If getError is set to false, then this function will always return nil.
// Otherwise, it will return the error as given by the `runCommandInContainer`
// function.
func runStreamedCommand(img, cmd string, getError bool) error {
	if img == "" {
		logAndFatalf("Error: no image name specified.\n")
		return nil
	}

	cmd = formatZypperCommand("ref", cmd)
	id, err := runCommandInContainer(img, []string{cmd}, true)
	removeContainer(id)

	if getError {
		return err
	}
	if err != nil {
		log.Printf("Error: %s\n", err)
		exitWithCode(1)
	}
	return nil
}

// Run the given command in a container based on the given image. The given
// image string is just the ID of said image.
// The STDOUT and STDERR of the container can be streamed to the host's STDOUT
// by setting the `streaming` parameter to true.
// It returns the ID of the container spawned from the image.
// Note well: the container is NOT deleted when the given command terminates.
func runCommandInContainer(img string, cmd []string, streaming bool) (string, error) {
	return startContainer(img, cmd, streaming, true)
}

// Get the host config to be used for starting containers.
func getHostConfig() *dockerclient.HostConfig {
	if currentContext == nil {
		return &dockerclient.HostConfig{}
	}
	return &dockerclient.HostConfig{ExtraHosts: currentContext.GlobalStringSlice("add-host")}
}

// Start a container from the specified image and then runs the given command
// inside of it. The given image string is just the ID of said image.
// The STDOUT and STDERR of the container can be streamed to the host's STDOUT
// by setting the `streaming` parameter to true.
// When `wait` is set to true the function will wait untill the container exits,
// otherwise it will timeout raising an error.
// Note well: the container is NOT deleted when the given command terminates.
// This is again up to the caller.
//
// It returns the ID of the container spawned from the image. The error
// returned can be of type dockerError. This only happens when the container
// has run normally (no signals, no timeout), but the exit code is not 0. Read
// the documentation on the `dockerError` command on why we do this.
func startContainer(img string, cmd []string, streaming, wait bool) (string, error) {
	id, err := createContainer(img, cmd)
	if err != nil {
		log.Println(err)
		return "", err
	}

	client := getDockerClient()
	if err = client.StartContainer(id, getHostConfig()); err != nil {
		// Silently fail, since it might be "zypper" not existing and we don't
		// want to add noise to the log.
		return id, err
	}
	resizeTty(id)

	sc := make(chan bool)

	if streaming {
		// setup logging
		rc, err := dockerClient.ContainerLogs(id, &dockerclient.LogOptions{
			Follow: true, // required to keep streaming
			Stdout: true,
			Stderr: true,
		})
		if err != nil {
			log.Println(err)
			return id, err
		}
		defer func() {
			if err := rc.Close(); err != nil {
				log.Print(err)
			}
		}()
		go func() {
			if _, err := io.Copy(os.Stdout, rc); err != nil {
				log.Print(err)
			}
			sc <- true
		}()
	}

	timeout := make(chan int)
	go func() {
		if !wait {
			time.Sleep(containerTimeout)
			timeout <- 1
		}
	}()

	select {
	case res := <-client.Wait(id):
		if streaming {
			<-sc
		}
		if res.Error != nil {
			return id, res.Error
		} else if res.ExitCode != 0 {
			return id, dockerError{res}
		}
	case <-timeout:
		return id, fmt.Errorf("Timed out when waiting for a container.\n")
	case <-killChannel:
		if err := client.KillContainer(id, "KILL"); err != nil {
			fmt.Println("Error while killing running container:", err)
		} else {
			removeContainer(id)
		}
		exitWithCode(1)
	}

	return id, nil
}

// Creates a container based on the given image. The given image string is just
// the ID of said image. The command specified is set as the entry point of the
// container.
// It returns the ID of the spawned container when successful, nil otherwise.
// The error is set accordingly when it's not possible to create the container.
// Note well: the container is not running at this time, it must be started via
// the `startContainer` function.
func createContainer(img string, cmd []string) (string, error) {
	client := getDockerClient()

	// First of all we create a container in which we will run the command.
	config := &dockerclient.ContainerConfig{
		Image:        img,
		Cmd:          cmd,
		Entrypoint:   []string{"/bin/sh", "-c"},
		AttachStdout: true,
		AttachStderr: true,
		// required to avoid garbage when cmd overwrites the terminal
		// like "zypper ref" does
		Tty: true,
	}
	id, err := client.CreateContainer(config, "")
	if err != nil {
		return "", err
	}
	return id, nil
}

// Safely remove the given container. It will deal with the error by logging
// it.
func removeContainer(id string) {
	client := getDockerClient()
	if err := client.RemoveContainer(id, true, true); err != nil {
		log.Println(err)
	}
}

// Create a Docker image from the container specified by containerId and
// returns the image ID of the created one.
// repo is the name of the reposity, hence it includes also the namespace (eg: suse/sle11sp3)
// tag is the version of the image (eg: 1.0.0)
func commitContainerToImage(containerId, repo, tag, comment, author string) (string, error) {
	client := getDockerClient()
	imageId, err := client.Commit(containerId, &dockerclient.ContainerConfig{}, repo, tag, comment, author)
	return imageId, err
}

// Spawns a container from the specified image, runs the specified command inside
// of it and commits the results to a new image.
// The name of the new image is specified via target_repo and target_tag.
// The container is always deleted.
// If something goes wrong an error message is returned.
// Returns the ID of the new image on success.
func runCommandAndCommitToImage(img, target_repo, target_tag, cmd, comment, author string) (string, error) {
	containerId, err := runCommandInContainer(img, []string{cmd}, true)
	if err != nil {
		return "", err
	}

	imageId, err := commitContainerToImage(containerId, target_repo, target_tag, comment, author)

	// always remove the container
	removeContainer(containerId)

	return imageId, err
}

// Looks for the specified running container and makes sure it's running either
// SUSE or openSUSE.
func checkContainerRunning(id string) (*dockerclient.Container, error) {
	client := getDockerClient()
	var container *dockerclient.Container

	containers, err := client.ListContainers(false, false, "")
	if err != nil {
		return nil, fmt.Errorf("Error while fetching running containers: %v", err)
	}

	for _, c := range containers {
		if id == c.Id {
			container = &c
			break
		}
		// look also for the short version of the container ID
		if len(id) >= 12 && strings.Index(c.Id, id) == 0 {
			container = &c
			break
		}
		// for some reason the daemon has all the names prefixed by "/"
		if arrayIncludeString(c.Names, "/"+id) {
			container = &c
			break
		}
	}

	if container == nil {
		return nil, fmt.Errorf("Cannot find running container: %s", id)
	}

	cache := getCacheFile()
	if !cache.isSUSE(container.Image) {
		return nil, fmt.Errorf(
			"The container %s is based on the Docker image %s which is not a SUSE system",
			id, container.Image)
	}

	return container, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"

	"golang.org/x/oauth2"
)

const (
	pathUploadStart = "%s/api/repos/%s/%s/upload/start"
	pathUploadFile  = "%s/api/repos/%s/%s/upload/file/%s/%s"
	pathUploadDone  = "%s/api/repos/%s/%s/upload/done/%s"
)

type UploadStart struct {
	SessionID string `json:"session_id"`
}

type client struct {
	client *http.Client
	base   string
}

func NewClient(uri string) *client {
	return &client{http.DefaultClient, uri}
}

func NewClientToken(uri, token string) *client {
	auther := oauth2.NewClient(oauth2.NoContext, StaticTokenSource(&oauth2.Token{AccessToken: token}))
	return &client{auther, uri}
}

func (c *client) UploadStart(owner, repo string) (*UploadStart, error) {
	out := new(UploadStart)
	uri := fmt.Sprintf(pathUploadStart, c.base, owner, repo)
	err := c.post(uri, nil, out)
	return out, err
}

func (c *client) UploadFile(owner, repo, filename, sessionID string, in io.Reader) error {
	uri := fmt.Sprintf(pathUploadFile, c.base, owner, repo, filename, sessionID)
	err := c.postRaw(uri, in, nil)
	return err
}

func (c *client) UploadDone(owner, repo, sessionID string) error {
	uri := fmt.Sprintf(pathUploadDone, c.base, owner, repo, sessionID)
	err := c.post(uri, nil, nil)
	return err
}

func (c *client) post(rawurl string, in, out interface{}) error {
	return c.do(rawurl, "POST", in, out)
}

func (c *client) postRaw(rawurl string, in, out interface{}) error {
	return c.doRaw(rawurl, "POST", in, out)
}

func (c *client) do(rawurl, method string, in, out interface{}) error {
	body, err := c.request(rawurl, method, in, out)
	if err != nil {
		return err
	}
	defer body.Close()

	if out != nil {
		return json.NewDecoder(body).Decode(out)
	}
	return nil
}

func (c *client) doRaw(rawurl, method string, in, out interface{}) error {
	uri, err := url.Parse(rawurl)
	if err != nil {
		return err
	}

	var buf io.Reader
	if in != nil {
		buf = in.(io.Reader)
	}

	req, err := http.NewRequest(method, uri.String(), buf)
	if err != nil {
		return err
	}

	if in != nil {
		req.Header.Set("Content-Type", "application/octet-stream")
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode > 208 {
		out, _ := ioutil.ReadAll(resp.Body)
		return errors.New(string(out))
	}

	if out != nil {
		return json.NewDecoder(resp.Body).Decode(out)
	}
	return nil
}

func (c *client) request(rawurl, method string, in, out interface{}) (io.ReadCloser, error) {
	uri, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}

	var buf io.ReadWriter
	if in != nil {
		buf = new(bytes.Buffer)
		err := json.NewEncoder(buf).Encode(in)
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(method, uri.String(), buf)
	if err != nil {
		return nil, err
	}
	if in != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode > 208 {
		defer resp.Body.Close()
		out, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("%d - %s", resp.StatusCode, string(out))
	}
	return resp.Body, nil
}
<commit_msg>Fix import<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"

	"golang.org/x/oauth2"
)

const (
	pathUploadStart = "%s/api/repos/%s/%s/upload/start"
	pathUploadFile  = "%s/api/repos/%s/%s/upload/file/%s/%s"
	pathUploadDone  = "%s/api/repos/%s/%s/upload/done/%s"
)

type UploadStart struct {
	SessionID string `json:"session_id"`
}

type client struct {
	client *http.Client
	base   string
}

func NewClient(uri string) *client {
	return &client{http.DefaultClient, uri}
}

func NewClientToken(uri, token string) *client {
	auther := oauth2.NewClient(
		oauth2.NoContext,
		oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: token},
		),
	)
	return &client{auther, uri}
}

func (c *client) UploadStart(owner, repo string) (*UploadStart, error) {
	out := new(UploadStart)
	uri := fmt.Sprintf(pathUploadStart, c.base, owner, repo)
	err := c.post(uri, nil, out)
	return out, err
}

func (c *client) UploadFile(owner, repo, filename, sessionID string, in io.Reader) error {
	uri := fmt.Sprintf(pathUploadFile, c.base, owner, repo, filename, sessionID)
	err := c.postRaw(uri, in, nil)
	return err
}

func (c *client) UploadDone(owner, repo, sessionID string) error {
	uri := fmt.Sprintf(pathUploadDone, c.base, owner, repo, sessionID)
	err := c.post(uri, nil, nil)
	return err
}

func (c *client) post(rawurl string, in, out interface{}) error {
	return c.do(rawurl, "POST", in, out)
}

func (c *client) postRaw(rawurl string, in, out interface{}) error {
	return c.doRaw(rawurl, "POST", in, out)
}

func (c *client) do(rawurl, method string, in, out interface{}) error {
	body, err := c.request(rawurl, method, in, out)
	if err != nil {
		return err
	}
	defer body.Close()

	if out != nil {
		return json.NewDecoder(body).Decode(out)
	}
	return nil
}

func (c *client) doRaw(rawurl, method string, in, out interface{}) error {
	uri, err := url.Parse(rawurl)
	if err != nil {
		return err
	}

	var buf io.Reader
	if in != nil {
		buf = in.(io.Reader)
	}

	req, err := http.NewRequest(method, uri.String(), buf)
	if err != nil {
		return err
	}

	if in != nil {
		req.Header.Set("Content-Type", "application/octet-stream")
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode > 208 {
		out, _ := ioutil.ReadAll(resp.Body)
		return errors.New(string(out))
	}

	if out != nil {
		return json.NewDecoder(resp.Body).Decode(out)
	}
	return nil
}

func (c *client) request(rawurl, method string, in, out interface{}) (io.ReadCloser, error) {
	uri, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}

	var buf io.ReadWriter
	if in != nil {
		buf = new(bytes.Buffer)
		err := json.NewEncoder(buf).Encode(in)
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(method, uri.String(), buf)
	if err != nil {
		return nil, err
	}
	if in != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode > 208 {
		defer resp.Body.Close()
		out, _ := ioutil.ReadAll(resp.Body)
		return nil, fmt.Errorf("%d - %s", resp.StatusCode, string(out))
	}
	return resp.Body, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
	"time"

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

const MSG_BUFFER int = 10

const HELP_TEXT string = `-> Available commands:
   /about
   /exit
   /help
   /list
   /nick $NAME
   /whois $NAME
`

const ABOUT_TEXT string = `-> ssh-chat is made by @shazow.

   It is a custom ssh server built in Go to serve a chat experience
   instead of a shell.

   Source: https://github.com/shazow/ssh-chat

   For more, visit shazow.net or follow at twitter.com/shazow
`

type Client struct {
	Server        *Server
	Conn          *ssh.ServerConn
	Msg           chan string
	Name          string
	Op            bool
	term          *terminal.Terminal
	termWidth     int
	termHeight    int
	silencedUntil time.Time
}

func NewClient(server *Server, conn *ssh.ServerConn) *Client {
	return &Client{
		Server: server,
		Conn:   conn,
		Name:   conn.User(),
		Msg:    make(chan string, MSG_BUFFER),
	}
}

func (c *Client) Write(msg string) {
	c.term.Write([]byte(msg + "\r\n"))
}

func (c *Client) WriteLines(msg []string) {
	for _, line := range msg {
		c.Write(line)
	}
}

func (c *Client) IsSilenced() bool {
	return c.silencedUntil.After(time.Now())
}

func (c *Client) Silence(d time.Duration) {
	c.silencedUntil = time.Now().Add(d)
}

func (c *Client) Resize(width int, height int) error {
	err := c.term.SetSize(width, height)
	if err != nil {
		logger.Errorf("Resize failed: %dx%d", width, height)
		return err
	}
	c.termWidth, c.termHeight = width, height
	return nil
}

func (c *Client) Rename(name string) {
	c.Name = name
	c.term.SetPrompt(fmt.Sprintf("[%s] ", name))
}

func (c *Client) handleShell(channel ssh.Channel) {
	defer channel.Close()

	go func() {
		for msg := range c.Msg {
			c.Write(msg)
		}
	}()

	for {
		line, err := c.term.ReadLine()
		if err != nil {
			break
		}

		parts := strings.SplitN(line, " ", 2)
		isCmd := strings.HasPrefix(parts[0], "/")

		if isCmd {
			switch parts[0] {
			case "/exit":
				channel.Close()
			case "/help":
				c.WriteLines(strings.Split(HELP_TEXT, "\n"))
			case "/about":
				c.WriteLines(strings.Split(ABOUT_TEXT, "\n"))
			case "/nick":
				if len(parts) == 2 {
					c.Server.Rename(c, parts[1])
				} else {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /nick $NAME")
				}
			case "/whois":
				if len(parts) == 2 {
					client := c.Server.Who(parts[1])
					c.Msg <- fmt.Sprintf("-> %s is %s via %s", client.Name, client.Conn.RemoteAddr(), client.Conn.ClientVersion())
				} else {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /whois $NAME")
				}
			case "/list":
				names := c.Server.List(nil)
				c.Msg <- fmt.Sprintf("-> %d connected: %s", len(names), strings.Join(names, ","))
			default:
				c.Msg <- fmt.Sprintf("-> Invalid command: %s", line)
			}
			continue
		}

		msg := fmt.Sprintf("%s: %s", c.Name, line)
		if c.IsSilenced() {
			c.Msg <- fmt.Sprintf("-> Message rejected, silenced.")
			continue
		}
		c.Server.Broadcast(msg, c)
	}

}

func (c *Client) handleChannels(channels <-chan ssh.NewChannel) {
	prompt := fmt.Sprintf("[%s] ", c.Name)

	for ch := range channels {
		if t := ch.ChannelType(); t != "session" {
			ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
			continue
		}

		channel, requests, err := ch.Accept()
		if err != nil {
			logger.Errorf("Could not accept channel: %v", err)
			continue
		}

		c.term = terminal.NewTerminal(channel, prompt)

		go func(in <-chan *ssh.Request) {
			defer channel.Close()
			hasShell := false
			for req := range in {
				var width, height int
				var ok bool

				switch req.Type {
				case "shell":
					if c.term != nil && !hasShell {
						go c.handleShell(channel)
						ok = true
						hasShell = true
					}
				case "pty-req":
					width, height, ok = parsePtyRequest(req.Payload)
					if ok {
						err := c.Resize(width, height)
						ok = err == nil
					}
				case "window-change":
					width, height, ok = parseWinchRequest(req.Payload)
					if ok {
						err := c.Resize(width, height)
						ok = err == nil
					}
				}

				if req.WantReply {
					req.Reply(ok, nil)
				}
			}
		}(requests)

		// We don't care about other channels?
		return
	}
}
<commit_msg>List formatting.<commit_after>package main

import (
	"fmt"
	"strings"
	"time"

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

const MSG_BUFFER int = 10

const HELP_TEXT string = `-> Available commands:
   /about
   /exit
   /help
   /list
   /nick $NAME
   /whois $NAME
`

const ABOUT_TEXT string = `-> ssh-chat is made by @shazow.

   It is a custom ssh server built in Go to serve a chat experience
   instead of a shell.

   Source: https://github.com/shazow/ssh-chat

   For more, visit shazow.net or follow at twitter.com/shazow
`

type Client struct {
	Server        *Server
	Conn          *ssh.ServerConn
	Msg           chan string
	Name          string
	Op            bool
	term          *terminal.Terminal
	termWidth     int
	termHeight    int
	silencedUntil time.Time
}

func NewClient(server *Server, conn *ssh.ServerConn) *Client {
	return &Client{
		Server: server,
		Conn:   conn,
		Name:   conn.User(),
		Msg:    make(chan string, MSG_BUFFER),
	}
}

func (c *Client) Write(msg string) {
	c.term.Write([]byte(msg + "\r\n"))
}

func (c *Client) WriteLines(msg []string) {
	for _, line := range msg {
		c.Write(line)
	}
}

func (c *Client) IsSilenced() bool {
	return c.silencedUntil.After(time.Now())
}

func (c *Client) Silence(d time.Duration) {
	c.silencedUntil = time.Now().Add(d)
}

func (c *Client) Resize(width int, height int) error {
	err := c.term.SetSize(width, height)
	if err != nil {
		logger.Errorf("Resize failed: %dx%d", width, height)
		return err
	}
	c.termWidth, c.termHeight = width, height
	return nil
}

func (c *Client) Rename(name string) {
	c.Name = name
	c.term.SetPrompt(fmt.Sprintf("[%s] ", name))
}

func (c *Client) handleShell(channel ssh.Channel) {
	defer channel.Close()

	go func() {
		for msg := range c.Msg {
			c.Write(msg)
		}
	}()

	for {
		line, err := c.term.ReadLine()
		if err != nil {
			break
		}

		parts := strings.SplitN(line, " ", 2)
		isCmd := strings.HasPrefix(parts[0], "/")

		if isCmd {
			switch parts[0] {
			case "/exit":
				channel.Close()
			case "/help":
				c.WriteLines(strings.Split(HELP_TEXT, "\n"))
			case "/about":
				c.WriteLines(strings.Split(ABOUT_TEXT, "\n"))
			case "/nick":
				if len(parts) == 2 {
					c.Server.Rename(c, parts[1])
				} else {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /nick $NAME")
				}
			case "/whois":
				if len(parts) == 2 {
					client := c.Server.Who(parts[1])
					c.Msg <- fmt.Sprintf("-> %s is %s via %s", client.Name, client.Conn.RemoteAddr(), client.Conn.ClientVersion())
				} else {
					c.Msg <- fmt.Sprintf("-> Missing $NAME from: /whois $NAME")
				}
			case "/list":
				names := c.Server.List(nil)
				c.Msg <- fmt.Sprintf("-> %d connected: %s", len(names), strings.Join(names, ", "))
			default:
				c.Msg <- fmt.Sprintf("-> Invalid command: %s", line)
			}
			continue
		}

		msg := fmt.Sprintf("%s: %s", c.Name, line)
		if c.IsSilenced() {
			c.Msg <- fmt.Sprintf("-> Message rejected, silenced.")
			continue
		}
		c.Server.Broadcast(msg, c)
	}

}

func (c *Client) handleChannels(channels <-chan ssh.NewChannel) {
	prompt := fmt.Sprintf("[%s] ", c.Name)

	for ch := range channels {
		if t := ch.ChannelType(); t != "session" {
			ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
			continue
		}

		channel, requests, err := ch.Accept()
		if err != nil {
			logger.Errorf("Could not accept channel: %v", err)
			continue
		}

		c.term = terminal.NewTerminal(channel, prompt)

		go func(in <-chan *ssh.Request) {
			defer channel.Close()
			hasShell := false
			for req := range in {
				var width, height int
				var ok bool

				switch req.Type {
				case "shell":
					if c.term != nil && !hasShell {
						go c.handleShell(channel)
						ok = true
						hasShell = true
					}
				case "pty-req":
					width, height, ok = parsePtyRequest(req.Payload)
					if ok {
						err := c.Resize(width, height)
						ok = err == nil
					}
				case "window-change":
					width, height, ok = parseWinchRequest(req.Payload)
					if ok {
						err := c.Resize(width, height)
						ok = err == nil
					}
				}

				if req.WantReply {
					req.Reply(ok, nil)
				}
			}
		}(requests)

		// We don't care about other channels?
		return
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/flynn/go-discoverd/discoverd"
	"log"
	"os"
)

type clientCmd struct {
	client *discoverd.Client
}

func (cmd *clientCmd) InitClient(silent bool) {
	client, err := discoverd.NewClient()
	if err != nil {
		if silent {
			os.Exit(1)
		}
		log.Fatal("Error making client: ", err.Error())
	}
	cmd.client = client
}
<commit_msg>Update discoverd usage<commit_after>package main

import (
	"github.com/flynn/go-discoverd"
	"log"
	"os"
)

type clientCmd struct {
	client *discoverd.Client
}

func (cmd *clientCmd) InitClient(silent bool) {
	client, err := discoverd.NewClient()
	if err != nil {
		if silent {
			os.Exit(1)
		}
		log.Fatal("Error making client: ", err.Error())
	}
	cmd.client = client
}
<|endoftext|>


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

package main

import (
	"fmt"
	"io"
	"io/ioutil"

	"bytes"
	"github.com/cheggaaa/pb"
	"github.com/minio-io/cli"
	"github.com/minio-io/iodine"
	"github.com/minio-io/minio/pkg/utils/log"
	"sync"
)

// doCopyCmd copies objects into and from a bucket or between buckets
func multiCopy(targetURLs []string, sourceURL string) (err error) {
	var targetBuffer bytes.Buffer
	fmt.Fprint(&targetBuffer, targetURLs)
	errParams := map[string]string{
		"targetURLs": targetBuffer.String(),
		"sourceURL":  sourceURL,
	}
	numTargets := len(targetURLs)

	// Parse URL to bucket and object names
	sourceBucket, sourceObject, err := url2Object(sourceURL)
	if err != nil {
		return iodine.New(err, errParams)
	}

	// Initialize a new client object for the source
	sourceClnt, err := getNewClient(globalDebugFlag, sourceURL)
	if err != nil {
		return iodine.New(err, errParams)
	}

	// Get a reader for the source object
	sourceReader, sourceSize, err := sourceClnt.Get(sourceBucket, sourceObject)
	if err != nil {
		return iodine.New(err, errParams)
	}

	targetReaders := make([]io.Reader, numTargets)
	targetWriters := make([]io.Writer, numTargets)
	doneCh := make(chan error)

	for i := 0; i < numTargets; i++ {
		targetReaders[i], targetWriters[i] = io.Pipe()
	}

	go func() {
		var bar *pb.ProgressBar
		if !globalQuietFlag {
			bar = startBar(sourceSize)
			bar.Start()
			sourceReader = ioutil.NopCloser(io.TeeReader(sourceReader, bar))
		}

		io.CopyN(io.MultiWriter(targetWriters...), sourceReader, sourceSize)
		if !globalQuietFlag {
			bar.Finish()
		}
	}()

	var wg sync.WaitGroup
	for i := 0; i < numTargets; i++ {
		targetBucket, targetObject, err := url2Object(targetURLs[i])

		if err != nil {
			return iodine.New(err, errParams)
		}

		targetClnt, err := getNewClient(globalDebugFlag, targetURLs[i])
		if err != nil {
			return iodine.New(err, errParams)
		}

		wg.Add(1)
		go func(index int) {
			defer wg.Done()
			// return err via external variable
			doneCh <- targetClnt.Put(targetBucket, targetObject, sourceSize, targetReaders[index])
			fmt.Println("Done:", targetReaders[index])
		}(i)
	}
	wg.Wait()
	go close(doneCh)

	for err := range doneCh {
		if err != nil {
			log.Println("Failed error:", iodine.New(err, nil))
		}
	}

	return nil
}

// doCopyCmd copies objects into and from a bucket or between buckets
func doCopyCmd(ctx *cli.Context) {
	if len(ctx.Args()) < 2 {
		cli.ShowCommandHelpAndExit(ctx, "cp", 1) // last argument is exit code
	}
	// Convert arguments to URLs: expand alias, fix format...
	urlList, err := parseURLs(ctx)
	if err != nil {
		log.Fatalln(iodine.New(err, nil))
		return
	}
	sourceURL := urlList[0]   // First arg is source
	targetURLs := urlList[1:] // 1 or more targets

	err = multiCopy(targetURLs, sourceURL)
	if err != nil {
		log.Fatalln(iodine.New(err, nil))
		return
	}

	return
}
<commit_msg>When using waitGroup avoid channel, they seem to hang<commit_after>/*
 * Minimalist Object Storage, (C) 2014,2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"fmt"
	"io"
	"io/ioutil"

	"bytes"
	"sync"

	"github.com/cheggaaa/pb"
	"github.com/minio-io/cli"
	"github.com/minio-io/iodine"
	"github.com/minio-io/minio/pkg/utils/log"
)

// doCopyCmd copies objects into and from a bucket or between buckets
func multiCopy(targetURLs []string, sourceURL string) (err error) {
	var targetBuffer bytes.Buffer
	fmt.Fprint(&targetBuffer, targetURLs)
	errParams := map[string]string{
		"targetURLs": targetBuffer.String(),
		"sourceURL":  sourceURL,
	}
	numTargets := len(targetURLs)

	// Parse URL to bucket and object names
	sourceBucket, sourceObject, err := url2Object(sourceURL)
	if err != nil {
		return iodine.New(err, errParams)
	}

	// Initialize a new client object for the source
	sourceClnt, err := getNewClient(globalDebugFlag, sourceURL)
	if err != nil {
		return iodine.New(err, errParams)
	}

	// Get a reader for the source object
	sourceReader, sourceSize, err := sourceClnt.Get(sourceBucket, sourceObject)
	if err != nil {
		return iodine.New(err, errParams)
	}

	targetReaders := make([]io.Reader, numTargets)
	targetWriters := make([]io.Writer, numTargets)

	for i := 0; i < numTargets; i++ {
		targetReaders[i], targetWriters[i] = io.Pipe()
	}

	go func() {
		var bar *pb.ProgressBar
		if !globalQuietFlag {
			bar = startBar(sourceSize)
			bar.Start()
			sourceReader = ioutil.NopCloser(io.TeeReader(sourceReader, bar))
		}

		io.CopyN(io.MultiWriter(targetWriters...), sourceReader, sourceSize)
		if !globalQuietFlag {
			bar.Finish()
		}
	}()

	var wg sync.WaitGroup
	for i := 0; i < numTargets; i++ {
		targetBucket, targetObject, err := url2Object(targetURLs[i])

		if err != nil {
			return iodine.New(err, errParams)
		}

		targetClnt, err := getNewClient(globalDebugFlag, targetURLs[i])
		if err != nil {
			return iodine.New(err, errParams)
		}

		wg.Add(1)
		go func(index int) {
			defer wg.Done()
			if err := targetClnt.Put(targetBucket, targetObject, sourceSize, targetReaders[index]); err != nil {
				fatal(iodine.New(err, nil).Error())
			}
			info(fmt.Sprintf("Done: %s", targetURLs[index]))
		}(i)
	}
	wg.Wait()
	return nil
}

// doCopyCmd copies objects into and from a bucket or between buckets
func doCopyCmd(ctx *cli.Context) {
	if len(ctx.Args()) < 2 {
		cli.ShowCommandHelpAndExit(ctx, "cp", 1) // last argument is exit code
	}
	// Convert arguments to URLs: expand alias, fix format...
	urlList, err := parseURLs(ctx)
	if err != nil {
		log.Fatalln(iodine.New(err, nil))
		return
	}
	sourceURL := urlList[0]   // First arg is source
	targetURLs := urlList[1:] // 1 or more targets

	err = multiCopy(targetURLs, sourceURL)
	if err != nil {
		log.Fatalln(iodine.New(err, nil))
		return
	}

	return
}
<|endoftext|>


<commit_before>// Copyright 2014 Hajime Hoshi
//
// 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 ebiten

import (
	"math"
)

// ColorMDim is a dimension of a ColorM.
const ColorMDim = 5

// A ColorM represents a matrix to transform coloring when rendering an image.
//
// A ColorM is applied to the source alpha color
// while an Image's pixels' format is alpha premultiplied.
// Before applying a matrix, a color is un-multiplied, and after applying the matrix,
// the color is multiplied again.
//
// The initial value is identity.
type ColorM struct {
	initialized bool
	es          [ColorMDim - 1][ColorMDim]float64
}

func (c *ColorM) dim() int {
	return ColorMDim
}

func (c *ColorM) initialize() {
	c.initialized = true
	c.es[0][0] = 1
	c.es[1][1] = 1
	c.es[2][2] = 1
	c.es[3][3] = 1
}

// Element returns a value of a matrix at (i, j).
func (c *ColorM) Element(i, j int) float64 {
	if !c.initialized {
		if i == j {
			return 1
		}
		return 0
	}
	return c.es[i][j]
}

// Concat multiplies a color matrix with the other color matrix.
// This is same as muptiplying the matrix other and the matrix c in this order.
func (c *ColorM) Concat(other ColorM) {
	if !c.initialized {
		c.initialize()
	}
	result := ColorM{}
	mul(&other, c, &result)
	*c = result
}

// Add adds a color matrix with the other color matrix.
func (c *ColorM) Add(other ColorM) {
	if !c.initialized {
		c.initialize()
	}
	result := ColorM{}
	add(&other, c, &result)
	*c = result
}

// Scale scales the matrix by (x, y).
func (c *ColorM) Scale(r, g, b, a float64) {
	if !c.initialized {
		c.initialize()
	}
	for i := 0; i < ColorMDim; i++ {
		c.es[0][i] *= r
		c.es[1][i] *= g
		c.es[2][i] *= b
		c.es[3][i] *= a
	}
}

// Translate translates the matrix by (x, y).
func (c *ColorM) Translate(r, g, b, a float64) {
	if !c.initialized {
		c.initialize()
	}
	c.es[0][4] += r
	c.es[1][4] += g
	c.es[2][4] += b
	c.es[3][4] += a
}

// RotateHue rotates the hue.
func (c *ColorM) RotateHue(theta float64) {
	sin, cos := math.Sincos(theta)
	v1 := cos + (1.0-cos)/3.0
	v2 := (1.0/3.0)*(1.0-cos) - math.Sqrt(1.0/3.0)*sin
	v3 := (1.0/3.0)*(1.0-cos) + math.Sqrt(1.0/3.0)*sin
	c.Concat(ColorM{
		initialized: true,
		es: [ColorMDim - 1][ColorMDim]float64{
			{v1, v2, v3, 0, 0},
			{v3, v1, v2, 0, 0},
			{v2, v3, v1, 0, 0},
			{0, 0, 0, 1, 0},
		},
	})
}

// SetElement sets an element at (i, j).
func (c *ColorM) SetElement(i, j int, element float64) {
	if !c.initialized {
		c.initialize()
	}
	c.es[i][j] = element
}

// Monochrome returns a color matrix to make an image monochrome.
func Monochrome() ColorM {
	const r = 6968.0 / 32768.0
	const g = 23434.0 / 32768.0
	const b = 2366.0 / 32768.0
	return ColorM{
		initialized: true,
		es: [ColorMDim - 1][ColorMDim]float64{
			{r, g, b, 0, 0},
			{r, g, b, 0, 0},
			{r, g, b, 0, 0},
			{0, 0, 0, 1, 0},
		},
	}
}

// Deprecated as of 1.2.0-alpha. Use Scale instead.
func ScaleColor(r, g, b, a float64) ColorM {
	return ColorM{
		initialized: true,
		es: [ColorMDim - 1][ColorMDim]float64{
			{r, 0, 0, 0, 0},
			{0, g, 0, 0, 0},
			{0, 0, b, 0, 0},
			{0, 0, 0, a, 0},
		},
	}
}

// Deprecated as of 1.2.0-alpha. Use Translate instead.
func TranslateColor(r, g, b, a float64) ColorM {
	return ColorM{
		initialized: true,
		es: [ColorMDim - 1][ColorMDim]float64{
			{1, 0, 0, 0, r},
			{0, 1, 0, 0, g},
			{0, 0, 1, 0, b},
			{0, 0, 0, 1, a},
		},
	}
}

// Deprecated as of 1.2.0-alpha. Use RotateHue member function instead.
func RotateHue(theta float64) ColorM {
	c := ColorM{}
	c.RotateHue(theta)
	return c
}
<commit_msg>graphics: Bug fix: comment<commit_after>// Copyright 2014 Hajime Hoshi
//
// 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 ebiten

import (
	"math"
)

// ColorMDim is a dimension of a ColorM.
const ColorMDim = 5

// A ColorM represents a matrix to transform coloring when rendering an image.
//
// A ColorM is applied to the source alpha color
// while an Image's pixels' format is alpha premultiplied.
// Before applying a matrix, a color is un-multiplied, and after applying the matrix,
// the color is multiplied again.
//
// The initial value is identity.
type ColorM struct {
	initialized bool
	es          [ColorMDim - 1][ColorMDim]float64
}

func (c *ColorM) dim() int {
	return ColorMDim
}

func (c *ColorM) initialize() {
	c.initialized = true
	c.es[0][0] = 1
	c.es[1][1] = 1
	c.es[2][2] = 1
	c.es[3][3] = 1
}

// Element returns a value of a matrix at (i, j).
func (c *ColorM) Element(i, j int) float64 {
	if !c.initialized {
		if i == j {
			return 1
		}
		return 0
	}
	return c.es[i][j]
}

// Concat multiplies a color matrix with the other color matrix.
// This is same as muptiplying the matrix other and the matrix c in this order.
func (c *ColorM) Concat(other ColorM) {
	if !c.initialized {
		c.initialize()
	}
	result := ColorM{}
	mul(&other, c, &result)
	*c = result
}

// Add adds a color matrix with the other color matrix.
func (c *ColorM) Add(other ColorM) {
	if !c.initialized {
		c.initialize()
	}
	result := ColorM{}
	add(&other, c, &result)
	*c = result
}

// Scale scales the matrix by (r, g, b, a).
func (c *ColorM) Scale(r, g, b, a float64) {
	if !c.initialized {
		c.initialize()
	}
	for i := 0; i < ColorMDim; i++ {
		c.es[0][i] *= r
		c.es[1][i] *= g
		c.es[2][i] *= b
		c.es[3][i] *= a
	}
}

// Translate translates the matrix by (r, g, b, a).
func (c *ColorM) Translate(r, g, b, a float64) {
	if !c.initialized {
		c.initialize()
	}
	c.es[0][4] += r
	c.es[1][4] += g
	c.es[2][4] += b
	c.es[3][4] += a
}

// RotateHue rotates the hue.
func (c *ColorM) RotateHue(theta float64) {
	sin, cos := math.Sincos(theta)
	v1 := cos + (1.0-cos)/3.0
	v2 := (1.0/3.0)*(1.0-cos) - math.Sqrt(1.0/3.0)*sin
	v3 := (1.0/3.0)*(1.0-cos) + math.Sqrt(1.0/3.0)*sin
	c.Concat(ColorM{
		initialized: true,
		es: [ColorMDim - 1][ColorMDim]float64{
			{v1, v2, v3, 0, 0},
			{v3, v1, v2, 0, 0},
			{v2, v3, v1, 0, 0},
			{0, 0, 0, 1, 0},
		},
	})
}

// SetElement sets an element at (i, j).
func (c *ColorM) SetElement(i, j int, element float64) {
	if !c.initialized {
		c.initialize()
	}
	c.es[i][j] = element
}

// Monochrome returns a color matrix to make an image monochrome.
func Monochrome() ColorM {
	const r = 6968.0 / 32768.0
	const g = 23434.0 / 32768.0
	const b = 2366.0 / 32768.0
	return ColorM{
		initialized: true,
		es: [ColorMDim - 1][ColorMDim]float64{
			{r, g, b, 0, 0},
			{r, g, b, 0, 0},
			{r, g, b, 0, 0},
			{0, 0, 0, 1, 0},
		},
	}
}

// Deprecated as of 1.2.0-alpha. Use Scale instead.
func ScaleColor(r, g, b, a float64) ColorM {
	return ColorM{
		initialized: true,
		es: [ColorMDim - 1][ColorMDim]float64{
			{r, 0, 0, 0, 0},
			{0, g, 0, 0, 0},
			{0, 0, b, 0, 0},
			{0, 0, 0, a, 0},
		},
	}
}

// Deprecated as of 1.2.0-alpha. Use Translate instead.
func TranslateColor(r, g, b, a float64) ColorM {
	return ColorM{
		initialized: true,
		es: [ColorMDim - 1][ColorMDim]float64{
			{1, 0, 0, 0, r},
			{0, 1, 0, 0, g},
			{0, 0, 1, 0, b},
			{0, 0, 0, 1, a},
		},
	}
}

// Deprecated as of 1.2.0-alpha. Use RotateHue member function instead.
func RotateHue(theta float64) ColorM {
	c := ColorM{}
	c.RotateHue(theta)
	return c
}
<|endoftext|>


<commit_before>// Copyright 2016 Nikugame. All Rights Reserved.

package ShortMessagingService

import (
	"bufio"
	"crypto/md5"
	"encoding/hex"
	"io"
	"math/rand"
	"os"
	"strings"
	"time"
)

//LoadConfigure load configure file
func LoadConfigure(filename string) (map[string]map[string]string, error) {

	result := make(map[string]map[string]string)

	file, err := os.Open(filename)
	if err != nil {
		return result, err
	}
	defer file.Close()

	section := "default"
	m := make(map[string]string)
	buf := bufio.NewReader(file)
	for {
		line, _, err := buf.ReadLine()
		if err == io.EOF {
			break
		}
		if len(line) == 0 {
			continue
		}
		l := strings.TrimSpace(string(line))
		if strings.HasPrefix(l, "[") && strings.HasSuffix(l, "]") {
			l := strings.TrimSpace(l)
			section = l[1 : len(l)-1]
			m = make(map[string]string)
			continue
		}
		if strings.HasPrefix(l, "#") || strings.HasPrefix(l, ";") {
			continue
		}
		parts := strings.SplitN(l, "=", 2)
		// fmt.Printf("configure value : %s = %s \n", parts[0], parts[1])
		name := strings.ToLower(strings.TrimSpace(parts[0]))
		value := strings.TrimSpace(parts[1])
		if strings.HasPrefix(value, "\"") {
			// fmt.Printf("has \" : xx%sxx \n", value)
			value = value[1 : len(value)-1]
		}
		m[name] = value
		result[section] = m
	}

	return result, nil
}

//MD5 md5 function
func MD5(str string) string {
	ctx := md5.New()
	ctx.Write([]byte(str))
	s := ctx.Sum(nil)
	return hex.EncodeToString(s)
}

//RandomString build random string
func RandomString(length int, character ...string) string {
	random := strings.Join([]string(character), "")
	bytes := []byte(random)
	res := []byte{}
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < length; i++ {
		res = append(res, bytes[r.Intn(len(bytes))])
	}
	return string(res)
}

const (
	//NUMBER The pure digital
	NUMBER = "0123456789"
	//LOWCHARACTER lower-case letters
	LOWCHARACTER = "abcdefghijklmnopqrstuvwxyz"
	//UPCHARACTER capital letter
	UPCHARACTER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
<commit_msg>fixbug configure not support capital<commit_after>// Copyright 2016 Nikugame. All Rights Reserved.

package ShortMessagingService

import (
	"bufio"
	"crypto/md5"
	"encoding/hex"
	"io"
	"math/rand"
	"os"
	"strings"
	"time"
)

//LoadConfigure load configure file
func LoadConfigure(filename string) (map[string]map[string]string, error) {

	result := make(map[string]map[string]string)

	file, err := os.Open(filename)
	if err != nil {
		return result, err
	}
	defer file.Close()

	section := "default"
	m := make(map[string]string)
	buf := bufio.NewReader(file)
	for {
		line, _, err := buf.ReadLine()
		if err == io.EOF {
			break
		}
		if len(line) == 0 {
			continue
		}
		l := strings.TrimSpace(string(line))
		if strings.HasPrefix(l, "[") && strings.HasSuffix(l, "]") {
			l := strings.TrimSpace(l)
			section = strings.ToLower(l[1 : len(l)-1])
			m = make(map[string]string)
			continue
		}
		if strings.HasPrefix(l, "#") || strings.HasPrefix(l, ";") {
			continue
		}
		parts := strings.SplitN(l, "=", 2)
		// fmt.Printf("configure value : %s = %s \n", parts[0], parts[1])
		name := strings.ToLower(strings.TrimSpace(parts[0]))
		value := strings.TrimSpace(parts[1])
		if strings.HasPrefix(value, "\"") {
			// fmt.Printf("has \" : xx%sxx \n", value)
			value = value[1 : len(value)-1]
		}
		m[name] = value
		result[section] = m
	}

	return result, nil
}

//MD5 md5 function
func MD5(str string) string {
	ctx := md5.New()
	ctx.Write([]byte(str))
	s := ctx.Sum(nil)
	return hex.EncodeToString(s)
}

//RandomString build random string
func RandomString(length int, character ...string) string {
	random := strings.Join([]string(character), "")
	bytes := []byte(random)
	res := []byte{}
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := 0; i < length; i++ {
		res = append(res, bytes[r.Intn(len(bytes))])
	}
	return string(res)
}

const (
	//NUMBER The pure digital
	NUMBER = "0123456789"
	//LOWCHARACTER lower-case letters
	LOWCHARACTER = "abcdefghijklmnopqrstuvwxyz"
	//UPCHARACTER capital letter
	UPCHARACTER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
<|endoftext|>


<commit_before>package git

import (
	"container/list"
)

// Commit represents a git commit.
type Commit struct {
	Tree
	Id            sha1 // The id of this commit object
	Author        *Signature
	Committer     *Signature
	CommitMessage string

	parents []sha1 // sha1 strings
}

// Return the commit message. Same as retrieving CommitMessage directly.
func (c *Commit) Message() string {
	return c.CommitMessage
}

// Return parent number n (0-based index)
func (c *Commit) Parent(n int) (*Commit, error) {
	id, err := c.ParentId(n)
	if err != nil {
		return nil, err
	}
	parent, err := c.repo.getCommit(id)
	if err != nil {
		return nil, err
	}
	return parent, nil
}

// Return oid of the parent number n (0-based index). Return nil if no such parent exists.
func (c *Commit) ParentId(n int) (id sha1, err error) {
	if n >= len(c.parents) {
		err = IdNotExist
		return
	}
	return c.parents[n], nil
}

// Return the number of parents of the commit. 0 if this is the
// root commit, otherwise 1,2,...
func (c *Commit) ParentCount() int {
	return len(c.parents)
}

// Return oid of the (root) tree of this commit.
func (c *Commit) TreeId() sha1 {
	return c.Tree.Id
}

func (c *Commit) CommitsBefore() (*list.List, error) {
	return c.repo.getCommitsBefore(c.Id)
}

func (c *Commit) CommitsBeforeUntil(commitId string) (*list.List, error) {
	ec, err := c.repo.GetCommit(commitId)
	if err != nil {
		return nil, err
	}
	return c.repo.CommitsBetween(c, ec)
}

func (c *Commit) CommitsCount() (int, error) {
	return c.repo.commitsCount(c.Id)
}

func (c *Commit) SearchCommits(keyword string) (*list.List, error) {
	return c.repo.searchCommits(c.Id, keyword)
}

func (c *Commit) CommitsByRange(page int) (*list.List, error) {
	return c.repo.commitsByRange(c.Id, page)
}

func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) {
	return c.repo.getCommitOfRelPath(c.Id, relPath)
}
<commit_msg>Add commit summary method<commit_after>package git

import (
	"container/list"
	"strings"
)

// Commit represents a git commit.
type Commit struct {
	Tree
	Id            sha1 // The id of this commit object
	Author        *Signature
	Committer     *Signature
	CommitMessage string

	parents []sha1 // sha1 strings
}

func (c *Commit) Summary() string {
	return strings.Split(c.CommitMessage, "\n")[0]
}

// Return the commit message. Same as retrieving CommitMessage directly.
func (c *Commit) Message() string {
	return c.CommitMessage
}

// Return parent number n (0-based index)
func (c *Commit) Parent(n int) (*Commit, error) {
	id, err := c.ParentId(n)
	if err != nil {
		return nil, err
	}
	parent, err := c.repo.getCommit(id)
	if err != nil {
		return nil, err
	}
	return parent, nil
}

// Return oid of the parent number n (0-based index). Return nil if no such parent exists.
func (c *Commit) ParentId(n int) (id sha1, err error) {
	if n >= len(c.parents) {
		err = IdNotExist
		return
	}
	return c.parents[n], nil
}

// Return the number of parents of the commit. 0 if this is the
// root commit, otherwise 1,2,...
func (c *Commit) ParentCount() int {
	return len(c.parents)
}

// Return oid of the (root) tree of this commit.
func (c *Commit) TreeId() sha1 {
	return c.Tree.Id
}

func (c *Commit) CommitsBefore() (*list.List, error) {
	return c.repo.getCommitsBefore(c.Id)
}

func (c *Commit) CommitsBeforeUntil(commitId string) (*list.List, error) {
	ec, err := c.repo.GetCommit(commitId)
	if err != nil {
		return nil, err
	}
	return c.repo.CommitsBetween(c, ec)
}

func (c *Commit) CommitsCount() (int, error) {
	return c.repo.commitsCount(c.Id)
}

func (c *Commit) SearchCommits(keyword string) (*list.List, error) {
	return c.repo.searchCommits(c.Id, keyword)
}

func (c *Commit) CommitsByRange(page int) (*list.List, error) {
	return c.repo.commitsByRange(c.Id, page)
}

func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) {
	return c.repo.getCommitOfRelPath(c.Id, relPath)
}
<|endoftext|>


<commit_before>package git

/*
#include <git2.h>

extern int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr);
*/
import "C"

import (
	"runtime"
	"unsafe"
)

// Commit
type Commit struct {
	Object
	cast_ptr *C.git_commit
}

func (c Commit) Message() string {
	return C.GoString(C.git_commit_message(c.cast_ptr))
}

func (c Commit) RawMessage() string {
	return C.GoString(C.git_commit_message_raw(c.cast_ptr))
}

func (c Commit) Summary() string {
	return C.GoString(C.git_commit_summary(c.cast_ptr))
}

func (c Commit) Tree() (*Tree, error) {
	var ptr *C.git_tree

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	err := C.git_commit_tree(&ptr, c.cast_ptr)
	if err < 0 {
		return nil, MakeGitError(err)
	}

	return allocTree(ptr, c.repo), nil
}

func (c Commit) TreeId() *Oid {
	return newOidFromC(C.git_commit_tree_id(c.cast_ptr))
}

func (c Commit) Author() *Signature {
	cast_ptr := C.git_commit_author(c.cast_ptr)
	return newSignatureFromC(cast_ptr)
}

func (c Commit) Committer() *Signature {
	cast_ptr := C.git_commit_committer(c.cast_ptr)
	return newSignatureFromC(cast_ptr)
}

func (c *Commit) Parent(n uint) *Commit {
	var cobj *C.git_commit
	ret := C.git_commit_parent(&cobj, c.cast_ptr, C.uint(n))
	if ret != 0 {
		return nil
	}

	return allocCommit(cobj, c.repo)
}

func (c *Commit) ParentId(n uint) *Oid {
	return newOidFromC(C.git_commit_parent_id(c.cast_ptr, C.uint(n)))
}

func (c *Commit) ParentCount() uint {
	return uint(C.git_commit_parentcount(c.cast_ptr))
}

func (c *Commit) Amend(refname string, author, committer *Signature, message string, tree *Tree) (*Oid, error) {
	var cref *C.char
	if refname == "" {
		cref = nil
	} else {
		cref = C.CString(refname)
		defer C.free(unsafe.Pointer(cref))
	}

	cmsg := C.CString(message)
	defer C.free(unsafe.Pointer(cmsg))

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	authorSig, err := author.toC()
	if err != nil {
		return nil, err
	}
	defer C.git_signature_free(authorSig)

	committerSig, err := committer.toC()
	if err != nil {
		return nil, err
	}
	defer C.git_signature_free(committerSig)

	oid := new(Oid)

	cerr := C.git_commit_amend(oid.toC(), c.cast_ptr, cref, authorSig, committerSig, nil, cmsg, tree.cast_ptr)
	if cerr < 0 {
		return nil, MakeGitError(cerr)
	}

	return oid, nil
}
<commit_msg>Added Commit.ExtractSignature to wrap git_commit_extract_signature<commit_after>package git

/*
#include <git2.h>

extern int _go_git_treewalk(git_tree *tree, git_treewalk_mode mode, void *ptr);
*/
import "C"

import (
	"runtime"
	"unsafe"
)

// Commit
type Commit struct {
	Object
	cast_ptr *C.git_commit
}

func (c Commit) Message() string {
	return C.GoString(C.git_commit_message(c.cast_ptr))
}

func (c Commit) RawMessage() string {
	return C.GoString(C.git_commit_message_raw(c.cast_ptr))
}

func (c Commit) ExtractSignature() (string, string, error) {

	var c_signed C.git_buf
	defer C.git_buf_free(&c_signed)

	var c_signature C.git_buf
	defer C.git_buf_free(&c_signature)

	oid := c.Id()

	repo := C.git_commit_owner(c.cast_ptr)
	ret := C.git_commit_extract_signature(&c_signature, &c_signed, repo, oid.toC(), nil)

	if ret < 0 {
		return "", "", MakeGitError(ret)
	} else {
		return C.GoString(c_signature.ptr), C.GoString(c_signed.ptr), nil
	}

}

func (c Commit) Summary() string {
	return C.GoString(C.git_commit_summary(c.cast_ptr))
}

func (c Commit) Tree() (*Tree, error) {
	var ptr *C.git_tree

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	err := C.git_commit_tree(&ptr, c.cast_ptr)
	if err < 0 {
		return nil, MakeGitError(err)
	}

	return allocTree(ptr, c.repo), nil
}

func (c Commit) TreeId() *Oid {
	return newOidFromC(C.git_commit_tree_id(c.cast_ptr))
}

func (c Commit) Author() *Signature {
	cast_ptr := C.git_commit_author(c.cast_ptr)
	return newSignatureFromC(cast_ptr)
}

func (c Commit) Committer() *Signature {
	cast_ptr := C.git_commit_committer(c.cast_ptr)
	return newSignatureFromC(cast_ptr)
}

func (c *Commit) Parent(n uint) *Commit {
	var cobj *C.git_commit
	ret := C.git_commit_parent(&cobj, c.cast_ptr, C.uint(n))
	if ret != 0 {
		return nil
	}

	return allocCommit(cobj, c.repo)
}

func (c *Commit) ParentId(n uint) *Oid {
	return newOidFromC(C.git_commit_parent_id(c.cast_ptr, C.uint(n)))
}

func (c *Commit) ParentCount() uint {
	return uint(C.git_commit_parentcount(c.cast_ptr))
}

func (c *Commit) Amend(refname string, author, committer *Signature, message string, tree *Tree) (*Oid, error) {
	var cref *C.char
	if refname == "" {
		cref = nil
	} else {
		cref = C.CString(refname)
		defer C.free(unsafe.Pointer(cref))
	}

	cmsg := C.CString(message)
	defer C.free(unsafe.Pointer(cmsg))

	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	authorSig, err := author.toC()
	if err != nil {
		return nil, err
	}
	defer C.git_signature_free(authorSig)

	committerSig, err := committer.toC()
	if err != nil {
		return nil, err
	}
	defer C.git_signature_free(committerSig)

	oid := new(Oid)

	cerr := C.git_commit_amend(oid.toC(), c.cast_ptr, cref, authorSig, committerSig, nil, cmsg, tree.cast_ptr)
	if cerr < 0 {
		return nil, MakeGitError(cerr)
	}

	return oid, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/golock"
	"github.com/heroku/heroku-cli/gode"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Version  string     `json:"version"`
	Topics   TopicSet   `json:"topics"`
	Topic    *Topic     `json:"topic"`
	Commands CommandSet `json:"commands"`
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	gode.SetRootPath(AppDir())
	setup, err := gode.IsSetup()
	WarnIfError(err)
	if !setup {
		if err := gode.Setup(); err != nil {
			panic(err)
		}
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins map[string]*Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		if plugin.Topic != nil {
			cli.AddTopic(plugin.Topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
	sort.Sort(cli.Topics)
	sort.Sort(cli.Commands)
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		action("Installing plugin "+name, "done", func() {
			ExitIfError(installPlugins(name))
		})
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := pluginPath(name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin, err := ParsePlugin(name)
		ExitIfError(err)
		if name != plugin.Name {
			path = newPath
			newPath = pluginPath(plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("Symlinked", plugin.Name)
		AddPluginsToCache(plugin)
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if !contains(PluginNames(), name) {
			ExitIfError(errors.New(name + " is not installed"))
		}
		Errf("Uninstalling plugin %s...", name)
		ExitIfError(gode.RemovePackages(name))
		RemovePluginFromCache(name)
		Errln(" done")
	},
}

var pluginsListCmd = &Command{
	Topic:            "plugins",
	Hidden:           true,
	Description:      "Lists installed plugins",
	DisableAnalytics: true,
	Help: `
Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		SetupBuiltinPlugins()
		var plugins []string
		for _, plugin := range GetPlugins() {
			symlinked := ""
			if isPluginSymlinked(plugin.Name) {
				symlinked = " (symlinked)"
			}
			plugins = append(plugins, fmt.Sprintf("%s %s %s", plugin.Name, plugin.Version, symlinked))
		}
		sort.Strings(plugins)
		for _, plugin := range plugins {
			Println(plugin)
		}
	},
}

func runFn(plugin *Plugin, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		readLockPlugin(plugin.Name)
		ctx.Dev = isPluginSymlinked(plugin.Name)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		title, _ := json.Marshal(processTitle(ctx))
		script := fmt.Sprintf(`'use strict';
var moduleName = '%s';
var moduleVersion = '%s';
var topic = '%s';
var command = '%s';
process.title = %s;
var ctx = %s;
ctx.version = ctx.version + ' ' + moduleName + '/' + moduleVersion + ' node-' + process.version;
var logPath = %s;
process.chdir(ctx.cwd);
if (!ctx.dev) {
	process.on('uncaughtException', function (err) {
		// ignore EPIPE errors (usually from piping to head)
		if (err.code === "EPIPE") return;
		console.error(' !   Error in ' + moduleName + ':')
		console.error(' !   ' + err.message || err);
		if (err.stack) {
			var fs = require('fs');
			var log = function (line) {
				var d = new Date().toISOString()
				.replace(/T/, ' ')
				.replace(/-/g, '/')
				.replace(/\..+/, '');
				fs.appendFileSync(logPath, d + ' ' + line + '\n');
			}
			log('Error during ' + topic + ':' + command);
			log(err.stack);
			console.error(' !   See ' + logPath + ' for more info.');
		}
		process.exit(1);
	});
}
if (command === '') { command = null }
var module = require(moduleName);
var cmd = module.commands.filter(function (c) {
	return c.topic === topic && c.command == command;
})[0];
cmd.run(ctx);
`, plugin.Name, plugin.Version, topic, command, string(title), ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSigint = true

		currentAnalyticsCommand.Plugin = plugin.Name
		currentAnalyticsCommand.Version = plugin.Version
		currentAnalyticsCommand.Language = fmt.Sprintf("node/%s", gode.NodeVersion)

		cmd, done := gode.RunScript(script)
		defer done()
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if ctx.Flags["debugger"] == true {
			cmd = gode.DebugScript(script)
		}
		err = cmd.Run()
		Exit(getExitCode(err))
	}
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case nil:
		return 0
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

// ParsePlugin requires the plugin's node module
// to get the commands and metadata
func ParsePlugin(name string) (*Plugin, error) {
	script := `
	var plugin = require('` + name + `');
	if (!plugin.commands) throw new Error('Contains no commands. Is this a real plugin?');
	var pjson  = require('` + name + `/package.json');

	plugin.name    = pjson.name;
	plugin.version = pjson.version;

	console.log(JSON.stringify(plugin))`
	cmd, done := gode.RunScript(script)
	cmd.Stderr = Stderr
	output, err := cmd.Output()
	done()
	if err != nil {
		// try again but this time grab stdout and stderr
		cmd, done := gode.RunScript(script)
		output, _ := cmd.CombinedOutput()
		done()
		return nil, fmt.Errorf("Error reading plugin: %s\n%s\n%s", name, err, output)
	}
	var plugin Plugin
	err = json.Unmarshal([]byte(output), &plugin)
	if err != nil {
		return nil, fmt.Errorf("Error parsing plugin: %s\n%s\n%s", name, err, string(output))
	}
	for _, command := range plugin.Commands {
		if command == nil {
			continue
		}
		command.Plugin = plugin.Name
		command.Help = strings.TrimSpace(command.Help)
	}
	return &plugin, nil
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() map[string]*Plugin {
	plugins := FetchPluginCache()
	for name, plugin := range plugins {
		if plugin == nil || !pluginExists(name) || plugin.Commands.Len() == 0 {
			delete(plugins, name)
		} else {
			for _, command := range plugin.Commands {
				command.Run = runFn(plugin, command.Topic, command.Command)
			}
		}
	}
	return plugins
}

// PluginNames lists all the plugin names
func PluginNames() []string {
	plugins := FetchPluginCache()
	names := make([]string, 0, len(plugins))
	for _, plugin := range plugins {
		if plugin != nil && pluginExists(plugin.Name) && len(plugin.Commands) > 0 {
			names = append(names, plugin.Name)
		}
	}
	return names
}

// PluginNamesNotSymlinked returns all the plugins that are not symlinked
func PluginNamesNotSymlinked() []string {
	a := PluginNames()
	b := make([]string, 0, len(a))
	for _, plugin := range a {
		if !isPluginSymlinked(plugin) {
			b = append(b, plugin)
		}
	}
	return b
}

func isPluginSymlinked(plugin string) bool {
	path := filepath.Join(AppDir(), "node_modules", plugin)
	fi, err := os.Lstat(path)
	if err != nil {
		return false
	}
	return fi.Mode()&os.ModeSymlink != 0
}

// SetupBuiltinPlugins ensures all the builtinPlugins are installed
func SetupBuiltinPlugins() {
	pluginNames := difference(BuiltinPlugins, PluginNames())
	if len(pluginNames) == 0 {
		return
	}
	action("heroku-cli: Installing core plugins", "done", func() {
		if err := installPlugins(pluginNames...); err != nil {
			// retry once
			WarnIfError(gode.RemovePackages(pluginNames...))
			WarnIfError(gode.ClearCache())
			Err("\rheroku-cli: Installing core plugins (retrying)...")
			ExitIfError(installPlugins(pluginNames...))
		}
	})
}

func difference(a, b []string) []string {
	res := make([]string, 0, len(a))
	for _, aa := range a {
		if !contains(b, aa) {
			res = append(res, aa)
		}
	}
	return res
}

func contains(arr []string, s string) bool {
	for _, a := range arr {
		if a == s {
			return true
		}
	}
	return false
}

func installPlugins(names ...string) error {
	for _, name := range names {
		lockPlugin(name)
	}
	defer func() {
		for _, name := range names {
			unlockPlugin(name)
		}
	}()
	err := gode.InstallPackages(names...)
	if err != nil {
		return err
	}
	plugins := make([]*Plugin, len(names))
	for i, name := range names {
		plugin, err := ParsePlugin(name)
		if err != nil {
			return err
		}
		plugins[i] = plugin
	}
	AddPluginsToCache(plugins...)
	return nil
}

func pluginExists(plugin string) bool {
	exists, _ := fileExists(pluginPath(plugin))
	return exists
}

// directory location of plugin
func pluginPath(plugin string) string {
	return filepath.Join(AppDir(), "node_modules", plugin)
}

// lock a plugin for reading
func readLockPlugin(name string) {
	lockfile := updateLockPath + "." + name
	locked, err := golock.IsLocked(lockfile)
	LogIfError(err)
	if locked {
		lockPlugin(name)
		unlockPlugin(name)
	}
}

// lock a plugin for writing
func lockPlugin(name string) {
	LogIfError(golock.Lock(updateLockPath + "." + name))
}

// unlock a plugin
func unlockPlugin(name string) {
	LogIfError(golock.Unlock(updateLockPath + "." + name))
}
<commit_msg>added plugin install retry step<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/golock"
	"github.com/heroku/heroku-cli/gode"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Version  string     `json:"version"`
	Topics   TopicSet   `json:"topics"`
	Topic    *Topic     `json:"topic"`
	Commands CommandSet `json:"commands"`
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	gode.SetRootPath(AppDir())
	setup, err := gode.IsSetup()
	WarnIfError(err)
	if !setup {
		if err := gode.Setup(); err != nil {
			panic(err)
		}
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins map[string]*Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		if plugin.Topic != nil {
			cli.AddTopic(plugin.Topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
	sort.Sort(cli.Topics)
	sort.Sort(cli.Commands)
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		action("Installing plugin "+name, "done", func() {
			ExitIfError(installPlugins(name))
		})
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := pluginPath(name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin, err := ParsePlugin(name)
		ExitIfError(err)
		if name != plugin.Name {
			path = newPath
			newPath = pluginPath(plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("Symlinked", plugin.Name)
		AddPluginsToCache(plugin)
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if !contains(PluginNames(), name) {
			ExitIfError(errors.New(name + " is not installed"))
		}
		Errf("Uninstalling plugin %s...", name)
		ExitIfError(gode.RemovePackages(name))
		RemovePluginFromCache(name)
		Errln(" done")
	},
}

var pluginsListCmd = &Command{
	Topic:            "plugins",
	Hidden:           true,
	Description:      "Lists installed plugins",
	DisableAnalytics: true,
	Help: `
Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		SetupBuiltinPlugins()
		var plugins []string
		for _, plugin := range GetPlugins() {
			symlinked := ""
			if isPluginSymlinked(plugin.Name) {
				symlinked = " (symlinked)"
			}
			plugins = append(plugins, fmt.Sprintf("%s %s %s", plugin.Name, plugin.Version, symlinked))
		}
		sort.Strings(plugins)
		for _, plugin := range plugins {
			Println(plugin)
		}
	},
}

func runFn(plugin *Plugin, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		readLockPlugin(plugin.Name)
		ctx.Dev = isPluginSymlinked(plugin.Name)
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		title, _ := json.Marshal(processTitle(ctx))
		script := fmt.Sprintf(`'use strict';
var moduleName = '%s';
var moduleVersion = '%s';
var topic = '%s';
var command = '%s';
process.title = %s;
var ctx = %s;
ctx.version = ctx.version + ' ' + moduleName + '/' + moduleVersion + ' node-' + process.version;
var logPath = %s;
process.chdir(ctx.cwd);
if (!ctx.dev) {
	process.on('uncaughtException', function (err) {
		// ignore EPIPE errors (usually from piping to head)
		if (err.code === "EPIPE") return;
		console.error(' !   Error in ' + moduleName + ':')
		console.error(' !   ' + err.message || err);
		if (err.stack) {
			var fs = require('fs');
			var log = function (line) {
				var d = new Date().toISOString()
				.replace(/T/, ' ')
				.replace(/-/g, '/')
				.replace(/\..+/, '');
				fs.appendFileSync(logPath, d + ' ' + line + '\n');
			}
			log('Error during ' + topic + ':' + command);
			log(err.stack);
			console.error(' !   See ' + logPath + ' for more info.');
		}
		process.exit(1);
	});
}
if (command === '') { command = null }
var module = require(moduleName);
var cmd = module.commands.filter(function (c) {
	return c.topic === topic && c.command == command;
})[0];
cmd.run(ctx);
`, plugin.Name, plugin.Version, topic, command, string(title), ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSigint = true

		currentAnalyticsCommand.Plugin = plugin.Name
		currentAnalyticsCommand.Version = plugin.Version
		currentAnalyticsCommand.Language = fmt.Sprintf("node/%s", gode.NodeVersion)

		cmd, done := gode.RunScript(script)
		defer done()
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if ctx.Flags["debugger"] == true {
			cmd = gode.DebugScript(script)
		}
		err = cmd.Run()
		Exit(getExitCode(err))
	}
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case nil:
		return 0
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

var pluginInstallRetrying = false

// ParsePlugin requires the plugin's node module
// to get the commands and metadata
func ParsePlugin(name string) (*Plugin, error) {
	script := `
	var plugin = require('` + name + `');
	if (!plugin.commands) throw new Error('Contains no commands. Is this a real plugin?');
	var pjson  = require('` + name + `/package.json');

	plugin.name    = pjson.name;
	plugin.version = pjson.version;

	console.log(JSON.stringify(plugin))`
	cmd, done := gode.RunScript(script)
	cmd.Stderr = Stderr
	output, err := cmd.Output()
	done()
	if err != nil {
		// try again but this time grab stdout and stderr
		cmd, done := gode.RunScript(script)
		output, _ := cmd.CombinedOutput()
		done()
		if !pluginInstallRetrying && strings.Contains(string(output), "Error: Cannot find module") {
			pluginInstallRetrying = true
			Warn("Failed to install " + name + ". Retrying...")
			WarnIfError(gode.RemovePackages(name))
			WarnIfError(gode.ClearCache())
			WarnIfError(gode.InstallPackages(name))
			return ParsePlugin(name)
		}
		return nil, fmt.Errorf("Error reading plugin: %s\n%s\n%s", name, err, output)
	}
	var plugin Plugin
	err = json.Unmarshal([]byte(output), &plugin)
	if err != nil {
		return nil, fmt.Errorf("Error parsing plugin: %s\n%s\n%s", name, err, string(output))
	}
	for _, command := range plugin.Commands {
		if command == nil {
			continue
		}
		command.Plugin = plugin.Name
		command.Help = strings.TrimSpace(command.Help)
	}
	return &plugin, nil
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() map[string]*Plugin {
	plugins := FetchPluginCache()
	for name, plugin := range plugins {
		if plugin == nil || !pluginExists(name) || plugin.Commands.Len() == 0 {
			delete(plugins, name)
		} else {
			for _, command := range plugin.Commands {
				command.Run = runFn(plugin, command.Topic, command.Command)
			}
		}
	}
	return plugins
}

// PluginNames lists all the plugin names
func PluginNames() []string {
	plugins := FetchPluginCache()
	names := make([]string, 0, len(plugins))
	for _, plugin := range plugins {
		if plugin != nil && pluginExists(plugin.Name) && len(plugin.Commands) > 0 {
			names = append(names, plugin.Name)
		}
	}
	return names
}

// PluginNamesNotSymlinked returns all the plugins that are not symlinked
func PluginNamesNotSymlinked() []string {
	a := PluginNames()
	b := make([]string, 0, len(a))
	for _, plugin := range a {
		if !isPluginSymlinked(plugin) {
			b = append(b, plugin)
		}
	}
	return b
}

func isPluginSymlinked(plugin string) bool {
	path := filepath.Join(AppDir(), "node_modules", plugin)
	fi, err := os.Lstat(path)
	if err != nil {
		return false
	}
	return fi.Mode()&os.ModeSymlink != 0
}

// SetupBuiltinPlugins ensures all the builtinPlugins are installed
func SetupBuiltinPlugins() {
	pluginNames := difference(BuiltinPlugins, PluginNames())
	if len(pluginNames) == 0 {
		return
	}
	action("heroku-cli: Installing core plugins", "done", func() {
		if err := installPlugins(pluginNames...); err != nil {
			// retry once
			WarnIfError(gode.RemovePackages(pluginNames...))
			WarnIfError(gode.ClearCache())
			Err("\rheroku-cli: Installing core plugins (retrying)...")
			ExitIfError(installPlugins(pluginNames...))
		}
	})
}

func difference(a, b []string) []string {
	res := make([]string, 0, len(a))
	for _, aa := range a {
		if !contains(b, aa) {
			res = append(res, aa)
		}
	}
	return res
}

func contains(arr []string, s string) bool {
	for _, a := range arr {
		if a == s {
			return true
		}
	}
	return false
}

func installPlugins(names ...string) error {
	for _, name := range names {
		lockPlugin(name)
	}
	defer func() {
		for _, name := range names {
			unlockPlugin(name)
		}
	}()
	err := gode.InstallPackages(names...)
	if err != nil {
		return err
	}
	plugins := make([]*Plugin, len(names))
	for i, name := range names {
		plugin, err := ParsePlugin(name)
		if err != nil {
			return err
		}
		plugins[i] = plugin
	}
	AddPluginsToCache(plugins...)
	return nil
}

func pluginExists(plugin string) bool {
	exists, _ := fileExists(pluginPath(plugin))
	return exists
}

// directory location of plugin
func pluginPath(plugin string) string {
	return filepath.Join(AppDir(), "node_modules", plugin)
}

// lock a plugin for reading
func readLockPlugin(name string) {
	lockfile := updateLockPath + "." + name
	locked, err := golock.IsLocked(lockfile)
	LogIfError(err)
	if locked {
		lockPlugin(name)
		unlockPlugin(name)
	}
}

// lock a plugin for writing
func lockPlugin(name string) {
	LogIfError(golock.Lock(updateLockPath + "." + name))
}

// unlock a plugin
func unlockPlugin(name string) {
	LogIfError(golock.Unlock(updateLockPath + "." + name))
}
<|endoftext|>