<commit_before>package docker

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"strings"

	docker "github.com/fsouza/go-dockerclient"
	"github.com/pkg/errors"
	"github.com/tsuru/tsuru/app/image"
	"github.com/tsuru/tsuru/builder"
	"github.com/tsuru/tsuru/event"
	tsuruIo "github.com/tsuru/tsuru/io"
	"github.com/tsuru/tsuru/net"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/provision/dockercommon"
)

var _ builder.Builder = &dockerBuilder{}

const (
	defaultArchiveName = "archive.tar.gz"
	defaultArchivePath = "/home/application"
)

type dockerBuilder struct{}

func init() {
	builder.Register("docker", &dockerBuilder{})
}

func (b *dockerBuilder) Build(p provision.BuilderDeploy, app provision.App, evt *event.Event, opts builder.BuildOpts) (string, error) {
	archiveFullPath := fmt.Sprintf("%s/%s", defaultArchivePath, defaultArchiveName)
	if opts.BuildFromFile {
		return "", errors.New("build image from Dockerfile is not yet supported")
	}
	client, err := p.GetDockerClient(app)
	if err != nil {
		return "", err
	}
	var tarFile io.ReadCloser
	if opts.ArchiveFile != nil && opts.ArchiveSize != 0 {
		tarFile = dockercommon.AddDeployTarFile(opts.ArchiveFile, opts.ArchiveSize, defaultArchiveName)
	} else if opts.Rebuild {
		var rcont *docker.Container
		tarFile, rcont, err = downloadFromContainer(client, app, archiveFullPath)
		if err != nil {
			return "", err
		}
		defer client.RemoveContainer(docker.RemoveContainerOptions{ID: rcont.ID, Force: true})
	} else if opts.ArchiveURL != "" {
		tarFile, err = downloadFromURL(opts.ArchiveURL)
		if err != nil {
			return "", err
		}
	} else if opts.ImageID != "" {
		return imageBuild(client, app, opts.ImageID, evt)
	} else {
		return "", errors.New("no valid files found")
	}
	defer tarFile.Close()
	imageName := image.GetBuildImage(app)
	w := evt
	fmt.Fprintln(w, "---- Pulling image to node ----")
	pullOpts := docker.PullImageOptions{
		Repository:        imageName,
		OutputStream:      w,
		InactivityTimeout: net.StreamInactivityTimeout,
	}
	err = client.PullImage(pullOpts, dockercommon.RegistryAuthConfig())
	if err != nil {
		return "", err
	}
	imageID, err := b.buildPipeline(p, client, app, tarFile, evt, opts.Tag)
	if err != nil {
		return "", err
	}
	return imageID, nil
}

func imageBuild(client provision.BuilderDockerClient, app provision.App, imageID string, evt *event.Event) (string, error) {
	if !strings.Contains(imageID, ":") {
		imageID = fmt.Sprintf("%s:latest", imageID)
	}
	w := evt
	fmt.Fprintln(w, "---- Pulling image to tsuru ----")
	pullOpts := docker.PullImageOptions{
		Repository:        imageID,
		OutputStream:      &tsuruIo.DockerErrorCheckWriter{W: w},
		InactivityTimeout: net.StreamInactivityTimeout,
		RawJSONStream:     true,
	}
	err := client.PullImage(pullOpts, dockercommon.RegistryAuthConfig())
	if err != nil {
		return "", err
	}
	fmt.Fprintln(w, "---- Getting process from image ----")
	cmd := "(cat /home/application/current/Procfile || cat /app/user/Procfile || cat /Procfile || true) 2>/dev/null"
	var buf bytes.Buffer
	err = runCommandInContainer(client, imageID, cmd, app, &buf, nil)
	if err != nil {
		return "", err
	}
	newImage, err := dockercommon.PrepareImageForDeploy(dockercommon.PrepareImageArgs{
		Client:      client,
		App:         app,
		ProcfileRaw: buf.String(),
		ImageID:     imageID,
		Out:         w,
	})
	if err != nil {
		return "", err
	}
	return newImage, nil
}

func runCommandInContainer(client provision.BuilderDockerClient, image string, command string, app provision.App, stdout, stderr io.Writer) error {
	createOptions := docker.CreateContainerOptions{
		Config: &docker.Config{
			AttachStdout: true,
			AttachStderr: true,
			Image:        image,
			Entrypoint:   []string{"/bin/sh", "-c"},
			Cmd:          []string{command},
		},
	}
	cont, err := client.CreateContainer(createOptions)
	if err != nil {
		return err
	}
	attachOptions := docker.AttachToContainerOptions{
		Container:    cont.ID,
		OutputStream: stdout,
		ErrorStream:  stderr,
		Stream:       true,
		Stdout:       true,
		Stderr:       true,
		Success:      make(chan struct{}),
	}
	waiter, err := client.AttachToContainerNonBlocking(attachOptions)
	if err != nil {
		return err
	}
	<-attachOptions.Success
	close(attachOptions.Success)
	err = client.StartContainer(cont.ID, nil)
	if err != nil {
		return err
	}
	waiter.Wait()
	return nil
}

func downloadFromContainer(client provision.BuilderDockerClient, app provision.App, filePath string) (io.ReadCloser, *docker.Container, error) {
	imageName, err := image.AppCurrentBuilderImageName(app.GetName())
	if err != nil {
		return nil, nil, errors.Errorf("App %s image not found", app.GetName())
	}
	options := docker.CreateContainerOptions{
		Config: &docker.Config{
			AttachStdout: true,
			AttachStderr: true,
			Image:        imageName,
		},
	}
	cont, err := client.CreateContainer(options)
	if err != nil {
		return nil, nil, err
	}
	archiveFile, err := dockercommon.DownloadFromContainer(client, cont.ID, filePath)
	if err != nil {
		return nil, nil, errors.Errorf("App %s raw image not found", app.GetName())
	}
	return archiveFile, cont, nil
}

func downloadFromURL(url string) (io.ReadCloser, error) {
	var out bytes.Buffer
	client := net.Dial5Full300Client
	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	s, err := io.Copy(&out, resp.Body)
	if err != nil {
		return nil, err
	}
	if s == 0 {
		return nil, errors.New("archive file is empty")
	}
	return ioutil.NopCloser(&out), nil
}
<commit_msg>builder/docker: add missing copyright header<commit_after>// Copyright 2017 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"strings"

	docker "github.com/fsouza/go-dockerclient"
	"github.com/pkg/errors"
	"github.com/tsuru/tsuru/app/image"
	"github.com/tsuru/tsuru/builder"
	"github.com/tsuru/tsuru/event"
	tsuruIo "github.com/tsuru/tsuru/io"
	"github.com/tsuru/tsuru/net"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/provision/dockercommon"
)

var _ builder.Builder = &dockerBuilder{}

const (
	defaultArchiveName = "archive.tar.gz"
	defaultArchivePath = "/home/application"
)

type dockerBuilder struct{}

func init() {
	builder.Register("docker", &dockerBuilder{})
}

func (b *dockerBuilder) Build(p provision.BuilderDeploy, app provision.App, evt *event.Event, opts builder.BuildOpts) (string, error) {
	archiveFullPath := fmt.Sprintf("%s/%s", defaultArchivePath, defaultArchiveName)
	if opts.BuildFromFile {
		return "", errors.New("build image from Dockerfile is not yet supported")
	}
	client, err := p.GetDockerClient(app)
	if err != nil {
		return "", err
	}
	var tarFile io.ReadCloser
	if opts.ArchiveFile != nil && opts.ArchiveSize != 0 {
		tarFile = dockercommon.AddDeployTarFile(opts.ArchiveFile, opts.ArchiveSize, defaultArchiveName)
	} else if opts.Rebuild {
		var rcont *docker.Container
		tarFile, rcont, err = downloadFromContainer(client, app, archiveFullPath)
		if err != nil {
			return "", err
		}
		defer client.RemoveContainer(docker.RemoveContainerOptions{ID: rcont.ID, Force: true})
	} else if opts.ArchiveURL != "" {
		tarFile, err = downloadFromURL(opts.ArchiveURL)
		if err != nil {
			return "", err
		}
	} else if opts.ImageID != "" {
		return imageBuild(client, app, opts.ImageID, evt)
	} else {
		return "", errors.New("no valid files found")
	}
	defer tarFile.Close()
	imageName := image.GetBuildImage(app)
	w := evt
	fmt.Fprintln(w, "---- Pulling image to node ----")
	pullOpts := docker.PullImageOptions{
		Repository:        imageName,
		OutputStream:      w,
		InactivityTimeout: net.StreamInactivityTimeout,
	}
	err = client.PullImage(pullOpts, dockercommon.RegistryAuthConfig())
	if err != nil {
		return "", err
	}
	imageID, err := b.buildPipeline(p, client, app, tarFile, evt, opts.Tag)
	if err != nil {
		return "", err
	}
	return imageID, nil
}

func imageBuild(client provision.BuilderDockerClient, app provision.App, imageID string, evt *event.Event) (string, error) {
	if !strings.Contains(imageID, ":") {
		imageID = fmt.Sprintf("%s:latest", imageID)
	}
	w := evt
	fmt.Fprintln(w, "---- Pulling image to tsuru ----")
	pullOpts := docker.PullImageOptions{
		Repository:        imageID,
		OutputStream:      &tsuruIo.DockerErrorCheckWriter{W: w},
		InactivityTimeout: net.StreamInactivityTimeout,
		RawJSONStream:     true,
	}
	err := client.PullImage(pullOpts, dockercommon.RegistryAuthConfig())
	if err != nil {
		return "", err
	}
	fmt.Fprintln(w, "---- Getting process from image ----")
	cmd := "(cat /home/application/current/Procfile || cat /app/user/Procfile || cat /Procfile || true) 2>/dev/null"
	var buf bytes.Buffer
	err = runCommandInContainer(client, imageID, cmd, app, &buf, nil)
	if err != nil {
		return "", err
	}
	newImage, err := dockercommon.PrepareImageForDeploy(dockercommon.PrepareImageArgs{
		Client:      client,
		App:         app,
		ProcfileRaw: buf.String(),
		ImageID:     imageID,
		Out:         w,
	})
	if err != nil {
		return "", err
	}
	return newImage, nil
}

func runCommandInContainer(client provision.BuilderDockerClient, image string, command string, app provision.App, stdout, stderr io.Writer) error {
	createOptions := docker.CreateContainerOptions{
		Config: &docker.Config{
			AttachStdout: true,
			AttachStderr: true,
			Image:        image,
			Entrypoint:   []string{"/bin/sh", "-c"},
			Cmd:          []string{command},
		},
	}
	cont, err := client.CreateContainer(createOptions)
	if err != nil {
		return err
	}
	attachOptions := docker.AttachToContainerOptions{
		Container:    cont.ID,
		OutputStream: stdout,
		ErrorStream:  stderr,
		Stream:       true,
		Stdout:       true,
		Stderr:       true,
		Success:      make(chan struct{}),
	}
	waiter, err := client.AttachToContainerNonBlocking(attachOptions)
	if err != nil {
		return err
	}
	<-attachOptions.Success
	close(attachOptions.Success)
	err = client.StartContainer(cont.ID, nil)
	if err != nil {
		return err
	}
	waiter.Wait()
	return nil
}

func downloadFromContainer(client provision.BuilderDockerClient, app provision.App, filePath string) (io.ReadCloser, *docker.Container, error) {
	imageName, err := image.AppCurrentBuilderImageName(app.GetName())
	if err != nil {
		return nil, nil, errors.Errorf("App %s image not found", app.GetName())
	}
	options := docker.CreateContainerOptions{
		Config: &docker.Config{
			AttachStdout: true,
			AttachStderr: true,
			Image:        imageName,
		},
	}
	cont, err := client.CreateContainer(options)
	if err != nil {
		return nil, nil, err
	}
	archiveFile, err := dockercommon.DownloadFromContainer(client, cont.ID, filePath)
	if err != nil {
		return nil, nil, errors.Errorf("App %s raw image not found", app.GetName())
	}
	return archiveFile, cont, nil
}

func downloadFromURL(url string) (io.ReadCloser, error) {
	var out bytes.Buffer
	client := net.Dial5Full300Client
	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	s, err := io.Copy(&out, resp.Body)
	if err != nil {
		return nil, err
	}
	if s == 0 {
		return nil, errors.New("archive file is empty")
	}
	return ioutil.NopCloser(&out), nil
}
<|endoftext|>


<commit_before>package resolves

import (
	"strconv"
	"strings"
	"time"

	"github.com/hysios/apiai-go"
	"github.com/wanliu/brain_data/database"
	"github.com/wanliu/flow/builtin/ai"

	. "github.com/wanliu/flow/context"

	client "github.com/wanliu/flow/builtin/graphql_client"
)

// 处理开单的逻辑结构, 不需要是组件
// 作为context的一个部分，或者存在一个Value中
type OrderResolve struct {
	AiParams  ai.AiOrder
	Products  ItemsResolve
	Gifts     ItemsResolve
	Address   string
	Customer  string
	Time      time.Time
	DefTime   string
	Current   Resolve
	Note      string
	UpdatedAt time.Time
	Editing   bool
	Canceled  bool

	User *database.User
}

func NewOrderResolve(ctx Context) *OrderResolve {
	resolve := new(OrderResolve)
	resolve.Touch()

	aiResult := ctx.Value("Result").(apiai.Result)

	resolve.AiParams = ai.ApiAiOrder{AiResult: aiResult}
	resolve.ExtractFromParams()

	if viewer := ctx.Value("Viewer"); viewer != nil {
		user := viewer.(*database.User)
		resolve.User = user
	}

	return resolve
}

func (r *OrderResolve) Solve(aiResult apiai.Result) string {
	return r.Answer()
}

func (r *OrderResolve) Touch() {
	r.UpdatedAt = time.Now()
}

func (r OrderResolve) Modifable(expireMin int) bool {
	return !r.Expired(expireMin) || r.Submited()
}

// TODO
func (r OrderResolve) Cancelable() bool {
	return true
}

// TODO
func (r *OrderResolve) Cancel() bool {
	r.Canceled = true
	return true
}

func (r OrderResolve) Fulfiled() bool {
	return len(r.Products.Products) > 0 && (r.Address != "" || r.Customer != "")
}

func (r OrderResolve) Expired(expireMin int) bool {
	return r.UpdatedAt.Add(time.Duration(expireMin)*time.Minute).UnixNano() < time.Now().UnixNano()
}

// TODO
func (r OrderResolve) Submited() bool {
	return false
}

// 从ｌｕｉｓ数据构造结构数据
func (r *OrderResolve) ExtractFromParams() {
	r.ExtractItems()
	r.ExtractGiftItems()
	r.ExtractAddress()
	r.ExtractCustomer()
	r.ExtractTime()
	r.ExtractNote()
}

func (r *OrderResolve) ExtractItems() {
	for _, i := range r.AiParams.Items() {
		name := strings.Replace(i.Product, "%", "%%", -1)
		item := &ItemResolve{
			Resolved: true,
			Name:     name,
			Price:    i.Price,
			Quantity: i.Quantity,
			Product:  name,
		}

		r.Products.Products = append(r.Products.Products, item)
	}
}

func (r *OrderResolve) ExtractGiftItems() {
	for _, i := range r.AiParams.GiftItems() {
		name := strings.Replace(i.Product, "%", "%%", -1)
		item := &ItemResolve{
			Resolved: true,
			Name:     name,
			Price:    i.Price,
			Quantity: i.Quantity,
			Product:  name,
		}

		r.Gifts.Products = append(r.Gifts.Products, item)
	}
}

func (r *OrderResolve) ExtractAddress() {
	r.Address = r.AiParams.Address()
}

func (r *OrderResolve) ExtractCustomer() {
	r.Customer = r.AiParams.Customer()
}

func (r *OrderResolve) ExtractTime() {
	r.Time = r.AiParams.Time()
}

func (r *OrderResolve) ExtractNote() {
	r.Note = r.AiParams.Note()
}

func (r *OrderResolve) SetDefTime(t string) {
	r.DefTime = t

	if r.Time.IsZero() && r.DefTime != "" {
		r.SetTimeByDef()
	}
}

func (r *OrderResolve) SetTimeByDef() {
	if r.DefTime == "今天" {
		r.Time = time.Now()
	} else if r.DefTime == "明天" {
		r.Time = time.Now().Add(24 * time.Hour)
	}
}

func (r OrderResolve) EmptyProducts() bool {
	return len(r.Products.Products) == 0
}

func (r OrderResolve) Answer() string {
	if r.Fulfiled() {
		return r.PostOrderAndAnswer()
	} else {
		return r.AnswerHead() + r.AnswerFooter("")
	}
}

func (r *OrderResolve) PostOrderAndAnswer() string {
	mutationStr := `mutation createOrderMutation($input: OrderInput!) {
        createOrder(input: $input) {
            __typename
            order {
                address
                note
                id
                no
                deliveryTime
                items {
                    product {
                        name
                        picUrl
                        price
                    }
                    price
                    quantity
                }
                gifts {
                    product {
                        name
                        picUrl
                        price
                    }
                    quantity
                }
                saler {
                    id
                    name
                }
            }
        }
    }`

	variables := `{"input": {"address":"` + r.Address + `","deliveryTime":"` + r.Time.Format(time.RFC3339) + `","clientMutationId":0` + `,"items":[`

	itemStrs := []string{}
	for _, item := range r.Products.Products {
		iStr := `{"quantity":` + strconv.Itoa(item.Quantity) + `,"productName":"` + item.Product + `"}`
		itemStrs = append(itemStrs, iStr)
	}
	variables = variables + strings.Join(itemStrs, ",") + `]`

	if len(r.Gifts.Products) > 0 {
		variables = variables + `,"gifts":[`
		giftStrs := []string{}
		for _, gift := range r.Gifts.Products {
			iStr := `{"quantity":` + strconv.Itoa(gift.Quantity) + `,"productName":"` + gift.Product + `"}`
			giftStrs = append(giftStrs, iStr)
		}
		variables = variables + strings.Join(giftStrs, ",") + `]}`
	}

	if r.Note != "" {
		variables = variables + `,"note":"` + r.Note + `"`
	}

	variables = variables + "}}"

	_ := client.QueryToRequest(mutationStr, variables)
	r.User.CreateSaledOrder()
	// res, _ := client.MakeGraphqlRequest(requstStr)
	return ""
	// return r.AnswerHead() + res.AnswerBody() + r.AnswerFooter(res.OrderNo())
}

func (r OrderResolve) AddressInfo() string {
	if r.Address != "" && r.Customer != "" {
		return "地址:" + r.Address + r.Customer + "\n"
	} else if r.Address != "" {
		return "地址:" + r.Address + "\n"
	} else if r.Customer != "" {
		return "客户:" + r.Customer + "\n"
	} else {
		return ""
	}
}

func (r OrderResolve) AnswerHead() string {
	desc := "订单正在处理, 已经添加" + CnNum(len(r.Products.Products)) + "种产品"

	if r.Fulfiled() {
		desc = "订单已经生成, 共" + CnNum(len(r.Products.Products)) + "种产品"
	}

	if len(r.Gifts.Products) > 0 {
		desc = desc + ", " + CnNum(len(r.Gifts.Products)) + "种赠品" + "\n"
	} else {
		desc = desc + "\n"
	}

	return desc
}

func (r OrderResolve) AnswerBody() string {
	desc := ""

	for _, p := range r.Products.Products {
		desc = desc + p.Product + " " + strconv.Itoa(p.Quantity) + "件\n"
	}

	if len(r.Gifts.Products) > 0 {
		desc = desc + "申请的赠品:\n"

		for _, g := range r.Gifts.Products {
			desc = desc + g.Product + " " + strconv.Itoa(g.Quantity) + "件\n"
		}
	}

	desc = desc + "时间:" + r.Time.Format("2006年01月02日") + "\n"

	if r.Note != "" {
		desc = desc + "备注：" + r.Note + "\n"
	}

	return desc
}

func (r OrderResolve) AnswerFooter(no string) string {
	desc := ""

	if r.Fulfiled() {
		desc = desc + r.AddressInfo()
		desc = desc + "订单已经生成，订单号为：" + no + "\n"
		desc = desc + "订单入口: http://wanliu.biz/orders/" + no
	} else {
		desc = desc + "还缺少收货地址或客户信息\n"
	}

	return desc
}

func CnNum(num int) string {
	switch num {
	case 1:
		return "一"
	case 2:
		return "两"
	case 3:
		return "三"
	case 4:
		return "四"
	case 5:
		return "五"
	case 6:
		return "六"
	case 7:
		return "七"
	case 8:
		return "八"
	case 9:
		return "九"
	case 10:
		return "十"
	default:
		return strconv.Itoa(num)
	}
}
<commit_msg>user create order test<commit_after>package resolves

import (
	"strconv"
	"strings"
	"time"

	"github.com/hysios/apiai-go"
	"github.com/wanliu/brain_data/database"
	"github.com/wanliu/flow/builtin/ai"

	. "github.com/wanliu/flow/context"

	client "github.com/wanliu/flow/builtin/graphql_client"
)

// 处理开单的逻辑结构, 不需要是组件
// 作为context的一个部分，或者存在一个Value中
type OrderResolve struct {
	AiParams  ai.AiOrder
	Products  ItemsResolve
	Gifts     ItemsResolve
	Address   string
	Customer  string
	Time      time.Time
	DefTime   string
	Current   Resolve
	Note      string
	UpdatedAt time.Time
	Editing   bool
	Canceled  bool

	User *database.User
}

func NewOrderResolve(ctx Context) *OrderResolve {
	resolve := new(OrderResolve)
	resolve.Touch()

	aiResult := ctx.Value("Result").(apiai.Result)

	resolve.AiParams = ai.ApiAiOrder{AiResult: aiResult}
	resolve.ExtractFromParams()

	if viewer := ctx.Value("Viewer"); viewer != nil {
		user := viewer.(*database.User)
		resolve.User = user
	}

	return resolve
}

func (r *OrderResolve) Solve(aiResult apiai.Result) string {
	return r.Answer()
}

func (r *OrderResolve) Touch() {
	r.UpdatedAt = time.Now()
}

func (r OrderResolve) Modifable(expireMin int) bool {
	return !r.Expired(expireMin) || r.Submited()
}

// TODO
func (r OrderResolve) Cancelable() bool {
	return true
}

// TODO
func (r *OrderResolve) Cancel() bool {
	r.Canceled = true
	return true
}

func (r OrderResolve) Fulfiled() bool {
	return len(r.Products.Products) > 0 && (r.Address != "" || r.Customer != "")
}

func (r OrderResolve) Expired(expireMin int) bool {
	return r.UpdatedAt.Add(time.Duration(expireMin)*time.Minute).UnixNano() < time.Now().UnixNano()
}

// TODO
func (r OrderResolve) Submited() bool {
	return false
}

// 从ｌｕｉｓ数据构造结构数据
func (r *OrderResolve) ExtractFromParams() {
	r.ExtractItems()
	r.ExtractGiftItems()
	r.ExtractAddress()
	r.ExtractCustomer()
	r.ExtractTime()
	r.ExtractNote()
}

func (r *OrderResolve) ExtractItems() {
	for _, i := range r.AiParams.Items() {
		name := strings.Replace(i.Product, "%", "%%", -1)
		item := &ItemResolve{
			Resolved: true,
			Name:     name,
			Price:    i.Price,
			Quantity: i.Quantity,
			Product:  name,
		}

		r.Products.Products = append(r.Products.Products, item)
	}
}

func (r *OrderResolve) ExtractGiftItems() {
	for _, i := range r.AiParams.GiftItems() {
		name := strings.Replace(i.Product, "%", "%%", -1)
		item := &ItemResolve{
			Resolved: true,
			Name:     name,
			Price:    i.Price,
			Quantity: i.Quantity,
			Product:  name,
		}

		r.Gifts.Products = append(r.Gifts.Products, item)
	}
}

func (r *OrderResolve) ExtractAddress() {
	r.Address = r.AiParams.Address()
}

func (r *OrderResolve) ExtractCustomer() {
	r.Customer = r.AiParams.Customer()
}

func (r *OrderResolve) ExtractTime() {
	r.Time = r.AiParams.Time()
}

func (r *OrderResolve) ExtractNote() {
	r.Note = r.AiParams.Note()
}

func (r *OrderResolve) SetDefTime(t string) {
	r.DefTime = t

	if r.Time.IsZero() && r.DefTime != "" {
		r.SetTimeByDef()
	}
}

func (r *OrderResolve) SetTimeByDef() {
	if r.DefTime == "今天" {
		r.Time = time.Now()
	} else if r.DefTime == "明天" {
		r.Time = time.Now().Add(24 * time.Hour)
	}
}

func (r OrderResolve) EmptyProducts() bool {
	return len(r.Products.Products) == 0
}

func (r OrderResolve) Answer() string {
	if r.Fulfiled() {
		return r.PostOrderAndAnswer()
	} else {
		return r.AnswerHead() + r.AnswerFooter("")
	}
}

func (r *OrderResolve) PostOrderAndAnswer() string {
	mutationStr := `mutation createOrderMutation($input: OrderInput!) {
        createOrder(input: $input) {
            __typename
            order {
                address
                note
                id
                no
                deliveryTime
                items {
                    product {
                        name
                        picUrl
                        price
                    }
                    price
                    quantity
                }
                gifts {
                    product {
                        name
                        picUrl
                        price
                    }
                    quantity
                }
                saler {
                    id
                    name
                }
            }
        }
    }`

	variables := `{"input": {"address":"` + r.Address + `","deliveryTime":"` + r.Time.Format(time.RFC3339) + `","clientMutationId":0` + `,"items":[`

	itemStrs := []string{}
	for _, item := range r.Products.Products {
		iStr := `{"quantity":` + strconv.Itoa(item.Quantity) + `,"productName":"` + item.Product + `"}`
		itemStrs = append(itemStrs, iStr)
	}
	variables = variables + strings.Join(itemStrs, ",") + `]`

	if len(r.Gifts.Products) > 0 {
		variables = variables + `,"gifts":[`
		giftStrs := []string{}
		for _, gift := range r.Gifts.Products {
			iStr := `{"quantity":` + strconv.Itoa(gift.Quantity) + `,"productName":"` + gift.Product + `"}`
			giftStrs = append(giftStrs, iStr)
		}
		variables = variables + strings.Join(giftStrs, ",") + `]}`
	}

	if r.Note != "" {
		variables = variables + `,"note":"` + r.Note + `"`
	}

	variables = variables + "}}"

	requstSt := client.QueryToRequest(mutationStr, variables)
	r.User.CreateSaledOrder()
	// res, _ := client.MakeGraphqlRequest(requstStr)
	return requstSt
	// return r.AnswerHead() + res.AnswerBody() + r.AnswerFooter(res.OrderNo())
}

func (r OrderResolve) AddressInfo() string {
	if r.Address != "" && r.Customer != "" {
		return "地址:" + r.Address + r.Customer + "\n"
	} else if r.Address != "" {
		return "地址:" + r.Address + "\n"
	} else if r.Customer != "" {
		return "客户:" + r.Customer + "\n"
	} else {
		return ""
	}
}

func (r OrderResolve) AnswerHead() string {
	desc := "订单正在处理, 已经添加" + CnNum(len(r.Products.Products)) + "种产品"

	if r.Fulfiled() {
		desc = "订单已经生成, 共" + CnNum(len(r.Products.Products)) + "种产品"
	}

	if len(r.Gifts.Products) > 0 {
		desc = desc + ", " + CnNum(len(r.Gifts.Products)) + "种赠品" + "\n"
	} else {
		desc = desc + "\n"
	}

	return desc
}

func (r OrderResolve) AnswerBody() string {
	desc := ""

	for _, p := range r.Products.Products {
		desc = desc + p.Product + " " + strconv.Itoa(p.Quantity) + "件\n"
	}

	if len(r.Gifts.Products) > 0 {
		desc = desc + "申请的赠品:\n"

		for _, g := range r.Gifts.Products {
			desc = desc + g.Product + " " + strconv.Itoa(g.Quantity) + "件\n"
		}
	}

	desc = desc + "时间:" + r.Time.Format("2006年01月02日") + "\n"

	if r.Note != "" {
		desc = desc + "备注：" + r.Note + "\n"
	}

	return desc
}

func (r OrderResolve) AnswerFooter(no string) string {
	desc := ""

	if r.Fulfiled() {
		desc = desc + r.AddressInfo()
		desc = desc + "订单已经生成，订单号为：" + no + "\n"
		desc = desc + "订单入口: http://wanliu.biz/orders/" + no
	} else {
		desc = desc + "还缺少收货地址或客户信息\n"
	}

	return desc
}

func CnNum(num int) string {
	switch num {
	case 1:
		return "一"
	case 2:
		return "两"
	case 3:
		return "三"
	case 4:
		return "四"
	case 5:
		return "五"
	case 6:
		return "六"
	case 7:
		return "七"
	case 8:
		return "八"
	case 9:
		return "九"
	case 10:
		return "十"
	default:
		return strconv.Itoa(num)
	}
}
<|endoftext|>


<commit_before>package alicloud

import (
	"fmt"
	"github.com/denverdino/aliyungo/ecs"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"strings"
	"time"
)

func resourceAliyunSecurityGroupRule() *schema.Resource {
	return &schema.Resource{
		Create: resourceAliyunSecurityGroupRuleCreate,
		Read:   resourceAliyunSecurityGroupRuleRead,
		Delete: resourceAliyunSecurityGroupRuleDelete,

		Schema: map[string]*schema.Schema{
			"type": &schema.Schema{
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateSecurityRuleType,
				Description:  "Type of rule, ingress (inbound) or egress (outbound).",
			},

			"ip_protocol": &schema.Schema{
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateSecurityRuleIpProtocol,
			},

			"nic_type": &schema.Schema{
				Type:         schema.TypeString,
				Optional:     true,
				ForceNew:     true,
				Computed:     true,
				ValidateFunc: validateSecurityRuleNicType,
			},

			"policy": &schema.Schema{
				Type:         schema.TypeString,
				Optional:     true,
				ForceNew:     true,
				ValidateFunc: validateSecurityRulePolicy,
			},

			"port_range": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"priority": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				ForceNew:     true,
				ValidateFunc: validateSecurityPriority,
			},

			"security_group_id": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"cidr_ip": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"source_security_group_id"},
			},

			"source_security_group_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"cidr_ip"},
			},

			"source_group_owner_account": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},
		},
	}
}

func resourceAliyunSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*AliyunClient)
	conn := client.ecsconn

	direction := d.Get("type").(string)
	sgId := d.Get("security_group_id").(string)
	ptl := d.Get("ip_protocol").(string)
	port := d.Get("port_range").(string)
	nicType := d.Get("nic_type").(string)

	var autherr error
	switch GroupRuleDirection(direction) {
	case GroupRuleIngress:
		args, err := buildAliyunSecurityIngressArgs(d, meta)
		if err != nil {
			return err
		}
		autherr = conn.AuthorizeSecurityGroup(args)
	case GroupRuleEgress:
		args, err := buildAliyunSecurityEgressArgs(d, meta)
		if err != nil {
			return err
		}
		autherr = conn.AuthorizeSecurityGroupEgress(args)
	default:
		return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'")
	}

	if autherr != nil {
		return fmt.Errorf(
			"Error authorizing security group rule type %s: %s",
			direction, autherr)
	}

	d.SetId(sgId + ":" + direction + ":" + ptl + ":" + port + ":" + nicType)

	return resourceAliyunSecurityGroupRuleRead(d, meta)
}

func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*AliyunClient)
	parts := strings.Split(d.Id(), ":")
	sgId := parts[0]
	direction := parts[1]
	ip_protocol := parts[2]
	port_range := parts[3]
	nic_type := parts[4]
	rule, err := client.DescribeSecurityGroupRule(sgId, direction, nic_type, ip_protocol, port_range)

	if err != nil {
		if NotFoundError(err) {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error SecurityGroup rule: %#v", err)
	}

	d.Set("type", rule.Direction)
	d.Set("ip_protocol", strings.ToLower(string(rule.IpProtocol)))
	d.Set("nic_type", rule.NicType)
	d.Set("policy", strings.ToLower(string(rule.Policy)))
	d.Set("port_range", rule.PortRange)
	d.Set("priority", rule.Priority)
	d.Set("security_group_id", sgId)
	//support source and desc by type
	if GroupRuleDirection(direction) == GroupRuleIngress {
		d.Set("cidr_ip", rule.SourceCidrIp)
		d.Set("source_security_group_id", rule.SourceGroupId)
		d.Set("source_group_owner_account", rule.SourceGroupOwnerAccount)
	} else {
		d.Set("cidr_ip", rule.DestCidrIp)
		d.Set("source_security_group_id", rule.DestGroupId)
		d.Set("source_group_owner_account", rule.DestGroupOwnerAccount)
	}

	return nil
}

func deleteSecurityGroupRule(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*AliyunClient)
	ruleType := d.Get("type").(string)

	if GroupRuleDirection(ruleType) == GroupRuleIngress {
		args, err := buildAliyunSecurityIngressArgs(d, meta)
		if err != nil {
			return err
		}
		revokeArgs := &ecs.RevokeSecurityGroupArgs{
			AuthorizeSecurityGroupArgs: *args,
		}
		return client.RevokeSecurityGroup(revokeArgs)
	}

	args, err := buildAliyunSecurityEgressArgs(d, meta)

	if err != nil {
		return err
	}
	revokeArgs := &ecs.RevokeSecurityGroupEgressArgs{
		AuthorizeSecurityGroupEgressArgs: *args,
	}
	return client.RevokeSecurityGroupEgress(revokeArgs)
}

func resourceAliyunSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*AliyunClient)
	parts := strings.Split(d.Id(), ":")
	sgId, direction, ip_protocol, port_range, nic_type := parts[0], parts[1], parts[2], parts[3], parts[4]

	return resource.Retry(5*time.Minute, func() *resource.RetryError {
		err := deleteSecurityGroupRule(d, meta)

		if err != nil {
			resource.RetryableError(fmt.Errorf("Security group rule in use - trying again while it is deleted."))
		}

		_, err = client.DescribeSecurityGroupRule(sgId, direction, nic_type, ip_protocol, port_range)
		if err != nil {
			if NotFoundError(err) {
				return nil
			}
			return resource.NonRetryableError(err)
		}

		return resource.RetryableError(fmt.Errorf("Security group rule in use - trying again while it is deleted."))
	})

}

func checkCidrAndSourceGroupId(cidrIp, sourceGroupId string) error {
	if cidrIp == "" && sourceGroupId == "" {
		return fmt.Errorf("Either cidr_ip or source_security_group_id is required.")
	}

	if cidrIp != "" && sourceGroupId != "" {
		return fmt.Errorf("You should set only one value of cidr_ip or source_security_group_id.")
	}
	return nil
}
func buildAliyunSecurityIngressArgs(d *schema.ResourceData, meta interface{}) (*ecs.AuthorizeSecurityGroupArgs, error) {
	conn := meta.(*AliyunClient).ecsconn

	args := &ecs.AuthorizeSecurityGroupArgs{
		RegionId: getRegion(d, meta),
	}

	if v := d.Get("ip_protocol").(string); v != "" {
		args.IpProtocol = ecs.IpProtocol(v)
	}

	if v := d.Get("port_range").(string); v != "" {
		args.PortRange = v
	}

	if v := d.Get("policy").(string); v != "" {
		args.Policy = ecs.PermissionPolicy(v)
	}

	if v := d.Get("priority").(int); v != 0 {
		args.Priority = v
	}

	if v := d.Get("cidr_ip").(string); v != "" {
		args.SourceCidrIp = v
	}

	if v := d.Get("source_security_group_id").(string); v != "" {
		args.SourceGroupId = v
	}

	if v := d.Get("source_group_owner_account").(string); v != "" {
		args.SourceGroupOwnerAccount = v
	}

	sgId := d.Get("security_group_id").(string)

	sgArgs := &ecs.DescribeSecurityGroupAttributeArgs{
		SecurityGroupId: sgId,
		RegionId:        getRegion(d, meta),
	}

	group, err := conn.DescribeSecurityGroupAttribute(sgArgs)
	if err != nil {
		return nil, fmt.Errorf("Error get security group %s error: %#v", sgId, err)
	}
	if (group != nil && group.VpcId != "") || args.SourceGroupId != "" {
		v := d.Get("nic_type").(string)
		if GroupRuleNicType(v) != GroupRuleIntranet {
			return nil, fmt.Errorf("When security group in the vpc or authorizing permission for source security group, " +
				"the nic_type must be 'intranet'.")
		}
		args.NicType = ecs.NicType(v)
	} else {
		if v := d.Get("nic_type").(string); v != "" {
			args.NicType = ecs.NicType(v)
		}
	}

	args.SecurityGroupId = sgId

	return args, nil
}

func buildAliyunSecurityEgressArgs(d *schema.ResourceData, meta interface{}) (*ecs.AuthorizeSecurityGroupEgressArgs, error) {
	conn := meta.(*AliyunClient).ecsconn

	args := &ecs.AuthorizeSecurityGroupEgressArgs{
		RegionId: getRegion(d, meta),
	}

	if v := d.Get("ip_protocol").(string); v != "" {
		args.IpProtocol = ecs.IpProtocol(v)
	}

	if v := d.Get("port_range").(string); v != "" {
		args.PortRange = v
	}

	if v := d.Get("policy").(string); v != "" {
		args.Policy = ecs.PermissionPolicy(v)
	}

	if v := d.Get("priority").(int); v != 0 {
		args.Priority = v
	}

	if v := d.Get("cidr_ip").(string); v != "" {
		args.DestCidrIp = v
	}

	if v := d.Get("source_security_group_id").(string); v != "" {
		args.DestGroupId = v
	}

	if v := d.Get("source_group_owner_account").(string); v != "" {
		args.DestGroupOwnerAccount = v
	}

	sgId := d.Get("security_group_id").(string)

	sgArgs := &ecs.DescribeSecurityGroupAttributeArgs{
		SecurityGroupId: sgId,
		RegionId:        getRegion(d, meta),
	}

	group, err := conn.DescribeSecurityGroupAttribute(sgArgs)
	if err != nil {
		return nil, fmt.Errorf("Error get security group %s error: %#v", sgId, err)
	}

	if (group != nil && group.VpcId != "") || args.DestGroupId != "" {
		v := d.Get("nic_type").(string)
		if GroupRuleNicType(v) != GroupRuleIntranet {
			return nil, fmt.Errorf("When security group in the vpc or authorizing permission for destination security group, " +
				"the nic_type must be 'intranet'.")
		}
		args.NicType = ecs.NicType(v)
	} else {
		if v := d.Get("nic_type").(string); v != "" {
			args.NicType = ecs.NicType(v)
		}
	}

	args.SecurityGroupId = sgId

	return args, nil
}
<commit_msg>fix security group intranet bug<commit_after>package alicloud

import (
	"fmt"
	"github.com/denverdino/aliyungo/ecs"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
	"strings"
	"time"
)

func resourceAliyunSecurityGroupRule() *schema.Resource {
	return &schema.Resource{
		Create: resourceAliyunSecurityGroupRuleCreate,
		Read:   resourceAliyunSecurityGroupRuleRead,
		Delete: resourceAliyunSecurityGroupRuleDelete,

		Schema: map[string]*schema.Schema{
			"type": &schema.Schema{
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateSecurityRuleType,
				Description:  "Type of rule, ingress (inbound) or egress (outbound).",
			},

			"ip_protocol": &schema.Schema{
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateSecurityRuleIpProtocol,
			},

			"nic_type": &schema.Schema{
				Type:         schema.TypeString,
				Optional:     true,
				ForceNew:     true,
				Computed:     true,
				ValidateFunc: validateSecurityRuleNicType,
			},

			"policy": &schema.Schema{
				Type:         schema.TypeString,
				Optional:     true,
				ForceNew:     true,
				ValidateFunc: validateSecurityRulePolicy,
			},

			"port_range": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"priority": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				ForceNew:     true,
				ValidateFunc: validateSecurityPriority,
			},

			"security_group_id": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"cidr_ip": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"source_security_group_id"},
			},

			"source_security_group_id": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"cidr_ip"},
			},

			"source_group_owner_account": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},
		},
	}
}

func resourceAliyunSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*AliyunClient)
	conn := client.ecsconn

	direction := d.Get("type").(string)
	sgId := d.Get("security_group_id").(string)
	ptl := d.Get("ip_protocol").(string)
	port := d.Get("port_range").(string)
	nicType := d.Get("nic_type").(string)

	var autherr error
	switch GroupRuleDirection(direction) {
	case GroupRuleIngress:
		args, err := buildAliyunSecurityIngressArgs(d, meta)
		if err != nil {
			return err
		}
		autherr = conn.AuthorizeSecurityGroup(args)
	case GroupRuleEgress:
		args, err := buildAliyunSecurityEgressArgs(d, meta)
		if err != nil {
			return err
		}
		autherr = conn.AuthorizeSecurityGroupEgress(args)
	default:
		return fmt.Errorf("Security Group Rule must be type 'ingress' or type 'egress'")
	}

	if autherr != nil {
		return fmt.Errorf(
			"Error authorizing security group rule type %s: %s",
			direction, autherr)
	}

	d.SetId(sgId + ":" + direction + ":" + ptl + ":" + port + ":" + nicType)

	return resourceAliyunSecurityGroupRuleRead(d, meta)
}

func resourceAliyunSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*AliyunClient)
	parts := strings.Split(d.Id(), ":")
	sgId := parts[0]
	direction := parts[1]
	ip_protocol := parts[2]
	port_range := parts[3]
	nic_type := parts[4]
	rule, err := client.DescribeSecurityGroupRule(sgId, direction, nic_type, ip_protocol, port_range)

	if err != nil {
		if NotFoundError(err) {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error SecurityGroup rule: %#v", err)
	}

	d.Set("type", rule.Direction)
	d.Set("ip_protocol", strings.ToLower(string(rule.IpProtocol)))
	d.Set("nic_type", rule.NicType)
	d.Set("policy", strings.ToLower(string(rule.Policy)))
	d.Set("port_range", rule.PortRange)
	d.Set("priority", rule.Priority)
	d.Set("security_group_id", sgId)
	//support source and desc by type
	if GroupRuleDirection(direction) == GroupRuleIngress {
		d.Set("cidr_ip", rule.SourceCidrIp)
		d.Set("source_security_group_id", rule.SourceGroupId)
		d.Set("source_group_owner_account", rule.SourceGroupOwnerAccount)
	} else {
		d.Set("cidr_ip", rule.DestCidrIp)
		d.Set("source_security_group_id", rule.DestGroupId)
		d.Set("source_group_owner_account", rule.DestGroupOwnerAccount)
	}

	return nil
}

func deleteSecurityGroupRule(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*AliyunClient)
	ruleType := d.Get("type").(string)

	if GroupRuleDirection(ruleType) == GroupRuleIngress {
		args, err := buildAliyunSecurityIngressArgs(d, meta)
		if err != nil {
			return err
		}
		revokeArgs := &ecs.RevokeSecurityGroupArgs{
			AuthorizeSecurityGroupArgs: *args,
		}
		return client.RevokeSecurityGroup(revokeArgs)
	}

	args, err := buildAliyunSecurityEgressArgs(d, meta)

	if err != nil {
		return err
	}
	revokeArgs := &ecs.RevokeSecurityGroupEgressArgs{
		AuthorizeSecurityGroupEgressArgs: *args,
	}
	return client.RevokeSecurityGroupEgress(revokeArgs)
}

func resourceAliyunSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*AliyunClient)
	parts := strings.Split(d.Id(), ":")
	sgId, direction, ip_protocol, port_range, nic_type := parts[0], parts[1], parts[2], parts[3], parts[4]

	return resource.Retry(5*time.Minute, func() *resource.RetryError {
		err := deleteSecurityGroupRule(d, meta)

		if err != nil {
			resource.RetryableError(fmt.Errorf("Security group rule in use - trying again while it is deleted."))
		}

		_, err = client.DescribeSecurityGroupRule(sgId, direction, nic_type, ip_protocol, port_range)
		if err != nil {
			if NotFoundError(err) {
				return nil
			}
			return resource.NonRetryableError(err)
		}

		return resource.RetryableError(fmt.Errorf("Security group rule in use - trying again while it is deleted."))
	})

}

func checkCidrAndSourceGroupId(cidrIp, sourceGroupId string) error {
	if cidrIp == "" && sourceGroupId == "" {
		return fmt.Errorf("Either cidr_ip or source_security_group_id is required.")
	}

	if cidrIp != "" && sourceGroupId != "" {
		return fmt.Errorf("You should set only one value of cidr_ip or source_security_group_id.")
	}
	return nil
}
func buildAliyunSecurityIngressArgs(d *schema.ResourceData, meta interface{}) (*ecs.AuthorizeSecurityGroupArgs, error) {
	conn := meta.(*AliyunClient).ecsconn

	args := &ecs.AuthorizeSecurityGroupArgs{
		RegionId: getRegion(d, meta),
	}

	if v := d.Get("ip_protocol").(string); v != "" {
		args.IpProtocol = ecs.IpProtocol(v)
	}

	if v := d.Get("port_range").(string); v != "" {
		args.PortRange = v
	}

	if v := d.Get("policy").(string); v != "" {
		args.Policy = ecs.PermissionPolicy(v)
	}

	if v := d.Get("priority").(int); v != 0 {
		args.Priority = v
	}

	if v := d.Get("cidr_ip").(string); v != "" {
		args.SourceCidrIp = v
	}

	if v := d.Get("source_security_group_id").(string); v != "" {
		args.SourceGroupId = v
	}

	if v := d.Get("source_group_owner_account").(string); v != "" {
		args.SourceGroupOwnerAccount = v
	}

	sgId := d.Get("security_group_id").(string)

	sgArgs := &ecs.DescribeSecurityGroupAttributeArgs{
		SecurityGroupId: sgId,
		RegionId:        getRegion(d, meta),
	}

	group, err := conn.DescribeSecurityGroupAttribute(sgArgs)
	if err != nil {
		return nil, fmt.Errorf("Error get security group %s error: %#v", sgId, err)
	}

	if v := d.Get("nic_type").(string); v != "" {
		if (group != nil && group.VpcId != "") || args.SourceGroupId != "" {
			if GroupRuleNicType(v) != GroupRuleIntranet {
				return nil, fmt.Errorf("When security group in the vpc or authorizing permission for source security group, " +
					"the nic_type must be 'intranet'.")
			}
		}
		args.NicType = ecs.NicType(v)
	}

	args.SecurityGroupId = sgId

	return args, nil
}

func buildAliyunSecurityEgressArgs(d *schema.ResourceData, meta interface{}) (*ecs.AuthorizeSecurityGroupEgressArgs, error) {
	conn := meta.(*AliyunClient).ecsconn

	args := &ecs.AuthorizeSecurityGroupEgressArgs{
		RegionId: getRegion(d, meta),
	}

	if v := d.Get("ip_protocol").(string); v != "" {
		args.IpProtocol = ecs.IpProtocol(v)
	}

	if v := d.Get("port_range").(string); v != "" {
		args.PortRange = v
	}

	if v := d.Get("policy").(string); v != "" {
		args.Policy = ecs.PermissionPolicy(v)
	}

	if v := d.Get("priority").(int); v != 0 {
		args.Priority = v
	}

	if v := d.Get("cidr_ip").(string); v != "" {
		args.DestCidrIp = v
	}

	if v := d.Get("source_security_group_id").(string); v != "" {
		args.DestGroupId = v
	}

	if v := d.Get("source_group_owner_account").(string); v != "" {
		args.DestGroupOwnerAccount = v
	}

	sgId := d.Get("security_group_id").(string)

	sgArgs := &ecs.DescribeSecurityGroupAttributeArgs{
		SecurityGroupId: sgId,
		RegionId:        getRegion(d, meta),
	}

	group, err := conn.DescribeSecurityGroupAttribute(sgArgs)
	if err != nil {
		return nil, fmt.Errorf("Error get security group %s error: %#v", sgId, err)
	}

	if v := d.Get("nic_type").(string); v != "" {
		if (group != nil && group.VpcId != "") || args.DestGroupId != "" {
			if GroupRuleNicType(v) != GroupRuleIntranet {
				return nil, fmt.Errorf("When security group in the vpc or authorizing permission for destination security group, " +
					"the nic_type must be 'intranet'.")
			}
		}
		args.NicType = ecs.NicType(v)
	}

	args.SecurityGroupId = sgId

	return args, nil
}
<|endoftext|>


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

package api

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"strconv"

	"gopkg.in/mgo.v2/bson"

	"github.com/tsuru/tsuru/auth"
	terrors "github.com/tsuru/tsuru/errors"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/rec"
)

type PoolsByTeam struct {
	Team  string
	Pools []string
}

func listPoolsToUser(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	u, err := t.User()
	if err != nil {
		return err
	}
	rec.Log(u.Email, "pool-list")
	teams, err := u.Teams()
	if err != nil {
		return err
	}
	poolsByTeam := []PoolsByTeam{}
	for _, t := range teams {
		pools, err := provision.ListPools(bson.M{"teams": t.Name})
		if err != nil {
			return err
		}
		pbt := PoolsByTeam{Team: t.Name, Pools: provision.GetPoolsNames(pools)}
		poolsByTeam = append(poolsByTeam, pbt)
	}
	publicPools, err := provision.ListPools(bson.M{"public": true})
	if err != nil {
		return err
	}
	p := map[string]interface{}{
		"pools_by_team": poolsByTeam,
		"public_pools":  publicPools,
	}
	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(p)
}

func addPoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var p provision.AddPoolOptions
	err = json.Unmarshal(b, &p)
	if err != nil {
		return err
	}
	forceAdd, _ := strconv.ParseBool(r.URL.Query().Get("force"))
	p.Force = forceAdd
	err = provision.AddPool(p)
	if err != nil {
		if err == provision.ErrDefaultPoolAlreadyExists {
			return &terrors.HTTP{
				Code:    http.StatusPreconditionFailed,
				Message: "Default pool already exists.",
			}
		}
		return err
	}
	return nil
}

func removePoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var params map[string]string
	err = json.Unmarshal(b, &params)
	if err != nil {
		return err
	}
	return provision.RemovePool(params["pool"])
}

func listPoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	pools, err := provision.ListPools(nil)
	if err != nil {
		return err
	}
	return json.NewEncoder(w).Encode(pools)
}

type teamsToPoolParams struct {
	Teams []string `json:"teams"`
}

func addTeamToPoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var params teamsToPoolParams
	err = json.Unmarshal(b, &params)
	if err != nil {
		return err
	}
	pool := r.URL.Query().Get(":name")
	return provision.AddTeamsToPool(pool, params.Teams)
}

func removeTeamToPoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var params teamsToPoolParams
	err = json.Unmarshal(b, &params)
	if err != nil {
		return err
	}
	pool := r.URL.Query().Get(":name")
	return provision.RemoveTeamsFromPool(pool, params.Teams)
}

func poolUpdateHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var params map[string]*bool
	err = json.Unmarshal(b, &params)
	if err != nil {
		return err
	}
	query := bson.M{}
	for k, v := range params {
		if v != nil {
			query[k] = *v
		}
	}
	poolName := r.URL.Query().Get(":name")
	forceDefault, _ := strconv.ParseBool(r.URL.Query().Get("force"))
	err = provision.PoolUpdate(poolName, query, forceDefault)
	if err != nil {
		if err == provision.ErrDefaultPoolAlreadyExists {
			return &terrors.HTTP{
				Code:    http.StatusPreconditionFailed,
				Message: "Default pool already exists.",
			}
		}
		return err
	}
	return nil
}
<commit_msg>api/pool: return 409 instead of 412 when default pool already exists<commit_after>// Copyright 2015 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package api

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"strconv"

	"gopkg.in/mgo.v2/bson"

	"github.com/tsuru/tsuru/auth"
	terrors "github.com/tsuru/tsuru/errors"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/rec"
)

type PoolsByTeam struct {
	Team  string
	Pools []string
}

func listPoolsToUser(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	u, err := t.User()
	if err != nil {
		return err
	}
	rec.Log(u.Email, "pool-list")
	teams, err := u.Teams()
	if err != nil {
		return err
	}
	poolsByTeam := []PoolsByTeam{}
	for _, t := range teams {
		pools, err := provision.ListPools(bson.M{"teams": t.Name})
		if err != nil {
			return err
		}
		pbt := PoolsByTeam{Team: t.Name, Pools: provision.GetPoolsNames(pools)}
		poolsByTeam = append(poolsByTeam, pbt)
	}
	publicPools, err := provision.ListPools(bson.M{"public": true})
	if err != nil {
		return err
	}
	p := map[string]interface{}{
		"pools_by_team": poolsByTeam,
		"public_pools":  publicPools,
	}
	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(p)
}

func addPoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var p provision.AddPoolOptions
	err = json.Unmarshal(b, &p)
	if err != nil {
		return err
	}
	forceAdd, _ := strconv.ParseBool(r.URL.Query().Get("force"))
	p.Force = forceAdd
	err = provision.AddPool(p)
	if err != nil {
		if err == provision.ErrDefaultPoolAlreadyExists {
			return &terrors.HTTP{
				Code:    http.StatusConflict,
				Message: "Default pool already exists.",
			}
		}
		return err
	}
	return nil
}

func removePoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var params map[string]string
	err = json.Unmarshal(b, &params)
	if err != nil {
		return err
	}
	return provision.RemovePool(params["pool"])
}

func listPoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	pools, err := provision.ListPools(nil)
	if err != nil {
		return err
	}
	return json.NewEncoder(w).Encode(pools)
}

type teamsToPoolParams struct {
	Teams []string `json:"teams"`
}

func addTeamToPoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var params teamsToPoolParams
	err = json.Unmarshal(b, &params)
	if err != nil {
		return err
	}
	pool := r.URL.Query().Get(":name")
	return provision.AddTeamsToPool(pool, params.Teams)
}

func removeTeamToPoolHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var params teamsToPoolParams
	err = json.Unmarshal(b, &params)
	if err != nil {
		return err
	}
	pool := r.URL.Query().Get(":name")
	return provision.RemoveTeamsFromPool(pool, params.Teams)
}

func poolUpdateHandler(w http.ResponseWriter, r *http.Request, t auth.Token) error {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	var params map[string]*bool
	err = json.Unmarshal(b, &params)
	if err != nil {
		return err
	}
	query := bson.M{}
	for k, v := range params {
		if v != nil {
			query[k] = *v
		}
	}
	poolName := r.URL.Query().Get(":name")
	forceDefault, _ := strconv.ParseBool(r.URL.Query().Get("force"))
	err = provision.PoolUpdate(poolName, query, forceDefault)
	if err != nil {
		if err == provision.ErrDefaultPoolAlreadyExists {
			return &terrors.HTTP{
				Code:    http.StatusPreconditionFailed,
				Message: "Default pool already exists.",
			}
		}
		return err
	}
	return nil
}
<|endoftext|>


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

// +build darwin dragonfly freebsd linux netbsd openbsd

package syscall

func Ioctl(fd, req, arg uintptr) (err Errno) {
	_, _, err = Syscall(SYS_IOCTL, fd, req, arg)
	return err
}
<commit_msg>syscall: build export_unix_test.go on solaris<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin dragonfly freebsd linux netbsd openbsd solaris

package syscall

func Ioctl(fd, req, arg uintptr) (err Errno) {
	_, _, err = Syscall(SYS_IOCTL, fd, req, arg)
	return err
}
<|endoftext|>


<commit_before>package gorm

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
)

// preloadCallback used to preload associations
func preloadCallback(scope *Scope) {
	if scope.Search.preload == nil || scope.HasError() {
		return
	}

	var (
		preloadedMap = map[string]bool{}
		fields       = scope.Fields()
	)

	for _, preload := range scope.Search.preload {
		var (
			preloadFields = strings.Split(preload.schema, ".")
			currentScope  = scope
			currentFields = fields
		)

		for idx, preloadField := range preloadFields {
			var currentPreloadConditions []interface{}

			// if not preloaded
			if preloadKey := strings.Join(preloadFields[:idx+1], "."); !preloadedMap[preloadKey] {

				// assign search conditions to last preload
				if idx == len(preloadFields)-1 {
					currentPreloadConditions = preload.conditions
				}

				for _, field := range currentFields {
					if field.Name != preloadField || field.Relationship == nil {
						continue
					}

					switch field.Relationship.Kind {
					case "has_one":
						currentScope.handleHasOnePreload(field, currentPreloadConditions)
					case "has_many":
						currentScope.handleHasManyPreload(field, currentPreloadConditions)
					case "belongs_to":
						currentScope.handleBelongsToPreload(field, currentPreloadConditions)
					case "many_to_many":
						currentScope.handleManyToManyPreload(field, currentPreloadConditions)
					default:
						scope.Err(errors.New("unsupported relation"))
					}

					preloadedMap[preloadKey] = true
					break
				}

				if !preloadedMap[preloadKey] {
					scope.Err(fmt.Errorf("can't preload field %s for %s", preloadField, currentScope.GetModelStruct().ModelType))
					return
				}
			}

			// preload next level
			if idx < len(preloadFields)-1 {
				currentScope = currentScope.getColumnAsScope(preloadField)
				currentFields = currentScope.Fields()
			}
		}
	}
}

func (scope *Scope) generatePreloadDBWithConditions(conditions []interface{}) (*DB, []interface{}) {
	var (
		preloadDB         = scope.NewDB()
		preloadConditions []interface{}
	)

	for _, condition := range conditions {
		if scopes, ok := condition.(func(*DB) *DB); ok {
			preloadDB = scopes(preloadDB)
		} else {
			preloadConditions = append(preloadConditions, condition)
		}
	}

	return preloadDB, preloadConditions
}

// handleHasOnePreload used to preload has one associations
func (scope *Scope) handleHasOnePreload(field *Field, conditions []interface{}) {
	relation := field.Relationship

	// get relations's primary keys
	primaryKeys := scope.getColumnAsArray(relation.AssociationForeignFieldNames, scope.Value)
	if len(primaryKeys) == 0 {
		return
	}

	// preload conditions
	preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)

	// find relations
	results := makeSlice(field.Struct.Type)
	scope.Err(preloadDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.ForeignDBNames), toQueryMarks(primaryKeys)), toQueryValues(primaryKeys)...).Find(results, preloadConditions...).Error)

	// assign find results
	var (
		resultsValue       = indirect(reflect.ValueOf(results))
		indirectScopeValue = scope.IndirectValue()
	)

	for i := 0; i < resultsValue.Len(); i++ {
		result := resultsValue.Index(i)
		if indirectScopeValue.Kind() == reflect.Slice {
			foreignValues := getValueFromFields(result, relation.ForeignFieldNames)
			for j := 0; j < indirectScopeValue.Len(); j++ {
				if indirectValue := indirect(indirectScopeValue.Index(j)); equalAsString(getValueFromFields(indirectValue, relation.AssociationForeignFieldNames), foreignValues) {
					indirectValue.FieldByName(field.Name).Set(result)
					break
				}
			}
		} else {
			scope.Err(field.Set(result))
		}
	}
}

// handleHasManyPreload used to preload has many associations
func (scope *Scope) handleHasManyPreload(field *Field, conditions []interface{}) {
	relation := field.Relationship

	// get relations's primary keys
	primaryKeys := scope.getColumnAsArray(relation.AssociationForeignFieldNames, scope.Value)
	if len(primaryKeys) == 0 {
		return
	}

	// preload conditions
	preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)

	// find relations
	results := makeSlice(field.Struct.Type)
	scope.Err(preloadDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.ForeignDBNames), toQueryMarks(primaryKeys)), toQueryValues(primaryKeys)...).Find(results, preloadConditions...).Error)

	// assign find results
	var (
		resultsValue       = indirect(reflect.ValueOf(results))
		indirectScopeValue = scope.IndirectValue()
	)

	if indirectScopeValue.Kind() == reflect.Slice {
		preloadMap := make(map[string][]reflect.Value)
		for i := 0; i < resultsValue.Len(); i++ {
			result := resultsValue.Index(i)
			foreignValues := getValueFromFields(result, relation.ForeignFieldNames)
			preloadMap[toString(foreignValues)] = append(preloadMap[toString(foreignValues)], result)
		}

		objects := scope.IndirectValue()
		for j := 0; j < objects.Len(); j++ {
			object := reflect.Indirect(objects.Index(j))
			objectRealValue := getValueFromFields(object, relation.AssociationForeignFieldNames)
			objectStringValue := toString(objectRealValue)
			if results, ok := preloadMap[objectStringValue]; ok {
				if object.Kind() == reflect.Ptr {
					object = object.Elem()
				}
				f := object.FieldByName(field.Name)
				f.Set(reflect.Append(f, results...))
			}
		}
	} else {
		scope.Err(field.Set(resultsValue))
	}
}

// handleBelongsToPreload used to preload belongs to associations
func (scope *Scope) handleBelongsToPreload(field *Field, conditions []interface{}) {
	relation := field.Relationship

	// preload conditions
	preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)

	// get relations's primary keys
	primaryKeys := scope.getColumnAsArray(relation.ForeignFieldNames, scope.Value)
	if len(primaryKeys) == 0 {
		return
	}

	// find relations
	results := makeSlice(field.Struct.Type)
	scope.Err(preloadDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.AssociationForeignDBNames), toQueryMarks(primaryKeys)), toQueryValues(primaryKeys)...).Find(results, preloadConditions...).Error)

	// assign find results
	var (
		resultsValue       = indirect(reflect.ValueOf(results))
		indirectScopeValue = scope.IndirectValue()
	)

	for i := 0; i < resultsValue.Len(); i++ {
		result := resultsValue.Index(i)
		if indirectScopeValue.Kind() == reflect.Slice {
			value := getValueFromFields(result, relation.AssociationForeignFieldNames)
			for j := 0; j < indirectScopeValue.Len(); j++ {
				object := indirect(indirectScopeValue.Index(j))
				if equalAsString(getValueFromFields(object, relation.ForeignFieldNames), value) {
					object.FieldByName(field.Name).Set(result)
				}
			}
		} else {
			scope.Err(field.Set(result))
		}
	}
}

// handleManyToManyPreload used to preload many to many associations
func (scope *Scope) handleManyToManyPreload(field *Field, conditions []interface{}) {
	var (
		relation         = field.Relationship
		joinTableHandler = relation.JoinTableHandler
		fieldType        = field.Struct.Type.Elem()
		foreignKeyValue  interface{}
		foreignKeyType   = reflect.ValueOf(&foreignKeyValue).Type()
		linkHash         = map[string][]reflect.Value{}
		isPtr            bool
	)

	if fieldType.Kind() == reflect.Ptr {
		isPtr = true
		fieldType = fieldType.Elem()
	}

	var sourceKeys = []string{}
	for _, key := range joinTableHandler.SourceForeignKeys() {
		sourceKeys = append(sourceKeys, key.DBName)
	}

	// preload conditions
	preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)

	// generate query with join table
	newScope := scope.New(reflect.New(fieldType).Interface())
	preloadDB = preloadDB.Table(newScope.TableName()).Select("*")
	preloadDB = joinTableHandler.JoinWith(joinTableHandler, preloadDB, scope.Value)

	// preload inline conditions
	if len(preloadConditions) > 0 {
		preloadDB = preloadDB.Where(preloadConditions[0], preloadConditions[1:]...)
	}

	rows, err := preloadDB.Rows()

	if scope.Err(err) != nil {
		return
	}
	defer rows.Close()

	columns, _ := rows.Columns()
	for rows.Next() {
		var (
			elem   = reflect.New(fieldType).Elem()
			fields = scope.New(elem.Addr().Interface()).Fields()
		)

		// register foreign keys in join tables
		var joinTableFields []*Field
		for _, sourceKey := range sourceKeys {
			joinTableFields = append(joinTableFields, &Field{StructField: &StructField{DBName: sourceKey, IsNormal: true}, Field: reflect.New(foreignKeyType).Elem()})
		}

		scope.scan(rows, columns, append(fields, joinTableFields...))

		var foreignKeys = make([]interface{}, len(sourceKeys))
		// generate hashed forkey keys in join table
		for idx, joinTableField := range joinTableFields {
			if !joinTableField.Field.IsNil() {
				foreignKeys[idx] = joinTableField.Field.Elem().Interface()
			}
		}
		hashedSourceKeys := toString(foreignKeys)

		if isPtr {
			linkHash[hashedSourceKeys] = append(linkHash[hashedSourceKeys], elem.Addr())
		} else {
			linkHash[hashedSourceKeys] = append(linkHash[hashedSourceKeys], elem)
		}
	}

	// assign find results
	var (
		indirectScopeValue = scope.IndirectValue()
		fieldsSourceMap    = map[string]reflect.Value{}
		foreignFieldNames  = []string{}
	)

	for _, dbName := range relation.ForeignFieldNames {
		if field, ok := scope.FieldByName(dbName); ok {
			foreignFieldNames = append(foreignFieldNames, field.Name)
		}
	}

	if indirectScopeValue.Kind() == reflect.Slice {
		for j := 0; j < indirectScopeValue.Len(); j++ {
			object := indirect(indirectScopeValue.Index(j))
			fieldsSourceMap[toString(getValueFromFields(object, foreignFieldNames))] = object.FieldByName(field.Name)
		}
	} else if indirectScopeValue.IsValid() {
		fieldsSourceMap[toString(getValueFromFields(indirectScopeValue, foreignFieldNames))] = indirectScopeValue.FieldByName(field.Name)
	}

	for source, link := range linkHash {
		fieldsSourceMap[source].Set(reflect.Append(fieldsSourceMap[source], link...))
	}
}
<commit_msg>Refactor handleHasManyPreload<commit_after>package gorm

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
)

// preloadCallback used to preload associations
func preloadCallback(scope *Scope) {
	if scope.Search.preload == nil || scope.HasError() {
		return
	}

	var (
		preloadedMap = map[string]bool{}
		fields       = scope.Fields()
	)

	for _, preload := range scope.Search.preload {
		var (
			preloadFields = strings.Split(preload.schema, ".")
			currentScope  = scope
			currentFields = fields
		)

		for idx, preloadField := range preloadFields {
			var currentPreloadConditions []interface{}

			// if not preloaded
			if preloadKey := strings.Join(preloadFields[:idx+1], "."); !preloadedMap[preloadKey] {

				// assign search conditions to last preload
				if idx == len(preloadFields)-1 {
					currentPreloadConditions = preload.conditions
				}

				for _, field := range currentFields {
					if field.Name != preloadField || field.Relationship == nil {
						continue
					}

					switch field.Relationship.Kind {
					case "has_one":
						currentScope.handleHasOnePreload(field, currentPreloadConditions)
					case "has_many":
						currentScope.handleHasManyPreload(field, currentPreloadConditions)
					case "belongs_to":
						currentScope.handleBelongsToPreload(field, currentPreloadConditions)
					case "many_to_many":
						currentScope.handleManyToManyPreload(field, currentPreloadConditions)
					default:
						scope.Err(errors.New("unsupported relation"))
					}

					preloadedMap[preloadKey] = true
					break
				}

				if !preloadedMap[preloadKey] {
					scope.Err(fmt.Errorf("can't preload field %s for %s", preloadField, currentScope.GetModelStruct().ModelType))
					return
				}
			}

			// preload next level
			if idx < len(preloadFields)-1 {
				currentScope = currentScope.getColumnAsScope(preloadField)
				currentFields = currentScope.Fields()
			}
		}
	}
}

func (scope *Scope) generatePreloadDBWithConditions(conditions []interface{}) (*DB, []interface{}) {
	var (
		preloadDB         = scope.NewDB()
		preloadConditions []interface{}
	)

	for _, condition := range conditions {
		if scopes, ok := condition.(func(*DB) *DB); ok {
			preloadDB = scopes(preloadDB)
		} else {
			preloadConditions = append(preloadConditions, condition)
		}
	}

	return preloadDB, preloadConditions
}

// handleHasOnePreload used to preload has one associations
func (scope *Scope) handleHasOnePreload(field *Field, conditions []interface{}) {
	relation := field.Relationship

	// get relations's primary keys
	primaryKeys := scope.getColumnAsArray(relation.AssociationForeignFieldNames, scope.Value)
	if len(primaryKeys) == 0 {
		return
	}

	// preload conditions
	preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)

	// find relations
	results := makeSlice(field.Struct.Type)
	scope.Err(preloadDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.ForeignDBNames), toQueryMarks(primaryKeys)), toQueryValues(primaryKeys)...).Find(results, preloadConditions...).Error)

	// assign find results
	var (
		resultsValue       = indirect(reflect.ValueOf(results))
		indirectScopeValue = scope.IndirectValue()
	)

	for i := 0; i < resultsValue.Len(); i++ {
		result := resultsValue.Index(i)
		if indirectScopeValue.Kind() == reflect.Slice {
			foreignValues := getValueFromFields(result, relation.ForeignFieldNames)
			for j := 0; j < indirectScopeValue.Len(); j++ {
				if indirectValue := indirect(indirectScopeValue.Index(j)); equalAsString(getValueFromFields(indirectValue, relation.AssociationForeignFieldNames), foreignValues) {
					indirectValue.FieldByName(field.Name).Set(result)
					break
				}
			}
		} else {
			scope.Err(field.Set(result))
		}
	}
}

// handleHasManyPreload used to preload has many associations
func (scope *Scope) handleHasManyPreload(field *Field, conditions []interface{}) {
	relation := field.Relationship

	// get relations's primary keys
	primaryKeys := scope.getColumnAsArray(relation.AssociationForeignFieldNames, scope.Value)
	if len(primaryKeys) == 0 {
		return
	}

	// preload conditions
	preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)

	// find relations
	results := makeSlice(field.Struct.Type)
	scope.Err(preloadDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.ForeignDBNames), toQueryMarks(primaryKeys)), toQueryValues(primaryKeys)...).Find(results, preloadConditions...).Error)

	// assign find results
	var (
		resultsValue       = indirect(reflect.ValueOf(results))
		indirectScopeValue = scope.IndirectValue()
	)

	if indirectScopeValue.Kind() == reflect.Slice {
		preloadMap := make(map[string][]reflect.Value)
		for i := 0; i < resultsValue.Len(); i++ {
			result := resultsValue.Index(i)
			foreignValues := getValueFromFields(result, relation.ForeignFieldNames)
			preloadMap[toString(foreignValues)] = append(preloadMap[toString(foreignValues)], result)
		}

		for j := 0; j < indirectScopeValue.Len(); j++ {
			object := indirect(indirectScopeValue.Index(j))
			objectRealValue := getValueFromFields(object, relation.AssociationForeignFieldNames)
			if results, ok := preloadMap[toString(objectRealValue)]; ok {
				f := object.FieldByName(field.Name)
				f.Set(reflect.Append(f, results...))
			}
		}
	} else {
		scope.Err(field.Set(resultsValue))
	}
}

// handleBelongsToPreload used to preload belongs to associations
func (scope *Scope) handleBelongsToPreload(field *Field, conditions []interface{}) {
	relation := field.Relationship

	// preload conditions
	preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)

	// get relations's primary keys
	primaryKeys := scope.getColumnAsArray(relation.ForeignFieldNames, scope.Value)
	if len(primaryKeys) == 0 {
		return
	}

	// find relations
	results := makeSlice(field.Struct.Type)
	scope.Err(preloadDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.AssociationForeignDBNames), toQueryMarks(primaryKeys)), toQueryValues(primaryKeys)...).Find(results, preloadConditions...).Error)

	// assign find results
	var (
		resultsValue       = indirect(reflect.ValueOf(results))
		indirectScopeValue = scope.IndirectValue()
	)

	for i := 0; i < resultsValue.Len(); i++ {
		result := resultsValue.Index(i)
		if indirectScopeValue.Kind() == reflect.Slice {
			value := getValueFromFields(result, relation.AssociationForeignFieldNames)
			for j := 0; j < indirectScopeValue.Len(); j++ {
				object := indirect(indirectScopeValue.Index(j))
				if equalAsString(getValueFromFields(object, relation.ForeignFieldNames), value) {
					object.FieldByName(field.Name).Set(result)
				}
			}
		} else {
			scope.Err(field.Set(result))
		}
	}
}

// handleManyToManyPreload used to preload many to many associations
func (scope *Scope) handleManyToManyPreload(field *Field, conditions []interface{}) {
	var (
		relation         = field.Relationship
		joinTableHandler = relation.JoinTableHandler
		fieldType        = field.Struct.Type.Elem()
		foreignKeyValue  interface{}
		foreignKeyType   = reflect.ValueOf(&foreignKeyValue).Type()
		linkHash         = map[string][]reflect.Value{}
		isPtr            bool
	)

	if fieldType.Kind() == reflect.Ptr {
		isPtr = true
		fieldType = fieldType.Elem()
	}

	var sourceKeys = []string{}
	for _, key := range joinTableHandler.SourceForeignKeys() {
		sourceKeys = append(sourceKeys, key.DBName)
	}

	// preload conditions
	preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions)

	// generate query with join table
	newScope := scope.New(reflect.New(fieldType).Interface())
	preloadDB = preloadDB.Table(newScope.TableName()).Select("*")
	preloadDB = joinTableHandler.JoinWith(joinTableHandler, preloadDB, scope.Value)

	// preload inline conditions
	if len(preloadConditions) > 0 {
		preloadDB = preloadDB.Where(preloadConditions[0], preloadConditions[1:]...)
	}

	rows, err := preloadDB.Rows()

	if scope.Err(err) != nil {
		return
	}
	defer rows.Close()

	columns, _ := rows.Columns()
	for rows.Next() {
		var (
			elem   = reflect.New(fieldType).Elem()
			fields = scope.New(elem.Addr().Interface()).Fields()
		)

		// register foreign keys in join tables
		var joinTableFields []*Field
		for _, sourceKey := range sourceKeys {
			joinTableFields = append(joinTableFields, &Field{StructField: &StructField{DBName: sourceKey, IsNormal: true}, Field: reflect.New(foreignKeyType).Elem()})
		}

		scope.scan(rows, columns, append(fields, joinTableFields...))

		var foreignKeys = make([]interface{}, len(sourceKeys))
		// generate hashed forkey keys in join table
		for idx, joinTableField := range joinTableFields {
			if !joinTableField.Field.IsNil() {
				foreignKeys[idx] = joinTableField.Field.Elem().Interface()
			}
		}
		hashedSourceKeys := toString(foreignKeys)

		if isPtr {
			linkHash[hashedSourceKeys] = append(linkHash[hashedSourceKeys], elem.Addr())
		} else {
			linkHash[hashedSourceKeys] = append(linkHash[hashedSourceKeys], elem)
		}
	}

	// assign find results
	var (
		indirectScopeValue = scope.IndirectValue()
		fieldsSourceMap    = map[string]reflect.Value{}
		foreignFieldNames  = []string{}
	)

	for _, dbName := range relation.ForeignFieldNames {
		if field, ok := scope.FieldByName(dbName); ok {
			foreignFieldNames = append(foreignFieldNames, field.Name)
		}
	}

	if indirectScopeValue.Kind() == reflect.Slice {
		for j := 0; j < indirectScopeValue.Len(); j++ {
			object := indirect(indirectScopeValue.Index(j))
			fieldsSourceMap[toString(getValueFromFields(object, foreignFieldNames))] = object.FieldByName(field.Name)
		}
	} else if indirectScopeValue.IsValid() {
		fieldsSourceMap[toString(getValueFromFields(indirectScopeValue, foreignFieldNames))] = indirectScopeValue.FieldByName(field.Name)
	}

	for source, link := range linkHash {
		fieldsSourceMap[source].Set(reflect.Append(fieldsSourceMap[source], link...))
	}
}
<|endoftext|>


<commit_before>package lb

import (
	"archive/tar"
	"bytes"
	"fmt"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"text/template"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/ehazlett/interlock/config"
	"github.com/ehazlett/interlock/ext"
	"github.com/ehazlett/interlock/ext/lb/haproxy"
	"github.com/ehazlett/interlock/ext/lb/nginx"
	"github.com/ehazlett/ttlcache"
	"github.com/samalba/dockerclient"
)

const (
	pluginName      = "lb"
	ReloadThreshold = time.Millisecond * 500
)

var (
	errChan                 chan (error)
	restartChan             = make(chan bool)
	lbUpdateChan            chan (bool)
	proxyNetworkCleanupChan chan ([]proxyContainerNetworkConfig)
)

type proxyContainerNetworkConfig struct {
	ContainerID   string
	ProxyNetworks map[string]string
}

type LoadBalancerBackend interface {
	Name() string
	ConfigPath() string
	GenerateProxyConfig(c []dockerclient.Container) (interface{}, error)
	Template() string
	Reload(proxyContainers []dockerclient.Container) error
}

type LoadBalancer struct {
	cfg     *config.ExtensionConfig
	client  *dockerclient.DockerClient
	cache   *ttlcache.TTLCache
	lock    *sync.Mutex
	backend LoadBalancerBackend
	nodeID  string
}

func log() *logrus.Entry {
	return logrus.WithFields(logrus.Fields{
		"ext": pluginName,
	})
}

type eventArgs struct {
	Image string
}

func NewLoadBalancer(c *config.ExtensionConfig, client *dockerclient.DockerClient) (*LoadBalancer, error) {
	// parse config base dir
	c.ConfigBasePath = filepath.Dir(c.ConfigPath)

	errChan = make(chan error)
	go func() {
		for err := range errChan {
			log().Error(err)
		}
	}()

	lbUpdateChan = make(chan bool)

	proxyNetworkCleanupChan = make(chan []proxyContainerNetworkConfig)

	cache, err := ttlcache.NewTTLCache(ReloadThreshold)
	if err != nil {
		return nil, err
	}

	cache.SetCallback(func(k string, v interface{}) {
		log().Debugf("triggering reload from cache")
		lbUpdateChan <- true
	})

	// load nodeID
	nodeID, err := getNodeID()
	if err != nil {
		return nil, err
	}

	log().Infof("interlock node: id=%s", nodeID)

	extension := &LoadBalancer{
		cfg:    c,
		client: client,
		cache:  cache,
		lock:   &sync.Mutex{},
		nodeID: nodeID,
	}

	// select backend
	switch c.Name {
	case "haproxy":
		p, err := haproxy.NewHAProxyLoadBalancer(c, client)
		if err != nil {
			return nil, fmt.Errorf("error setting backend: %s", err)
		}
		extension.backend = p
	case "nginx":
		p, err := nginx.NewNginxLoadBalancer(c, client)
		if err != nil {
			return nil, fmt.Errorf("error setting backend: %s", err)
		}
		extension.backend = p
	default:
		return nil, fmt.Errorf("unknown load balancer backend: %s", c.Name)
	}

	// proxy network cleanup chan
	// this waits for a reload event and removes the proxy containers
	// from unused proxy networks
	go func() {
		for {
			nc := <-proxyNetworkCleanupChan

			log().Debug("checking to remove proxy containers from networks")

			for _, c := range nc {
				cID := c.ContainerID
				cnt, err := client.InspectContainer(cID)
				if err != nil {
					log().Errorf("error inspecting proxy container: id=%s err=%s", cID, err)
					continue
				}

				for net, _ := range cnt.NetworkSettings.Networks {
					// HACK?: special ignore case for bridge
					if net == "bridge" {
						continue
					}

					if _, ok := c.ProxyNetworks[net]; !ok {
						// attempt to disconnect
						log().Debugf("disconnecting proxy container from network: id=%s net=%s", cID, net)

						retries := 5
						for i := 0; i < retries; i++ {
							err := client.DisconnectNetwork(net, cID, false)
							if err == nil {
								break
							}

							log().Warnf("unable to disconnect proxy container %s from network %s (retrying): %s", cID, net, err)

							// wait for network to disconnect
							time.Sleep(2 * time.Second)
						}
					}

				}
			}
		}
	}()

	// lbUpdateChan handler
	go func() {
		for range lbUpdateChan {
			log().Debug("checking to reload")
			if v := extension.cache.Get("reload"); v != nil {
				log().Debug("skipping reload: too many requests")
				continue
			}

			start := time.Now()

			log().Debug("updating load balancers")

			containers, err := client.ListContainers(true, false, "")
			if err != nil {
				errChan <- err
				continue
			}

			// generate proxy config
			log().Debug("generating proxy config")
			cfg, err := extension.backend.GenerateProxyConfig(containers)
			if err != nil {
				errChan <- err
				continue
			}

			// save proxy config
			configPath := extension.backend.ConfigPath()
			log().Debugf("proxy config path: %s", configPath)

			proxyNetworks := map[string]string{}

			proxyContainers, err := extension.ProxyContainers(extension.backend.Name())
			if err != nil {
				errChan <- err
				continue
			}

			// save config
			log().Debug("saving proxy config")
			if err := extension.SaveConfig(configPath, cfg, proxyContainers); err != nil {
				errChan <- err
				continue
			}

			// connect to networks
			switch extension.backend.Name() {
			case "nginx":
				proxyConfig := cfg.(*nginx.Config)
				proxyNetworks = proxyConfig.Networks

			case "haproxy":
				proxyConfig := cfg.(*haproxy.Config)
				proxyNetworks = proxyConfig.Networks
			default:
				errChan <- fmt.Errorf("unable to connect to networks; unknown backend: %s", extension.backend.Name())
				continue
			}

			proxyContainerNetworkConfigs := []proxyContainerNetworkConfig{}

			for _, cnt := range proxyContainers {
				proxyContainerNetworkConfigs = append(proxyContainerNetworkConfigs, proxyContainerNetworkConfig{
					ContainerID:   cnt.Id,
					ProxyNetworks: proxyNetworks,
				})
				for net, _ := range proxyNetworks {
					if _, ok := cnt.NetworkSettings.Networks[net]; !ok {
						log().Debugf("connecting proxy container %s to network %s", cnt.Id, net)

						// connect
						if err := client.ConnectNetwork(net, cnt.Id); err != nil {
							log().Warnf("unable to connect container %s to network %s: %s", cnt.Id, net, err)
							continue
						}
					}
				}
			}

			// get interlock nodes
			interlockNodes := []dockerclient.Container{}

			for _, cnt := range containers {
				// always include self container
				if cnt.Id == nodeID {
					interlockNodes = append(interlockNodes, cnt)
					continue
				}

				cInfo, err := client.InspectContainer(cnt.Id)
				if err != nil {
					log().Errorf("unable to inspect interlock container: %s", err)
					continue
				}

				if strings.Index(cInfo.Config.Image, "interlock") > 0 {
					if _, ok := cInfo.Config.Labels[ext.InterlockAppLabel]; ok {
						interlockNodes = append(interlockNodes, cnt)
					}
				}
			}

			proxyContainersToRestart := extension.proxyContainersToRestart(interlockNodes, proxyContainers)

			// trigger reload
			log().Debug("signaling reload")
			if err := extension.backend.Reload(proxyContainersToRestart); err != nil {
				errChan <- err
				continue
			}

			d := time.Since(start)
			duration := float64(d.Seconds() * float64(1000))

			log().Debug("triggering proxy network cleanup")
			proxyNetworkCleanupChan <- proxyContainerNetworkConfigs

			log().Infof("reload duration: %0.2fms", duration)
		}
	}()

	return extension, nil
}

func (l *LoadBalancer) Name() string {
	return pluginName
}

func (l *LoadBalancer) ProxyContainers(name string) ([]dockerclient.Container, error) {
	containers, err := l.client.ListContainers(true, false, "")
	if err != nil {
		return nil, err
	}

	proxyContainers := []dockerclient.Container{}

	// find interlock proxy containers
	for _, cnt := range containers {
		if v, ok := cnt.Labels[ext.InterlockExtNameLabel]; ok && v == l.backend.Name() {
			proxyContainers = append(proxyContainers, cnt)
		}
	}

	return proxyContainers, nil
}

func (l *LoadBalancer) SaveConfig(configPath string, cfg interface{}, proxyContainers []dockerclient.Container) error {
	t := template.New("lb")
	confTmpl := l.backend.Template()

	var c bytes.Buffer

	tmpl, err := t.Parse(confTmpl)
	if err != nil {
		return err
	}

	// cast to config type
	switch l.backend.Name() {
	case "nginx":
		config := cfg.(*nginx.Config)
		if err := tmpl.Execute(&c, config); err != nil {
			return err
		}
	case "haproxy":
		config := cfg.(*haproxy.Config)
		if err := tmpl.Execute(&c, config); err != nil {
			return err
		}
	default:
		return fmt.Errorf("unknown backend type: %s", l.backend.Name())
	}

	fName := path.Base(l.backend.ConfigPath())
	proxyConfigPath := path.Dir(l.backend.ConfigPath())

	data := c.Bytes()

	// copy to proxy nodes
	for _, cnt := range proxyContainers {
		log().Debugf("updating proxy config: id=%s", cnt.Id)
		// create tar stream to copy
		buf := new(bytes.Buffer)
		tw := tar.NewWriter(buf)
		hdr := &tar.Header{
			Name: fName,
			Mode: 0644,
			Size: int64(len(data)),
		}

		if err := tw.WriteHeader(hdr); err != nil {
			return fmt.Errorf("error writing proxy config header: %s", err)
		}

		if _, err := tw.Write(data); err != nil {
			return fmt.Errorf("error writing proxy config: %s", err)
		}

		if err := tw.Close(); err != nil {
			return fmt.Errorf("error closing tar writer: %s", err)
		}

		if err := l.client.CopyToContainer(cnt.Id, proxyConfigPath, buf); err != nil {
			log().Errorf("error copying proxy config: %s", err)
			continue
		}
	}

	return nil
}

func (l *LoadBalancer) HandleEvent(event *dockerclient.Event) error {
	reload := false

	// container event
	switch event.Status {
	case "start":
		reload = l.isExposedContainer(event.ID)
	case "stop":
		reload = l.isExposedContainer(event.ID)

		// wait for container to stop
		time.Sleep(time.Millisecond * 250)
	case "interlock-start", "destroy":
		// force reload
		reload = true
	}

	// network event
	switch event.Action {
	case "connect", "disconnect":
		// since event.ID is blank on an action we must get the proper ID
		id, ok := event.Actor.Attributes["container"]
		if !ok {
			return fmt.Errorf("unable to detect container id for network event")
		}

		reload = l.isExposedContainer(id)
	}

	if reload {
		log().Debug("triggering reload")
		l.cache.Set("reload", true)
	}

	return nil
}

// proxyContainersToRestart returns a slice of proxy containers to restart
// based upon this instance's hash
func (l *LoadBalancer) proxyContainersToRestart(nodes []dockerclient.Container, proxyContainers []dockerclient.Container) []dockerclient.Container {
	numNodes := len(nodes)
	if numNodes == 0 {
		return nil
	}

	log().Debugf("calculating restart across interlock nodes: num=%d", numNodes)

	sub := len(proxyContainers) / numNodes

	work := map[string][]dockerclient.Container{}

	for i := 0; i < len(nodes)-1; i++ {
		p, n := proxyContainers[:len(proxyContainers)-sub], proxyContainers[len(proxyContainers)-sub:]
		proxyContainers = p
		work[nodes[i].Id] = n
	}

	work[nodes[len(nodes)-1].Id] = proxyContainers

	containersToRestart := work[l.nodeID]

	ids := []string{}
	for _, c := range containersToRestart {
		ids = append(ids, c.Id[:8])
	}

	log().Debugf("proxy containers to restart: num=%d containers=%s", len(containersToRestart), strings.Join(ids, ","))

	return containersToRestart
}

func (l *LoadBalancer) isExposedContainer(id string) bool {
	log().Debugf("inspecting container: id=%s", id)
	c, err := l.client.InspectContainer(id)
	if err != nil {
		// ignore inspect errors
		log().Errorf("error: id=%s err=%s", id, err)
		return false
	}

	log().Debugf("checking container labels: id=%s", id)
	// ignore proxy containers
	if _, ok := c.Config.Labels[ext.InterlockExtNameLabel]; ok {
		log().Debugf("ignoring proxy container: id=%s", id)
		return false
	}

	if _, ok := c.Config.Labels[ext.InterlockAppLabel]; ok {
		log().Debugf("ignoring interlock container: id=%s", id)
		return false
	}

	log().Debugf("checking container ports: id=%s", id)
	// ignore containers without exposed ports
	if len(c.Config.ExposedPorts) == 0 {
		log().Debugf("no ports exposed; ignoring: id=%s", id)
		return false
	}

	log().Debugf("container is monitored; triggering reload: id=%s", id)
	return true
}
<commit_msg>fixed issue with restart proxy containers<commit_after>package lb

import (
	"archive/tar"
	"bytes"
	"fmt"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"text/template"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/ehazlett/interlock/config"
	"github.com/ehazlett/interlock/ext"
	"github.com/ehazlett/interlock/ext/lb/haproxy"
	"github.com/ehazlett/interlock/ext/lb/nginx"
	"github.com/ehazlett/ttlcache"
	"github.com/samalba/dockerclient"
)

const (
	pluginName      = "lb"
	ReloadThreshold = time.Millisecond * 500
)

var (
	errChan                 chan (error)
	restartChan             = make(chan bool)
	lbUpdateChan            chan (bool)
	proxyNetworkCleanupChan chan ([]proxyContainerNetworkConfig)
)

type proxyContainerNetworkConfig struct {
	ContainerID   string
	ProxyNetworks map[string]string
}

type LoadBalancerBackend interface {
	Name() string
	ConfigPath() string
	GenerateProxyConfig(c []dockerclient.Container) (interface{}, error)
	Template() string
	Reload(proxyContainers []dockerclient.Container) error
}

type LoadBalancer struct {
	cfg     *config.ExtensionConfig
	client  *dockerclient.DockerClient
	cache   *ttlcache.TTLCache
	lock    *sync.Mutex
	backend LoadBalancerBackend
	nodeID  string
}

func log() *logrus.Entry {
	return logrus.WithFields(logrus.Fields{
		"ext": pluginName,
	})
}

type eventArgs struct {
	Image string
}

func NewLoadBalancer(c *config.ExtensionConfig, client *dockerclient.DockerClient) (*LoadBalancer, error) {
	// parse config base dir
	c.ConfigBasePath = filepath.Dir(c.ConfigPath)

	errChan = make(chan error)
	go func() {
		for err := range errChan {
			log().Error(err)
		}
	}()

	lbUpdateChan = make(chan bool)

	proxyNetworkCleanupChan = make(chan []proxyContainerNetworkConfig)

	cache, err := ttlcache.NewTTLCache(ReloadThreshold)
	if err != nil {
		return nil, err
	}

	cache.SetCallback(func(k string, v interface{}) {
		log().Debugf("triggering reload from cache")
		lbUpdateChan <- true
	})

	// load nodeID
	nodeID, err := getNodeID()
	if err != nil {
		return nil, err
	}

	log().Infof("interlock node: id=%s", nodeID)

	extension := &LoadBalancer{
		cfg:    c,
		client: client,
		cache:  cache,
		lock:   &sync.Mutex{},
		nodeID: nodeID,
	}

	// select backend
	switch c.Name {
	case "haproxy":
		p, err := haproxy.NewHAProxyLoadBalancer(c, client)
		if err != nil {
			return nil, fmt.Errorf("error setting backend: %s", err)
		}
		extension.backend = p
	case "nginx":
		p, err := nginx.NewNginxLoadBalancer(c, client)
		if err != nil {
			return nil, fmt.Errorf("error setting backend: %s", err)
		}
		extension.backend = p
	default:
		return nil, fmt.Errorf("unknown load balancer backend: %s", c.Name)
	}

	// proxy network cleanup chan
	// this waits for a reload event and removes the proxy containers
	// from unused proxy networks
	go func() {
		for {
			nc := <-proxyNetworkCleanupChan

			log().Debug("checking to remove proxy containers from networks")

			for _, c := range nc {
				cID := c.ContainerID
				cnt, err := client.InspectContainer(cID)
				if err != nil {
					log().Errorf("error inspecting proxy container: id=%s err=%s", cID, err)
					continue
				}

				for net, _ := range cnt.NetworkSettings.Networks {
					// HACK?: special ignore case for bridge
					if net == "bridge" {
						continue
					}

					if _, ok := c.ProxyNetworks[net]; !ok {
						// attempt to disconnect
						log().Debugf("disconnecting proxy container from network: id=%s net=%s", cID, net)

						retries := 5
						for i := 0; i < retries; i++ {
							err := client.DisconnectNetwork(net, cID, false)
							if err == nil {
								break
							}

							log().Warnf("unable to disconnect proxy container %s from network %s (retrying): %s", cID, net, err)

							// wait for network to disconnect
							time.Sleep(2 * time.Second)
						}
					}

				}
			}
		}
	}()

	// lbUpdateChan handler
	go func() {
		for range lbUpdateChan {
			log().Debug("checking to reload")
			if v := extension.cache.Get("reload"); v != nil {
				log().Debug("skipping reload: too many requests")
				continue
			}

			start := time.Now()

			log().Debug("updating load balancers")

			containers, err := client.ListContainers(true, false, "")
			if err != nil {
				errChan <- err
				continue
			}

			// generate proxy config
			log().Debug("generating proxy config")
			cfg, err := extension.backend.GenerateProxyConfig(containers)
			if err != nil {
				errChan <- err
				continue
			}

			// save proxy config
			configPath := extension.backend.ConfigPath()
			log().Debugf("proxy config path: %s", configPath)

			proxyNetworks := map[string]string{}

			proxyContainers, err := extension.ProxyContainers(extension.backend.Name())
			if err != nil {
				errChan <- err
				continue
			}

			log().Debugf("proxyContainers: %v", proxyContainers)

			// save config
			log().Debug("saving proxy config")
			if err := extension.SaveConfig(configPath, cfg, proxyContainers); err != nil {
				errChan <- err
				continue
			}

			// connect to networks
			switch extension.backend.Name() {
			case "nginx":
				proxyConfig := cfg.(*nginx.Config)
				proxyNetworks = proxyConfig.Networks

			case "haproxy":
				proxyConfig := cfg.(*haproxy.Config)
				proxyNetworks = proxyConfig.Networks
			default:
				errChan <- fmt.Errorf("unable to connect to networks; unknown backend: %s", extension.backend.Name())
				continue
			}

			proxyContainerNetworkConfigs := []proxyContainerNetworkConfig{}

			for _, cnt := range proxyContainers {
				proxyContainerNetworkConfigs = append(proxyContainerNetworkConfigs, proxyContainerNetworkConfig{
					ContainerID:   cnt.Id,
					ProxyNetworks: proxyNetworks,
				})
				for net, _ := range proxyNetworks {
					if _, ok := cnt.NetworkSettings.Networks[net]; !ok {
						log().Debugf("connecting proxy container %s to network %s", cnt.Id, net)

						// connect
						if err := client.ConnectNetwork(net, cnt.Id); err != nil {
							log().Warnf("unable to connect container %s to network %s: %s", cnt.Id, net, err)
							continue
						}
					}
				}
			}

			// get interlock nodes
			interlockNodes := []dockerclient.Container{}

			for _, cnt := range containers {
				// always include self container
				if cnt.Id == nodeID {
					interlockNodes = append(interlockNodes, cnt)
					continue
				}

				cInfo, err := client.InspectContainer(cnt.Id)
				if err != nil {
					log().Errorf("unable to inspect interlock container: %s", err)
					continue
				}

				if strings.Index(cInfo.Config.Image, "interlock") > 0 {
					if _, ok := cInfo.Config.Labels[ext.InterlockAppLabel]; ok {
						interlockNodes = append(interlockNodes, cnt)
					}
				}
			}

			proxyContainersToRestart := extension.proxyContainersToRestart(interlockNodes, proxyContainers)

			// trigger reload
			log().Debug("signaling reload")
			if err := extension.backend.Reload(proxyContainersToRestart); err != nil {
				errChan <- err
				continue
			}

			d := time.Since(start)
			duration := float64(d.Seconds() * float64(1000))

			log().Debug("triggering proxy network cleanup")
			proxyNetworkCleanupChan <- proxyContainerNetworkConfigs

			log().Infof("reload duration: %0.2fms", duration)
		}
	}()

	return extension, nil
}

func (l *LoadBalancer) Name() string {
	return pluginName
}

func (l *LoadBalancer) ProxyContainers(name string) ([]dockerclient.Container, error) {
	containers, err := l.client.ListContainers(true, false, "")
	if err != nil {
		return nil, err
	}

	proxyContainers := []dockerclient.Container{}

	// find interlock proxy containers
	for _, cnt := range containers {
		if v, ok := cnt.Labels[ext.InterlockExtNameLabel]; ok && v == l.backend.Name() {
			log().Debugf("detected proxy container: id=%s backend=%v", cnt.Id, v)
			proxyContainers = append(proxyContainers, cnt)
		}
	}

	return proxyContainers, nil
}

func (l *LoadBalancer) SaveConfig(configPath string, cfg interface{}, proxyContainers []dockerclient.Container) error {
	t := template.New("lb")
	confTmpl := l.backend.Template()

	var c bytes.Buffer

	tmpl, err := t.Parse(confTmpl)
	if err != nil {
		return err
	}

	// cast to config type
	switch l.backend.Name() {
	case "nginx":
		config := cfg.(*nginx.Config)
		if err := tmpl.Execute(&c, config); err != nil {
			return err
		}
	case "haproxy":
		config := cfg.(*haproxy.Config)
		if err := tmpl.Execute(&c, config); err != nil {
			return err
		}
	default:
		return fmt.Errorf("unknown backend type: %s", l.backend.Name())
	}

	fName := path.Base(l.backend.ConfigPath())
	proxyConfigPath := path.Dir(l.backend.ConfigPath())

	data := c.Bytes()

	// copy to proxy nodes
	for _, cnt := range proxyContainers {
		log().Debugf("updating proxy config: id=%s", cnt.Id)
		// create tar stream to copy
		buf := new(bytes.Buffer)
		tw := tar.NewWriter(buf)
		hdr := &tar.Header{
			Name: fName,
			Mode: 0644,
			Size: int64(len(data)),
		}

		if err := tw.WriteHeader(hdr); err != nil {
			return fmt.Errorf("error writing proxy config header: %s", err)
		}

		if _, err := tw.Write(data); err != nil {
			return fmt.Errorf("error writing proxy config: %s", err)
		}

		if err := tw.Close(); err != nil {
			return fmt.Errorf("error closing tar writer: %s", err)
		}

		if err := l.client.CopyToContainer(cnt.Id, proxyConfigPath, buf); err != nil {
			log().Errorf("error copying proxy config: %s", err)
			continue
		}
	}

	return nil
}

func (l *LoadBalancer) HandleEvent(event *dockerclient.Event) error {
	reload := false

	// container event
	switch event.Status {
	case "start":
		reload = l.isExposedContainer(event.ID)
	case "stop":
		reload = l.isExposedContainer(event.ID)

		// wait for container to stop
		time.Sleep(time.Millisecond * 250)
	case "interlock-start", "destroy":
		// force reload
		reload = true
	}

	// network event
	switch event.Action {
	case "connect", "disconnect":
		// since event.ID is blank on an action we must get the proper ID
		id, ok := event.Actor.Attributes["container"]
		if !ok {
			return fmt.Errorf("unable to detect container id for network event")
		}

		reload = l.isExposedContainer(id)
	}

	if reload {
		log().Debug("triggering reload")
		l.cache.Set("reload", true)
	}

	return nil
}

// proxyContainersToRestart returns a slice of proxy containers to restart
// based upon this instance's hash
func (l *LoadBalancer) proxyContainersToRestart(nodes []dockerclient.Container, proxyContainers []dockerclient.Container) []dockerclient.Container {
	numNodes := len(nodes)
	if numNodes == 0 {
		return nil
	}

	if numNodes == 1 {
		return proxyContainers
	}

	log().Debugf("calculating restart across interlock nodes: num=%d", numNodes)

	sub := len(proxyContainers) / numNodes

	work := map[string][]dockerclient.Container{}

	for i := 0; i < len(nodes)-1; i++ {
		p, n := proxyContainers[:len(proxyContainers)-sub], proxyContainers[len(proxyContainers)-sub:]
		proxyContainers = p
		work[nodes[i].Id] = n
	}

	work[nodes[len(nodes)-1].Id] = proxyContainers

	containersToRestart := work[l.nodeID]

	ids := []string{}
	for _, c := range containersToRestart {
		ids = append(ids, c.Id[:8])
	}

	log().Debugf("proxy containers to restart: num=%d containers=%s", len(containersToRestart), strings.Join(ids, ","))

	return containersToRestart
}

func (l *LoadBalancer) isExposedContainer(id string) bool {
	log().Debugf("inspecting container: id=%s", id)
	c, err := l.client.InspectContainer(id)
	if err != nil {
		// ignore inspect errors
		log().Errorf("error: id=%s err=%s", id, err)
		return false
	}

	log().Debugf("checking container labels: id=%s", id)
	// ignore proxy containers
	if _, ok := c.Config.Labels[ext.InterlockExtNameLabel]; ok {
		log().Debugf("ignoring proxy container: id=%s", id)
		return false
	}

	if _, ok := c.Config.Labels[ext.InterlockAppLabel]; ok {
		log().Debugf("ignoring interlock container: id=%s", id)
		return false
	}

	log().Debugf("checking container ports: id=%s", id)
	// ignore containers without exposed ports
	if len(c.Config.ExposedPorts) == 0 {
		log().Debugf("no ports exposed; ignoring: id=%s", id)
		return false
	}

	log().Debugf("container is monitored; triggering reload: id=%s", id)
	return true
}
<|endoftext|>