<commit_before>// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package promise

import (
	"context"
	"sync"

	"github.com/cilium/cilium/pkg/lock"
)

// A promise for a future value.
type Promise[T any] interface {
	// Await blocks until the value is resolved or rejected.
	Await(context.Context) (T, error)
}

// Resolver can resolve or reject a promise.
// These methods are separate from 'Promise' to make it clear where the promise is resolved
// from.
type Resolver[T any] interface {
	// Resolve a promise. Unblocks all Await()s. Future calls of Await()
	// return the resolved value immediately.
	//
	// Only the first call to resolve (or reject) has an effect and
	// further calls are ignored.
	Resolve(T)

	// Reject a promise with an error.
	Reject(error)
}

// New creates a new promise for value T.
// Returns a resolver and the promise.
func New[T any]() (Resolver[T], Promise[T]) {
	promise := &promise[T]{}
	promise.cond = sync.NewCond(promise)
	return promise, promise
}

const (
	promiseUnresolved = iota
	promiseResolved
	promiseRejected
)

type promise[T any] struct {
	lock.Mutex
	cond  *sync.Cond
	state int
	value T
	err   error
}

func (p *promise[T]) Resolve(value T) {
	p.Lock()
	defer p.Unlock()
	if p.state != promiseUnresolved {
		return
	}
	p.state = promiseResolved
	p.value = value
	p.cond.Broadcast()
}

func (p *promise[T]) Reject(err error) {
	p.Lock()
	defer p.Unlock()
	if p.state != promiseUnresolved {
		return
	}
	p.state = promiseRejected
	p.err = err
	p.cond.Broadcast()
}

// Await blocks until the promise has been resolved, rejected or context cancelled.
func (p *promise[T]) Await(ctx context.Context) (value T, err error) {
	// Fork off a goroutine to wait for cancellation and wake up.
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	go func() {
		<-ctx.Done()
		p.cond.Broadcast()
	}()

	p.Lock()
	defer p.Unlock()

	// Wait until the promise is resolved or context cancelled.
	for p.state == promiseUnresolved && (ctx == nil || ctx.Err() == nil) {
		p.cond.Wait()
	}

	if ctx.Err() != nil {
		err = ctx.Err()
	} else if p.state == promiseResolved {
		value = p.value
	} else {
		err = p.err
	}
	return
}

type wrappedPromise[T any] func(context.Context) (T, error)

func (await wrappedPromise[T]) Await(ctx context.Context) (T, error) {
	return await(ctx)
}

// Map transforms the value of a promise with the provided function.
func Map[A, B any](p Promise[A], transform func(A) B) Promise[B] {
	return wrappedPromise[B](func(ctx context.Context) (out B, err error) {
		v, err := p.Await(ctx)
		if err != nil {
			return out, err
		}
		return transform(v), nil
	})
}
<commit_msg>promise: Document the Resolve/Reject functions<commit_after>// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package promise

import (
	"context"
	"sync"

	"github.com/cilium/cilium/pkg/lock"
)

// A promise for a future value.
type Promise[T any] interface {
	// Await blocks until the value is resolved or rejected.
	Await(context.Context) (T, error)
}

// Resolver can resolve or reject a promise.
// These methods are separate from 'Promise' to make it clear where the promise is resolved
// from.
type Resolver[T any] interface {
	// Resolve a promise. Unblocks all Await()s. Future calls of Await()
	// return the resolved value immediately.
	//
	// Only the first call to resolve (or reject) has an effect and
	// further calls are ignored.
	Resolve(T)

	// Reject a promise with an error.
	Reject(error)
}

// New creates a new promise for value T.
// Returns a resolver and the promise.
func New[T any]() (Resolver[T], Promise[T]) {
	promise := &promise[T]{}
	promise.cond = sync.NewCond(promise)
	return promise, promise
}

const (
	promiseUnresolved = iota
	promiseResolved
	promiseRejected
)

type promise[T any] struct {
	lock.Mutex
	cond  *sync.Cond
	state int
	value T
	err   error
}

// Resolve informs all other codepaths who are Await()ing on the received
// promise that T is now successfully initialized and available for usage.
//
// Initialization logic for T should either call Resolve() or Reject(), and
// must not call these functions more than once.
func (p *promise[T]) Resolve(value T) {
	p.Lock()
	defer p.Unlock()
	if p.state != promiseUnresolved {
		return
	}
	p.state = promiseResolved
	p.value = value
	p.cond.Broadcast()
}

// Reject informs all other codepaths who are Await()ing on the received
// promise that T could not be initialized and cannot be used to due the
// specified error reason.
//
// Initialization logic for T should either call Resolve() or Reject(), and
// must not call these functions more than once.
func (p *promise[T]) Reject(err error) {
	p.Lock()
	defer p.Unlock()
	if p.state != promiseUnresolved {
		return
	}
	p.state = promiseRejected
	p.err = err
	p.cond.Broadcast()
}

// Await blocks until the promise has been resolved, rejected or context cancelled.
func (p *promise[T]) Await(ctx context.Context) (value T, err error) {
	// Fork off a goroutine to wait for cancellation and wake up.
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	go func() {
		<-ctx.Done()
		p.cond.Broadcast()
	}()

	p.Lock()
	defer p.Unlock()

	// Wait until the promise is resolved or context cancelled.
	for p.state == promiseUnresolved && (ctx == nil || ctx.Err() == nil) {
		p.cond.Wait()
	}

	if ctx.Err() != nil {
		err = ctx.Err()
	} else if p.state == promiseResolved {
		value = p.value
	} else {
		err = p.err
	}
	return
}

type wrappedPromise[T any] func(context.Context) (T, error)

func (await wrappedPromise[T]) Await(ctx context.Context) (T, error) {
	return await(ctx)
}

// Map transforms the value of a promise with the provided function.
func Map[A, B any](p Promise[A], transform func(A) B) Promise[B] {
	return wrappedPromise[B](func(ctx context.Context) (out B, err error) {
		v, err := p.Await(ctx)
		if err != nil {
			return out, err
		}
		return transform(v), nil
	})
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 Pani Networks
// 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 commands

import (
	"encoding/json"
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/romana/core/common/api"

	"github.com/go-resty/resty"
	cli "github.com/spf13/cobra"
	config "github.com/spf13/viper"
	"net/http"
)

// hostCmd represents the host commands
var hostCmd = &cli.Command{
	Use:   "host [add|show|list|remove]",
	Short: "Add, Remove or Show hosts for romana services.",
	Long: `Add, Remove or Show hosts for romana services.

host requires a subcommand, e.g. ` + "`romana host add`." + `

For more information, please check http://romana.io
`,
}

func init() {
	hostCmd.AddCommand(hostAddCmd)
	hostCmd.AddCommand(hostShowCmd)
	hostCmd.AddCommand(hostListCmd)
	hostCmd.AddCommand(hostRemoveCmd)
}

var hostAddCmd = &cli.Command{
	Use:          "add [hostname][hostip][(optional)romana cidr][(optional)agent port]",
	Short:        "Add a new host.",
	Long:         `Add a new host.`,
	RunE:         hostAdd,
	SilenceUsage: true,
}

var hostShowCmd = &cli.Command{
	Use:          "show [hostname1][hostname2]...",
	Short:        "Show details for a specific host.",
	Long:         `Show details for a specific host.`,
	RunE:         hostShow,
	SilenceUsage: true,
}

var hostListCmd = &cli.Command{
	Use:          "list",
	Short:        "List all hosts.",
	Long:         `List all hosts.`,
	RunE:         hostList,
	SilenceUsage: true,
}

var hostRemoveCmd = &cli.Command{
	Use:          "remove [hostname|hostip]",
	Short:        "Remove a host.",
	Long:         `Remove a host.`,
	RunE:         hostRemove,
	SilenceUsage: true,
}

func hostAdd(cmd *cli.Command, args []string) error {
	fmt.Println("Unimplemented: Add host/s.")
	return nil
}

func hostShow(cmd *cli.Command, args []string) error {
	fmt.Println("Unimplemented: Show host details.")
	return nil
}

func hostList(cmd *cli.Command, args []string) error {
	rootURL := config.GetString("RootURL")
	resp, err := resty.R().Get(rootURL + "/hosts")
	if err != nil {
		return err
	}

	if config.GetString("Format") == "json" {
		JSONFormat(resp.Body(), os.Stdout)
	} else {
		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 0, 8, 0, '\t', 0)

		if resp.StatusCode() == http.StatusOK {
			hosts := []api.Host{}
			fmt.Println("Host List")
			fmt.Fprintln(w,
				"Host IP\t",
				"Agent Port\t",
				"Romana CIDR\t")
			for _, host := range hosts {
				fmt.Fprintln(w, host.IP, "\t",
					host.AgentPort, "\t",
					host.RomanaIp, "\t")
			}
		} else {
			var e Error
			json.Unmarshal(resp.Body(), &e)

			fmt.Println("Host Error")
			fmt.Fprintln(w, "Fields\t", e.Fields)
			fmt.Fprintln(w, "Message\t", e.Message)
			fmt.Fprintln(w, "Status\t", resp.StatusCode())
		}
		w.Flush()
	}

	return nil
}

func hostRemove(cmd *cli.Command, args []string) error {
	fmt.Println("Unimplemented: Remove a host.")
	return nil
}
<commit_msg>cli: change host command docs to reflect latest changes.<commit_after>// Copyright (c) 2016 Pani Networks
// 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 commands

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"text/tabwriter"

	"github.com/romana/core/common/api"

	"github.com/go-resty/resty"
	cli "github.com/spf13/cobra"
	config "github.com/spf13/viper"
)

// hostCmd represents the host commands
var hostCmd = &cli.Command{
	Use:   "host [add|show|list|remove]",
	Short: "Add, Remove or Show hosts for romana services.",
	Long: `Add, Remove or Show hosts for romana services.

host requires a subcommand, e.g. ` + "`romana host add`." + `

For more information, please check http://romana.io
`,
}

func init() {
	hostCmd.AddCommand(hostAddCmd)
	hostCmd.AddCommand(hostShowCmd)
	hostCmd.AddCommand(hostListCmd)
	hostCmd.AddCommand(hostRemoveCmd)
}

var hostAddCmd = &cli.Command{
	Use:          "add [hostip][(optional)romana cidr][(optional)agent port]",
	Short:        "Add a new host.",
	Long:         `Add a new host.`,
	RunE:         hostAdd,
	SilenceUsage: true,
}

var hostShowCmd = &cli.Command{
	Use:          "show [hostip1][hostip2]...",
	Short:        "Show details for a specific host.",
	Long:         `Show details for a specific host.`,
	RunE:         hostShow,
	SilenceUsage: true,
}

var hostListCmd = &cli.Command{
	Use:          "list",
	Short:        "List all hosts.",
	Long:         `List all hosts.`,
	RunE:         hostList,
	SilenceUsage: true,
}

var hostRemoveCmd = &cli.Command{
	Use:          "remove [hostip]",
	Short:        "Remove a host.",
	Long:         `Remove a host.`,
	RunE:         hostRemove,
	SilenceUsage: true,
}

func hostAdd(cmd *cli.Command, args []string) error {
	fmt.Println("Unimplemented: Add host/s.")
	return nil
}

func hostShow(cmd *cli.Command, args []string) error {
	fmt.Println("Unimplemented: Show host details.")
	return nil
}

func hostList(cmd *cli.Command, args []string) error {
	rootURL := config.GetString("RootURL")
	resp, err := resty.R().Get(rootURL + "/hosts")
	if err != nil {
		return err
	}

	if config.GetString("Format") == "json" {
		JSONFormat(resp.Body(), os.Stdout)
	} else {
		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 0, 8, 0, '\t', 0)

		if resp.StatusCode() == http.StatusOK {
			hosts := []api.Host{}
			fmt.Println("Host List")
			fmt.Fprintln(w,
				"Host IP\t",
				"Agent Port\t",
				"Romana CIDR\t")
			for _, host := range hosts {
				fmt.Fprintln(w, host.IP, "\t",
					host.AgentPort, "\t",
					host.RomanaIp, "\t")
			}
		} else {
			var e Error
			json.Unmarshal(resp.Body(), &e)

			fmt.Println("Host Error")
			fmt.Fprintln(w, "Fields\t", e.Fields)
			fmt.Fprintln(w, "Message\t", e.Message)
			fmt.Fprintln(w, "Status\t", resp.StatusCode())
		}
		w.Flush()
	}

	return nil
}

func hostRemove(cmd *cli.Command, args []string) error {
	fmt.Println("Unimplemented: Remove a host.")
	return nil
}
<|endoftext|>


<commit_before>package spec

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/pkg/api/v1"
)

// MaintenanceWindow describes the time window when the operator is allowed to do maintenance on a cluster.
type MaintenanceWindow struct {
	Everyday  bool
	Weekday   time.Weekday
	StartTime time.Time // Start time
	EndTime   time.Time // End time
}

// Volume describes a single volume in the manifest.
type Volume struct {
	Size         string `json:"size"`
	StorageClass string `json:"storageClass"`
}

// PostgresqlParam describes PostgreSQL version and pairs of configuration parameter name - values.
type PostgresqlParam struct {
	PgVersion  string            `json:"version"`
	Parameters map[string]string `json:"parameters"`
}

// ResourceDescription describes CPU and memory resources defined for a cluster.
type ResourceDescription struct {
	CPU    string `json:"cpu"`
	Memory string `json:"memory"`
}

// Resources describes requests and limits for the cluster resouces.
type Resources struct {
	ResourceRequest ResourceDescription `json:"requests,omitempty"`
	ResourceLimits  ResourceDescription `json:"limits,omitempty"`
}

// Patroni contains Patroni-specific configuration
type Patroni struct {
	InitDB               map[string]string `json:"initdb"`
	PgHba                []string          `json:"pg_hba"`
	TTL                  uint32            `json:"ttl"`
	LoopWait             uint32            `json:"loop_wait"`
	RetryTimeout         uint32            `json:"retry_timeout"`
	MaximumLagOnFailover float32           `json:"maximum_lag_on_failover"` // float32 because https://github.com/kubernetes/kubernetes/issues/30213
}

// CloneDescription describes which cluster the new should clone and up to which point in time
type CloneDescription struct {
	ClusterName  string `json:"cluster,omitempty"`
	Uid          string `json:"uid,omitempty"`
	EndTimestamp string `json:"timestamp,omitempty"`
}

type UserFlags []string

// PostgresStatus contains status of the PostgreSQL cluster (running, creation failed etc.)
type PostgresStatus string

// possible values for PostgreSQL cluster statuses
const (
	ClusterStatusUnknown      PostgresStatus = ""
	ClusterStatusCreating     PostgresStatus = "Creating"
	ClusterStatusUpdating     PostgresStatus = "Updating"
	ClusterStatusUpdateFailed PostgresStatus = "UpdateFailed"
	ClusterStatusSyncFailed   PostgresStatus = "SyncFailed"
	ClusterStatusAddFailed    PostgresStatus = "CreateFailed"
	ClusterStatusRunning      PostgresStatus = "Running"
	ClusterStatusInvalid      PostgresStatus = "Invalid"
)

// Postgresql defines PostgreSQL Custom Resource Definition Object.
type Postgresql struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata"`

	Spec   PostgresSpec   `json:"spec"`
	Status PostgresStatus `json:"status,omitempty"`
	Error  error          `json:"-"`
}

// PostgresSpec defines the specification for the PostgreSQL TPR.
type PostgresSpec struct {
	PostgresqlParam `json:"postgresql"`
	Volume          `json:"volume,omitempty"`
	Patroni         `json:"patroni,omitempty"`
	Resources       `json:"resources,omitempty"`

	TeamID              string   `json:"teamId"`
	AllowedSourceRanges []string `json:"allowedSourceRanges"`
	DockerImage         string   `json:"dockerImage,omitempty"`
	// EnableLoadBalancer  is a pointer, since it is important to know if that parameters is omitted from the manifest
	UseLoadBalancer     *bool                `json:"useLoadBalancer,omitempty"`
	ReplicaLoadBalancer bool                 `json:"replicaLoadBalancer,omitempty"`
	NumberOfInstances   int32                `json:"numberOfInstances"`
	Users               map[string]UserFlags `json:"users"`
	MaintenanceWindows  []MaintenanceWindow  `json:"maintenanceWindows,omitempty"`
	Clone               CloneDescription     `json:"clone"`
	ClusterName         string               `json:"-"`
	Databases           map[string]string    `json:"databases,omitempty"`
	Tolerations         []v1.Toleration      `json:"tolerations,omitempty"`
}

// PostgresqlList defines a list of PostgreSQL clusters.
type PostgresqlList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	Items []Postgresql `json:"items"`
}

var weekdays = map[string]int{"Sun": 0, "Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6}

func parseTime(s string) (time.Time, error) {
	parts := strings.Split(s, ":")
	if len(parts) != 2 {
		return time.Time{}, fmt.Errorf("incorrect time format")
	}
	timeLayout := "15:04"

	tp, err := time.Parse(timeLayout, s)
	if err != nil {
		return time.Time{}, err
	}

	return tp.UTC(), nil
}

func parseWeekday(s string) (time.Weekday, error) {
	weekday, ok := weekdays[s]
	if !ok {
		return time.Weekday(0), fmt.Errorf("incorrect weekday")
	}

	return time.Weekday(weekday), nil
}

// MarshalJSON converts a maintenance window definition to JSON.
func (m *MaintenanceWindow) MarshalJSON() ([]byte, error) {
	if m.Everyday {
		return []byte(fmt.Sprintf("\"%s-%s\"",
			m.StartTime.Format("15:04"),
			m.EndTime.Format("15:04"))), nil
	}

	return []byte(fmt.Sprintf("\"%s:%s-%s\"",
		m.Weekday.String()[:3],
		m.StartTime.Format("15:04"),
		m.EndTime.Format("15:04"))), nil
}

// UnmarshalJSON convets a JSON to the maintenance window definition.
func (m *MaintenanceWindow) UnmarshalJSON(data []byte) error {
	var (
		got MaintenanceWindow
		err error
	)

	parts := strings.Split(string(data[1:len(data)-1]), "-")
	if len(parts) != 2 {
		return fmt.Errorf("incorrect maintenance window format")
	}

	fromParts := strings.Split(parts[0], ":")
	switch len(fromParts) {
	case 3:
		got.Everyday = false
		got.Weekday, err = parseWeekday(fromParts[0])
		if err != nil {
			return fmt.Errorf("could not parse weekday: %v", err)
		}

		got.StartTime, err = parseTime(fromParts[1] + ":" + fromParts[2])
	case 2:
		got.Everyday = true
		got.StartTime, err = parseTime(fromParts[0] + ":" + fromParts[1])
	default:
		return fmt.Errorf("incorrect maintenance window format")
	}
	if err != nil {
		return fmt.Errorf("could not parse start time: %v", err)
	}

	got.EndTime, err = parseTime(parts[1])
	if err != nil {
		return fmt.Errorf("could not parse end time: %v", err)
	}

	if got.EndTime.Before(got.StartTime) {
		return fmt.Errorf("'From' time must be prior to the 'To' time")
	}

	*m = got

	return nil
}

func extractClusterName(clusterName string, teamName string) (string, error) {
	teamNameLen := len(teamName)
	if len(clusterName) < teamNameLen+2 {
		return "", fmt.Errorf("name is too short")
	}

	if teamNameLen == 0 {
		return "", fmt.Errorf("team name is empty")
	}

	if strings.ToLower(clusterName[:teamNameLen+1]) != strings.ToLower(teamName)+"-" {
		return "", fmt.Errorf("name must match {TEAM}-{NAME} format")
	}

	return clusterName[teamNameLen+1:], nil
}

type postgresqlListCopy PostgresqlList
type postgresqlCopy Postgresql

// UnmarshalJSON converts a JSON into the PostgreSQL object.
func (p *Postgresql) UnmarshalJSON(data []byte) error {
	var tmp postgresqlCopy

	err := json.Unmarshal(data, &tmp)
	if err != nil {
		metaErr := json.Unmarshal(data, &tmp.ObjectMeta)
		if metaErr != nil {
			return err
		}

		tmp.Error = err
		tmp.Status = ClusterStatusInvalid

		*p = Postgresql(tmp)

		return nil
	}
	tmp2 := Postgresql(tmp)

	clusterName, err := extractClusterName(tmp2.ObjectMeta.Name, tmp2.Spec.TeamID)
	if err == nil {
		tmp2.Spec.ClusterName = clusterName
	} else {
		tmp2.Error = err
		tmp2.Status = ClusterStatusInvalid
	}
	// The assumption below is that a cluster to clone, if any, belongs to the same team
	if tmp2.Spec.Clone.ClusterName != "" {
		_, err := extractClusterName(tmp2.Spec.Clone.ClusterName, tmp2.Spec.TeamID)
		if err != nil {
			tmp2.Error = fmt.Errorf("%s for the cluster to clone", err)
			tmp2.Spec.Clone = CloneDescription{}
			tmp2.Status = ClusterStatusInvalid
		}
	}
	*p = tmp2

	return nil
}

// UnmarshalJSON converts a JSON into the PostgreSQL List object.
func (pl *PostgresqlList) UnmarshalJSON(data []byte) error {
	var tmp postgresqlListCopy

	err := json.Unmarshal(data, &tmp)
	if err != nil {
		return err
	}
	tmp2 := PostgresqlList(tmp)
	*pl = tmp2

	return nil
}
<commit_msg>Document intended usage of useLoadBalancer<commit_after>package spec

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/pkg/api/v1"
)

// MaintenanceWindow describes the time window when the operator is allowed to do maintenance on a cluster.
type MaintenanceWindow struct {
	Everyday  bool
	Weekday   time.Weekday
	StartTime time.Time // Start time
	EndTime   time.Time // End time
}

// Volume describes a single volume in the manifest.
type Volume struct {
	Size         string `json:"size"`
	StorageClass string `json:"storageClass"`
}

// PostgresqlParam describes PostgreSQL version and pairs of configuration parameter name - values.
type PostgresqlParam struct {
	PgVersion  string            `json:"version"`
	Parameters map[string]string `json:"parameters"`
}

// ResourceDescription describes CPU and memory resources defined for a cluster.
type ResourceDescription struct {
	CPU    string `json:"cpu"`
	Memory string `json:"memory"`
}

// Resources describes requests and limits for the cluster resouces.
type Resources struct {
	ResourceRequest ResourceDescription `json:"requests,omitempty"`
	ResourceLimits  ResourceDescription `json:"limits,omitempty"`
}

// Patroni contains Patroni-specific configuration
type Patroni struct {
	InitDB               map[string]string `json:"initdb"`
	PgHba                []string          `json:"pg_hba"`
	TTL                  uint32            `json:"ttl"`
	LoopWait             uint32            `json:"loop_wait"`
	RetryTimeout         uint32            `json:"retry_timeout"`
	MaximumLagOnFailover float32           `json:"maximum_lag_on_failover"` // float32 because https://github.com/kubernetes/kubernetes/issues/30213
}

// CloneDescription describes which cluster the new should clone and up to which point in time
type CloneDescription struct {
	ClusterName  string `json:"cluster,omitempty"`
	Uid          string `json:"uid,omitempty"`
	EndTimestamp string `json:"timestamp,omitempty"`
}

type UserFlags []string

// PostgresStatus contains status of the PostgreSQL cluster (running, creation failed etc.)
type PostgresStatus string

// possible values for PostgreSQL cluster statuses
const (
	ClusterStatusUnknown      PostgresStatus = ""
	ClusterStatusCreating     PostgresStatus = "Creating"
	ClusterStatusUpdating     PostgresStatus = "Updating"
	ClusterStatusUpdateFailed PostgresStatus = "UpdateFailed"
	ClusterStatusSyncFailed   PostgresStatus = "SyncFailed"
	ClusterStatusAddFailed    PostgresStatus = "CreateFailed"
	ClusterStatusRunning      PostgresStatus = "Running"
	ClusterStatusInvalid      PostgresStatus = "Invalid"
)

// Postgresql defines PostgreSQL Custom Resource Definition Object.
type Postgresql struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata"`

	Spec   PostgresSpec   `json:"spec"`
	Status PostgresStatus `json:"status,omitempty"`
	Error  error          `json:"-"`
}

// PostgresSpec defines the specification for the PostgreSQL TPR.
type PostgresSpec struct {
	PostgresqlParam `json:"postgresql"`
	Volume          `json:"volume,omitempty"`
	Patroni         `json:"patroni,omitempty"`
	Resources       `json:"resources,omitempty"`

	TeamID              string   `json:"teamId"`
	AllowedSourceRanges []string `json:"allowedSourceRanges"`
	DockerImage         string   `json:"dockerImage,omitempty"`
	// EnableLoadBalancer  is a pointer, since it is important to know if that parameters is omitted from the Postgres manifest
	// in that case UseLoadBalancer == nil and the value is taken from the operator config
	UseLoadBalancer     *bool                `json:"useLoadBalancer,omitempty"`
	ReplicaLoadBalancer bool                 `json:"replicaLoadBalancer,omitempty"`
	NumberOfInstances   int32                `json:"numberOfInstances"`
	Users               map[string]UserFlags `json:"users"`
	MaintenanceWindows  []MaintenanceWindow  `json:"maintenanceWindows,omitempty"`
	Clone               CloneDescription     `json:"clone"`
	ClusterName         string               `json:"-"`
	Databases           map[string]string    `json:"databases,omitempty"`
	Tolerations         []v1.Toleration      `json:"tolerations,omitempty"`
}

// PostgresqlList defines a list of PostgreSQL clusters.
type PostgresqlList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`

	Items []Postgresql `json:"items"`
}

var weekdays = map[string]int{"Sun": 0, "Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6}

func parseTime(s string) (time.Time, error) {
	parts := strings.Split(s, ":")
	if len(parts) != 2 {
		return time.Time{}, fmt.Errorf("incorrect time format")
	}
	timeLayout := "15:04"

	tp, err := time.Parse(timeLayout, s)
	if err != nil {
		return time.Time{}, err
	}

	return tp.UTC(), nil
}

func parseWeekday(s string) (time.Weekday, error) {
	weekday, ok := weekdays[s]
	if !ok {
		return time.Weekday(0), fmt.Errorf("incorrect weekday")
	}

	return time.Weekday(weekday), nil
}

// MarshalJSON converts a maintenance window definition to JSON.
func (m *MaintenanceWindow) MarshalJSON() ([]byte, error) {
	if m.Everyday {
		return []byte(fmt.Sprintf("\"%s-%s\"",
			m.StartTime.Format("15:04"),
			m.EndTime.Format("15:04"))), nil
	}

	return []byte(fmt.Sprintf("\"%s:%s-%s\"",
		m.Weekday.String()[:3],
		m.StartTime.Format("15:04"),
		m.EndTime.Format("15:04"))), nil
}

// UnmarshalJSON convets a JSON to the maintenance window definition.
func (m *MaintenanceWindow) UnmarshalJSON(data []byte) error {
	var (
		got MaintenanceWindow
		err error
	)

	parts := strings.Split(string(data[1:len(data)-1]), "-")
	if len(parts) != 2 {
		return fmt.Errorf("incorrect maintenance window format")
	}

	fromParts := strings.Split(parts[0], ":")
	switch len(fromParts) {
	case 3:
		got.Everyday = false
		got.Weekday, err = parseWeekday(fromParts[0])
		if err != nil {
			return fmt.Errorf("could not parse weekday: %v", err)
		}

		got.StartTime, err = parseTime(fromParts[1] + ":" + fromParts[2])
	case 2:
		got.Everyday = true
		got.StartTime, err = parseTime(fromParts[0] + ":" + fromParts[1])
	default:
		return fmt.Errorf("incorrect maintenance window format")
	}
	if err != nil {
		return fmt.Errorf("could not parse start time: %v", err)
	}

	got.EndTime, err = parseTime(parts[1])
	if err != nil {
		return fmt.Errorf("could not parse end time: %v", err)
	}

	if got.EndTime.Before(got.StartTime) {
		return fmt.Errorf("'From' time must be prior to the 'To' time")
	}

	*m = got

	return nil
}

func extractClusterName(clusterName string, teamName string) (string, error) {
	teamNameLen := len(teamName)
	if len(clusterName) < teamNameLen+2 {
		return "", fmt.Errorf("name is too short")
	}

	if teamNameLen == 0 {
		return "", fmt.Errorf("team name is empty")
	}

	if strings.ToLower(clusterName[:teamNameLen+1]) != strings.ToLower(teamName)+"-" {
		return "", fmt.Errorf("name must match {TEAM}-{NAME} format")
	}

	return clusterName[teamNameLen+1:], nil
}

type postgresqlListCopy PostgresqlList
type postgresqlCopy Postgresql

// UnmarshalJSON converts a JSON into the PostgreSQL object.
func (p *Postgresql) UnmarshalJSON(data []byte) error {
	var tmp postgresqlCopy

	err := json.Unmarshal(data, &tmp)
	if err != nil {
		metaErr := json.Unmarshal(data, &tmp.ObjectMeta)
		if metaErr != nil {
			return err
		}

		tmp.Error = err
		tmp.Status = ClusterStatusInvalid

		*p = Postgresql(tmp)

		return nil
	}
	tmp2 := Postgresql(tmp)

	clusterName, err := extractClusterName(tmp2.ObjectMeta.Name, tmp2.Spec.TeamID)
	if err == nil {
		tmp2.Spec.ClusterName = clusterName
	} else {
		tmp2.Error = err
		tmp2.Status = ClusterStatusInvalid
	}
	// The assumption below is that a cluster to clone, if any, belongs to the same team
	if tmp2.Spec.Clone.ClusterName != "" {
		_, err := extractClusterName(tmp2.Spec.Clone.ClusterName, tmp2.Spec.TeamID)
		if err != nil {
			tmp2.Error = fmt.Errorf("%s for the cluster to clone", err)
			tmp2.Spec.Clone = CloneDescription{}
			tmp2.Status = ClusterStatusInvalid
		}
	}
	*p = tmp2

	return nil
}

// UnmarshalJSON converts a JSON into the PostgreSQL List object.
func (pl *PostgresqlList) UnmarshalJSON(data []byte) error {
	var tmp postgresqlListCopy

	err := json.Unmarshal(data, &tmp)
	if err != nil {
		return err
	}
	tmp2 := PostgresqlList(tmp)
	*pl = tmp2

	return nil
}
<|endoftext|>


<commit_before>package run

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"

	"github.com/runabove/venom"
	ex "github.com/runabove/venom/executors/exec"
	"github.com/runabove/venom/executors/http"
	"github.com/runabove/venom/executors/readfile"
	"github.com/runabove/venom/executors/smtp"
)

var (
	path           []string
	alias          []string
	format         string
	parallel       int
	logLevel       string
	outputDir      string
	detailsLevel   string
	resumeFailures bool
	resume         bool
)

func init() {
	Cmd.Flags().StringSliceVarP(&alias, "alias", "", []string{""}, "--alias cds:'cds -f config.json' --alias cds2:'cds -f config.json'")
	Cmd.Flags().StringVarP(&format, "format", "", "xml", "--formt:yaml, json, xml")
	Cmd.Flags().IntVarP(&parallel, "parallel", "", 1, "--parallel=2 : launches 2 Test Suites in parallel")
	Cmd.PersistentFlags().StringVarP(&logLevel, "log", "", "warn", "Log Level : debug, info or warn")
	Cmd.PersistentFlags().StringVarP(&outputDir, "output-dir", "", "", "Output Directory: create tests results file inside this directory")
	Cmd.PersistentFlags().StringVarP(&detailsLevel, "details", "", "medium", "Output Details Level : low, medium, high")
	Cmd.PersistentFlags().BoolVarP(&resume, "resume", "", true, "Output Resume: one line with Total, TotalOK, TotalKO, TotalSkipped, TotalTestSuite")
	Cmd.PersistentFlags().BoolVarP(&resumeFailures, "resumeFailures", "", true, "Output Resume Failures")
}

// Cmd run
var Cmd = &cobra.Command{
	Use:   "run",
	Short: "Run Tests",
	PreRun: func(cmd *cobra.Command, args []string) {

		if len(args) == 0 {
			path = append(path, ".")
		}
		path = args[0:]

		venom.RegisterExecutor(ex.Name, ex.New())
		venom.RegisterExecutor(http.Name, http.New())
		venom.RegisterExecutor(smtp.Name, smtp.New())
		venom.RegisterExecutor(readfile.Name, readfile.New())
	},
	Run: func(cmd *cobra.Command, args []string) {
		if parallel < 0 {
			parallel = 1
		}

		switch logLevel {
		case "debug":
			log.SetLevel(log.DebugLevel)
		case "info":
			log.SetLevel(log.InfoLevel)
		case "error":
			log.SetLevel(log.WarnLevel)
		default:
			log.SetLevel(log.WarnLevel)
		}

		switch detailsLevel {
		case venom.DetailsLow, venom.DetailsMedium, venom.DetailsHigh:
			log.Infof("Detail Level: %s", detailsLevel)
		default:
			log.Fatalf("Invalid details. Must be low, medium or high")
		}

		start := time.Now()
		tests, err := venom.Process(path, alias, parallel, detailsLevel)
		if err != nil {
			log.Fatal(err)
		}

		elapsed := time.Since(start)

		outputResult(tests, elapsed)
	},
}

func outputResult(tests venom.Tests, elapsed time.Duration) {
	var data []byte
	var err error
	switch format {
	case "json":
		data, err = json.Marshal(tests)
		if err != nil {
			log.Fatalf("Error: cannot format output json (%s)", err)
		}
	case "yml", "yaml":
		data, err = yaml.Marshal(tests)
		if err != nil {
			log.Fatalf("Error: cannot format output yaml (%s)", err)
		}
	default:
		dataxml, errm := xml.Marshal(tests)
		if errm != nil {
			log.Fatalf("Error: cannot format xml output: %s", errm)
		}
		data = append([]byte("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"), dataxml...)
	}

	if detailsLevel == "high" {
		fmt.Printf(string(data))
	}

	if resume {
		outputResume(tests, elapsed)
	}

	if outputDir != "" {
		filename := outputDir + "/" + "test_results" + "." + format
		if err := ioutil.WriteFile(filename, data, 0644); err != nil {
			fmt.Printf("Error while creating file %s, err:%s", filename, err)
			os.Exit(1)
		}
	}
}

func outputResume(tests venom.Tests, elapsed time.Duration) {

	if resumeFailures {
		for _, t := range tests.TestSuites {
			if t.Failures > 0 || t.Errors > 0 {
				fmt.Printf("FAILED %s\n", t.Name)
				fmt.Printf("--------------\n")

				for _, tc := range t.TestCases {
					for _, f := range tc.Failures {
						fmt.Printf("%s\n", f.Value)
					}
					for _, f := range tc.Errors {
						fmt.Printf("%s\n", f.Value)
					}
				}
				fmt.Printf("-=-=-=-=-=-=-=-=-\n")
			}
		}
	}

	totalTestCases := 0
	totalTestSteps := 0
	for _, t := range tests.TestSuites {
		if t.Failures > 0 || t.Errors > 0 {
			fmt.Printf("FAILED %s\n", t.Name)
		}
		totalTestCases += len(t.TestCases)
		for _, tc := range t.TestCases {
			totalTestSteps += len(tc.TestSteps)
		}
	}

	fmt.Printf("Total:%d TotalOK:%d TotalKO:%d TotalSkipped:%d TotalTestSuite:%d TotalTestCase:%d TotalTestStep:%d Duration:%s\n",
		tests.Total,
		tests.TotalOK,
		tests.TotalKO,
		tests.TotalSkipped,
		len(tests.TestSuites),
		totalTestCases,
		totalTestSteps,
		elapsed,
	)

}
<commit_msg>fix: cli (#30)<commit_after>package run

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"

	"github.com/runabove/venom"
	ex "github.com/runabove/venom/executors/exec"
	"github.com/runabove/venom/executors/http"
	"github.com/runabove/venom/executors/readfile"
	"github.com/runabove/venom/executors/smtp"
)

var (
	path           []string
	alias          []string
	format         string
	parallel       int
	logLevel       string
	outputDir      string
	detailsLevel   string
	resumeFailures bool
	resume         bool
)

func init() {
	Cmd.Flags().StringSliceVarP(&alias, "alias", "", []string{""}, "--alias cds:'cds -f config.json' --alias cds2:'cds -f config.json'")
	Cmd.Flags().StringVarP(&format, "format", "", "xml", "--formt:yaml, json, xml")
	Cmd.Flags().IntVarP(&parallel, "parallel", "", 1, "--parallel=2 : launches 2 Test Suites in parallel")
	Cmd.PersistentFlags().StringVarP(&logLevel, "log", "", "warn", "Log Level : debug, info or warn")
	Cmd.PersistentFlags().StringVarP(&outputDir, "output-dir", "", "", "Output Directory: create tests results file inside this directory")
	Cmd.PersistentFlags().StringVarP(&detailsLevel, "details", "", "medium", "Output Details Level : low, medium, high")
	Cmd.PersistentFlags().BoolVarP(&resume, "resume", "", true, "Output Resume: one line with Total, TotalOK, TotalKO, TotalSkipped, TotalTestSuite")
	Cmd.PersistentFlags().BoolVarP(&resumeFailures, "resumeFailures", "", true, "Output Resume Failures")
}

// Cmd run
var Cmd = &cobra.Command{
	Use:   "run",
	Short: "Run Tests",
	PreRun: func(cmd *cobra.Command, args []string) {

		if len(args) == 0 {
			path = append(path, ".")
		} else {
			path = args[0:]
		}

		venom.RegisterExecutor(ex.Name, ex.New())
		venom.RegisterExecutor(http.Name, http.New())
		venom.RegisterExecutor(smtp.Name, smtp.New())
		venom.RegisterExecutor(readfile.Name, readfile.New())
	},
	Run: func(cmd *cobra.Command, args []string) {
		if parallel < 0 {
			parallel = 1
		}

		switch logLevel {
		case "debug":
			log.SetLevel(log.DebugLevel)
		case "info":
			log.SetLevel(log.InfoLevel)
		case "error":
			log.SetLevel(log.WarnLevel)
		default:
			log.SetLevel(log.WarnLevel)
		}

		switch detailsLevel {
		case venom.DetailsLow, venom.DetailsMedium, venom.DetailsHigh:
			log.Infof("Detail Level: %s", detailsLevel)
		default:
			log.Fatalf("Invalid details. Must be low, medium or high")
		}

		start := time.Now()
		tests, err := venom.Process(path, alias, parallel, detailsLevel)
		if err != nil {
			log.Fatal(err)
		}

		elapsed := time.Since(start)

		outputResult(tests, elapsed)
	},
}

func outputResult(tests venom.Tests, elapsed time.Duration) {
	var data []byte
	var err error
	switch format {
	case "json":
		data, err = json.Marshal(tests)
		if err != nil {
			log.Fatalf("Error: cannot format output json (%s)", err)
		}
	case "yml", "yaml":
		data, err = yaml.Marshal(tests)
		if err != nil {
			log.Fatalf("Error: cannot format output yaml (%s)", err)
		}
	default:
		dataxml, errm := xml.Marshal(tests)
		if errm != nil {
			log.Fatalf("Error: cannot format xml output: %s", errm)
		}
		data = append([]byte("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"), dataxml...)
	}

	if detailsLevel == "high" {
		fmt.Printf(string(data))
	}

	if resume {
		outputResume(tests, elapsed)
	}

	if outputDir != "" {
		filename := outputDir + "/" + "test_results" + "." + format
		if err := ioutil.WriteFile(filename, data, 0644); err != nil {
			fmt.Printf("Error while creating file %s, err:%s", filename, err)
			os.Exit(1)
		}
	}
}

func outputResume(tests venom.Tests, elapsed time.Duration) {

	if resumeFailures {
		for _, t := range tests.TestSuites {
			if t.Failures > 0 || t.Errors > 0 {
				fmt.Printf("FAILED %s\n", t.Name)
				fmt.Printf("--------------\n")

				for _, tc := range t.TestCases {
					for _, f := range tc.Failures {
						fmt.Printf("%s\n", f.Value)
					}
					for _, f := range tc.Errors {
						fmt.Printf("%s\n", f.Value)
					}
				}
				fmt.Printf("-=-=-=-=-=-=-=-=-\n")
			}
		}
	}

	totalTestCases := 0
	totalTestSteps := 0
	for _, t := range tests.TestSuites {
		if t.Failures > 0 || t.Errors > 0 {
			fmt.Printf("FAILED %s\n", t.Name)
		}
		totalTestCases += len(t.TestCases)
		for _, tc := range t.TestCases {
			totalTestSteps += len(tc.TestSteps)
		}
	}

	fmt.Printf("Total:%d TotalOK:%d TotalKO:%d TotalSkipped:%d TotalTestSuite:%d TotalTestCase:%d TotalTestStep:%d Duration:%s\n",
		tests.Total,
		tests.TotalOK,
		tests.TotalKO,
		tests.TotalSkipped,
		len(tests.TestSuites),
		totalTestCases,
		totalTestSteps,
		elapsed,
	)

}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestGetAlive(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(alive))
	defer ts.Close()

	res, err := http.Get(ts.URL)
	if err != nil {
		t.Fatalf(err.Error())
	}

	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		t.Fatalf(err.Error())
	}
	found := string(body[:])
	expected := "I'm alive"
	if found != expected {
		t.Errorf("Expected\n---\n%s\n---\nbut found\n---\n%s\n---\n", expected, found)
	}
}
<commit_msg>from unit tests to integration tests<commit_after>package main

import (
	"io/ioutil"
	"net/http"
	"net/url"
	"testing"
)

const HOST = "http://localhost:8765/"

func TestGetAlive(t *testing.T) {
	response, err := http.Get(HOST)
	if err != nil {
		t.Fatalf("Unexpected error found - %s", err.Error())
	}
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	found := string(body[:])

	expected := "I'm alive"
	if found != expected {
		t.Errorf("Expected\n---\n%s\n---\nbut found\n---\n%s\n---\n", expected, found)
	}
}

func TestPlay(t *testing.T) {
	filename := "test_sounds/beep9.mp3"
	url := HOST + "play/" + url.QueryEscape(filename)

	client := &http.Client{}
	request, err := http.NewRequest("PUT", url, nil)
	res, err := client.Do(request)

	if err != nil {
		t.Fatalf("Unexpected error found - %s", err.Error())
	}

	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		t.Fatalf(err.Error())
	}
	found := string(body[:])
	expected := "Playing, test_sounds/beep9.mp3!"
	if found != expected {
		t.Errorf("Expected\n---\n%s\n---\nbut found\n---\n%s\n---\n", expected, found)
	}
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// 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
//
//     https://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 (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"time"

	pack "github.com/buildpacks/pack/pkg/client"
)

const (
	image      = "conformance-test-func"
	builderURL = "us.gcr.io/fn-img/buildpacks/%s/builder:%s"
)

type buildpacksFunctionServer struct {
	output     string
	source     string
	target     string
	funcType   string
	runtime    string
	tag        string
	ctID       string
	logStdout  *os.File
	logStderr  *os.File
	stdoutFile string
	stderrFile string
}

func (b *buildpacksFunctionServer) Start(stdoutFile, stderrFile string) (func(), error) {
	b.stdoutFile = stdoutFile
	b.stderrFile = stderrFile
	typ := *functionType
	if typ == "legacyevent" {
		typ = "event"
	}

	ctx := context.Background()
	if err := b.build(ctx); err != nil {
		return nil, fmt.Errorf("building function container: %v", err)
	}

	shutdown, err := b.run()
	if err != nil {
		return nil, fmt.Errorf("running function container: %v", err)
	}

	return shutdown, nil
}

func (b *buildpacksFunctionServer) OutputFile() ([]byte, error) {
	cmd := exec.Command("docker", "cp", fmt.Sprintf("%s:/workspace/%s", b.containerID(), b.output), ".")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed to copy output file from the container: %v: %s", err, string(output))
	}
	return ioutil.ReadFile(b.output)
}

func (b *buildpacksFunctionServer) build(ctx context.Context) error {
	builder := fmt.Sprintf(builderURL, b.runtime, b.tag)

	cmd := exec.Command("docker", "pull", builder)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to pull builder image %s: %v: %s", builder, err, string(output))
	}

	packClient, err := pack.NewClient()
	if err != nil {
		return fmt.Errorf("getting pack client: %v", err)
	}
	err = packClient.Build(ctx, pack.BuildOptions{
		Image:    image,
		Builder:  builder,
		AppPath:  b.source,
		Registry: "",
		Env: map[string]string{
			"GOOGLE_FUNCTION_TARGET":         b.target,
			"GOOGLE_FUNCTION_SIGNATURE_TYPE": b.funcType,
		},
	})
	if err != nil {
		return fmt.Errorf("building function image: %v", err)
	}

	return nil
}

func (b *buildpacksFunctionServer) run() (func(), error) {
	// Create logs output files.
	var err error
	b.logStdout, err = os.Create(b.stdoutFile)
	if err != nil {
		return nil, err
	}

	b.logStderr, err = os.Create(b.stderrFile)
	if err != nil {
		return nil, err
	}

	args := []string{"docker", "run",
		"--network=host",
		// TODO: figure out why these aren't getting set in the buildpack.
		"--env=FUNCTION_TARGET=" + b.target,
		"--env=FUNCTION_SIGNATURE_TYPE=" + b.funcType,
		image,
	}
	cmd := exec.Command(args[0], args[1:]...)

	err = cmd.Start()

	// TODO: figure out why this isn't picking up errors.
	if err != nil {
		return nil, err
	}

	// Give it some time to do its setup.
	time.Sleep(time.Duration(*startDelay) * time.Second)

	log.Printf("Framework container %q started.", b.containerID())

	return func() {
		if err := b.logs(); err != nil {
			log.Fatalf("getting container logs: %v", err)
		}
		if err := cmd.Process.Kill(); err != nil {
			log.Fatalf("failed to kill process: %v", err)
		}
		if err := b.killContainer(); err != nil {
			log.Fatalf("failed to kill container: %v", err)
		}
		log.Printf("Framework server shut down. Wrote logs to %v and %v.", b.stdoutFile, b.stderrFile)
	}, nil
}

func (b *buildpacksFunctionServer) containerID() string {
	if b.ctID != "" {
		return b.ctID
	}
	cmd := exec.Command("docker", "ps", "--latest", "--format", "{{.ID}}")
	containerID, err := cmd.Output()
	if err != nil {
		log.Fatalf("failed to get container ID: %v", err)
	}
	b.ctID = string(bytes.TrimSpace(containerID))
	return b.ctID
}

func (b *buildpacksFunctionServer) killContainer() error {
	cmd := exec.Command("docker", "kill", b.containerID())
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to kill the container %q: %v: %s", b.containerID(), err, string(output))
	}
	return nil
}

func (b *buildpacksFunctionServer) logs() error {
	defer b.logStdout.Close()
	defer b.logStderr.Close()

	args := []string{"docker", "logs", b.containerID()}
	logsCmd := exec.Command(args[0], args[1:]...)
	logsCmd.Stdout = b.logStdout
	logsCmd.Stderr = b.logStderr

	err := logsCmd.Run()
	if err != nil {
		log.Fatalf("failed to retrieve container logs: %v", err)
	}
	return nil
}
<commit_msg>fix: add GCF as a target platform when testing with GCF buildpacks (#102)<commit_after>// Copyright 2020 Google LLC
//
// 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
//
//     https://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 (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"time"

	pack "github.com/buildpacks/pack/pkg/client"
)

const (
	image             = "conformance-test-func"
	builderURL        = "us.gcr.io/fn-img/buildpacks/%s/builder:%s"
	gcfTargetPlatform = "gcf"
)

type buildpacksFunctionServer struct {
	output     string
	source     string
	target     string
	funcType   string
	runtime    string
	tag        string
	ctID       string
	logStdout  *os.File
	logStderr  *os.File
	stdoutFile string
	stderrFile string
}

func (b *buildpacksFunctionServer) Start(stdoutFile, stderrFile string) (func(), error) {
	b.stdoutFile = stdoutFile
	b.stderrFile = stderrFile
	typ := *functionType
	if typ == "legacyevent" {
		typ = "event"
	}

	ctx := context.Background()
	if err := b.build(ctx); err != nil {
		return nil, fmt.Errorf("building function container: %v", err)
	}

	shutdown, err := b.run()
	if err != nil {
		return nil, fmt.Errorf("running function container: %v", err)
	}

	return shutdown, nil
}

func (b *buildpacksFunctionServer) OutputFile() ([]byte, error) {
	cmd := exec.Command("docker", "cp", fmt.Sprintf("%s:/workspace/%s", b.containerID(), b.output), ".")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed to copy output file from the container: %v: %s", err, string(output))
	}
	return ioutil.ReadFile(b.output)
}

func (b *buildpacksFunctionServer) build(ctx context.Context) error {
	builder := fmt.Sprintf(builderURL, b.runtime, b.tag)

	cmd := exec.Command("docker", "pull", builder)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to pull builder image %s: %v: %s", builder, err, string(output))
	}

	packClient, err := pack.NewClient()
	if err != nil {
		return fmt.Errorf("getting pack client: %v", err)
	}
	err = packClient.Build(ctx, pack.BuildOptions{
		Image:    image,
		Builder:  builder,
		AppPath:  b.source,
		Registry: "",
		Env: map[string]string{
			"GOOGLE_FUNCTION_TARGET":         b.target,
			"GOOGLE_FUNCTION_SIGNATURE_TYPE": b.funcType,
			"X_GOOGLE_TARGET_PLATFORM":       gcfTargetPlatform,
		},
	})
	if err != nil {
		return fmt.Errorf("building function image: %v", err)
	}

	return nil
}

func (b *buildpacksFunctionServer) run() (func(), error) {
	// Create logs output files.
	var err error
	b.logStdout, err = os.Create(b.stdoutFile)
	if err != nil {
		return nil, err
	}

	b.logStderr, err = os.Create(b.stderrFile)
	if err != nil {
		return nil, err
	}

	args := []string{"docker", "run",
		"--network=host",
		// TODO: figure out why these aren't getting set in the buildpack.
		"--env=FUNCTION_TARGET=" + b.target,
		"--env=FUNCTION_SIGNATURE_TYPE=" + b.funcType,
		image,
	}
	cmd := exec.Command(args[0], args[1:]...)

	err = cmd.Start()

	// TODO: figure out why this isn't picking up errors.
	if err != nil {
		return nil, err
	}

	// Give it some time to do its setup.
	time.Sleep(time.Duration(*startDelay) * time.Second)

	log.Printf("Framework container %q started.", b.containerID())

	return func() {
		if err := b.logs(); err != nil {
			log.Fatalf("getting container logs: %v", err)
		}
		if err := cmd.Process.Kill(); err != nil {
			log.Fatalf("failed to kill process: %v", err)
		}
		if err := b.killContainer(); err != nil {
			log.Fatalf("failed to kill container: %v", err)
		}
		log.Printf("Framework server shut down. Wrote logs to %v and %v.", b.stdoutFile, b.stderrFile)
	}, nil
}

func (b *buildpacksFunctionServer) containerID() string {
	if b.ctID != "" {
		return b.ctID
	}
	cmd := exec.Command("docker", "ps", "--latest", "--format", "{{.ID}}")
	containerID, err := cmd.Output()
	if err != nil {
		log.Fatalf("failed to get container ID: %v", err)
	}
	b.ctID = string(bytes.TrimSpace(containerID))
	return b.ctID
}

func (b *buildpacksFunctionServer) killContainer() error {
	cmd := exec.Command("docker", "kill", b.containerID())
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to kill the container %q: %v: %s", b.containerID(), err, string(output))
	}
	return nil
}

func (b *buildpacksFunctionServer) logs() error {
	defer b.logStdout.Close()
	defer b.logStderr.Close()

	args := []string{"docker", "logs", b.containerID()}
	logsCmd := exec.Command(args[0], args[1:]...)
	logsCmd.Stdout = b.logStdout
	logsCmd.Stderr = b.logStderr

	err := logsCmd.Run()
	if err != nil {
		log.Fatalf("failed to retrieve container logs: %v", err)
	}
	return nil
}
<|endoftext|>


<commit_before>package driver

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/hashicorp/go-plugin"
	"github.com/hashicorp/go-version"
	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/config"
	"github.com/hashicorp/nomad/client/driver/executor"
	cstructs "github.com/hashicorp/nomad/client/driver/structs"
	"github.com/hashicorp/nomad/client/fingerprint"
	"github.com/hashicorp/nomad/helper/discover"
	"github.com/hashicorp/nomad/nomad/structs"
	"github.com/mitchellh/mapstructure"
)

var (
	reRktVersion  = regexp.MustCompile(`rkt [vV]ersion[:]? (\d[.\d]+)`)
	reAppcVersion = regexp.MustCompile(`appc [vV]ersion[:]? (\d[.\d]+)`)
)

const (
	// minRktVersion is the earliest supported version of rkt. rkt added support
	// for CPU and memory isolators in 0.14.0. We cannot support an earlier
	// version to maintain an uniform interface across all drivers
	minRktVersion = "0.14.0"

	// bytesToMB is the conversion from bytes to megabytes.
	bytesToMB = 1024 * 1024
)

// RktDriver is a driver for running images via Rkt
// We attempt to chose sane defaults for now, with more configuration available
// planned in the future
type RktDriver struct {
	DriverContext
	fingerprint.StaticFingerprinter
}

type RktDriverConfig struct {
	ImageName string   `mapstructure:"image"`
	Args      []string `mapstructure:"args"`
}

// rktHandle is returned from Start/Open as a handle to the PID
type rktHandle struct {
	pluginClient *plugin.Client
	userPid      int
	executor     executor.Executor
	taskDir      string
	allocDir     *allocdir.AllocDir
	image        string
	logger       *log.Logger
	killTimeout  time.Duration
	waitCh       chan *cstructs.WaitResult
	doneCh       chan struct{}
}

// rktPID is a struct to map the pid running the process to the vm image on
// disk
type rktPID struct {
	PluginConfig *PluginReattachConfig
	TaskDir      string
	AllocDir     *allocdir.AllocDir
	UserPid      int
	Image        string
	KillTimeout  time.Duration
}

// NewRktDriver is used to create a new exec driver
func NewRktDriver(ctx *DriverContext) Driver {
	return &RktDriver{DriverContext: *ctx}
}

func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
	// Only enable if we are root when running on non-windows systems.
	if runtime.GOOS != "windows" && syscall.Geteuid() != 0 {
		d.logger.Printf("[DEBUG] driver.rkt: must run as root user, disabling")
		return false, nil
	}

	outBytes, err := exec.Command("rkt", "version").Output()
	if err != nil {
		return false, nil
	}
	out := strings.TrimSpace(string(outBytes))

	rktMatches := reRktVersion.FindStringSubmatch(out)
	appcMatches := reAppcVersion.FindStringSubmatch(out)
	if len(rktMatches) != 2 || len(appcMatches) != 2 {
		return false, fmt.Errorf("Unable to parse Rkt version string: %#v", rktMatches)
	}

	node.Attributes["driver.rkt"] = "1"
	node.Attributes["driver.rkt.version"] = rktMatches[1]
	node.Attributes["driver.rkt.appc.version"] = appcMatches[1]

	minVersion, _ := version.NewVersion(minRktVersion)
	currentVersion, _ := version.NewVersion(node.Attributes["driver.rkt.version"])
	if currentVersion.LessThan(minVersion) {
		// Do not allow rkt < 0.14.0
		d.logger.Printf("[WARN] driver.rkt: please upgrade rkt to a version >= %s", minVersion)
		node.Attributes["driver.rkt"] = "0"
	}
	return true, nil
}

// Run an existing Rkt image.
func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
	var driverConfig RktDriverConfig
	if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
		return nil, err
	}
	// Validate that the config is valid.
	img := driverConfig.ImageName
	if img == "" {
		return nil, fmt.Errorf("Missing ACI image for rkt")
	}

	// Get the tasks local directory.
	taskName := d.DriverContext.taskName
	taskDir, ok := ctx.AllocDir.TaskDirs[taskName]
	if !ok {
		return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
	}

	// Build the command.
	var cmdArgs []string

	// Add the given trust prefix
	trustPrefix, trustCmd := task.Config["trust_prefix"]
	insecure := false
	if trustCmd {
		var outBuf, errBuf bytes.Buffer
		cmd := exec.Command("rkt", "trust", "--skip-fingerprint-review=true", fmt.Sprintf("--prefix=%s", trustPrefix))
		cmd.Stdout = &outBuf
		cmd.Stderr = &errBuf
		if err := cmd.Run(); err != nil {
			return nil, fmt.Errorf("Error running rkt trust: %s\n\nOutput: %s\n\nError: %s",
				err, outBuf.String(), errBuf.String())
		}
		d.logger.Printf("[DEBUG] driver.rkt: added trust prefix: %q", trustPrefix)
	} else {
		// Disble signature verification if the trust command was not run.
		insecure = true
	}

	local, ok := ctx.AllocDir.TaskDirs[task.Name]
	if !ok {
		return nil, fmt.Errorf("Failed to find task local directory: %v", task.Name)
	}

	cmdArgs = append(cmdArgs, "run")
	cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", task.Name, local))
	cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", task.Name, ctx.AllocDir.SharedDir))
	cmdArgs = append(cmdArgs, img)
	if insecure == true {
		cmdArgs = append(cmdArgs, "--insecure-options=all")
	}

	// Inject enviornment variables
	for k, v := range d.taskEnv.EnvMap() {
		cmdArgs = append(cmdArgs, fmt.Sprintf("--set-env=%v=%v", k, v))
	}

	// Check if the user has overriden the exec command.
	if execCmd, ok := task.Config["command"]; ok {
		cmdArgs = append(cmdArgs, fmt.Sprintf("--exec=%v", execCmd))
	}

	if task.Resources.MemoryMB == 0 {
		return nil, fmt.Errorf("Memory limit cannot be zero")
	}
	if task.Resources.CPU == 0 {
		return nil, fmt.Errorf("CPU limit cannot be zero")
	}

	// Add memory isolator
	cmdArgs = append(cmdArgs, fmt.Sprintf("--memory=%vM", int64(task.Resources.MemoryMB)*bytesToMB))

	// Add CPU isolator
	cmdArgs = append(cmdArgs, fmt.Sprintf("--cpu=%vm", int64(task.Resources.CPU)))

	// Add user passed arguments.
	if len(driverConfig.Args) != 0 {
		parsed := d.taskEnv.ParseAndReplace(driverConfig.Args)

		// Need to start arguments with "--"
		if len(parsed) > 0 {
			cmdArgs = append(cmdArgs, "--")
		}

		for _, arg := range parsed {
			cmdArgs = append(cmdArgs, fmt.Sprintf("%v", arg))
		}
	}

	bin, err := discover.NomadExecutable()
	if err != nil {
		return nil, fmt.Errorf("unable to find the nomad binary: %v", err)
	}

	pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name))
	pluginConfig := &plugin.ClientConfig{
		Cmd: exec.Command(bin, "executor", pluginLogFile),
	}

	exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
	if err != nil {
		return nil, err
	}
	executorCtx := &executor.ExecutorContext{
		TaskEnv:          d.taskEnv,
		AllocDir:         ctx.AllocDir,
		TaskName:         task.Name,
		TaskResources:    task.Resources,
		UnprivilegedUser: false,
		LogConfig:        task.LogConfig,
	}

	ps, err := exec.LaunchCmd(&executor.ExecCommand{Cmd: "/usr/bin/rkt", Args: cmdArgs}, executorCtx)
	if err != nil {
		pluginClient.Kill()
		return nil, fmt.Errorf("error starting process via the plugin: %v", err)
	}

	d.logger.Printf("[DEBUG] driver.rkt: started ACI %q with: %v", img, cmdArgs)
	h := &rktHandle{
		pluginClient: pluginClient,
		executor:     exec,
		image:        img,
		userPid:      ps.Pid,
		logger:       d.logger,
		killTimeout:  d.DriverContext.KillTimeout(task),
		doneCh:       make(chan struct{}),
		waitCh:       make(chan *cstructs.WaitResult, 1),
	}
	go h.run()
	return h, nil
}

func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
	// Parse the handle
	pidBytes := []byte(strings.TrimPrefix(handleID, "Rkt:"))
	qpid := &rktPID{}
	if err := json.Unmarshal(pidBytes, qpid); err != nil {
		return nil, fmt.Errorf("failed to parse Rkt handle '%s': %v", handleID, err)
	}

	pluginConfig := &plugin.ClientConfig{
		Reattach: qpid.PluginConfig.PluginConfig(),
	}
	executor, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
	if err != nil {
		d.logger.Println("[ERROR] driver.rkt: error connecting to plugin so destroying plugin pid and user pid")
		if e := destroyPlugin(qpid.PluginConfig.Pid, qpid.UserPid); e != nil {
			d.logger.Printf("[ERROR] driver.rkt: error destroying plugin and userpid: %v", e)
		}
		return nil, fmt.Errorf("error connecting to plugin: %v", err)
	}

	// Return a driver handle
	h := &rktHandle{
		pluginClient: pluginClient,
		userPid:      qpid.UserPid,
		taskDir:      qpid.TaskDir,
		allocDir:     qpid.AllocDir,
		executor:     executor,
		image:        qpid.Image,
		logger:       d.logger,
		killTimeout:  qpid.KillTimeout,
		doneCh:       make(chan struct{}),
		waitCh:       make(chan *cstructs.WaitResult, 1),
	}

	go h.run()
	return h, nil
}

func (h *rktHandle) ID() string {
	// Return a handle to the PID
	pid := &rktPID{
		PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()),
		Image:        h.image,
		KillTimeout:  h.killTimeout,
		UserPid:      h.userPid,
		TaskDir:      h.taskDir,
		AllocDir:     h.allocDir,
	}
	data, err := json.Marshal(pid)
	if err != nil {
		h.logger.Printf("[ERR] driver.rkt: failed to marshal rkt PID to JSON: %s", err)
	}
	return fmt.Sprintf("Rkt:%s", string(data))
}

func (h *rktHandle) WaitCh() chan *cstructs.WaitResult {
	return h.waitCh
}

func (h *rktHandle) Update(task *structs.Task) error {
	// Store the updated kill timeout.
	h.killTimeout = task.KillTimeout
	h.executor.UpdateLogConfig(task.LogConfig)

	// Update is not possible
	return nil
}

// Kill is used to terminate the task. We send an Interrupt
// and then provide a 5 second grace period before doing a Kill.
func (h *rktHandle) Kill() error {
	h.executor.ShutDown()
	select {
	case <-h.doneCh:
		return nil
	case <-time.After(h.killTimeout):
		return h.executor.Exit()
	}
}

func (h *rktHandle) run() {
	ps, err := h.executor.Wait()
	close(h.doneCh)
	h.waitCh <- &cstructs.WaitResult{ExitCode: ps.ExitCode, Signal: 0, Err: err}
	close(h.waitCh)
	h.pluginClient.Kill()
}
<commit_msg>Do not hard-code binary location<commit_after>package driver

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"syscall"
	"time"

	"github.com/hashicorp/go-plugin"
	"github.com/hashicorp/go-version"
	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/config"
	"github.com/hashicorp/nomad/client/driver/executor"
	cstructs "github.com/hashicorp/nomad/client/driver/structs"
	"github.com/hashicorp/nomad/client/fingerprint"
	"github.com/hashicorp/nomad/helper/discover"
	"github.com/hashicorp/nomad/nomad/structs"
	"github.com/mitchellh/mapstructure"
)

var (
	reRktVersion  = regexp.MustCompile(`rkt [vV]ersion[:]? (\d[.\d]+)`)
	reAppcVersion = regexp.MustCompile(`appc [vV]ersion[:]? (\d[.\d]+)`)
)

const (
	// minRktVersion is the earliest supported version of rkt. rkt added support
	// for CPU and memory isolators in 0.14.0. We cannot support an earlier
	// version to maintain an uniform interface across all drivers
	minRktVersion = "0.14.0"

	// bytesToMB is the conversion from bytes to megabytes.
	bytesToMB = 1024 * 1024
)

// RktDriver is a driver for running images via Rkt
// We attempt to chose sane defaults for now, with more configuration available
// planned in the future
type RktDriver struct {
	DriverContext
	fingerprint.StaticFingerprinter
}

type RktDriverConfig struct {
	ImageName string   `mapstructure:"image"`
	Args      []string `mapstructure:"args"`
}

// rktHandle is returned from Start/Open as a handle to the PID
type rktHandle struct {
	pluginClient *plugin.Client
	userPid      int
	executor     executor.Executor
	taskDir      string
	allocDir     *allocdir.AllocDir
	image        string
	logger       *log.Logger
	killTimeout  time.Duration
	waitCh       chan *cstructs.WaitResult
	doneCh       chan struct{}
}

// rktPID is a struct to map the pid running the process to the vm image on
// disk
type rktPID struct {
	PluginConfig *PluginReattachConfig
	TaskDir      string
	AllocDir     *allocdir.AllocDir
	UserPid      int
	Image        string
	KillTimeout  time.Duration
}

// NewRktDriver is used to create a new exec driver
func NewRktDriver(ctx *DriverContext) Driver {
	return &RktDriver{DriverContext: *ctx}
}

func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
	// Only enable if we are root when running on non-windows systems.
	if runtime.GOOS != "windows" && syscall.Geteuid() != 0 {
		d.logger.Printf("[DEBUG] driver.rkt: must run as root user, disabling")
		return false, nil
	}

	outBytes, err := exec.Command("rkt", "version").Output()
	if err != nil {
		return false, nil
	}
	out := strings.TrimSpace(string(outBytes))

	rktMatches := reRktVersion.FindStringSubmatch(out)
	appcMatches := reAppcVersion.FindStringSubmatch(out)
	if len(rktMatches) != 2 || len(appcMatches) != 2 {
		return false, fmt.Errorf("Unable to parse Rkt version string: %#v", rktMatches)
	}

	node.Attributes["driver.rkt"] = "1"
	node.Attributes["driver.rkt.version"] = rktMatches[1]
	node.Attributes["driver.rkt.appc.version"] = appcMatches[1]

	minVersion, _ := version.NewVersion(minRktVersion)
	currentVersion, _ := version.NewVersion(node.Attributes["driver.rkt.version"])
	if currentVersion.LessThan(minVersion) {
		// Do not allow rkt < 0.14.0
		d.logger.Printf("[WARN] driver.rkt: please upgrade rkt to a version >= %s", minVersion)
		node.Attributes["driver.rkt"] = "0"
	}
	return true, nil
}

// Run an existing Rkt image.
func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
	var driverConfig RktDriverConfig
	if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
		return nil, err
	}
	// Validate that the config is valid.
	img := driverConfig.ImageName
	if img == "" {
		return nil, fmt.Errorf("Missing ACI image for rkt")
	}

	// Get the tasks local directory.
	taskName := d.DriverContext.taskName
	taskDir, ok := ctx.AllocDir.TaskDirs[taskName]
	if !ok {
		return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
	}

	// Build the command.
	var cmdArgs []string

	// Add the given trust prefix
	trustPrefix, trustCmd := task.Config["trust_prefix"]
	insecure := false
	if trustCmd {
		var outBuf, errBuf bytes.Buffer
		cmd := exec.Command("rkt", "trust", "--skip-fingerprint-review=true", fmt.Sprintf("--prefix=%s", trustPrefix))
		cmd.Stdout = &outBuf
		cmd.Stderr = &errBuf
		if err := cmd.Run(); err != nil {
			return nil, fmt.Errorf("Error running rkt trust: %s\n\nOutput: %s\n\nError: %s",
				err, outBuf.String(), errBuf.String())
		}
		d.logger.Printf("[DEBUG] driver.rkt: added trust prefix: %q", trustPrefix)
	} else {
		// Disble signature verification if the trust command was not run.
		insecure = true
	}

	local, ok := ctx.AllocDir.TaskDirs[task.Name]
	if !ok {
		return nil, fmt.Errorf("Failed to find task local directory: %v", task.Name)
	}

	cmdArgs = append(cmdArgs, "run")
	cmdArgs = append(cmdArgs, fmt.Sprintf("--volume=%s,kind=host,source=%s", task.Name, local))
	cmdArgs = append(cmdArgs, fmt.Sprintf("--mount=volume=%s,target=%s", task.Name, ctx.AllocDir.SharedDir))
	cmdArgs = append(cmdArgs, img)
	if insecure == true {
		cmdArgs = append(cmdArgs, "--insecure-options=all")
	}

	// Inject enviornment variables
	for k, v := range d.taskEnv.EnvMap() {
		cmdArgs = append(cmdArgs, fmt.Sprintf("--set-env=%v=%v", k, v))
	}

	// Check if the user has overriden the exec command.
	if execCmd, ok := task.Config["command"]; ok {
		cmdArgs = append(cmdArgs, fmt.Sprintf("--exec=%v", execCmd))
	}

	if task.Resources.MemoryMB == 0 {
		return nil, fmt.Errorf("Memory limit cannot be zero")
	}
	if task.Resources.CPU == 0 {
		return nil, fmt.Errorf("CPU limit cannot be zero")
	}

	// Add memory isolator
	cmdArgs = append(cmdArgs, fmt.Sprintf("--memory=%vM", int64(task.Resources.MemoryMB)*bytesToMB))

	// Add CPU isolator
	cmdArgs = append(cmdArgs, fmt.Sprintf("--cpu=%vm", int64(task.Resources.CPU)))

	// Add user passed arguments.
	if len(driverConfig.Args) != 0 {
		parsed := d.taskEnv.ParseAndReplace(driverConfig.Args)

		// Need to start arguments with "--"
		if len(parsed) > 0 {
			cmdArgs = append(cmdArgs, "--")
		}

		for _, arg := range parsed {
			cmdArgs = append(cmdArgs, fmt.Sprintf("%v", arg))
		}
	}

	bin, err := discover.NomadExecutable()
	if err != nil {
		return nil, fmt.Errorf("unable to find the nomad binary: %v", err)
	}

	pluginLogFile := filepath.Join(taskDir, fmt.Sprintf("%s-executor.out", task.Name))
	pluginConfig := &plugin.ClientConfig{
		Cmd: exec.Command(bin, "executor", pluginLogFile),
	}

	exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
	if err != nil {
		return nil, err
	}
	executorCtx := &executor.ExecutorContext{
		TaskEnv:          d.taskEnv,
		AllocDir:         ctx.AllocDir,
		TaskName:         task.Name,
		TaskResources:    task.Resources,
		UnprivilegedUser: false,
		LogConfig:        task.LogConfig,
	}

	ps, err := exec.LaunchCmd(&executor.ExecCommand{Cmd: "rkt", Args: cmdArgs}, executorCtx)
	if err != nil {
		pluginClient.Kill()
		return nil, fmt.Errorf("error starting process via the plugin: %v", err)
	}

	d.logger.Printf("[DEBUG] driver.rkt: started ACI %q with: %v", img, cmdArgs)
	h := &rktHandle{
		pluginClient: pluginClient,
		executor:     exec,
		image:        img,
		userPid:      ps.Pid,
		logger:       d.logger,
		killTimeout:  d.DriverContext.KillTimeout(task),
		doneCh:       make(chan struct{}),
		waitCh:       make(chan *cstructs.WaitResult, 1),
	}
	go h.run()
	return h, nil
}

func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
	// Parse the handle
	pidBytes := []byte(strings.TrimPrefix(handleID, "Rkt:"))
	qpid := &rktPID{}
	if err := json.Unmarshal(pidBytes, qpid); err != nil {
		return nil, fmt.Errorf("failed to parse Rkt handle '%s': %v", handleID, err)
	}

	pluginConfig := &plugin.ClientConfig{
		Reattach: qpid.PluginConfig.PluginConfig(),
	}
	executor, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
	if err != nil {
		d.logger.Println("[ERROR] driver.rkt: error connecting to plugin so destroying plugin pid and user pid")
		if e := destroyPlugin(qpid.PluginConfig.Pid, qpid.UserPid); e != nil {
			d.logger.Printf("[ERROR] driver.rkt: error destroying plugin and userpid: %v", e)
		}
		return nil, fmt.Errorf("error connecting to plugin: %v", err)
	}

	// Return a driver handle
	h := &rktHandle{
		pluginClient: pluginClient,
		userPid:      qpid.UserPid,
		taskDir:      qpid.TaskDir,
		allocDir:     qpid.AllocDir,
		executor:     executor,
		image:        qpid.Image,
		logger:       d.logger,
		killTimeout:  qpid.KillTimeout,
		doneCh:       make(chan struct{}),
		waitCh:       make(chan *cstructs.WaitResult, 1),
	}

	go h.run()
	return h, nil
}

func (h *rktHandle) ID() string {
	// Return a handle to the PID
	pid := &rktPID{
		PluginConfig: NewPluginReattachConfig(h.pluginClient.ReattachConfig()),
		Image:        h.image,
		KillTimeout:  h.killTimeout,
		UserPid:      h.userPid,
		TaskDir:      h.taskDir,
		AllocDir:     h.allocDir,
	}
	data, err := json.Marshal(pid)
	if err != nil {
		h.logger.Printf("[ERR] driver.rkt: failed to marshal rkt PID to JSON: %s", err)
	}
	return fmt.Sprintf("Rkt:%s", string(data))
}

func (h *rktHandle) WaitCh() chan *cstructs.WaitResult {
	return h.waitCh
}

func (h *rktHandle) Update(task *structs.Task) error {
	// Store the updated kill timeout.
	h.killTimeout = task.KillTimeout
	h.executor.UpdateLogConfig(task.LogConfig)

	// Update is not possible
	return nil
}

// Kill is used to terminate the task. We send an Interrupt
// and then provide a 5 second grace period before doing a Kill.
func (h *rktHandle) Kill() error {
	h.executor.ShutDown()
	select {
	case <-h.doneCh:
		return nil
	case <-time.After(h.killTimeout):
		return h.executor.Exit()
	}
}

func (h *rktHandle) run() {
	ps, err := h.executor.Wait()
	close(h.doneCh)
	h.waitCh <- &cstructs.WaitResult{ExitCode: ps.ExitCode, Signal: 0, Err: err}
	close(h.waitCh)
	h.pluginClient.Kill()
}
<|endoftext|>


<commit_before>// Package df implements a plugin to monitor disk usage and notify when low.
package df

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/StalkR/goircbot/bot"
	"github.com/StalkR/goircbot/lib/disk"
	"github.com/StalkR/goircbot/lib/size"
)

const delay = 5 * time.Minute

// An Alarm represents a path and disk size limit (in bytes) when to notify.
// Notification happens once when limit is crossed then is reset if
// it goes back above the limit.
type Alarm struct {
	Path     string
	Limit    uint64
	notified bool
}

// NewAlarm creates a new Alarm as specified.
func NewAlarm(path string, limit size.Byte) Alarm {
	return Alarm{Path: path, Limit: uint64(limit)}
}

// Monitor monitors a path and notifies when limit is crossed.
func (a *Alarm) Monitor(b bot.Bot) {
	for ; ; time.Sleep(delay) {
		total, free, err := disk.Space(a.Path)
		if err != nil {
			log.Printf("df: error: %v", err)
			continue
		}
		if free > a.Limit {
			a.notified = false
			continue
		}
		if !a.notified {
			a.Notify(b, total, free)
		}
	}
}

// status reports disk free/used status of a path.
func status(path string) (string, error) {
	total, free, err := disk.Space(path)
	if err != nil {
		return "", err
	}
	percent := 100 * (total - free) / total
	return fmt.Sprintf("%v has %v free (%v%% of %v used)",
		path, size.Byte(free), percent, size.Byte(total)), nil
}

// Notify notifies disk usage on all channels.
func (a *Alarm) Notify(b bot.Bot, total, free uint64) {
	if !b.Connected() {
		return
	}
	a.notified = true
	percent := 100 * (total - free) / total
	line := fmt.Sprintf("Warning: %v has %v free (%v%% of %v used)",
		a.Path, size.Byte(free), percent, size.Byte(total))
	for _, channel := range b.Channels() {
		b.Privmsg(channel, line)
	}
}

func df(e *bot.Event, paths map[string]bool) {
	if len(paths) == 0 {
		e.Bot.Privmsg(e.Target, "error: no paths configured")
		return
	}
	var pathlist []string
	for p := range paths {
		pathlist = append(pathlist, p)
	}

	path := strings.TrimSpace(e.Args)
	if path == "" {
		if len(paths) > 1 {
			e.Bot.Privmsg(e.Target, fmt.Sprintf("usage: df <path> (%v)", strings.Join(pathlist, ", ")))
			return
		}
		path = pathlist[0]
	}
	if !paths[path] {
		e.Bot.Privmsg(e.Target, "error: path not configured")
		return
	}
	line, err := status(path)
	if err != nil {
		e.Bot.Privmsg(e.Target, fmt.Sprintf("error: %v", err))
		return
	}
	e.Bot.Privmsg(e.Target, line)
}

// Register registers the plugin with a bot.
func Register(b bot.Bot, alarms ...Alarm) {
	paths := map[string]bool{}
	for _, a := range alarms {
		a := a // https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
		paths[a.Path] = true
		go a.Monitor(b)
	}

	b.Commands().Add("df", bot.Command{
		Help:    "See disk usage",
		Handler: func(e *bot.Event) { df(e, paths) },
		Pub:     true,
		Priv:    false,
		Hidden:  false})
}
<commit_msg>plugins/df: make Alarm fields private, clarify they are not modified elsewhere<commit_after>// Package df implements a plugin to monitor disk usage and notify when low.
package df

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/StalkR/goircbot/bot"
	"github.com/StalkR/goircbot/lib/disk"
	"github.com/StalkR/goircbot/lib/size"
)

const delay = 5 * time.Minute

// An Alarm represents a path and disk size limit (in bytes) when to notify.
// Notification happens once when limit is crossed then is reset if
// it goes back above the limit.
type Alarm struct {
	path     string
	limit    uint64
	notified bool
}

// NewAlarm creates a new Alarm as specified.
func NewAlarm(path string, limit size.Byte) Alarm {
	return Alarm{path: path, limit: uint64(limit)}
}

// Monitor monitors a path and notifies when limit is crossed.
func (a *Alarm) Monitor(b bot.Bot) {
	for ; ; time.Sleep(delay) {
		total, free, err := disk.Space(a.path)
		if err != nil {
			log.Printf("df: error: %v", err)
			continue
		}
		if free > a.limit {
			a.notified = false
			continue
		}
		if !a.notified {
			a.Notify(b, total, free)
		}
	}
}

// status reports disk free/used status of a path.
func status(path string) (string, error) {
	total, free, err := disk.Space(path)
	if err != nil {
		return "", err
	}
	percent := 100 * (total - free) / total
	return fmt.Sprintf("%v has %v free (%v%% of %v used)",
		path, size.Byte(free), percent, size.Byte(total)), nil
}

// Notify notifies disk usage on all channels.
func (a *Alarm) Notify(b bot.Bot, total, free uint64) {
	if !b.Connected() {
		return
	}
	a.notified = true
	percent := 100 * (total - free) / total
	line := fmt.Sprintf("Warning: %v has %v free (%v%% of %v used)",
		a.path, size.Byte(free), percent, size.Byte(total))
	for _, channel := range b.Channels() {
		b.Privmsg(channel, line)
	}
}

func df(e *bot.Event, paths map[string]bool) {
	if len(paths) == 0 {
		e.Bot.Privmsg(e.Target, "error: no paths configured")
		return
	}
	var pathlist []string
	for p := range paths {
		pathlist = append(pathlist, p)
	}

	path := strings.TrimSpace(e.Args)
	if path == "" {
		if len(paths) > 1 {
			e.Bot.Privmsg(e.Target, fmt.Sprintf("usage: df <path> (%v)", strings.Join(pathlist, ", ")))
			return
		}
		path = pathlist[0]
	}
	if !paths[path] {
		e.Bot.Privmsg(e.Target, "error: path not configured")
		return
	}
	line, err := status(path)
	if err != nil {
		e.Bot.Privmsg(e.Target, fmt.Sprintf("error: %v", err))
		return
	}
	e.Bot.Privmsg(e.Target, line)
}

// Register registers the plugin with a bot.
func Register(b bot.Bot, alarms ...Alarm) {
	paths := map[string]bool{}
	for _, a := range alarms {
		a := a // https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
		paths[a.path] = true
		go a.Monitor(b)
	}

	b.Commands().Add("df", bot.Command{
		Help:    "See disk usage",
		Handler: func(e *bot.Event) { df(e, paths) },
		Pub:     true,
		Priv:    false,
		Hidden:  false})
}
<|endoftext|>


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

// +build windows

package clipboard

import (
	"syscall"
	"time"
	"unsafe"
)

const (
	cfUnicodetext = 13
	gmemFixed     = 0x0000
)

var (
	user32           = syscall.MustLoadDLL("user32")
	openClipboard    = user32.MustFindProc("OpenClipboard")
	closeClipboard   = user32.MustFindProc("CloseClipboard")
	emptyClipboard   = user32.MustFindProc("EmptyClipboard")
	getClipboardData = user32.MustFindProc("GetClipboardData")
	setClipboardData = user32.MustFindProc("SetClipboardData")

	kernel32     = syscall.NewLazyDLL("kernel32")
	globalAlloc  = kernel32.NewProc("GlobalAlloc")
	globalFree   = kernel32.NewProc("GlobalFree")
	globalLock   = kernel32.NewProc("GlobalLock")
	globalUnlock = kernel32.NewProc("GlobalUnlock")
	lstrcpy      = kernel32.NewProc("lstrcpyW")
)

// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
func waitOpenClipboard() error {
	started := time.Now()
	limit := started.Add(time.Second)
	var r uintptr
	var err error
	for time.Now().Before(limit) {
		r, _, err = openClipboard.Call(0)
		if r != 0 {
			return nil
		}
		time.Sleep(time.Millisecond)
	}
	return err
}

func readAll() (string, error) {
	err := waitOpenClipboard()
	if err != nil {
		return "", err
	}
	defer closeClipboard.Call()

	h, _, err := getClipboardData.Call(cfUnicodetext)
	if h == 0 {
		return "", err
	}

	l, _, err := globalLock.Call(h)
	if l == 0 {
		return "", err
	}

	text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])

	r, _, err := globalUnlock.Call(h)
	if r == 0 {
		return "", err
	}

	return text, nil
}

func writeAll(text string) error {
	err := waitOpenClipboard()
	if err != nil {
		return err
	}
	defer closeClipboard.Call()

	r, _, err := emptyClipboard.Call(0)
	if r == 0 {
		return err
	}

	data := syscall.StringToUTF16(text)

	h, _, err := globalAlloc.Call(gmemFixed, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
	if h == 0 {
		return err
	}
	defer func() {
		if h != 0 {
			globalFree.Call(h)
		}
	}()

	l, _, err := globalLock.Call(h)
	if l == 0 {
		return err
	}

	r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
	if r == 0 {
		return err
	}

	r, _, err = globalUnlock.Call(h)
	if r == 0 {
		return err
	}

	r, _, err = setClipboardData.Call(cfUnicodetext, h)
	if r == 0 {
		return err
	}
	h = 0 // suppress deferred cleanup
	return nil
}
<commit_msg>Use GMEM_MOVEABLE for clipboard data<commit_after>// Copyright 2013 @atotto. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build windows

package clipboard

import (
	"syscall"
	"time"
	"unsafe"
)

const (
	cfUnicodetext = 13
	gmemMoveable  = 0x0002
)

var (
	user32           = syscall.MustLoadDLL("user32")
	openClipboard    = user32.MustFindProc("OpenClipboard")
	closeClipboard   = user32.MustFindProc("CloseClipboard")
	emptyClipboard   = user32.MustFindProc("EmptyClipboard")
	getClipboardData = user32.MustFindProc("GetClipboardData")
	setClipboardData = user32.MustFindProc("SetClipboardData")

	kernel32     = syscall.NewLazyDLL("kernel32")
	globalAlloc  = kernel32.NewProc("GlobalAlloc")
	globalFree   = kernel32.NewProc("GlobalFree")
	globalLock   = kernel32.NewProc("GlobalLock")
	globalUnlock = kernel32.NewProc("GlobalUnlock")
	lstrcpy      = kernel32.NewProc("lstrcpyW")
)

// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
func waitOpenClipboard() error {
	started := time.Now()
	limit := started.Add(time.Second)
	var r uintptr
	var err error
	for time.Now().Before(limit) {
		r, _, err = openClipboard.Call(0)
		if r != 0 {
			return nil
		}
		time.Sleep(time.Millisecond)
	}
	return err
}

func readAll() (string, error) {
	err := waitOpenClipboard()
	if err != nil {
		return "", err
	}
	defer closeClipboard.Call()

	h, _, err := getClipboardData.Call(cfUnicodetext)
	if h == 0 {
		return "", err
	}

	l, _, err := globalLock.Call(h)
	if l == 0 {
		return "", err
	}

	text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])

	r, _, err := globalUnlock.Call(h)
	if r == 0 {
		return "", err
	}

	return text, nil
}

func writeAll(text string) error {
	err := waitOpenClipboard()
	if err != nil {
		return err
	}
	defer closeClipboard.Call()

	r, _, err := emptyClipboard.Call(0)
	if r == 0 {
		return err
	}

	data := syscall.StringToUTF16(text)

	// "If the hMem parameter identifies a memory object, the object must have
	// been allocated using the function with the GMEM_MOVEABLE flag."
	h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
	if h == 0 {
		return err
	}
	defer func() {
		if h != 0 {
			globalFree.Call(h)
		}
	}()

	l, _, err := globalLock.Call(h)
	if l == 0 {
		return err
	}

	r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
	if r == 0 {
		return err
	}

	r, _, err = globalUnlock.Call(h)
	if r == 0 {
		if err.(syscall.Errno) != 0 {
			return err
		}
	}

	r, _, err = setClipboardData.Call(cfUnicodetext, h)
	if r == 0 {
		return err
	}
	h = 0 // suppress deferred cleanup
	return nil
}
<|endoftext|>


<commit_before>// Queue implements a queue that will grow as needed, minimize unnecessary growth, and
// can be safely accessed from multiple routines without introducing race conditions.
// If necessary, the growth of the queue can be capped to a configurable size.
//
// A queue is created with a minimum length and an optional maximum size (capacity).
// If the mzx size of the queue == 0, the queue will be unbounded. The growth rate of
// the queue is similar to that of a slice. When the queue grows, all items in the
// queue are shifted so that the head of the queue points to the first element in the
// queue.
//
// Before an item is enqueued, the queue is checked to see if thie new item
// will cause it to grow. If the tail == length, growth may occur. If the
// head of the queue is past a certain point in the queue, which is currently
// calculated using a percentage, the items in the queue will be shifted to start at
// the beginning of the slice, instead of growing the slice. The queue's head and tail
// will then be updated to reflect the shift.
//
// After dequeuing an item, the head position will be checked. If the queue
// io empty, head > tail, head and tail will be set to 0. This allows for
// efficient reuse of the queue without having to check to see if the queue items
// should be shifted or the queue should be grown.
//
// Once a queue grows, it will not be shrunk.
//
// All publicly exposed methods on the queue use locking to protect the queue from
// race conditions in situations where the queue is being accessed concurrently.
// Unexposed methods do not do any locking/unlocking since it is expected that the
// calling function has already obtained the lock and will release it as appropriate.
package queue

import (
	"fmt"
	"sync"
)

// shiftPercent is the default value for shifting the queue items to the
// front of the queue instead of growing the queue. If at least the % of
// the items have been removed from the queue, the items in the queue will
// be shifted to make room; otherwise the queue will grow
var shiftPercent = 20

// Queue represents a queue and everything needed to manage it. The preferred method
// for creating a new Queue is to use the New() func.
type Queue struct {
	mu           sync.Mutex
	items        []interface{}
	head         int // current item in queue
	tail         int // tail is the next insert point. last item is tail - 1
	length       int
	maxCapacity  int
	shiftPercent int
}

// New returns an empty queue with a capacity of size. If maxCap is > 0,
// the queue will not grow larger than maxCapacity; if it is at maxCa[acity
// and growth is requred to enqueue an item, an error will occur.
// cu
func New(size, maxCap int) *Queue {
	return &Queue{items: make([]interface{}, size, size), length: size, maxCapacity: maxCap, shiftPercent: shiftPercent}
}

// Enqueue: adds an item to the queue. If adding the item requires growing
// the queue, the queue will be expanded. If the queue cannot be grown, an
// error will be returned.
func (q *Queue) Enqueue(item interface{}) error {
	q.mu.Lock()
	defer q.mu.Unlock()
	// See if it needs to grow
	if q.tail == q.length {
		shifted := q.shift()
		// if we weren't able to make room by shifting, grow the queue/
		if !shifted {
			err := q.grow()
			if err != nil {
				return err
			}
		}
	}
	q.items[q.tail] = item
	q.tail++
	return nil
}

// Dequeue removes an item from the queue. If the removal of the item empties
// the queue, the head and tail will be reset.
func (q *Queue) Dequeue() interface{} {
	q.mu.Lock()
	i := q.items[q.head]
	q.head++
	if q.head > q.tail {
		q.mu.Unlock()
		q.Reset()
		return i
	}
	q.mu.Unlock()
	return i
}

// IsEmpty returns whether or not the queue is empty
func (q *Queue) IsEmpty() bool {
	q.mu.Lock()
	if q.tail == 0 || q.head > q.tail {
		q.mu.Unlock()
		return true
	}
	q.mu.Unlock()
	return false
}

// shift: if shiftPercent items have been removed from the queue, the remaining items
// in the queue will be shifted to element 0-n, where n is the number of remaining
// items in the queue. Returns whether or not a shift occurred
func (q *Queue) shift() bool {
	if q.head <= (q.length*q.shiftPercent)/100 {
		return false
	}
	copy(q.items, q.items[q.head:q.tail])
	// set the pointers to the correct position
	q.tail = q.tail - q.head
	q.head = 0
	return true
}

// grow grows the slice using an algorithm similar to growSlice(). This is a bit slower
// than relying on slice's automatic growth, but allows for capacity enforcement w/o
// growing the slice cap beyond the configured max capacity, if applicable.
//
// Since a temporary slice is created to store the current queue, all items in queue
// are automatically shifted
func (q *Queue) grow() error {
	if q.length == q.maxCapacity && q.maxCapacity > 0 {
		return fmt.Errorf("groweQueue: cannot grow beyond max capacity of %d", q.maxCapacity)
	}
	if q.length < 1024 {
		q.length += q.length
	} else {
		q.length += q.length / 4
	}
	// If the maxCapacity is set, cannot grow it beyond that
	if q.length > q.maxCapacity && q.maxCapacity > 0 {
		q.length = q.maxCapacity
	}
	// grow the slice
	l := q.tail - q.head
	tmp := make([]interface{}, l, l)
	copy(tmp, q.items[q.head:q.tail])
	q.items = make([]interface{}, q.length, q.length)
	copy(q.items, tmp)
	q.tail = l
	q.head = 0
	return nil
}

// reset resets the queue; head and tail point to element 0
func (q *Queue) Reset() {
	q.mu.Lock()
	q.head = 0
	q.tail = 0
	q.mu.Unlock()
}
<commit_msg>minor updates to comments<commit_after>// Queue implements a queue that will grow as needed, minimize unnecessary growth, and
// can be safely accessed from multiple routines without introducing race conditions.
// If necessary, the growth of the queue can be capped to a configurable size. When a
// maximum capacity for the queue is defined, an error will occur if the queue is full
// and an attempt is made to add another item to the queue.
//
// A queue is created with a minimum length and an optional maximum size (capacity).
// If the max size of the queue == 0, the queue will be unbounded. The growth rate of
// the queue is similar to that of a slice. When the queue grows, all items in the
// queue are shifted so that the head of the queue points to the first element in the
// queue.
//
// Before an item is enqueued, the queue is checked to see if thie new item
// will cause it to grow. If the tail == length, growth may occur. If the
// head of the queue is past a certain point in the queue, which is currently
// calculated using a percentage, the items in the queue will be shifted to start at
// the beginning of the slice, instead of growing the slice. The queue's head and tail
// will then be updated to reflect the shift.
//
// After dequeuing an item, the head position will be checked. If the queue
// io empty, head > tail, head and tail will be set to 0. This allows for
// efficient reuse of the queue without having to check to see if the queue items
// should be shifted or the queue should be grown.
//
// Once a queue grows, it will not be shrunk. This behavior may change in the future.
//
// All publicly exposed methods on the queue use locking to protect the queue from
// race conditions in situations where the queue is being accessed concurrently.
// Unexposed methods do not do any locking/unlocking since it is expected that the
// calling function has already obtained the lock and will release it as appropriate.
package queue

import (
	"fmt"
	"sync"
)

// shiftPercent is the default value for shifting the queue items to the
// front of the queue instead of growing the queue. If at least the % of
// the items have been removed from the queue, the items in the queue will
// be shifted to make room; otherwise the queue will grow
var shiftPercent = 20

// Queue represents a queue and everything needed to manage it. The preferred method
// for creating a new Queue is to use the New() func.
type Queue struct {
	mu           sync.Mutex
	items        []interface{}
	head         int // current item in queue
	tail         int // tail is the next insert point. last item is tail - 1
	length       int // the current length (cap) of the queue,
	maxCap       int // if > 0, the queue's cap cannot grow beyond this value
	shiftPercent int // the % of items that need to be removed before shifting occurs
}

// New returns an empty queue with a capacity equal to the recieved size value. If
// maxCap is > 0, the queue will not grow larger than maxCap; if it is at maxCap
// and growth is requred to enqueue an item, an error will occur.
func New(size, maxCap int) *Queue {
	return &Queue{items: make([]interface{}, size, size), length: size, maxCapacity: maxCap, shiftPercent: shiftPercent}
}

// Enqueue: adds an item to the queue. If adding the item requires growing
// the queue, the queue will either be shifted, to make room at the end of the queue
// or it will grow. If the queue cannot be grown, an error will be returned.
func (q *Queue) Enqueue(item interface{}) error {
	q.mu.Lock()
	defer q.mu.Unlock()
	// See if it needs to grow
	if q.tail == q.length {
		shifted := q.shift()
		// if we weren't able to make room by shifting, grow the queue/
		if !shifted {
			err := q.grow()
			if err != nil {
				return err
			}
		}
	}
	q.items[q.tail] = item
	q.tail++
	return nil
}

// Dequeue removes an item from the queue. If the removal of the item empties
// the queue, the head and tail will be set to 0.
func (q *Queue) Dequeue() interface{} {
	q.mu.Lock()
	i := q.items[q.head]
	q.head++
	if q.head > q.tail {
		q.mu.Unlock()
		q.Reset()
		return i
	}
	q.mu.Unlock()
	return i
}

// IsEmpty returns whether or not the queue is empty
func (q *Queue) IsEmpty() bool {
	q.mu.Lock()
	if q.tail == 0 || q.head > q.tail {
		q.mu.Unlock()
		return true
	}
	q.mu.Unlock()
	return false
}

// shift: if shiftPercent items have been removed from the queue, the remaining items
// in the queue will be shifted to element 0-n, where n is the number of remaining
// items in the queue. Returns whether or not a shift occurred
func (q *Queue) shift() bool {
	if q.head <= (q.length*q.shiftPercent)/100 {
		return false
	}
	copy(q.items, q.items[q.head:q.tail])
	// set the pointers to the correct position
	q.tail = q.tail - q.head
	q.head = 0
	return true
}

// grow grows the slice using an algorithm similar to growSlice(). This is a bit slower
// than relying on slice's automatic growth, but allows for capacity enforcement w/o
// growing the slice cap beyond the configured maxCap, if applicable.
//
// Since a temporary slice is created to store the current queue, all items in queue
// are automatically shifted
func (q *Queue) grow() error {
	if q.length == q.maxCapacity && q.maxCapacity > 0 {
		return fmt.Errorf("groweQueue: cannot grow beyond max capacity of %d", q.maxCapacity)
	}
	if q.length < 1024 {
		q.length += q.length
	} else {
		q.length += q.length / 4
	}
	// If the maxCapacity is set, cannot grow it beyond that
	if q.length > q.maxCapacity && q.maxCapacity > 0 {
		q.length = q.maxCapacity
	}
	// grow the slice
	l := q.tail - q.head
	tmp := make([]interface{}, l, l)
	copy(tmp, q.items[q.head:q.tail])
	q.items = make([]interface{}, q.length, q.length)
	copy(q.items, tmp)
	q.tail = l
	q.head = 0
	return nil
}

// Reset resets the queue; head and tail point to element 0.
func (q *Queue) Reset() {
	q.mu.Lock()
	q.head = 0
	q.tail = 0
	q.mu.Unlock()
}
<|endoftext|>


<commit_before>package processor

import (
	"bytes"
	"image"
	"image/draw"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"io/ioutil"
	"math"

	"code.google.com/p/graphics-go/graphics"
	"code.google.com/p/graphics-go/graphics/interp"
	"github.com/rwcarlsen/goexif/exif"
)

var affines map[int]graphics.Affine = map[int]graphics.Affine{
	1: graphics.I,
	2: graphics.I.Scale(-1, 1),
	3: graphics.I.Scale(-1, -1),
	4: graphics.I.Scale(1, -1),
	5: graphics.I.Rotate(toRadian(90)).Scale(-1, 1),
	6: graphics.I.Rotate(toRadian(90)),
	7: graphics.I.Rotate(toRadian(-90)).Scale(-1, 1),
	8: graphics.I.Rotate(toRadian(-90)),
}

func Process(r io.Reader) (d image.Image, err error) {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return
	}
	s, _, err := image.Decode(bytes.NewReader(b))
	if err != nil {
		return
	}
	o, err := ReadOrientation(bytes.NewReader(b))
	if err != nil {
		return s, nil
	}
	d = ApplyOrientation(s, o)
	return
}

func ReadOrientation(r io.Reader) (o int, err error) {
	e, err := exif.Decode(r)
	if err != nil {
		return
	}
	tag, err := e.Get(exif.Orientation)
	if err != nil {
		return
	}
	o, err = tag.Int(0)
	if err != nil {
		return
	}
	return
}

func ApplyOrientation(s image.Image, o int) (d draw.Image) {
	bounds := s.Bounds()
	// orientation=5~8 なら画像サイズの縦横を入れ替える
	if o >= 5 && o <= 8 {
		bounds = rotateRect(bounds)
	}
	d = image.NewRGBA64(bounds)
	affine := affines[o]
	affine.TransformCenter(d, s, interp.Bilinear)
	return
}

func toRadian(d float64) float64 {
	return math.Pi * d / 180
}

func rotateRect(r image.Rectangle) image.Rectangle {
	s := r.Size()
	return image.Rectangle{r.Min, image.Point{s.Y, s.X}}
}
<commit_msg>Add comments<commit_after>package processor

import (
	"bytes"
	"image"
	"image/draw"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"io/ioutil"
	"math"

	"code.google.com/p/graphics-go/graphics"
	"code.google.com/p/graphics-go/graphics/interp"
	"github.com/rwcarlsen/goexif/exif"
)

var affines map[int]graphics.Affine = map[int]graphics.Affine{
	1: graphics.I,
	2: graphics.I.Scale(-1, 1),
	3: graphics.I.Scale(-1, -1),
	4: graphics.I.Scale(1, -1),
	5: graphics.I.Rotate(toRadian(90)).Scale(-1, 1),
	6: graphics.I.Rotate(toRadian(90)),
	7: graphics.I.Rotate(toRadian(-90)).Scale(-1, 1),
	8: graphics.I.Rotate(toRadian(-90)),
}

// Process returns an image that is applied Exif orientation tag.
func Process(r io.Reader) (d image.Image, err error) {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return
	}
	s, _, err := image.Decode(bytes.NewReader(b))
	if err != nil {
		return
	}
	o, err := ReadOrientation(bytes.NewReader(b))
	if err != nil {
		// When orientation can't be read, return original image.
		return s, nil
	}
	d = ApplyOrientation(s, o)
	return
}

// ReadOrientation returns Exif orientation tag.
func ReadOrientation(r io.Reader) (o int, err error) {
	e, err := exif.Decode(r)
	if err != nil {
		return
	}
	tag, err := e.Get(exif.Orientation)
	if err != nil {
		return
	}
	o, err = tag.Int(0)
	if err != nil {
		return
	}
	return
}

// ApplyOrientation applies orientation to image.
func ApplyOrientation(s image.Image, o int) (d draw.Image) {
	bounds := s.Bounds()
	// Swap width and height when orientation between 5 and 8
	if o >= 5 && o <= 8 {
		bounds = rotateRect(bounds)
	}
	d = image.NewRGBA64(bounds)
	affine := affines[o]
	affine.TransformCenter(d, s, interp.Bilinear)
	return
}

func toRadian(d float64) float64 {
	return math.Pi * d / 180
}

func rotateRect(r image.Rectangle) image.Rectangle {
	s := r.Size()
	return image.Rectangle{r.Min, image.Point{s.Y, s.X}}
}
<|endoftext|>