<commit_before>package qingcloud

import (
	"fmt"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/yunify/qingcloud-sdk-go/client"
	qc "github.com/yunify/qingcloud-sdk-go/service"
	"time"
)

func resourceQingcloudEip() *schema.Resource {
	return &schema.Resource{
		Create: resourceQingcloudEipCreate,
		Read:   resourceQingcloudEipRead,
		Update: resourceQingcloudEipUpdate,
		Delete: resourceQingcloudEipDelete,
		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:        schema.TypeString,
				Optional:    true,
				Description: "the name of eip",
			},
			"description": &schema.Schema{
				Type:        schema.TypeString,
				Optional:    true,
				Description: "the description of eip",
			},
			"bandwidth": &schema.Schema{
				Type:        schema.TypeInt,
				Required:    true,
				Description: "Maximum bandwidth to the elastic public network, measured in Mbps",
			},
			"billing_mode": &schema.Schema{
				Type:         schema.TypeString,
				Optional:     true,
				Default:      "bandwidth",
				Description:  "Internet charge type of the EIP : bandwidth , traffic ,default bandwidth",
				ValidateFunc: withinArrayString("traffic", "bandwidth"),
			},
			"need_icp": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				Default:      0,
				Description:  "need icp , 1 need , 0 no need ,default 0",
				ValidateFunc: withinArrayInt(0, 1),
			},
			"tag_ids": &schema.Schema{
				Type:        schema.TypeSet,
				Optional:    true,
				Elem:        &schema.Schema{Type: schema.TypeString},
				Set:         schema.HashString,
				Description: "tag ids , eip wants to use",
			},
			"tag_names": &schema.Schema{
				Type:        schema.TypeSet,
				Computed:    true,
				Elem:        &schema.Schema{Type: schema.TypeString},
				Set:         schema.HashString,
				Description: "compute by tag ids",
			},
			"addr": &schema.Schema{
				Type:        schema.TypeString,
				Computed:    true,
				Description: "ip address of this eip",
			},
			"status": &schema.Schema{
				Type:        schema.TypeString,
				Computed:    true,
				Description: "the status of eip",
			},
			"resource": &schema.Schema{
				Type:         schema.TypeMap,
				Computed:     true,
				ComputedWhen: []string{"id"},
				Description:  "the resource who use this eip",
			},
		},
	}
}

func resourceQingcloudEipCreate(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).eip
	input := new(qc.AllocateEIPsInput)
	input.Bandwidth = qc.Int(d.Get("bandwidth").(int))
	input.BillingMode = qc.String(d.Get("billing_mode").(string))
	input.NeedICP = qc.Int(d.Get("need_icp").(int))
	input.Count = qc.Int(1)
	if d.Get("name") != "" {
		input.EIPName = qc.String(d.Get("name").(string))
	} else {
		input.EIPName = nil
	}
	output, err := clt.AllocateEIPs(input)
	if err != nil {
		return fmt.Errorf("Error create eip: %s", err)
	}
	if *output.RetCode != 0 {
		return fmt.Errorf("Error create eip: %s", *output.Message)
	}
	d.SetId(qc.StringValue(output.EIPs[0]))

	if _, err := EIPTransitionStateRefresh(clt, d.Id()); err != nil {
		return nil
	}
	return resourceQingcloudEipUpdate(d, meta)
}

func resourceQingcloudEipRead(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).eip
	input := new(qc.DescribeEIPsInput)
	input.EIPs = []*string{qc.String(d.Id())}
	input.Verbose = qc.Int(1)
	output, err := clt.DescribeEIPs(input)
	if err != nil {
		return fmt.Errorf("Error describe eip: %s", err)
	}
	if *output.RetCode != 0 {
		return fmt.Errorf("Error describe eip: %s", *output.Message)
	}
	if len(output.EIPSet) == 0 {
		d.SetId("")
		return nil
	}
	ip := output.EIPSet[0]
	d.Set("name", qc.StringValue(ip.EIPName))
	d.Set("billing_mode", qc.StringValue(ip.BillingMode))
	d.Set("bandwidth", qc.IntValue(ip.Bandwidth))
	d.Set("need_icp", qc.IntValue(ip.NeedICP))
	d.Set("description", qc.StringValue(ip.Description))
	// 如下状态是稍等来获取的
	d.Set("addr", qc.StringValue(ip.EIPAddr))
	d.Set("status", qc.StringValue(ip.Status))
	if err := d.Set("resource", getEIPResourceMap(ip)); err != nil {
		return fmt.Errorf("Error set eip resource %v", err)
	}
	resourceSetTag(d, ip.Tags)
	return nil
}

func resourceQingcloudEipUpdate(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).eip
	d.Partial(true)
	waitEipLease(d, meta)
	if d.HasChange("need_icp") && !d.IsNewResource() {
		return fmt.Errorf("Errorf EIP need_icp could not be updated")
	}
	if d.HasChange("bandwidth") && !d.IsNewResource() {
		input := new(qc.ChangeEIPsBandwidthInput)
		input.EIPs = []*string{qc.String(d.Id())}
		input.Bandwidth = qc.Int(d.Get("bandwidth").(int))
		output, err := clt.ChangeEIPsBandwidth(input)
		if err != nil {
			return fmt.Errorf("Errorf Change EIP bandwidth input: %s", err)
		}
		if *output.RetCode != 0 {
			return fmt.Errorf("Errorf Change EIP bandwidth input: %s", err)
		}
		if _, err := EIPTransitionStateRefresh(clt, d.Id()); err != nil {
			return nil
		}
		d.SetPartial("bandwidth")
	}
	if d.HasChange("billing_mode") && !d.IsNewResource() {
		input := new(qc.ChangeEIPsBillingModeInput)
		input.EIPs = []*string{qc.String(d.Id())}
		input.BillingMode = qc.String(d.Get("billing_mode").(string))
		output, err := clt.ChangeEIPsBillingMode(input)
		if err != nil {
			return fmt.Errorf("Errorf Change EIPs billing_mode %s", err)
		}
		if *output.RetCode != 0 {
			return fmt.Errorf("Errorf Change EIP billing_mode %s", *output.Message)
		}
		if _, err := EIPTransitionStateRefresh(clt, d.Id()); err != nil {
			return nil
		}
		d.SetPartial("billing_mode")
	}
	if err := modifyEipAttributes(d, meta); err != nil {
		return err
	}
	d.SetPartial("description")
	d.SetPartial("name")
	if err := resourceUpdateTag(d, meta, qingcloudResourceTypeEIP); err != nil {
		return err
	}
	d.SetPartial("tag_ids")
	d.Partial(false)
	return resourceQingcloudEipRead(d, meta)
}

func resourceQingcloudEipDelete(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).eip
	_, err := EIPTransitionStateRefresh(clt, d.Id())
	waitEipLease(d, meta)
	if err != nil {
		return err
	}
	input := new(qc.ReleaseEIPsInput)
	input.EIPs = []*string{qc.String(d.Id())}
	output, err := clt.ReleaseEIPs(input)
	if err != nil {
		return fmt.Errorf("Error release eip: %s", err)
	}
	if *output.RetCode != 0 {
		return fmt.Errorf("Error describe eip: %s", *output.Message)
	}
	client.WaitJob(meta.(*QingCloudClient).job,
		qc.StringValue(output.JobID),
		time.Duration(10)*time.Second, time.Duration(1)*time.Second)
	d.SetId("")
	return nil
}
<commit_msg>add deleted check  condition<commit_after>package qingcloud

import (
	"fmt"

	"github.com/hashicorp/terraform/helper/schema"
	"github.com/yunify/qingcloud-sdk-go/client"
	qc "github.com/yunify/qingcloud-sdk-go/service"
	"time"
)

func resourceQingcloudEip() *schema.Resource {
	return &schema.Resource{
		Create: resourceQingcloudEipCreate,
		Read:   resourceQingcloudEipRead,
		Update: resourceQingcloudEipUpdate,
		Delete: resourceQingcloudEipDelete,
		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:        schema.TypeString,
				Optional:    true,
				Description: "the name of eip",
			},
			"description": &schema.Schema{
				Type:        schema.TypeString,
				Optional:    true,
				Description: "the description of eip",
			},
			"bandwidth": &schema.Schema{
				Type:        schema.TypeInt,
				Required:    true,
				Description: "Maximum bandwidth to the elastic public network, measured in Mbps",
			},
			"billing_mode": &schema.Schema{
				Type:         schema.TypeString,
				Optional:     true,
				Default:      "bandwidth",
				Description:  "Internet charge type of the EIP : bandwidth , traffic ,default bandwidth",
				ValidateFunc: withinArrayString("traffic", "bandwidth"),
			},
			"need_icp": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				Default:      0,
				Description:  "need icp , 1 need , 0 no need ,default 0",
				ValidateFunc: withinArrayInt(0, 1),
			},
			"tag_ids": &schema.Schema{
				Type:        schema.TypeSet,
				Optional:    true,
				Elem:        &schema.Schema{Type: schema.TypeString},
				Set:         schema.HashString,
				Description: "tag ids , eip wants to use",
			},
			"tag_names": &schema.Schema{
				Type:        schema.TypeSet,
				Computed:    true,
				Elem:        &schema.Schema{Type: schema.TypeString},
				Set:         schema.HashString,
				Description: "compute by tag ids",
			},
			"addr": &schema.Schema{
				Type:        schema.TypeString,
				Computed:    true,
				Description: "ip address of this eip",
			},
			"status": &schema.Schema{
				Type:        schema.TypeString,
				Computed:    true,
				Description: "the status of eip",
			},
			"resource": &schema.Schema{
				Type:         schema.TypeMap,
				Computed:     true,
				ComputedWhen: []string{"id"},
				Description:  "the resource who use this eip",
			},
		},
	}
}

func resourceQingcloudEipCreate(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).eip
	input := new(qc.AllocateEIPsInput)
	input.Bandwidth = qc.Int(d.Get("bandwidth").(int))
	input.BillingMode = qc.String(d.Get("billing_mode").(string))
	input.NeedICP = qc.Int(d.Get("need_icp").(int))
	input.Count = qc.Int(1)
	if d.Get("name") != "" {
		input.EIPName = qc.String(d.Get("name").(string))
	} else {
		input.EIPName = nil
	}
	output, err := clt.AllocateEIPs(input)
	if err != nil {
		return fmt.Errorf("Error create eip: %s", err)
	}
	if *output.RetCode != 0 {
		return fmt.Errorf("Error create eip: %s", *output.Message)
	}
	d.SetId(qc.StringValue(output.EIPs[0]))

	if _, err := EIPTransitionStateRefresh(clt, d.Id()); err != nil {
		return nil
	}
	return resourceQingcloudEipUpdate(d, meta)
}

func resourceQingcloudEipRead(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).eip
	input := new(qc.DescribeEIPsInput)
	input.EIPs = []*string{qc.String(d.Id())}
	input.Verbose = qc.Int(1)
	output, err := clt.DescribeEIPs(input)
	if err != nil {
		return fmt.Errorf("Error describe eip: %s", err)
	}
	if *output.RetCode != 0 {
		return fmt.Errorf("Error describe eip: %s", *output.Message)
	}
	if len(output.EIPSet) == 0 || qc.StringValue(output.EIPSet[0].Status) == "ceased" || qc.StringValue(output.EIPSet[0].Status) == "released" {
		d.SetId("")
		return nil
	}
	ip := output.EIPSet[0]
	d.Set("name", qc.StringValue(ip.EIPName))
	d.Set("billing_mode", qc.StringValue(ip.BillingMode))
	d.Set("bandwidth", qc.IntValue(ip.Bandwidth))
	d.Set("need_icp", qc.IntValue(ip.NeedICP))
	d.Set("description", qc.StringValue(ip.Description))
	// 如下状态是稍等来获取的
	d.Set("addr", qc.StringValue(ip.EIPAddr))
	d.Set("status", qc.StringValue(ip.Status))
	if err := d.Set("resource", getEIPResourceMap(ip)); err != nil {
		return fmt.Errorf("Error set eip resource %v", err)
	}
	resourceSetTag(d, ip.Tags)
	return nil
}

func resourceQingcloudEipUpdate(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).eip
	d.Partial(true)
	waitEipLease(d, meta)
	if d.HasChange("need_icp") && !d.IsNewResource() {
		return fmt.Errorf("Errorf EIP need_icp could not be updated")
	}
	if d.HasChange("bandwidth") && !d.IsNewResource() {
		input := new(qc.ChangeEIPsBandwidthInput)
		input.EIPs = []*string{qc.String(d.Id())}
		input.Bandwidth = qc.Int(d.Get("bandwidth").(int))
		output, err := clt.ChangeEIPsBandwidth(input)
		if err != nil {
			return fmt.Errorf("Errorf Change EIP bandwidth input: %s", err)
		}
		if *output.RetCode != 0 {
			return fmt.Errorf("Errorf Change EIP bandwidth input: %s", err)
		}
		if _, err := EIPTransitionStateRefresh(clt, d.Id()); err != nil {
			return nil
		}
		d.SetPartial("bandwidth")
	}
	if d.HasChange("billing_mode") && !d.IsNewResource() {
		input := new(qc.ChangeEIPsBillingModeInput)
		input.EIPs = []*string{qc.String(d.Id())}
		input.BillingMode = qc.String(d.Get("billing_mode").(string))
		output, err := clt.ChangeEIPsBillingMode(input)
		if err != nil {
			return fmt.Errorf("Errorf Change EIPs billing_mode %s", err)
		}
		if *output.RetCode != 0 {
			return fmt.Errorf("Errorf Change EIP billing_mode %s", *output.Message)
		}
		if _, err := EIPTransitionStateRefresh(clt, d.Id()); err != nil {
			return nil
		}
		d.SetPartial("billing_mode")
	}
	if err := modifyEipAttributes(d, meta); err != nil {
		return err
	}
	d.SetPartial("description")
	d.SetPartial("name")
	if err := resourceUpdateTag(d, meta, qingcloudResourceTypeEIP); err != nil {
		return err
	}
	d.SetPartial("tag_ids")
	d.Partial(false)
	return resourceQingcloudEipRead(d, meta)
}

func resourceQingcloudEipDelete(d *schema.ResourceData, meta interface{}) error {
	clt := meta.(*QingCloudClient).eip
	_, err := EIPTransitionStateRefresh(clt, d.Id())
	waitEipLease(d, meta)
	if err != nil {
		return err
	}
	input := new(qc.ReleaseEIPsInput)
	input.EIPs = []*string{qc.String(d.Id())}
	output, err := clt.ReleaseEIPs(input)
	if err != nil {
		return fmt.Errorf("Error release eip: %s", err)
	}
	if *output.RetCode != 0 {
		return fmt.Errorf("Error describe eip: %s", *output.Message)
	}
	client.WaitJob(meta.(*QingCloudClient).job,
		qc.StringValue(output.JobID),
		time.Duration(10)*time.Second, time.Duration(1)*time.Second)
	d.SetId("")
	return nil
}
<|endoftext|>


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

package main

import (
	"io"
	"os"
	"path/filepath"
	"strings"

	"gopkg.in/alecthomas/kingpin.v2"

	"github.com/petergtz/pegomock/pegomock/mockgen"
	"github.com/petergtz/pegomock/pegomock/watch"
)

var (
	app = kingpin.New("pegomock", "Generates mocks based on interfaces.")
)

func main() {
	Run(os.Args, os.Stderr, app, make(chan bool))
}

func Run(cliArgs []string, out io.Writer, app *kingpin.Application, done chan bool) {

	workingDir, err := os.Getwd()
	app.FatalIfError(err, "")

	var (
		generateCmd = app.Command("generate", "Generate mocks based on the args provided. ")
		destination = generateCmd.Flag("output", "Output file; defaults to mock_<interface>_test.go.").Short('o').String()
		packageOut  = generateCmd.Flag("package", "Package of the generated code; defaults to the package from which pegomock was executed suffixed with _test").Default(filepath.Base(workingDir) + "_test").String()
		selfPackage = generateCmd.Flag("self_package", "If set, the package this mock will be part of.").String()
		debugParser = generateCmd.Flag("debug", "Print debug information.").Short('d').Bool()
		args        = generateCmd.Arg("args", "Interfaces or go files").Required().Strings()

		watchCmd       = app.Command("watch", "Watch ")
		watchRecursive = watchCmd.Flag("recursive", "TODO").Short('r').Hidden().Bool()
		watchPackages  = watchCmd.Arg("packages", "TODO").Strings()
	)

	app.Writer(out)
	switch kingpin.MustParse(app.Parse(cliArgs[1:])) {

	case generateCmd.FullCommand():
		validateArgs(*args)
		if sourceMode(*args) {
			if *destination == "" {
				*destination = filepath.Join(workingDir, "mock_"+strings.TrimSuffix((*args)[0], ".go")+"_test.go")
			}

			mockgen.Run((*args)[0], *destination, *packageOut, *selfPackage, *debugParser, out)
		} else {
			if *destination == "" {
				*destination = filepath.Join(workingDir, "mock_"+strings.ToLower((*args)[len(*args)-1])+"_test.go")
			}
			if len(*args) == 1 {
				mockgen.Run(
					"",
					*destination,
					*packageOut,
					*selfPackage,
					*debugParser,
					out,
					packagePathFromDirectory(os.Getenv("GOPATH"), workingDir),
					(*args)[0])
			} else if len(*args) == 2 {
				mockgen.Run(
					"",
					*destination,
					*packageOut,
					*selfPackage,
					*debugParser,
					out,
					(*args)[0],
					(*args)[1])
			} else {
				app.FatalUsage("Please provide exactly 1 interface or 1 package + 1 interface")
			}
		}

	case watchCmd.FullCommand():
		if len(*watchPackages) == 0 {
			watch.Watch([]string{workingDir}, *watchRecursive, done)
		} else {
			watch.Watch(*watchPackages, *watchRecursive, done)
		}
	}
}

func validateArgs(args []string) {
	if len(args) == 0 {
		app.FatalUsage("You must specify either exactly one source filename ending with .go, or at least one go interface name.")
	}
	if len(args) == 1 {
		return
	}
	if len(args) >= 2 {
		for _, arg := range args {
			if strings.HasSuffix(arg, ".go") {
				app.FatalUsage("You can specify at most one go source file.")
			}
		}
	}
}

func sourceMode(args []string) bool {
	if len(args) == 1 && strings.HasSuffix(args[0], ".go") {
		return true
	}
	return false
}

func packagePathFromDirectory(gopath, dir string) string {
	relativePackagePath, err := filepath.Rel(filepath.Join(gopath, "src"), dir)
	if err != nil {
		panic("Directory is not within a Go package path. GOPATH:" + gopath + "; dir: " + dir)
	}
	return relativePackagePath
}
<commit_msg>Improve help text<commit_after>// Copyright 2016 Peter Goetz
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"io"
	"os"
	"path/filepath"
	"strings"

	"gopkg.in/alecthomas/kingpin.v2"

	"github.com/petergtz/pegomock/pegomock/mockgen"
	"github.com/petergtz/pegomock/pegomock/watch"
)

var (
	app = kingpin.New("pegomock", "Generates mocks based on interfaces.")
)

func main() {
	Run(os.Args, os.Stderr, app, make(chan bool))
}

func Run(cliArgs []string, out io.Writer, app *kingpin.Application, done chan bool) {

	workingDir, err := os.Getwd()
	app.FatalIfError(err, "")

	var (
		generateCmd = app.Command("generate", "Generate mocks based on the args provided. ")
		destination = generateCmd.Flag("output", "Output file; defaults to mock_<interface>_test.go.").Short('o').String()
		packageOut  = generateCmd.Flag("package", "Package of the generated code; defaults to the package from which pegomock was executed suffixed with _test").Default(filepath.Base(workingDir) + "_test").String()
		selfPackage = generateCmd.Flag("self_package", "If set, the package this mock will be part of.").String()
		debugParser = generateCmd.Flag("debug", "Print debug information.").Short('d').Bool()
		args        = generateCmd.Arg("args", "A (optional) Go package path + space-separated interface or a .go file").Required().Strings()

		watchCmd       = app.Command("watch", "Watch ")
		watchRecursive = watchCmd.Flag("recursive", "TODO").Short('r').Hidden().Bool()
		watchPackages  = watchCmd.Arg("packages", "TODO").Strings()
	)

	app.Writer(out)
	switch kingpin.MustParse(app.Parse(cliArgs[1:])) {

	case generateCmd.FullCommand():
		validateArgs(*args)
		if sourceMode(*args) {
			if *destination == "" {
				*destination = filepath.Join(workingDir, "mock_"+strings.TrimSuffix((*args)[0], ".go")+"_test.go")
			}

			mockgen.Run((*args)[0], *destination, *packageOut, *selfPackage, *debugParser, out)
		} else {
			if *destination == "" {
				*destination = filepath.Join(workingDir, "mock_"+strings.ToLower((*args)[len(*args)-1])+"_test.go")
			}
			if len(*args) == 1 {
				mockgen.Run(
					"",
					*destination,
					*packageOut,
					*selfPackage,
					*debugParser,
					out,
					packagePathFromDirectory(os.Getenv("GOPATH"), workingDir),
					(*args)[0])
			} else if len(*args) == 2 {
				mockgen.Run(
					"",
					*destination,
					*packageOut,
					*selfPackage,
					*debugParser,
					out,
					(*args)[0],
					(*args)[1])
			} else {
				app.FatalUsage("Please provide exactly 1 interface or 1 package + 1 interface")
			}
		}

	case watchCmd.FullCommand():
		if len(*watchPackages) == 0 {
			watch.Watch([]string{workingDir}, *watchRecursive, done)
		} else {
			watch.Watch(*watchPackages, *watchRecursive, done)
		}
	}
}

func validateArgs(args []string) {
	if len(args) == 0 {
		app.FatalUsage("You must specify either exactly one source filename ending with .go, or at least one go interface name.")
	}
	if len(args) == 1 {
		return
	}
	if len(args) >= 2 {
		for _, arg := range args {
			if strings.HasSuffix(arg, ".go") {
				app.FatalUsage("You can specify at most one go source file.")
			}
		}
	}
}

func sourceMode(args []string) bool {
	if len(args) == 1 && strings.HasSuffix(args[0], ".go") {
		return true
	}
	return false
}

func packagePathFromDirectory(gopath, dir string) string {
	relativePackagePath, err := filepath.Rel(filepath.Join(gopath, "src"), dir)
	if err != nil {
		panic("Directory is not within a Go package path. GOPATH:" + gopath + "; dir: " + dir)
	}
	return relativePackagePath
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"

	"github.com/square/spincycle/proto"
	"github.com/square/spincycle/spinc/app"
)

type Status struct {
	ctx   app.Context
	reqId string
}

func NewStatus(ctx app.Context) *Status {
	return &Status{
		ctx: ctx,
	}
}

func (c *Status) Prepare() error {
	if len(c.ctx.Command.Args) == 0 {
		return fmt.Errorf("Usage: spinc status <id>\n")
	}
	c.reqId = c.ctx.Command.Args[0]
	return nil
}

func (c *Status) Run() error {
	status, err := c.ctx.RMClient.RequestStatus(c.reqId)
	if err != nil {
		return err
	}
	if c.ctx.Options.Debug {
		app.Debug("status: %#v", status)
	}

	if c.ctx.Hooks.CommandRunResult != nil {
		c.ctx.Hooks.CommandRunResult(status, err)
		return nil
	}

	fmt.Printf("state:      %s\n", proto.StateName[status.State])
	fmt.Printf("jobs done:  %d\n", status.FinishedJobs)
	fmt.Printf("jobs total: %d\n", status.TotalJobs)
	fmt.Printf("created:    %s\n", status.CreatedAt)
	fmt.Printf("started:    %s\n", status.StartedAt)
	fmt.Printf("finished:   %s\n", status.FinishedAt)
	fmt.Printf("user:       %s\n", status.User)
	fmt.Printf("type:       %s\n", status.Type)
	fmt.Printf("id:         %s\n", status.Id)

	return nil
}
<commit_msg>status with name<commit_after>package cmd

import (
	"fmt"

	"github.com/square/spincycle/proto"
	"github.com/square/spincycle/spinc/app"
)

type Status struct {
	ctx   app.Context
	reqId string
}

func NewStatus(ctx app.Context) *Status {
	return &Status{
		ctx: ctx,
	}
}

func (c *Status) Prepare() error {
	if len(c.ctx.Command.Args) == 0 {
		return fmt.Errorf("Usage: spinc status <id>\n")
	}
	c.reqId = c.ctx.Command.Args[0]
	return nil
}

func (c *Status) Run() error {
	status, err := c.ctx.RMClient.RequestStatus(c.reqId)
	if err != nil {
		return err
	}
	if c.ctx.Options.Debug {
		app.Debug("status: %#v", status)
	}

	if c.ctx.Hooks.CommandRunResult != nil {
		c.ctx.Hooks.CommandRunResult(status, err)
		return nil
	}

	running := []string{}
	js := status.JobChainStatus.JobStatuses
	for _, job := range js {
		if job.State == proto.STATE_RUNNING {
			running = append(running, job.Name)
		}
	}

	fmt.Printf("state:      %s\n", proto.StateName[status.State])
	fmt.Printf("running jobs: \n")
	for _, j := range running {
		fmt.Printf("\t%s\n", j)
	}
	fmt.Printf("jobs done:  %d\n", status.FinishedJobs)
	fmt.Printf("jobs total: %d\n", status.TotalJobs)
	fmt.Printf("created:    %s\n", status.CreatedAt)
	fmt.Printf("started:    %s\n", status.StartedAt)
	fmt.Printf("finished:   %s\n", status.FinishedAt)
	fmt.Printf("user:       %s\n", status.User)
	fmt.Printf("type:       %s\n", status.Type)
	fmt.Printf("id:         %s\n", status.Id)

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/google/go-github/github"
	"gopkg.in/alecthomas/kingpin.v2"
	"time"
)

var (
	owner            = kingpin.Arg("owner", "GitHub owner.").Required().String()
	repo             = kingpin.Arg("repo", "GitHub repository").Required().String()
	base             = kingpin.Arg("base", "Base tag/commit").Required().String()
	since *time.Time = nil
)

type Result struct {
	Issues   []github.Issue
	Response github.Response
}

func issueWorker(jobs <-chan int, results chan<- Result) {
	for page := range jobs {
		client := github.NewClient(nil)
		issues, response, err := client.Issues.ListByRepo(
			*owner,
			*repo,
			&github.IssueListByRepoOptions{
				State:       "closed",
				Since:       *since,
				ListOptions: github.ListOptions{Page: page, PerPage: 100},
			})
		if err != nil {
			panic(err)
		}
		fmt.Print(".")
		results <- Result{Issues: issues, Response: *response}
	}
}

func main() {
	kingpin.Parse()

	// Compare revisions
	client := github.NewClient(nil)
	comparison, _, err := client.Repositories.CompareCommits(*owner, *repo, *base, "master")
	if err != nil {
		panic(err)
	}
	fmt.Print(".")
	since = comparison.BaseCommit.Commit.Author.Date

	// Gather statistics
	contributors := make(map[string]bool)
	for _, commit := range comparison.Commits {
		contributors[*commit.Commit.Author.Name] = true
	}
	days := int(time.Since(*since).Hours() / 24)
	commits := *comparison.AheadBy

	// Enumerate issues
	jobs := make(chan int, 100)
	results := make(chan Result, 100)
	for w := 1; w <= 5; w++ {
		go issueWorker(jobs, results)
	}

	jobs <- 1
	result := <-results
	pages := result.Response.LastPage
	issues := result.Issues

	for p := 2; p <= pages; p++ {
		jobs <- p
	}
	close(jobs)

	for a := 2; a <= pages; a++ {
		issues = append(issues, (<-results).Issues...)
	}

	closedIssues := 0
	closedPRs := 0
	for _, issue := range issues {
		if issue.ClosedAt.After(*since) {
			if issue.PullRequestLinks != nil {
				closedPRs++
			} else {
				closedIssues++
			}
		}
	}

	fmt.Println("")
	fmt.Printf("Since the last release, the project has had %d commits by %d contributors, "+
		"resulting in %d closed issues and %d closed PRs, all of this in just over %d days.",
		commits, len(contributors), closedIssues, closedPRs, days)
	fmt.Println("")
}
<commit_msg>fix calculation of large diffs<commit_after>package main

import (
	"context"
	"fmt"
	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
	"gopkg.in/alecthomas/kingpin.v2"
	"sync"
	"time"
)

var (
	owner                     = kingpin.Arg("owner", "GitHub owner.").Required().String()
	repo                      = kingpin.Arg("repo", "GitHub repository").Required().String()
	base                      = kingpin.Arg("base", "Base tag/commit").Required().String()
	auth_token                = kingpin.Flag("token", "OAuth Token").Envar("GITHUB_TOKEN").String()
	since      *time.Time     = nil
	client     *github.Client = nil
)

func issueWorker(pages <-chan int, results chan<- github.Issue) {
	for page := range pages {
		issues, _, err := client.Issues.ListByRepo(
			context.Background(),
			*owner,
			*repo,
			&github.IssueListByRepoOptions{
				State:       "closed",
				Since:       *since,
				ListOptions: github.ListOptions{Page: page, PerPage: 100},
			})
		if err != nil {
			panic(err)
		}
		fmt.Print(".")
		for _, issue := range issues {
			results <- *issue
		}
	}
}

func commitWorker(pages <-chan int, results chan<- github.RepositoryCommit) {
	for page := range pages {
		commits, _, err := client.Repositories.ListCommits(
			context.Background(),
			*owner,
			*repo,
			&github.CommitsListOptions{
				SHA:         "master",
				Since:       *since,
				ListOptions: github.ListOptions{Page: page, PerPage: 100},
			})
		if err != nil {
			panic(err)
		}
		fmt.Print(".")
		for _, commit := range commits {
			results <- *commit
		}
	}
}

func main() {
	kingpin.Parse()

	fmt.Println("Getting repository data...")

	if *auth_token != "" {
		tc := oauth2.NewClient(
			context.Background(),
			oauth2.StaticTokenSource(
				&oauth2.Token{AccessToken: *auth_token},
			))
		client = github.NewClient(tc)
	} else {
		client = github.NewClient(nil)
	}

	base, _, err := client.Repositories.GetCommit(context.Background(), *owner, *repo, *base)
	if err != nil {
		panic(err)
	}
	since = base.Commit.Author.Date

	_, issueInfo, err := client.Issues.ListByRepo(
		context.Background(),
		*owner,
		*repo,
		&github.IssueListByRepoOptions{
			State:       "closed",
			Since:       *since,
			ListOptions: github.ListOptions{Page: 1, PerPage: 100},
		})
	if err != nil {
		panic(err)
	}
	_, commitInfo, err := client.Repositories.ListCommits(
		context.Background(),
		*owner,
		*repo,
		&github.CommitsListOptions{
			SHA:         "master",
			Since:       *since,
			ListOptions: github.ListOptions{Page: 1, PerPage: 100},
		})
	if err != nil {
		panic(err)
	}

	fmt.Printf("Fetching %d commit pages and %d issue pages...", commitInfo.LastPage, issueInfo.LastPage)
	// Enumerate commits
	commitPages := make(chan int, 100)
	commits := make(chan github.RepositoryCommit, 10000)
	commitWg := &sync.WaitGroup{}
	commitWg.Add(5)
	for w := 1; w <= 5; w++ {
		go func() {
			defer commitWg.Done()
			commitWorker(commitPages, commits)
		}()
	}
	for p := 1; p <= commitInfo.LastPage; p++ {
		commitPages <- p
	}
	close(commitPages)
	go func() {
		commitWg.Wait()
		close(commits)
	}()

	// Enumerate issues
	issuePages := make(chan int, 100)
	issues := make(chan github.Issue, 10000)
	issueWg := &sync.WaitGroup{}
	issueWg.Add(5)
	for w := 1; w <= 5; w++ {
		go func() {
			defer issueWg.Done()
			issueWorker(issuePages, issues)
		}()
	}
	for p := 1; p <= issueInfo.LastPage; p++ {
		issuePages <- p
	}
	close(issuePages)
	go func() {
		issueWg.Wait()
		close(issues)
	}()

	closedIssues := 0
	closedPRs := 0
	for issue := range issues {
		if issue.ClosedAt.After(*since) {
			if issue.PullRequestLinks != nil {
				closedPRs++
			} else {
				closedIssues++
			}
		}
	}

	contributors := make(map[string]bool)
	commitCount := 0
	for commit := range commits {
		contributors[*commit.Commit.Author.Name] = true
		commitCount += 1
	}
	days := int(time.Since(*since).Hours() / 24)

	fmt.Println("")
	fmt.Printf("Since the last release, the project has had %d commits by %d contributors, "+
		"resulting in %d closed issues and %d closed PRs, all of this in just over %d days.",
		commitCount, len(contributors), closedIssues, closedPRs, days)
	fmt.Println("")
}
<|endoftext|>


<commit_before>// Package storage provides clients for Microsoft Azure Storage Services.
package storage

import (
	"bytes"
	"encoding/base64"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

const (
	// DefaultBaseURL is the domain name used for storage requests when a
	// default client is created.
	DefaultBaseURL = "core.windows.net"

	// DefaultAPIVersion is the  Azure Storage API version string used when a
	// basic client is created.
	DefaultAPIVersion = "2014-02-14"

	defaultUseHTTPS = true

	blobServiceName  = "blob"
	tableServiceName = "table"
	queueServiceName = "queue"
	fileServiceName  = "file"
)

// Client is the object that needs to be constructed to perform
// operations on the storage account.
type Client struct {
	accountName string
	accountKey  []byte
	useHTTPS    bool
	baseURL     string
	apiVersion  string
}

type storageResponse struct {
	statusCode int
	headers    http.Header
	body       io.ReadCloser
}

// AzureStorageServiceError contains fields of the error response from
// Azure Storage Service REST API. See https://msdn.microsoft.com/en-us/library/azure/dd179382.aspx
// Some fields might be specific to certain calls.
type AzureStorageServiceError struct {
	Code                      string `xml:"Code"`
	Message                   string `xml:"Message"`
	AuthenticationErrorDetail string `xml:"AuthenticationErrorDetail"`
	QueryParameterName        string `xml:"QueryParameterName"`
	QueryParameterValue       string `xml:"QueryParameterValue"`
	Reason                    string `xml:"Reason"`
	StatusCode                int
	RequestID                 string
}

// UnexpectedStatusCodeError is returned when a storage service responds with neither an error
// nor with an HTTP status code indicating success.
type UnexpectedStatusCodeError struct {
	allowed []int
	got     int
}

func (e UnexpectedStatusCodeError) Error() string {
	s := func(i int) string { return fmt.Sprintf("%d %s", i, http.StatusText(i)) }

	got := s(e.got)
	expected := []string{}
	for _, v := range e.allowed {
		expected = append(expected, s(v))
	}
	return fmt.Sprintf("storage: status code from service response is %s; was expecting %s", got, strings.Join(expected, " or "))
}

// Got is the actual status code returned by Azure.
func (e UnexpectedStatusCodeError) Got() int {
	return e.got
}

// NewBasicClient constructs a Client with given storage service name and
// key.
func NewBasicClient(accountName, accountKey string) (Client, error) {
	return NewClient(accountName, accountKey, DefaultBaseURL, DefaultAPIVersion, defaultUseHTTPS)
}

// NewClient constructs a Client. This should be used if the caller wants
// to specify whether to use HTTPS, a specific REST API version or a custom
// storage endpoint than Azure Public Cloud.
func NewClient(accountName, accountKey, blobServiceBaseURL, apiVersion string, useHTTPS bool) (Client, error) {
	var c Client
	if accountName == "" {
		return c, fmt.Errorf("azure: account name required")
	} else if accountKey == "" {
		return c, fmt.Errorf("azure: account key required")
	} else if blobServiceBaseURL == "" {
		return c, fmt.Errorf("azure: base storage service url required")
	}

	key, err := base64.StdEncoding.DecodeString(accountKey)
	if err != nil {
		return c, err
	}

	return Client{
		accountName: accountName,
		accountKey:  key,
		useHTTPS:    useHTTPS,
		baseURL:     blobServiceBaseURL,
		apiVersion:  apiVersion,
	}, nil
}

func (c Client) getBaseURL(service string) string {
	scheme := "http"
	if c.useHTTPS {
		scheme = "https"
	}

	host := fmt.Sprintf("%s.%s.%s", c.accountName, service, c.baseURL)

	u := &url.URL{
		Scheme: scheme,
		Host:   host}
	return u.String()
}

func (c Client) getEndpoint(service, path string, params url.Values) string {
	u, err := url.Parse(c.getBaseURL(service))
	if err != nil {
		// really should not be happening
		panic(err)
	}

	if path == "" {
		path = "/" // API doesn't accept path segments not starting with '/'
	}

	u.Path = path
	u.RawQuery = params.Encode()
	return u.String()
}

// GetBlobService returns a BlobStorageClient which can operate on the blob
// service of the storage account.
func (c Client) GetBlobService() BlobStorageClient {
	return BlobStorageClient{c}
}

// GetQueueService returns a QueueServiceClient which can operate on the queue
// service of the storage account.
func (c Client) GetQueueService() QueueServiceClient {
	return QueueServiceClient{c}
}

// GetFileService returns a FileServiceClient which can operate on the file
// service of the storage account.
func (c Client) GetFileService() FileServiceClient {
	return FileServiceClient{c}
}

func (c Client) createAuthorizationHeader(canonicalizedString string) string {
	signature := c.computeHmac256(canonicalizedString)
	return fmt.Sprintf("%s %s:%s", "SharedKey", c.accountName, signature)
}

func (c Client) getAuthorizationHeader(verb, url string, headers map[string]string) (string, error) {
	canonicalizedResource, err := c.buildCanonicalizedResource(url)
	if err != nil {
		return "", err
	}

	canonicalizedString := c.buildCanonicalizedString(verb, headers, canonicalizedResource)
	return c.createAuthorizationHeader(canonicalizedString), nil
}

func (c Client) getStandardHeaders() map[string]string {
	return map[string]string{
		"x-ms-version": c.apiVersion,
		"x-ms-date":    currentTimeRfc1123Formatted(),
	}
}

func (c Client) buildCanonicalizedHeader(headers map[string]string) string {
	cm := make(map[string]string)

	for k, v := range headers {
		headerName := strings.TrimSpace(strings.ToLower(k))
		match, _ := regexp.MatchString("x-ms-", headerName)
		if match {
			cm[headerName] = v
		}
	}

	if len(cm) == 0 {
		return ""
	}

	keys := make([]string, 0, len(cm))
	for key := range cm {
		keys = append(keys, key)
	}

	sort.Strings(keys)

	ch := ""

	for i, key := range keys {
		if i == len(keys)-1 {
			ch += fmt.Sprintf("%s:%s", key, cm[key])
		} else {
			ch += fmt.Sprintf("%s:%s\n", key, cm[key])
		}
	}
	return ch
}

func (c Client) buildCanonicalizedResource(uri string) (string, error) {
	errMsg := "buildCanonicalizedResource error: %s"
	u, err := url.Parse(uri)
	if err != nil {
		return "", fmt.Errorf(errMsg, err.Error())
	}

	cr := "/" + c.accountName
	if len(u.Path) > 0 {
		cr += u.Path
	}

	params, err := url.ParseQuery(u.RawQuery)
	if err != nil {
		return "", fmt.Errorf(errMsg, err.Error())
	}

	if len(params) > 0 {
		cr += "\n"
		keys := make([]string, 0, len(params))
		for key := range params {
			keys = append(keys, key)
		}

		sort.Strings(keys)

		for i, key := range keys {
			if len(params[key]) > 1 {
				sort.Strings(params[key])
			}

			if i == len(keys)-1 {
				cr += fmt.Sprintf("%s:%s", key, strings.Join(params[key], ","))
			} else {
				cr += fmt.Sprintf("%s:%s\n", key, strings.Join(params[key], ","))
			}
		}
	}
	return cr, nil
}

func (c Client) buildCanonicalizedString(verb string, headers map[string]string, canonicalizedResource string) string {
	canonicalizedString := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s",
		verb,
		headers["Content-Encoding"],
		headers["Content-Language"],
		headers["Content-Length"],
		headers["Content-MD5"],
		headers["Content-Type"],
		headers["Date"],
		headers["If-Modified-Since"],
		headers["If-Match"],
		headers["If-None-Match"],
		headers["If-Unmodified-Since"],
		headers["Range"],
		c.buildCanonicalizedHeader(headers),
		canonicalizedResource)

	return canonicalizedString
}

func (c Client) exec(verb, url string, headers map[string]string, body io.Reader) (*storageResponse, error) {
	authHeader, err := c.getAuthorizationHeader(verb, url, headers)
	if err != nil {
		return nil, err
	}
	headers["Authorization"] = authHeader

	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(verb, url, body)
	if err != nil {
		return nil, errors.New("azure/storage: error creating request: " + err.Error())
	}
	if clstr, ok := headers["Content-Length"]; ok {
		// content length header is being signed, but completely ignored by golang.
		// instead we have to use the ContentLength property on the request struct
		// (see https://golang.org/src/net/http/request.go?s=18140:18370#L536 and
		// https://golang.org/src/net/http/transfer.go?s=1739:2467#L49)
		req.ContentLength, err = strconv.ParseInt(clstr, 10, 64)
		if err != nil {
			return nil, err
		}
	}
	for k, v := range headers {
		req.Header.Add(k, v)
	}
	httpClient := http.Client{}
	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, err
	}

	statusCode := resp.StatusCode
	if statusCode >= 400 && statusCode <= 505 {
		var respBody []byte
		respBody, err = readResponseBody(resp)
		if err != nil {
			return nil, err
		}

		if len(respBody) == 0 {
			// no error in response body
			err = fmt.Errorf("storage: service returned without a response body (%s)", resp.Status)
		} else {
			// response contains storage service error object, unmarshal
			storageErr, errIn := serviceErrFromXML(respBody, resp.StatusCode, resp.Header.Get("x-ms-request-id"))
			if err != nil { // error unmarshaling the error response
				err = errIn
			}
			err = storageErr
		}
		return &storageResponse{
			statusCode: resp.StatusCode,
			headers:    resp.Header,
			body:       ioutil.NopCloser(bytes.NewReader(respBody)), /* restore the body */
		}, err
	}

	return &storageResponse{
		statusCode: resp.StatusCode,
		headers:    resp.Header,
		body:       resp.Body}, nil
}

func readResponseBody(resp *http.Response) ([]byte, error) {
	defer resp.Body.Close()
	out, err := ioutil.ReadAll(resp.Body)
	if err == io.EOF {
		err = nil
	}
	return out, err
}

func serviceErrFromXML(body []byte, statusCode int, requestID string) (AzureStorageServiceError, error) {
	var storageErr AzureStorageServiceError
	if err := xml.Unmarshal(body, &storageErr); err != nil {
		return storageErr, err
	}
	storageErr.StatusCode = statusCode
	storageErr.RequestID = requestID
	return storageErr, nil
}

func (e AzureStorageServiceError) Error() string {
	return fmt.Sprintf("storage: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestId=%s, QueryParameterName=%s, QueryParameterValue=%s",
        e.StatusCode, e.Code, e.Message, e.RequestID, e.QueryParameterName, e.QueryParameterValue)
}

// checkRespCode returns UnexpectedStatusError if the given response code is not
// one of the allowed status codes; otherwise nil.
func checkRespCode(respCode int, allowed []int) error {
	for _, v := range allowed {
		if respCode == v {
			return nil
		}
	}
	return UnexpectedStatusCodeError{allowed, respCode}
}
<commit_msg>gofmt<commit_after>// Package storage provides clients for Microsoft Azure Storage Services.
package storage

import (
	"bytes"
	"encoding/base64"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

const (
	// DefaultBaseURL is the domain name used for storage requests when a
	// default client is created.
	DefaultBaseURL = "core.windows.net"

	// DefaultAPIVersion is the  Azure Storage API version string used when a
	// basic client is created.
	DefaultAPIVersion = "2014-02-14"

	defaultUseHTTPS = true

	blobServiceName  = "blob"
	tableServiceName = "table"
	queueServiceName = "queue"
	fileServiceName  = "file"
)

// Client is the object that needs to be constructed to perform
// operations on the storage account.
type Client struct {
	accountName string
	accountKey  []byte
	useHTTPS    bool
	baseURL     string
	apiVersion  string
}

type storageResponse struct {
	statusCode int
	headers    http.Header
	body       io.ReadCloser
}

// AzureStorageServiceError contains fields of the error response from
// Azure Storage Service REST API. See https://msdn.microsoft.com/en-us/library/azure/dd179382.aspx
// Some fields might be specific to certain calls.
type AzureStorageServiceError struct {
	Code                      string `xml:"Code"`
	Message                   string `xml:"Message"`
	AuthenticationErrorDetail string `xml:"AuthenticationErrorDetail"`
	QueryParameterName        string `xml:"QueryParameterName"`
	QueryParameterValue       string `xml:"QueryParameterValue"`
	Reason                    string `xml:"Reason"`
	StatusCode                int
	RequestID                 string
}

// UnexpectedStatusCodeError is returned when a storage service responds with neither an error
// nor with an HTTP status code indicating success.
type UnexpectedStatusCodeError struct {
	allowed []int
	got     int
}

func (e UnexpectedStatusCodeError) Error() string {
	s := func(i int) string { return fmt.Sprintf("%d %s", i, http.StatusText(i)) }

	got := s(e.got)
	expected := []string{}
	for _, v := range e.allowed {
		expected = append(expected, s(v))
	}
	return fmt.Sprintf("storage: status code from service response is %s; was expecting %s", got, strings.Join(expected, " or "))
}

// Got is the actual status code returned by Azure.
func (e UnexpectedStatusCodeError) Got() int {
	return e.got
}

// NewBasicClient constructs a Client with given storage service name and
// key.
func NewBasicClient(accountName, accountKey string) (Client, error) {
	return NewClient(accountName, accountKey, DefaultBaseURL, DefaultAPIVersion, defaultUseHTTPS)
}

// NewClient constructs a Client. This should be used if the caller wants
// to specify whether to use HTTPS, a specific REST API version or a custom
// storage endpoint than Azure Public Cloud.
func NewClient(accountName, accountKey, blobServiceBaseURL, apiVersion string, useHTTPS bool) (Client, error) {
	var c Client
	if accountName == "" {
		return c, fmt.Errorf("azure: account name required")
	} else if accountKey == "" {
		return c, fmt.Errorf("azure: account key required")
	} else if blobServiceBaseURL == "" {
		return c, fmt.Errorf("azure: base storage service url required")
	}

	key, err := base64.StdEncoding.DecodeString(accountKey)
	if err != nil {
		return c, err
	}

	return Client{
		accountName: accountName,
		accountKey:  key,
		useHTTPS:    useHTTPS,
		baseURL:     blobServiceBaseURL,
		apiVersion:  apiVersion,
	}, nil
}

func (c Client) getBaseURL(service string) string {
	scheme := "http"
	if c.useHTTPS {
		scheme = "https"
	}

	host := fmt.Sprintf("%s.%s.%s", c.accountName, service, c.baseURL)

	u := &url.URL{
		Scheme: scheme,
		Host:   host}
	return u.String()
}

func (c Client) getEndpoint(service, path string, params url.Values) string {
	u, err := url.Parse(c.getBaseURL(service))
	if err != nil {
		// really should not be happening
		panic(err)
	}

	if path == "" {
		path = "/" // API doesn't accept path segments not starting with '/'
	}

	u.Path = path
	u.RawQuery = params.Encode()
	return u.String()
}

// GetBlobService returns a BlobStorageClient which can operate on the blob
// service of the storage account.
func (c Client) GetBlobService() BlobStorageClient {
	return BlobStorageClient{c}
}

// GetQueueService returns a QueueServiceClient which can operate on the queue
// service of the storage account.
func (c Client) GetQueueService() QueueServiceClient {
	return QueueServiceClient{c}
}

// GetFileService returns a FileServiceClient which can operate on the file
// service of the storage account.
func (c Client) GetFileService() FileServiceClient {
	return FileServiceClient{c}
}

func (c Client) createAuthorizationHeader(canonicalizedString string) string {
	signature := c.computeHmac256(canonicalizedString)
	return fmt.Sprintf("%s %s:%s", "SharedKey", c.accountName, signature)
}

func (c Client) getAuthorizationHeader(verb, url string, headers map[string]string) (string, error) {
	canonicalizedResource, err := c.buildCanonicalizedResource(url)
	if err != nil {
		return "", err
	}

	canonicalizedString := c.buildCanonicalizedString(verb, headers, canonicalizedResource)
	return c.createAuthorizationHeader(canonicalizedString), nil
}

func (c Client) getStandardHeaders() map[string]string {
	return map[string]string{
		"x-ms-version": c.apiVersion,
		"x-ms-date":    currentTimeRfc1123Formatted(),
	}
}

func (c Client) buildCanonicalizedHeader(headers map[string]string) string {
	cm := make(map[string]string)

	for k, v := range headers {
		headerName := strings.TrimSpace(strings.ToLower(k))
		match, _ := regexp.MatchString("x-ms-", headerName)
		if match {
			cm[headerName] = v
		}
	}

	if len(cm) == 0 {
		return ""
	}

	keys := make([]string, 0, len(cm))
	for key := range cm {
		keys = append(keys, key)
	}

	sort.Strings(keys)

	ch := ""

	for i, key := range keys {
		if i == len(keys)-1 {
			ch += fmt.Sprintf("%s:%s", key, cm[key])
		} else {
			ch += fmt.Sprintf("%s:%s\n", key, cm[key])
		}
	}
	return ch
}

func (c Client) buildCanonicalizedResource(uri string) (string, error) {
	errMsg := "buildCanonicalizedResource error: %s"
	u, err := url.Parse(uri)
	if err != nil {
		return "", fmt.Errorf(errMsg, err.Error())
	}

	cr := "/" + c.accountName
	if len(u.Path) > 0 {
		cr += u.Path
	}

	params, err := url.ParseQuery(u.RawQuery)
	if err != nil {
		return "", fmt.Errorf(errMsg, err.Error())
	}

	if len(params) > 0 {
		cr += "\n"
		keys := make([]string, 0, len(params))
		for key := range params {
			keys = append(keys, key)
		}

		sort.Strings(keys)

		for i, key := range keys {
			if len(params[key]) > 1 {
				sort.Strings(params[key])
			}

			if i == len(keys)-1 {
				cr += fmt.Sprintf("%s:%s", key, strings.Join(params[key], ","))
			} else {
				cr += fmt.Sprintf("%s:%s\n", key, strings.Join(params[key], ","))
			}
		}
	}
	return cr, nil
}

func (c Client) buildCanonicalizedString(verb string, headers map[string]string, canonicalizedResource string) string {
	canonicalizedString := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s",
		verb,
		headers["Content-Encoding"],
		headers["Content-Language"],
		headers["Content-Length"],
		headers["Content-MD5"],
		headers["Content-Type"],
		headers["Date"],
		headers["If-Modified-Since"],
		headers["If-Match"],
		headers["If-None-Match"],
		headers["If-Unmodified-Since"],
		headers["Range"],
		c.buildCanonicalizedHeader(headers),
		canonicalizedResource)

	return canonicalizedString
}

func (c Client) exec(verb, url string, headers map[string]string, body io.Reader) (*storageResponse, error) {
	authHeader, err := c.getAuthorizationHeader(verb, url, headers)
	if err != nil {
		return nil, err
	}
	headers["Authorization"] = authHeader

	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(verb, url, body)
	if err != nil {
		return nil, errors.New("azure/storage: error creating request: " + err.Error())
	}
	if clstr, ok := headers["Content-Length"]; ok {
		// content length header is being signed, but completely ignored by golang.
		// instead we have to use the ContentLength property on the request struct
		// (see https://golang.org/src/net/http/request.go?s=18140:18370#L536 and
		// https://golang.org/src/net/http/transfer.go?s=1739:2467#L49)
		req.ContentLength, err = strconv.ParseInt(clstr, 10, 64)
		if err != nil {
			return nil, err
		}
	}
	for k, v := range headers {
		req.Header.Add(k, v)
	}
	httpClient := http.Client{}
	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, err
	}

	statusCode := resp.StatusCode
	if statusCode >= 400 && statusCode <= 505 {
		var respBody []byte
		respBody, err = readResponseBody(resp)
		if err != nil {
			return nil, err
		}

		if len(respBody) == 0 {
			// no error in response body
			err = fmt.Errorf("storage: service returned without a response body (%s)", resp.Status)
		} else {
			// response contains storage service error object, unmarshal
			storageErr, errIn := serviceErrFromXML(respBody, resp.StatusCode, resp.Header.Get("x-ms-request-id"))
			if err != nil { // error unmarshaling the error response
				err = errIn
			}
			err = storageErr
		}
		return &storageResponse{
			statusCode: resp.StatusCode,
			headers:    resp.Header,
			body:       ioutil.NopCloser(bytes.NewReader(respBody)), /* restore the body */
		}, err
	}

	return &storageResponse{
		statusCode: resp.StatusCode,
		headers:    resp.Header,
		body:       resp.Body}, nil
}

func readResponseBody(resp *http.Response) ([]byte, error) {
	defer resp.Body.Close()
	out, err := ioutil.ReadAll(resp.Body)
	if err == io.EOF {
		err = nil
	}
	return out, err
}

func serviceErrFromXML(body []byte, statusCode int, requestID string) (AzureStorageServiceError, error) {
	var storageErr AzureStorageServiceError
	if err := xml.Unmarshal(body, &storageErr); err != nil {
		return storageErr, err
	}
	storageErr.StatusCode = statusCode
	storageErr.RequestID = requestID
	return storageErr, nil
}

func (e AzureStorageServiceError) Error() string {
	return fmt.Sprintf("storage: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestId=%s, QueryParameterName=%s, QueryParameterValue=%s",
		e.StatusCode, e.Code, e.Message, e.RequestID, e.QueryParameterName, e.QueryParameterValue)
}

// checkRespCode returns UnexpectedStatusError if the given response code is not
// one of the allowed status codes; otherwise nil.
func checkRespCode(respCode int, allowed []int) error {
	for _, v := range allowed {
		if respCode == v {
			return nil
		}
	}
	return UnexpectedStatusCodeError{allowed, respCode}
}
<|endoftext|>


<commit_before>package store

//TODO
<commit_msg>Delete dynamoDB.go<commit_after><|endoftext|>


<commit_before>package archive

import (
	"fmt"
	"io"
	"os"
	"path"
	"time"

	"crypto"
	"crypto/sha512"

	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/packet"

	"pault.ag/go/blobstore"
	"pault.ag/go/debian/control"
	"pault.ag/go/debian/dependency"
	"pault.ag/go/debian/transput"
)

// New {{{

// Create a new Archive object
func New(path string, signer *openpgp.Entity) (*Archive, error) {
	store, err := blobstore.Load(path)
	if err != nil {
		return nil, err
	}

	return &Archive{
		store:      *store,
		signingKey: signer,
	}, nil
}

// }}}

// Archive magic {{{

type Archive struct {
	store      blobstore.Store
	signingKey *openpgp.Entity
}

// Suite {{{

func (a Archive) Suite(name string) (*Suite, error) {
	/* Get the Release / InRelease */
	inRelease := Release{}
	components := map[string]*Component{}

	fd, err := a.store.OpenPath(path.Join("dists", name, "InRelease"))
	if err == nil {
		defer fd.Close()
		if err := control.Unmarshal(&inRelease, fd); err != nil {
			return nil, err
		}

		for _, name := range inRelease.Components {
			components[name] = &Component{Packages: []Package{}}
		}
	}

	suite := Suite{
		Name:        name,
		Description: inRelease.Description,
		Origin:      inRelease.Origin,
		Label:       inRelease.Label,
		Version:     inRelease.Version,
		Suite:       inRelease.Suite,
		release:     inRelease,
		Components:  components,
	}

	suite.Pool = Pool{store: a.store, suite: &suite}
	suite.features.Hashes = []string{"sha256", "sha1"}

	return &suite, nil
}

// }}}

// Encoders {{{

// Encode (Hashed (from a Suite)) {{{

func (a Archive) encodeHashedBySuite(
	path string,
	suite Suite,
	data interface{},
) (*blobstore.Object, []control.FileHash, error) {

	hashers := []*transput.Hasher{}
	for _, algorithm := range suite.features.Hashes {
		hasher, err := transput.NewHasher(algorithm)
		if err != nil {
			return nil, nil, err
		}
		hashers = append(hashers, hasher)
	}

	return a.encodeHashed(path, hashers, data)
}

// }}}

// Encode (Hashed) {{{

func (a Archive) encodeHashed(
	path string,
	hashers []*transput.Hasher,
	data interface{},
) (*blobstore.Object, []control.FileHash, error) {

	writers := []io.Writer{}
	for _, hasher := range hashers {
		writers = append(writers, hasher)
	}

	obj, err := a.encode(data, io.MultiWriter(writers...))
	if err != nil {
		return nil, nil, err
	}

	fileHashs := []control.FileHash{}
	for _, hasher := range hashers {
		fileHashs = append(fileHashs, control.FileHashFromHasher(path, *hasher))
	}

	return obj, fileHashs, nil
}

// }}}

// Encode (Signed) {{{

func (a Archive) encodeSigned(
	data interface{},
) (*blobstore.Object, *blobstore.Object, error) {
	if a.signingKey == nil {
		return nil, nil, fmt.Errorf("No signing key loaded")
	}

	signature, err := a.store.Create()
	if err != nil {
		return nil, nil, err
	}
	defer signature.Close()

	hash := sha512.New()

	obj, err := a.encode(data, hash)
	if err != nil {
		return nil, nil, err
	}

	sig := new(packet.Signature)
	sig.SigType = packet.SigTypeBinary
	sig.PubKeyAlgo = a.signingKey.PrivateKey.PubKeyAlgo

	sig.Hash = crypto.SHA512

	sig.CreationTime = new(packet.Config).Now()
	sig.IssuerKeyId = &(a.signingKey.PrivateKey.KeyId)

	err = sig.Sign(hash, a.signingKey.PrivateKey, nil)
	if err != nil {
		return nil, nil, err
	}

	if err := sig.Serialize(signature); err != nil {
		return nil, nil, err
	}

	sigObj, err := a.store.Commit(*signature)
	if err != nil {
		return nil, nil, err
	}

	return obj, sigObj, nil
}

// }}}

// Encode {{{

func (a Archive) encode(data interface{}, tee io.Writer) (*blobstore.Object, error) {
	writer, err := a.store.Create()
	if err != nil {
		return nil, err
	}
	defer writer.Close()

	var target io.Writer = writer
	if tee != nil {
		target = io.MultiWriter(writer, tee)
	}

	encoder, err := control.NewEncoder(target)
	if err != nil {
		return nil, err
	}

	if err := encoder.Encode(data); err != nil {
		return nil, err
	}

	obj, err := a.store.Commit(*writer)
	if err != nil {
		return nil, err
	}

	return obj, nil
}

// }}}

// }}}

// Engross {{{

func (a Archive) Engross(suite Suite) (map[string]blobstore.Object, error) {
	files := map[string]blobstore.Object{}

	// duration, err := time.ParseDuration("24h")
	// if err != nil {
	// 	return nil, err
	// }
	when := time.Now()

	release := Release{
		Description:   suite.Description,
		Origin:        suite.Origin,
		Label:         suite.Label,
		Version:       suite.Version,
		Suite:         suite.Name,
		Codename:      "",
		Components:    suite.ComponenetNames(),
		Architectures: suite.Arches(),
		Date:          when.Format(time.RFC1123Z),
		// ValidUntil:    when.Add(duration).Format(time.RFC1123Z),
		SHA256: []control.SHA256FileHash{},
		SHA1:   []control.SHA1FileHash{},
		SHA512: []control.SHA512FileHash{},
		MD5Sum: []control.MD5FileHash{},
	}

	for name, component := range suite.Components {
		for arch, pkgs := range component.ByArch() {
			suitePath := path.Join(name, fmt.Sprintf("binary-%s", arch),
				"Packages")
			filePath := path.Join("dists", suite.Name, suitePath)

			obj, hashes, err := a.encodeHashedBySuite(suitePath, suite, pkgs)
			if err != nil {
				return nil, err
			}

			for _, hash := range hashes {
				if err := release.AddHash(hash); err != nil {
					return nil, err
				}
			}

			files[filePath] = *obj
		}
	}

	filePath := path.Join("dists", suite.Name, "Release")
	obj, sig, err := a.encodeSigned(release)
	if err != nil {
		return nil, err
	}
	files[filePath] = *obj
	files[fmt.Sprintf("%s.gpg", filePath)] = *sig

	return files, nil
}

// }}}

// Link {{{

func (a Archive) Link(blobs map[string]blobstore.Object) error {
	for p, obj := range blobs {
		if err := a.store.Link(obj, p); err != nil {
			return err
		}
	}
	return nil
}

// }}}

// Decruft {{{

func (a Archive) Decruft() error {
	return a.store.GC(blobstore.DumbGarbageCollector{})
}

// }}}

// }}}

// Suite magic {{{

type Suite struct {
	Name        string
	Description string
	Origin      string
	Label       string
	Version     string
	Suite       string

	release    Release
	Components map[string]*Component
	Pool       Pool

	features struct {
		Hashes []string
	}
}

// Arches {{{

func (s Suite) Arches() []dependency.Arch {
	ret := map[dependency.Arch]bool{}
	for _, component := range s.Components {
		for _, arch := range component.Arches() {
			ret[arch] = true
		}
	}
	r := []dependency.Arch{}
	for arch, _ := range ret {
		r = append(r, arch)
	}
	return r
}

// }}}

// ComponenetNames {{{

func (s Suite) ComponenetNames() []string {
	ret := []string{}
	for name, _ := range s.Components {
		ret = append(ret, name)
	}
	return ret
}

// }}}

// Add {{{

func (s Suite) Add(name string, pkg Package) {
	if _, ok := s.Components[name]; !ok {
		s.Components[name] = &Component{Packages: []Package{}}
	}
	s.Components[name].Add(pkg)
}

// }}}

// }}}

// Component magic {{{

type Component struct {
	Packages []Package
}

// ByArch {{{

func (c *Component) ByArch() map[dependency.Arch][]Package {
	ret := map[dependency.Arch][]Package{}

	for _, pkg := range c.Packages {
		packages := ret[pkg.Architecture]
		ret[pkg.Architecture] = append(packages, pkg)
	}

	return ret
}

// }}}

// Arches {{{

func (c *Component) Arches() []dependency.Arch {
	ret := []dependency.Arch{}
	for _, pkg := range c.Packages {
		ret = append(ret, pkg.Architecture)
	}
	return ret
}

// }}}

// Add {{{

func (c *Component) Add(p Package) {
	c.Packages = append(c.Packages, p)
}

// }}}

// }}}

// vim: foldmethod=marker
<commit_msg>Add docs<commit_after>package archive

import (
	"fmt"
	"io"
	"os"
	"path"
	"time"

	"crypto"
	"crypto/sha512"

	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/packet"

	"pault.ag/go/blobstore"
	"pault.ag/go/debian/control"
	"pault.ag/go/debian/dependency"
	"pault.ag/go/debian/transput"
)

// New {{{

// Create a new Archive at the given `root` on the filesystem, with the
// openpgp.Entity `signer` (an Entity which contains an OpenPGP Private
// Key).
//
// This interface is intended to *write* Archives, not *read* them. Extra
// steps must be taken to load an Archive over the network, and attention
// must be paid when handling the Cryptographic chain of trust.
func New(path string, signer *openpgp.Entity) (*Archive, error) {
	store, err := blobstore.Load(path)
	if err != nil {
		return nil, err
	}

	return &Archive{
		store:      *store,
		signingKey: signer,
	}, nil
}

// }}}

// Archive magic {{{

// Core Archive abstrcation. This contains helpers to write out package files,
// as well as handles creating underlying abstractions (such as Suites).
type Archive struct {
	store      blobstore.Store
	signingKey *openpgp.Entity
}

// Suite {{{

// Get a Suite for a given Archive. Information will be loaded from the
// InRelease file (if it exists) into the Suite object.
//
// The Suite object contains neat abstractions such as Components, and a
// number of helpers to collection information across all components, such
// as the `Arches()` helper.
func (a Archive) Suite(name string) (*Suite, error) {
	/* Get the Release / InRelease */
	inRelease := Release{}
	components := map[string]*Component{}

	fd, err := a.store.OpenPath(path.Join("dists", name, "InRelease"))
	if err == nil {
		defer fd.Close()
		if err := control.Unmarshal(&inRelease, fd); err != nil {
			return nil, err
		}

		for _, name := range inRelease.Components {
			components[name] = &Component{Packages: []Package{}}
		}
	}

	suite := Suite{
		Name:        name,
		Description: inRelease.Description,
		Origin:      inRelease.Origin,
		Label:       inRelease.Label,
		Version:     inRelease.Version,
		Suite:       inRelease.Suite,
		release:     inRelease,
		Components:  components,
	}

	suite.Pool = Pool{store: a.store, suite: &suite}
	suite.features.Hashes = []string{"sha256", "sha1"}

	return &suite, nil
}

// }}}

// Encoders {{{

// Encode (Hashed (from a Suite)) {{{

func (a Archive) encodeHashedBySuite(
	path string,
	suite Suite,
	data interface{},
) (*blobstore.Object, []control.FileHash, error) {

	hashers := []*transput.Hasher{}
	for _, algorithm := range suite.features.Hashes {
		hasher, err := transput.NewHasher(algorithm)
		if err != nil {
			return nil, nil, err
		}
		hashers = append(hashers, hasher)
	}

	return a.encodeHashed(path, hashers, data)
}

// }}}

// Encode (Hashed) {{{

func (a Archive) encodeHashed(
	path string,
	hashers []*transput.Hasher,
	data interface{},
) (*blobstore.Object, []control.FileHash, error) {

	writers := []io.Writer{}
	for _, hasher := range hashers {
		writers = append(writers, hasher)
	}

	obj, err := a.encode(data, io.MultiWriter(writers...))
	if err != nil {
		return nil, nil, err
	}

	fileHashs := []control.FileHash{}
	for _, hasher := range hashers {
		fileHashs = append(fileHashs, control.FileHashFromHasher(path, *hasher))
	}

	return obj, fileHashs, nil
}

// }}}

// Encode (Signed) {{{

func (a Archive) encodeSigned(
	data interface{},
) (*blobstore.Object, *blobstore.Object, error) {
	if a.signingKey == nil {
		return nil, nil, fmt.Errorf("No signing key loaded")
	}

	signature, err := a.store.Create()
	if err != nil {
		return nil, nil, err
	}
	defer signature.Close()

	hash := sha512.New()

	obj, err := a.encode(data, hash)
	if err != nil {
		return nil, nil, err
	}

	sig := new(packet.Signature)
	sig.SigType = packet.SigTypeBinary
	sig.PubKeyAlgo = a.signingKey.PrivateKey.PubKeyAlgo

	sig.Hash = crypto.SHA512

	sig.CreationTime = new(packet.Config).Now()
	sig.IssuerKeyId = &(a.signingKey.PrivateKey.KeyId)

	err = sig.Sign(hash, a.signingKey.PrivateKey, nil)
	if err != nil {
		return nil, nil, err
	}

	if err := sig.Serialize(signature); err != nil {
		return nil, nil, err
	}

	sigObj, err := a.store.Commit(*signature)
	if err != nil {
		return nil, nil, err
	}

	return obj, sigObj, nil
}

// }}}

// Encode {{{

func (a Archive) encode(data interface{}, tee io.Writer) (*blobstore.Object, error) {
	writer, err := a.store.Create()
	if err != nil {
		return nil, err
	}
	defer writer.Close()

	var target io.Writer = writer
	if tee != nil {
		target = io.MultiWriter(writer, tee)
	}

	encoder, err := control.NewEncoder(target)
	if err != nil {
		return nil, err
	}

	if err := encoder.Encode(data); err != nil {
		return nil, err
	}

	obj, err := a.store.Commit(*writer)
	if err != nil {
		return nil, err
	}

	return obj, nil
}

// }}}

// }}}

// Engross {{{

// Given a fully formed (and modified!) Suite object, go ahead and Engross
// the object to the Archive blobstore.
//
// This call will return a map of paths to blobs, which can be passed to
// `Link` to swap all files in at once. Simply Engrossing the Suite won't
// actually publish it.
func (a Archive) Engross(suite Suite) (map[string]blobstore.Object, error) {
	files := map[string]blobstore.Object{}

	// duration, err := time.ParseDuration("24h")
	// if err != nil {
	// 	return nil, err
	// }
	when := time.Now()

	release := Release{
		Description:   suite.Description,
		Origin:        suite.Origin,
		Label:         suite.Label,
		Version:       suite.Version,
		Suite:         suite.Name,
		Codename:      "",
		Components:    suite.ComponenetNames(),
		Architectures: suite.Arches(),
		Date:          when.Format(time.RFC1123Z),
		// ValidUntil:    when.Add(duration).Format(time.RFC1123Z),
		SHA256: []control.SHA256FileHash{},
		SHA1:   []control.SHA1FileHash{},
		SHA512: []control.SHA512FileHash{},
		MD5Sum: []control.MD5FileHash{},
	}

	for name, component := range suite.Components {
		for arch, pkgs := range component.ByArch() {
			suitePath := path.Join(name, fmt.Sprintf("binary-%s", arch),
				"Packages")
			filePath := path.Join("dists", suite.Name, suitePath)

			obj, hashes, err := a.encodeHashedBySuite(suitePath, suite, pkgs)
			if err != nil {
				return nil, err
			}

			for _, hash := range hashes {
				if err := release.AddHash(hash); err != nil {
					return nil, err
				}
			}

			files[filePath] = *obj
		}
	}

	filePath := path.Join("dists", suite.Name, "Release")
	obj, sig, err := a.encodeSigned(release)
	if err != nil {
		return nil, err
	}
	files[filePath] = *obj
	files[fmt.Sprintf("%s.gpg", filePath)] = *sig

	return files, nil
}

// }}}

// Link {{{

// Given a mapping of paths to Objects, link all of those objects
// into the Archive. Objects, after Engrossed, are stored in the
// blobstore, but won't be actually published until they're linked
// into place.
func (a Archive) Link(blobs map[string]blobstore.Object) error {
	for p, obj := range blobs {
		if err := a.store.Link(obj, p); err != nil {
			return err
		}
	}
	return nil
}

// }}}

// Decruft {{{

// Use the default backend to remove any unlinked files from the Blob store.
func (a Archive) Decruft() error {
	return a.store.GC(blobstore.DumbGarbageCollector{})
}

// }}}

// }}}

// Suite magic {{{

type Suite struct {
	Name        string
	Description string
	Origin      string
	Label       string
	Version     string
	Suite       string

	release    Release
	Components map[string]*Component
	Pool       Pool

	features struct {
		Hashes []string
	}
}

// Arches {{{

// For a Suite, iterate over all known Components, and return a list of
// unique architectures. Not all Components may have all these arches.
func (s Suite) Arches() []dependency.Arch {
	ret := map[dependency.Arch]bool{}
	for _, component := range s.Components {
		for _, arch := range component.Arches() {
			ret[arch] = true
		}
	}
	r := []dependency.Arch{}
	for arch, _ := range ret {
		r = append(r, arch)
	}
	return r
}

// }}}

// ComponenetNames {{{

// Return a list of unique component names.
func (s Suite) ComponenetNames() []string {
	ret := []string{}
	for name, _ := range s.Components {
		ret = append(ret, name)
	}
	return ret
}

// }}}

// Add {{{

// Add a package `pkg` to the component `name`.
func (s Suite) Add(name string, pkg Package) {
	if _, ok := s.Components[name]; !ok {
		s.Components[name] = &Component{Packages: []Package{}}
	}
	s.Components[name].Add(pkg)
}

// }}}

// }}}

// Component magic {{{

// Component is a section of the Archive, which is a set of Binary packages
// that are provided to the end user. Debian has three major ones, `main`,
// `contrib` and `non-free`.
type Component struct {
	Packages []Package
}

// ByArch {{{

// For a Component, get a list of Packages to provide, but split them
// into a map keyed by the Package's Arch.
func (c *Component) ByArch() map[dependency.Arch][]Package {
	ret := map[dependency.Arch][]Package{}

	for _, pkg := range c.Packages {
		packages := ret[pkg.Architecture]
		ret[pkg.Architecture] = append(packages, pkg)
	}

	return ret
}

// }}}

// Arches {{{

// For a Component, get the unique architectures contained in the Binary
// packages.
func (c *Component) Arches() []dependency.Arch {
	ret := []dependency.Arch{}
	for _, pkg := range c.Packages {
		ret = append(ret, pkg.Architecture)
	}
	return ret
}

// }}}

// Add {{{

// Add a Package to the Component.
func (c *Component) Add(p Package) {
	c.Packages = append(c.Packages, p)
}

// }}}

// }}}

// vim: foldmethod=marker
<|endoftext|>


<commit_before>package filesystem

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/naoty/todo/todo"
)

// FileSystem represents a repository backed by file system.
type FileSystem struct {
	root string
}

type index struct {
	Todos map[string][]int `json:"todos"`
}

// New returns a new FileSystem.
func New() (*FileSystem, error) {
	home := os.Getenv("TODOS_PATH")
	if home == "" {
		var err error
		home, err = os.UserHomeDir()
		if err != nil {
			return nil, err
		}
	}

	root := filepath.Join(home, ".todos")
	if _, err := os.Stat(root); os.IsNotExist(err) {
		err := os.Mkdir(root, 0755)
		if err != nil {
			return nil, err
		}
	}

	return &FileSystem{root: root}, nil
}

// Get implements Repository interface.
func (repo *FileSystem) Get(id int) (*todo.Todo, error) {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return nil, fmt.Errorf("TODO not found: %d", id)
	}

	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}
	defer file.Close()

	content, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}

	td, err := Parse(string(content))
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}

	td.ID = id
	return td, nil
}

// List implements Repository interface.
func (repo *FileSystem) List() ([]*todo.Todo, error) {
	todos := map[int]*todo.Todo{}

	err := filepath.Walk(repo.root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !strings.HasSuffix(path, ".md") {
			return nil
		}

		id, err := parseID(path)
		if err != nil {
			return err
		}

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

		content, err := ioutil.ReadAll(file)
		if err != nil {
			return err
		}

		td, err := Parse(string(content))
		if err != nil {
			return err
		}

		td.ID = id
		todos[id] = td

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to get todos from %s: %w", repo.root, err)
	}

	st, err := repo.readIndex()
	if err != nil {
		return nil, fmt.Errorf("failed to get todos from %s: %w", repo.root, err)
	}

	var ordered []*todo.Todo
	for k, ids := range st.Todos {
		// NOTE: when TODOs have subTODOs, k is parent TODO's ID.
		if k != "" {
			continue
		}

		for _, id := range ids {
			td, ok := todos[id]
			if !ok {
				continue
			}

			ordered = append(ordered, td)
		}
	}

	return ordered, nil
}

// Add implements Repository interface.
func (repo *FileSystem) Add(title string, parent *int) error {
	todos, err := repo.List()
	if err != nil {
		return fmt.Errorf("failed to get next id: %w", err)
	}

	lastID := 0
	for _, td := range todos {
		id := td.ID
		if id > lastID {
			lastID = id
		}
	}

	nextID := lastID + 1
	filename := fmt.Sprintf("%d.md", nextID)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); err == nil {
		return fmt.Errorf("already exist: %s", path)
	}

	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}
	defer file.Close()

	content := newContent(title)
	_, err = file.WriteString(content)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	st, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	parentID := ""
	if *parent > 0 {
		parentID = fmt.Sprintf("%d", *parent)
	}

	st.Todos[parentID] = append(st.Todos[parentID], nextID)
	err = repo.writeIndex(st)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	return nil
}

// Update implements Repository interface.
func (repo *FileSystem) Update(td *todo.Todo) error {
	filename := fmt.Sprintf("%d.md", td.ID)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return fmt.Errorf("file not found: %s", path)
	}

	file, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0644)
	if err != nil {
		return fmt.Errorf("failed to update TODO: %w", err)
	}
	defer file.Close()

	data := Marshal(td)
	_, err = file.Write(data)
	if err != nil {
		return fmt.Errorf("failed to update TODO: %w", err)
	}

	return nil
}

// Delete implements Repository interface.
func (repo *FileSystem) Delete(id int) error {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return fmt.Errorf("file not found: %s", path)
	}

	err := os.Remove(path)
	if err != nil {
		return fmt.Errorf("failed to delete TODO: %w", err)
	}

	index, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to delete TODO from index: %w", err)
	}

	for k, todos := range index.Todos {
		if k != "" {
			continue
		}

		var ids []int
		for _, _id := range todos {
			if _id == id {
				continue
			}
			ids = append(ids, _id)
		}
		index.Todos[k] = ids
	}

	err = repo.writeIndex(index)
	if err != nil {
		return fmt.Errorf("failed to delete TODO from index: %w", err)
	}

	return nil
}

// Open implements Repository interface.
func (repo *FileSystem) Open(id int) error {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	cmd := exec.Command("open", path)
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("failed to open %s: %w", path, err)
	}

	return nil
}

// Move implements Repository interface.
func (repo *FileSystem) Move(id, position int) error {
	if position < 1 {
		return fmt.Errorf("position number must be larger than 0: %d", position)
	}

	to := position - 1

	st, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	ids, _ := st.Todos[""]
	if to >= len(ids) {
		return fmt.Errorf("position number is too large: %d", position)
	}

	var from int
	for j, _id := range ids {
		if _id == id {
			from = j
		}
	}

	st.Todos[""] = swapped(ids, from, to)

	err = repo.writeIndex(st)
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	return nil
}

func parseID(path string) (int, error) {
	text := strings.TrimRight(filepath.Base(path), filepath.Ext(path))
	return strconv.Atoi(text)
}

func newContent(title string) string {
	return strings.Trim(fmt.Sprintf(`
---
title: %s
state: undone
---
`, title), "\n")
}

func (repo *FileSystem) readIndex() (*index, error) {
	path := filepath.Join(repo.root, "index.json")
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return &index{
			Todos: map[string][]int{
				"": {},
			},
		}, nil
	}

	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}
	defer file.Close()

	content, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}

	var i index
	err = json.Unmarshal(content, &i)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}

	return &i, nil
}

func (repo *FileSystem) writeIndex(i *index) error {
	path := filepath.Join(repo.root, "index.json")

	data, err := json.MarshalIndent(*i, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to write index to %s: %w", path, err)
	}

	err = ioutil.WriteFile(path, data, 0644)
	if err != nil {
		return fmt.Errorf("failed to write index to %s: %w", path, err)
	}

	return nil
}

func swapped(s []int, from, to int) []int {
	if from < 0 || from >= len(s) {
		return s
	}

	if to < 0 || to >= len(s) {
		return s
	}

	copied := make([]int, len(s), cap(s))
	copy(copied, s)

	e := copied[from]
	copied = append(copied[:from], copied[(from+1):]...)
	copied = append(copied[:to], append([]int{e}, copied[to:]...)...)
	return copied
}
<commit_msg>Delete sub-TODOs with parent<commit_after>package filesystem

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/naoty/todo/todo"
)

// FileSystem represents a repository backed by file system.
type FileSystem struct {
	root string
}

type index struct {
	Todos map[string][]int `json:"todos"`
}

// New returns a new FileSystem.
func New() (*FileSystem, error) {
	home := os.Getenv("TODOS_PATH")
	if home == "" {
		var err error
		home, err = os.UserHomeDir()
		if err != nil {
			return nil, err
		}
	}

	root := filepath.Join(home, ".todos")
	if _, err := os.Stat(root); os.IsNotExist(err) {
		err := os.Mkdir(root, 0755)
		if err != nil {
			return nil, err
		}
	}

	return &FileSystem{root: root}, nil
}

// Get implements Repository interface.
func (repo *FileSystem) Get(id int) (*todo.Todo, error) {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return nil, fmt.Errorf("TODO not found: %d", id)
	}

	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}
	defer file.Close()

	content, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}

	td, err := Parse(string(content))
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}

	td.ID = id
	return td, nil
}

// List implements Repository interface.
func (repo *FileSystem) List() ([]*todo.Todo, error) {
	todos := map[int]*todo.Todo{}

	err := filepath.Walk(repo.root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !strings.HasSuffix(path, ".md") {
			return nil
		}

		id, err := parseID(path)
		if err != nil {
			return err
		}

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

		content, err := ioutil.ReadAll(file)
		if err != nil {
			return err
		}

		td, err := Parse(string(content))
		if err != nil {
			return err
		}

		td.ID = id
		todos[id] = td

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to get todos from %s: %w", repo.root, err)
	}

	st, err := repo.readIndex()
	if err != nil {
		return nil, fmt.Errorf("failed to get todos from %s: %w", repo.root, err)
	}

	var ordered []*todo.Todo
	for k, ids := range st.Todos {
		// NOTE: when TODOs have subTODOs, k is parent TODO's ID.
		if k != "" {
			continue
		}

		for _, id := range ids {
			td, ok := todos[id]
			if !ok {
				continue
			}

			ordered = append(ordered, td)
		}
	}

	return ordered, nil
}

// Add implements Repository interface.
func (repo *FileSystem) Add(title string, parent *int) error {
	todos, err := repo.List()
	if err != nil {
		return fmt.Errorf("failed to get next id: %w", err)
	}

	lastID := 0
	for _, td := range todos {
		id := td.ID
		if id > lastID {
			lastID = id
		}
	}

	nextID := lastID + 1
	filename := fmt.Sprintf("%d.md", nextID)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); err == nil {
		return fmt.Errorf("already exist: %s", path)
	}

	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}
	defer file.Close()

	content := newContent(title)
	_, err = file.WriteString(content)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	st, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	parentID := ""
	if *parent > 0 {
		parentID = fmt.Sprintf("%d", *parent)
	}

	st.Todos[parentID] = append(st.Todos[parentID], nextID)
	err = repo.writeIndex(st)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	return nil
}

// Update implements Repository interface.
func (repo *FileSystem) Update(td *todo.Todo) error {
	filename := fmt.Sprintf("%d.md", td.ID)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return fmt.Errorf("file not found: %s", path)
	}

	file, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0644)
	if err != nil {
		return fmt.Errorf("failed to update TODO: %w", err)
	}
	defer file.Close()

	data := Marshal(td)
	_, err = file.Write(data)
	if err != nil {
		return fmt.Errorf("failed to update TODO: %w", err)
	}

	return nil
}

// Delete implements Repository interface.
func (repo *FileSystem) Delete(id int) error {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return fmt.Errorf("file not found: %s", path)
	}

	err := os.Remove(path)
	if err != nil {
		return fmt.Errorf("failed to delete TODO: %w", err)
	}

	index, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to delete TODO from index: %w", err)
	}

	key := fmt.Sprintf("%d", id)
	subIDs, ok := index.Todos[key]

	if ok {
		for _, id := range subIDs {
			filename := fmt.Sprintf("%d.md", id)
			path := filepath.Join(repo.root, filename)

			if _, err := os.Stat(path); os.IsNotExist(err) {
				return fmt.Errorf("file not found: %s", path)
			}

			err := os.Remove(path)
			if err != nil {
				return fmt.Errorf("failed to delete TODO: %w", err)
			}
		}
	}

	delete(index.Todos, key)

	for k, todos := range index.Todos {
		var ids []int
		for _, _id := range todos {
			if _id == id {
				continue
			}
			ids = append(ids, _id)
		}

		if len(ids) == 0 {
			delete(index.Todos, k)
			continue
		}

		index.Todos[k] = ids
	}

	err = repo.writeIndex(index)
	if err != nil {
		return fmt.Errorf("failed to delete TODO from index: %w", err)
	}

	return nil
}

// Open implements Repository interface.
func (repo *FileSystem) Open(id int) error {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	cmd := exec.Command("open", path)
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("failed to open %s: %w", path, err)
	}

	return nil
}

// Move implements Repository interface.
func (repo *FileSystem) Move(id, position int) error {
	if position < 1 {
		return fmt.Errorf("position number must be larger than 0: %d", position)
	}

	to := position - 1

	st, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	ids, _ := st.Todos[""]
	if to >= len(ids) {
		return fmt.Errorf("position number is too large: %d", position)
	}

	var from int
	for j, _id := range ids {
		if _id == id {
			from = j
		}
	}

	st.Todos[""] = swapped(ids, from, to)

	err = repo.writeIndex(st)
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	return nil
}

func parseID(path string) (int, error) {
	text := strings.TrimRight(filepath.Base(path), filepath.Ext(path))
	return strconv.Atoi(text)
}

func newContent(title string) string {
	return strings.Trim(fmt.Sprintf(`
---
title: %s
state: undone
---
`, title), "\n")
}

func (repo *FileSystem) readIndex() (*index, error) {
	path := filepath.Join(repo.root, "index.json")
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return &index{
			Todos: map[string][]int{
				"": {},
			},
		}, nil
	}

	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}
	defer file.Close()

	content, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}

	var i index
	err = json.Unmarshal(content, &i)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}

	return &i, nil
}

func (repo *FileSystem) writeIndex(i *index) error {
	path := filepath.Join(repo.root, "index.json")

	data, err := json.MarshalIndent(*i, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to write index to %s: %w", path, err)
	}

	err = ioutil.WriteFile(path, data, 0644)
	if err != nil {
		return fmt.Errorf("failed to write index to %s: %w", path, err)
	}

	return nil
}

func swapped(s []int, from, to int) []int {
	if from < 0 || from >= len(s) {
		return s
	}

	if to < 0 || to >= len(s) {
		return s
	}

	copied := make([]int, len(s), cap(s))
	copy(copied, s)

	e := copied[from]
	copied = append(copied[:from], copied[(from+1):]...)
	copied = append(copied[:to], append([]int{e}, copied[to:]...)...)
	return copied
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"

	"github.com/ClusterHQ/dvol/pkg/api"
)

const PLUGINS_DIR = "/run/docker/plugins"
const DVOL_SOCKET = PLUGINS_DIR + "/dvol.sock"
const VOL_DIR = "/var/lib/dvol/volumes"

type ResponseImplements struct {
	// A response to the Plugin.Activate request
	Implements []string
}

type RequestCreate struct {
	// A request to create a volume for Docker
	Name string
	Opts map[string]string
}

type RequestMount struct {
	// A request to mount a volume for Docker
	Name string
}

type RequestRemove struct {
	// A request to remove a volume for Docker
	Name string
}

type ResponseSimple struct {
	// A response which only indicates if there was an error or not
	Err string
}

type ResponseMount struct {
	// A response to the VolumeDriver.Mount request
	Mountpoint string
	Err        string
}

func main() {
	log.Print("Starting dvol plugin")

	if _, err := os.Stat(PLUGINS_DIR); err != nil {
		if err := os.MkdirAll(PLUGINS_DIR, 0700); err != nil {
			log.Fatalf("Could not make plugin directory %s: %v", PLUGINS_DIR, err)
		}
	}
	if _, err := os.Stat(DVOL_SOCKET); err == nil {
		if err = os.Remove(DVOL_SOCKET); err != nil {
			log.Fatalf("Could not clean up existing socket at %s: %v", DVOL_SOCKET, err)
		}
	}
	if _, err := os.Stat(VOL_DIR); err != nil {
		if err := os.MkdirAll(VOL_DIR, 0700); err != nil {
			log.Fatalf("Could not make volumes directory %s: %v", VOL_DIR, err)
		}
	}

	http.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
		log.Print("<= /Plugin.Activate")
		responseJSON, _ := json.Marshal(&ResponseImplements{
			Implements: []string{"VolumeDriver"},
		})
		w.Write(responseJSON)
	})

	http.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
		log.Print("<= /VolumeDriver.Create")
		requestJSON, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Fatalf("Unable to read response body %s", err)
		}
		request := new(RequestCreate)
		json.Unmarshal(requestJSON, request)
		name := request.Name
		dvol := api.NewDvolAPI(VOL_DIR)
		if dvol.VolumeExists(name) {
			log.Print("Volume already exists: %s", name)
		} else {
			err := dvol.CreateVolume(name)
			if err != nil {
				writeResponseErr(fmt.Errorf("Could not create volume %s: %v", name, err), w)
				return
			}
		}
		writeResponseOK(w)
	})

	http.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
		writeResponseOK(w)
	})

	http.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
	})

	http.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
		log.Print("<= /VolumeDriver.Mount")
		requestJSON, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Fatalf("Unable to read response body %s", err)
		}
		request := new(RequestMount)
		json.Unmarshal(requestJSON, request)
		name := request.Name

		dvol := api.NewDvolAPI(VOL_DIR)

		if dvol.VolumeExists(name) {
			err := dvol.SwitchVolume(name)
			if err != nil {
				writeResponseErr(err, w)
			}
			_, err = dvol.ActiveVolume()
			if err != nil {
				writeResponseErr(err, w)
			}
			// mountpoint should be:
			// /var/lib/docker/volumes/<volumename>/running_point
			responseJSON, _ := json.Marshal(&ResponseMount{
				Mountpoint: "/tmp", // TODO: Get the real path
				Err:        "",
			})
			w.Write(responseJSON)
		} else {
			writeResponseErr(fmt.Errorf("Requested to mount unknown volume %s", name), w)
		}
	})

	http.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
		writeResponseOK(w)
	})

	http.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
	})

	listener, err := net.Listen("unix", DVOL_SOCKET)
	if err != nil {
		log.Fatalf("Could not listen on %s: %v", DVOL_SOCKET, err)
	}

	http.Serve(listener, nil)
}

func writeResponseOK(w http.ResponseWriter) {
	// A shortcut to writing a ResponseOK to w
	responseJSON, _ := json.Marshal(&ResponseSimple{Err: ""})
	w.Write(responseJSON)
}

func writeResponseErr(err error, w http.ResponseWriter) {
	// A shortcut to responding with an error, and then log the error
	errString := fmt.Sprintln(err)
	log.Printf("Error: %v", err)
	responseJSON, _ := json.Marshal(&ResponseSimple{Err: errString})
	w.Write(responseJSON)
}
<commit_msg>Make travis happy.<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"

	"github.com/ClusterHQ/dvol/pkg/api"
)

const PLUGINS_DIR = "/run/docker/plugins"
const DVOL_SOCKET = PLUGINS_DIR + "/dvol.sock"
const VOL_DIR = "/var/lib/dvol/volumes"

type ResponseImplements struct {
	// A response to the Plugin.Activate request
	Implements []string
}

type RequestCreate struct {
	// A request to create a volume for Docker
	Name string
	Opts map[string]string
}

type RequestMount struct {
	// A request to mount a volume for Docker
	Name string
}

type RequestRemove struct {
	// A request to remove a volume for Docker
	Name string
}

type ResponseSimple struct {
	// A response which only indicates if there was an error or not
	Err string
}

type ResponseMount struct {
	// A response to the VolumeDriver.Mount request
	Mountpoint string
	Err        string
}

func main() {
	log.Print("Starting dvol plugin")

	if _, err := os.Stat(PLUGINS_DIR); err != nil {
		if err := os.MkdirAll(PLUGINS_DIR, 0700); err != nil {
			log.Fatalf("Could not make plugin directory %s: %v", PLUGINS_DIR, err)
		}
	}
	if _, err := os.Stat(DVOL_SOCKET); err == nil {
		if err = os.Remove(DVOL_SOCKET); err != nil {
			log.Fatalf("Could not clean up existing socket at %s: %v", DVOL_SOCKET, err)
		}
	}
	if _, err := os.Stat(VOL_DIR); err != nil {
		if err := os.MkdirAll(VOL_DIR, 0700); err != nil {
			log.Fatalf("Could not make volumes directory %s: %v", VOL_DIR, err)
		}
	}

	http.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
		log.Print("<= /Plugin.Activate")
		responseJSON, _ := json.Marshal(&ResponseImplements{
			Implements: []string{"VolumeDriver"},
		})
		w.Write(responseJSON)
	})

	http.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
		log.Print("<= /VolumeDriver.Create")
		requestJSON, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Fatalf("Unable to read response body %s", err)
		}
		request := new(RequestCreate)
		json.Unmarshal(requestJSON, request)
		name := request.Name
		dvol := api.NewDvolAPI(VOL_DIR)
		if dvol.VolumeExists(name) {
			log.Print("Volume already exists", name)
		} else {
			err := dvol.CreateVolume(name)
			if err != nil {
				writeResponseErr(fmt.Errorf("Could not create volume %s: %v", name, err), w)
				return
			}
		}
		writeResponseOK(w)
	})

	http.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
		writeResponseOK(w)
	})

	http.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
	})

	http.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
		log.Print("<= /VolumeDriver.Mount")
		requestJSON, err := ioutil.ReadAll(r.Body)
		if err != nil {
			log.Fatalf("Unable to read response body %s", err)
		}
		request := new(RequestMount)
		json.Unmarshal(requestJSON, request)
		name := request.Name

		dvol := api.NewDvolAPI(VOL_DIR)

		if dvol.VolumeExists(name) {
			err := dvol.SwitchVolume(name)
			if err != nil {
				writeResponseErr(err, w)
			}
			_, err = dvol.ActiveVolume()
			if err != nil {
				writeResponseErr(err, w)
			}
			// mountpoint should be:
			// /var/lib/docker/volumes/<volumename>/running_point
			responseJSON, _ := json.Marshal(&ResponseMount{
				Mountpoint: "/tmp", // TODO: Get the real path
				Err:        "",
			})
			w.Write(responseJSON)
		} else {
			writeResponseErr(fmt.Errorf("Requested to mount unknown volume %s", name), w)
		}
	})

	http.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
		writeResponseOK(w)
	})

	http.HandleFunc("/VolumeDriver.List", func(w http.ResponseWriter, r *http.Request) {
	})

	listener, err := net.Listen("unix", DVOL_SOCKET)
	if err != nil {
		log.Fatalf("Could not listen on %s: %v", DVOL_SOCKET, err)
	}

	http.Serve(listener, nil)
}

func writeResponseOK(w http.ResponseWriter) {
	// A shortcut to writing a ResponseOK to w
	responseJSON, _ := json.Marshal(&ResponseSimple{Err: ""})
	w.Write(responseJSON)
}

func writeResponseErr(err error, w http.ResponseWriter) {
	// A shortcut to responding with an error, and then log the error
	errString := fmt.Sprintln(err)
	log.Printf("Error: %v", err)
	responseJSON, _ := json.Marshal(&ResponseSimple{Err: errString})
	w.Write(responseJSON)
}
<|endoftext|>